struct-patch-0.4.1/.cargo_vcs_info.json0000644000000001520000000000100134550ustar { "git": { "sha1": "e6b251f81c1c7fcc32cedbc2470ee9d6791a4d39" }, "path_in_vcs": "struct-patch" }struct-patch-0.4.1/Cargo.lock0000644000000046630000000000100114430ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "proc-macro2" version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "serde" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "struct-patch" version = "0.4.1" dependencies = [ "serde", "serde_json", "struct-patch-derive", ] [[package]] name = "struct-patch-derive" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f14a349c27ebe59faba22f933c9c734d428da7231e88a247e9d8c61eea964ddb" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" struct-patch-0.4.1/Cargo.toml0000644000000021540000000000100114570ustar # 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" 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" [dependencies.struct-patch-derive] version = "=0.4.1" [dev-dependencies.serde] version = "1" features = ["derive"] [dev-dependencies.serde_json] version = "1.0" [features] default = ["status"] status = ["struct-patch-derive/status"] struct-patch-0.4.1/Cargo.toml.orig000064400000000000000000000010211046102023000151300ustar 00000000000000[package] name = "struct-patch" 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 [dependencies] struct-patch-derive = { version = "=0.4.1", path = "../struct-patch-derive" } [dev-dependencies] serde_json = "1.0" serde = { version = "1", features = ["derive"] } [features] default = ["status"] status = [ "struct-patch-derive/status" ] struct-patch-0.4.1/LICENSE000064400000000000000000000017771046102023000132700ustar 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-0.4.1/README.md000064400000000000000000000032001046102023000135210ustar 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-0.4.1/examples/diff.rs000064400000000000000000000010221046102023000153360ustar 00000000000000use struct_patch::Patch; #[derive(Default, Patch)] #[patch_derive(Debug, Default)] struct Item { field_bool: bool, field_int: usize, field_string: String, } fn main() { let item = Item::default(); let new_item = Item { field_int: 7, ..Default::default() }; // Diff on two items to get the patch let patch = new_item.into_patch_by_diff(item); assert_eq!( format!("{patch:?}"), "ItemPatch { field_bool: None, field_int: Some(7), field_string: None }" ); } struct-patch-0.4.1/examples/instance.rs000064400000000000000000000014331046102023000162400ustar 00000000000000use struct_patch::Patch; #[derive(Default, Patch)] #[patch_derive(Debug, Default)] struct Item { field_bool: bool, field_int: usize, field_string: String, } // Generated by Patch derive macro // // #[derive(Debug, Default)] // pass by patch_derive // struct ItemPatch { // field_bool: Option, // field_int: Option, // field_string: Option, // } fn main() { let mut item = Item::default(); let mut patch = Item::new_empty_patch(); patch.field_int = Some(7); assert_eq!( format!("{patch:?}"), "ItemPatch { field_bool: None, field_int: Some(7), field_string: None }" ); item.apply(patch); assert_eq!(item.field_bool, false); assert_eq!(item.field_int, 7); assert_eq!(item.field_string, ""); } struct-patch-0.4.1/examples/json.rs000064400000000000000000000012741046102023000154100ustar 00000000000000use serde::{Deserialize, Serialize}; use struct_patch::Patch; #[derive(Default, Debug, PartialEq, Patch)] #[patch_derive(Debug, Default, Deserialize, Serialize)] struct Item { field_bool: bool, field_int: usize, field_string: String, } fn main() { 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") } ); } struct-patch-0.4.1/examples/rename-patch-struct.rs000064400000000000000000000011751046102023000203250ustar 00000000000000use struct_patch::Patch; #[derive(Default, Patch)] #[patch_derive(Debug, Default)] #[patch_name = "ItemOverlay"] struct Item { field_bool: bool, field_int: usize, field_string: String, } // Generated by Patch derive macro // // #[derive(Debug, Default)] // pass by patch_derive // struct ItemOverlay { // pass by patch_name // field_bool: Option, // field_int: Option, // field_string: Option, // } fn main() { let patch = Item::new_empty_patch(); assert_eq!( format!("{patch:?}"), "ItemOverlay { field_bool: None, field_int: None, field_string: None }" ); } struct-patch-0.4.1/examples/status.rs000064400000000000000000000007311046102023000157570ustar 00000000000000use struct_patch::Patch; #[cfg(feature = "status")] use struct_patch::PatchStatus; #[derive(Default, Patch)] #[patch_derive(Debug, Default)] struct Item { field_bool: bool, field_int: usize, field_string: String, } fn main() { let mut patch = Item::new_empty_patch(); #[cfg(feature = "status")] assert!(patch.is_empty()); // provided by PatchStatus patch.field_int = Some(7); #[cfg(feature = "status")] assert!(!patch.is_empty()); } struct-patch-0.4.1/src/lib.rs000064400000000000000000000112011046102023000141450ustar 00000000000000//! 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. //! //! The following code shows how `struct-patch` can be used together with `serde` to patch structs with JSON objects. //! ```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") //! } //! ); //! } //! ``` //! //! More details on how to use the the derive macro, including what attributes are available, are available under [`Patch`] #![cfg_attr(not(test), no_std)] #[doc(hidden)] pub use struct_patch_derive::Patch; pub mod traits; pub use traits::*; #[cfg(test)] mod tests { use serde::Deserialize; use struct_patch::Patch; #[cfg(feature = "status")] use struct_patch::PatchStatus; use crate as struct_patch; #[test] fn test_basic() { #[derive(Patch, Debug, PartialEq)] struct Item { field: u32, other: String, } let mut item = Item { field: 1, other: String::from("hello"), }; let patch = ItemPatch { field: None, other: Some(String::from("bye")), }; item.apply(patch); assert_eq!( item, Item { field: 1, other: String::from("bye") } ); } #[test] #[cfg(feature = "status")] fn test_empty() { #[derive(Patch)] #[patch_derive(Debug, PartialEq)] struct Item { data: u32, } let patch = ItemPatch { data: None }; let other_patch = Item::new_empty_patch(); assert!(patch.is_empty()); assert_eq!(patch, other_patch); let patch = ItemPatch { data: Some(0) }; assert!(!patch.is_empty()); } #[test] fn test_derive() { #[derive(Patch)] #[patch_derive(Copy, Clone, PartialEq, Debug)] struct Item; let patch = ItemPatch {}; let other_patch = patch; assert_eq!(patch, other_patch); } #[test] fn test_name() { #[derive(Patch)] #[patch_name = "PatchItem"] struct Item; let patch = PatchItem {}; let mut item = Item; item.apply(patch); } #[test] fn test_skip() { #[derive(Patch, PartialEq, Debug)] #[patch_derive(PartialEq, Debug, Deserialize)] struct Item { #[patch_skip] id: u32, data: u32, } let mut item = Item { id: 1, data: 2 }; let data = r#"{ "id": 10, "data": 15 }"#; // Note: serde ignores unknown fields by default. let patch: ItemPatch = serde_json::from_str(data).unwrap(); assert_eq!(patch, ItemPatch { data: Some(15) }); item.apply(patch); assert_eq!(item, Item { id: 1, data: 15 }); } #[test] fn test_nested() { #[derive(PartialEq, Debug, Patch, Deserialize)] #[patch_derive(PartialEq, Debug, Deserialize)] struct B { c: u32, d: u32, } #[derive(PartialEq, Debug, Patch, Deserialize)] #[patch_derive(PartialEq, Debug, Deserialize)] struct A { #[patch_name = "BPatch"] b: B, } let mut a = A { b: B { c: 0, d: 0 }, }; let data = r#"{ "b": { "c": 1 } }"#; let patch: APatch = serde_json::from_str(data).unwrap(); // assert_eq!( // patch, // APatch { // b: Some(B { id: 1 }) // } // ); a.apply(patch); assert_eq!( a, A { b: B { c: 1, d: 0 } } ); } } struct-patch-0.4.1/src/traits.rs000064400000000000000000000044051046102023000147150ustar 00000000000000/// A struct that a patch can be applied to /// /// Deriving [`Patch`] will generate a patch struct and an accompanying trait impl so that it can be applied to the original struct. /// ```rust /// # use struct_patch::Patch; /// #[derive(Patch)] /// struct Item { /// field_bool: bool, /// field_int: usize, /// field_string: String, /// } /// /// // Generated struct /// // struct ItemPatch { /// // field_bool: Option, /// // field_int: Option, /// // field_string: Option, /// // } /// ``` /// ## Container attributes /// ### `#[patch_derive(...)]` /// Use this attribute to derive traits on the generated patch struct /// ```rust /// # use struct_patch::Patch; /// # use serde::{Serialize, Deserialize}; /// #[derive(Patch)] /// #[patch_derive(Debug, Default, Deserialize, Serialize)] /// struct Item; /// /// // Generated struct /// // #[derive(Debug, Default, Deserialize, Serialize)] /// // struct ItemPatch {} /// ``` /// /// ### `#[patch_name = "..."]` /// Use this attribute to change the name of the generated patch struct /// ```rust /// # use struct_patch::Patch; /// #[derive(Patch)] /// #[patch_name = "ItemOverlay"] /// struct Item { } /// /// // Generated struct /// // struct ItemOverlay {} /// ``` /// /// ## Field attributes /// ### `#[patch_skip]` /// If you want certain fields to be unpatchable, you can let the derive macro skip certain fields when creating the patch struct /// ```rust /// # use struct_patch::Patch; /// #[derive(Patch)] /// struct Item { /// #[patch_skip] /// id: String, /// data: String, /// } /// /// // Generated struct /// // struct ItemPatch { /// // data: Option, /// // } /// ``` pub trait Patch

{ /// Apply a patch fn apply(&mut self, patch: P); /// Returns a patch that when applied turns any struct of the same type into `Self` fn into_patch(self) -> P; /// Returns a patch that when applied turns `previous_struct` into `Self` fn into_patch_by_diff(self, previous_struct: Self) -> P; /// Get an empty patch instance fn new_empty_patch() -> P; } #[cfg(feature = "status")] /// A patch struct with extra status information pub trait PatchStatus { /// Returns `true` if all fields are `None`, `false` otherwise. fn is_empty(&self) -> bool; }