struct-patch-derive-0.4.1/.cargo_vcs_info.json0000644000000001610000000000100147310ustar { "git": { "sha1": "e6b251f81c1c7fcc32cedbc2470ee9d6791a4d39" }, "path_in_vcs": "struct-patch-derive" }struct-patch-derive-0.4.1/Cargo.toml0000644000000021040000000000100127260ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "struct-patch-derive" version = "0.4.1" authors = ["Antonio Yang "] description = "A library that helps you implement partial updates for your structs." readme = "README.md" keywords = [ "struct", "patch", "macro", "derive", "overlay", ] categories = ["development-tools"] license = "MIT" repository = "https://github.com/yanganto/struct-patch/" resolver = "1" [lib] proc-macro = true [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.syn] version = "2.0" features = ["parsing"] [features] status = [] struct-patch-derive-0.4.1/Cargo.toml.orig000064400000000000000000000006501046102023000164130ustar 00000000000000[package] name = "struct-patch-derive" authors.workspace = true version.workspace = true edition.workspace = true categories.workspace = true keywords.workspace = true repository.workspace = true description.workspace = true license.workspace = true readme.workspace = true [lib] proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0", features = ["parsing"] } [features] status = [] struct-patch-derive-0.4.1/LICENSE000064400000000000000000000017771046102023000145440ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. struct-patch-derive-0.4.1/README.md000064400000000000000000000032001046102023000147750ustar 00000000000000# Struct Patch [![Crates.io][crates-badge]][crate-url] [![MIT licensed][mit-badge]][mit-url] [![Docs][doc-badge]][doc-url] A lib help you patch Rust instance, and easy to partial update configures. ## Introduction This crate provides the `Patch` trait and an accompanying derive macro. Deriving `Patch` on a struct will generate a struct similar to the original one, but with all fields wrapped in an `Option`. An instance of such a patch struct can be applied onto the original struct, replacing values only if they are set to `Some`, leaving them unchanged otherwise. ## Quick Example ```rust use struct_patch::Patch; use serde::{Deserialize, Serialize}; #[derive(Default, Debug, PartialEq, Patch)] #[patch_derive(Debug, Default, Deserialize, Serialize)] struct Item { field_bool: bool, field_int: usize, field_string: String, } fn patch_json() { let mut item = Item { field_bool: true, field_int: 42, field_string: String::from("hello"), }; let data = r#"{ "field_int": 7 }"#; let patch: ItemPatch = serde_json::from_str(data).unwrap(); item.apply(patch); assert_eq!( item, Item { field_bool: true, field_int: 7, field_string: String::from("hello") } ); } ``` [crates-badge]: https://img.shields.io/crates/v/struct-patch.svg [crate-url]: https://crates.io/crates/struct-patch [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg [mit-url]: https://github.com/yanganto/struct-patch/blob/readme/LICENSE [doc-badge]: https://img.shields.io/badge/docs-rs-orange.svg [doc-url]: https://docs.rs/struct-patch/struct-patch-derive-0.4.1/src/lib.rs000064400000000000000000000220771046102023000154360ustar 00000000000000extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::quote; use syn::{Expr, Lit, Meta, MetaList, MetaNameValue, PathSegment}; #[proc_macro_derive(Patch, attributes(patch_derive, patch_name, patch_skip))] pub fn derive_patch(item: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(item as syn::DeriveInput); let struct_name = &input.ident; let mut patch_struct_name = None; let mut patch_derive = None; let attrs = input.attrs; for attr in attrs { let mut attr_clone = attr.clone(); let syn::Attribute { meta, .. } = attr; match meta { Meta::List(MetaList { path, tokens, delimiter, }) => { let mut path_clone = path.clone(); let mut segments = path.segments.clone(); if let Some("patch_derive") = path .segments .first() .map(|s| s.ident.to_string()) .as_deref() { if let Some(seg) = segments.first_mut() { *seg = PathSegment { ident: Ident::new("derive", Span::call_site()), arguments: seg.arguments.clone(), }; } path_clone.segments = segments; attr_clone.meta = Meta::List(MetaList { path: path_clone, tokens, delimiter, }); patch_derive = Some(attr_clone); } } Meta::NameValue(MetaNameValue { path, value: Expr::Lit(lit, ..), .. }) => { if let Some("patch_name") = path .segments .first() .map(|s| s.ident.to_string()) .as_deref() { if let Lit::Str(l) = lit.lit { patch_struct_name = Some(Ident::new( l.value().to_string().trim_matches('"'), Span::call_site(), )); } } } _ => (), } } let fields = if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &input.data { fields } else { return syn::Error::new(input.ident.span(), "Patch derive only use for struct") .to_compile_error() .into(); }; let fields_with_type = match fields { syn::Fields::Named(f) => f .clone() .named .into_pairs() .map(|p| p.into_value()) .map(|f| (f.ident.unwrap(), f.ty, f.attrs)) .collect::>(), syn::Fields::Unnamed(f) => f .clone() .unnamed .into_pairs() .map(|p| p.into_value()) .enumerate() .map(|(i, f)| (Ident::new(&i.to_string(), Span::call_site()), f.ty, f.attrs)) .collect::>(), syn::Fields::Unit => Vec::new(), }; let wrapped_fields = &mut fields_with_type .iter() .filter_map(|(f, t, attrs)| { if attrs.iter().any(|syn::Attribute { meta, .. }| { if let Meta::Path(path) = meta { path.segments .first() .map(|s| s.ident.to_string()) .as_deref() == Some("patch_skip") } else { false } }) { None } else { let rename = attrs .iter() .find(|syn::Attribute { meta, .. }| { if let Meta::NameValue(MetaNameValue { path, .. }) = meta { path.segments .first() .map(|s| s.ident.to_string()) .as_deref() == Some("patch_name") } else { false } }) .map(|syn::Attribute { meta, .. }| match meta { Meta::NameValue(MetaNameValue { path: _, value: Expr::Lit(lit, ..), .. }) => { if let Lit::Str(l) = &lit.lit { let ident = Some(Ident::new( l.value().trim_matches('"'), Span::call_site(), )); (quote!(Option<#ident>), true) } else { (quote!(Option<#t>), false) } } _ => (quote!(Option<#t>), false), }) .unwrap_or((quote!(Option<#t>), false)); let new_t = rename.0; let is_renamed = rename.1; let q = new_t.to_string().parse().unwrap(); Some((f.clone(), syn::parse::(q).unwrap(), is_renamed)) } }) .collect::>(); let field_names = wrapped_fields.iter().map(|(f, _, _)| f).collect::>(); let wrapped_types = wrapped_fields.iter().map(|(_, t, _)| t); let renamed_fields = wrapped_fields .iter() .map(|(_, _, r)| *r) .collect::>(); let renamed_field_names = field_names .iter() .zip(&renamed_fields) .filter(|(_, was_renamed)| **was_renamed) .map(|(name, _)| name) .collect::>(); let original_field_names = field_names .iter() .zip(&renamed_fields) .filter(|(_, was_renamed)| !*was_renamed) .map(|(name, _)| name) .collect::>(); let patch_struct_name = patch_struct_name .unwrap_or_else(|| Ident::new(&format!("{}Patch", struct_name), Span::call_site())); let patch_struct = if let Some(patch_derive) = patch_derive { quote!( #patch_derive pub struct #patch_struct_name { #(pub #field_names: #wrapped_types,)* } ) } else { quote::quote!( pub struct #patch_struct_name { #(pub #field_names: #wrapped_types,)* } ) }; #[cfg(feature = "status")] let patch_status_impl = quote!( impl struct_patch::traits::PatchStatus for #patch_struct_name { fn is_empty(&self) -> bool { #( if self.#field_names.is_some() { return false } )* true } } ); #[cfg(not(feature = "status"))] let patch_status_impl = quote!(); let patch_impl = quote! { impl struct_patch::traits::Patch< #patch_struct_name > for #struct_name { fn apply(&mut self, patch: #patch_struct_name) { #( if let Some(v) = patch.#renamed_field_names { self.#renamed_field_names.apply(v); } )* #( if let Some(v) = patch.#original_field_names { self.#original_field_names = v; } )* } fn into_patch(self) -> #patch_struct_name { let mut p = Self::new_empty_patch(); #( p.#renamed_field_names = Some(self.#renamed_field_names.into_patch()); )* #( p.#original_field_names = Some(self.#original_field_names); )* p } fn into_patch_by_diff(self, previous_struct: Self) -> #patch_struct_name { let mut p = Self::new_empty_patch(); #( if self.#renamed_field_names != previous_struct.#renamed_field_names { p.#renamed_field_names = Some(self.#renamed_field_names.into_patch_by_diff(previous_struct.#renamed_field_names)); } )* #( if self.#original_field_names != previous_struct.#original_field_names { p.#original_field_names = Some(self.#original_field_names); } )* p } fn new_empty_patch() -> #patch_struct_name { #patch_struct_name { #( #field_names: None, )* } } } }; let expanded = quote! { #patch_struct #patch_status_impl #patch_impl }; TokenStream::from(expanded) }