struct-patch-0.9.3/.cargo_vcs_info.json0000644000000001520000000000100134640ustar { "git": { "sha1": "40a7f25e49fb76822ed01740b1bf23399f2140fe" }, "path_in_vcs": "struct-patch" }struct-patch-0.9.3/Cargo.lock0000644000000372230000000000100114500ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "cc" version = "1.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", "windows-link", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "darling" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn", ] [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", "syn", ] [[package]] name = "deranged" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", "serde", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "humantime" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" [[package]] name = "humantime-serde" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" dependencies = [ "humantime", "serde", ] [[package]] name = "iana-time-zone" version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", "serde", ] [[package]] name = "indexmap" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.2", "serde", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "serde_with" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.9.0", "serde", "serde_derive", "serde_json", "serde_with_macros", "time", ] [[package]] name = "serde_with_macros" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ "darling", "proc-macro2", "quote", "syn", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "struct-patch" version = "0.9.3" dependencies = [ "humantime-serde", "serde", "serde_json", "serde_with", "struct-patch-derive", "toml", ] [[package]] name = "struct-patch-derive" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de988942f539673e613be15326b4fb3d4e01718084ce745a29a1199dd1710c58" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "time" version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", ] [[package]] name = "toml" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900f6c86a685850b1bc9f6223b20125115ee3f31e01207d81655bbcc0aea9231" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10558ed0bd2a1562e630926a2d1f0b98c827da99fabd3fe20920a59642504485" dependencies = [ "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", "toml_write", "winnow", ] [[package]] name = "toml_write" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28391a4201ba7eb1984cfeb6862c0b3ea2cfe23332298967c749dddc0d6cd976" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "windows-core" version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-interface" version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-link" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-result" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ "windows-link", ] [[package]] name = "winnow" version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" dependencies = [ "memchr", ] struct-patch-0.9.3/Cargo.toml0000644000000042320000000000100114650ustar # 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.9.3" authors = ["Antonio Yang "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false 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/" [features] box = [] default = [ "status", "op", ] keep_none = ["option"] merge = ["struct-patch-derive/merge"] none_as_default = ["option"] op = ["struct-patch-derive/op"] option = [] status = ["struct-patch-derive/status"] std = [ "box", "option", ] [lib] name = "struct_patch" path = "src/lib.rs" [[example]] name = "diff" path = "examples/diff.rs" [[example]] name = "filler" path = "examples/filler.rs" [[example]] name = "instance" path = "examples/instance.rs" [[example]] name = "json" path = "examples/json.rs" [[example]] name = "op" path = "examples/op.rs" [[example]] name = "option" path = "examples/option.rs" [[example]] name = "patch-attr" path = "examples/patch-attr.rs" [[example]] name = "rename-patch-struct" path = "examples/rename-patch-struct.rs" [[example]] name = "status" path = "examples/status.rs" [[example]] name = "time" path = "examples/time.rs" [dependencies.struct-patch-derive] version = "=0.9.3" [dev-dependencies.humantime-serde] version = "1.1.1" [dev-dependencies.serde] version = "1" features = ["derive"] [dev-dependencies.serde_json] version = "1.0" [dev-dependencies.serde_with] version = "3.9.0" [dev-dependencies.toml] version = "0.8.19" struct-patch-0.9.3/Cargo.toml.orig000064400000000000000000000014111046102023000151420ustar 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.9.3", path = "../struct-patch-derive" } [dev-dependencies] serde_json = "1.0" serde = { version = "1", features = ["derive"] } serde_with = "3.9.0" toml = "0.8.19" humantime-serde = "1.1.1" [features] default = ["status", "op"] status = [ "struct-patch-derive/status" ] op = [ "struct-patch-derive/op" ] merge = [ "struct-patch-derive/merge" ] std = ["box", "option"] box = [] option = [] none_as_default = ["option"] keep_none = ["option"] struct-patch-0.9.3/LICENSE000064400000000000000000000017771046102023000132770ustar 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.9.3/README.md000064400000000000000000000141621046102023000135410ustar 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`, `Filler` traits and accompanying derive macro. If the any field in `Patch` is some then it will overwrite the field of instance when apply. If the any field in the instance is none then it will try to fill the field with the `Filler`. Currently, `Filler` only support `Option` and `Vec` fields. The other fields and operator for Filler will implement later. The detail discussion is in [issue #81](https://github.com/yanganto/struct-patch/issues/81) ## Quick Example 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. ```rust use struct_patch::Patch; use serde::{Deserialize, Serialize}; #[derive(Default, Debug, PartialEq, Patch)] #[patch(attribute(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); // You can do // `let new_item = item << patch;` // For multiple patches, // you can do this // `let new_item = item << patch_1 << patch_2;` // or make an aggregated one, but please make sure the patch fields do not conflict, else will panic // ``` // let overall_patch = patch_1 + patch_2 + patch_3; // let new_item = item << overall_patch; // ``` assert_eq!( item, Item { field_bool: true, field_int: 7, field_string: String::from("hello") } ); } ``` Deriving `Filler` on a struct will generate a struct similar to the original one with the field with `Option`. Unlike `Patch`, the `Filler` only work on the empty fields of instance. ```rust use struct_patch::Filler; #[derive(Filler)] struct Item { field_int: usize, maybe_field_int: Option, list: Vec, } let mut item = Item { field_int: 0, maybe_field_int: None, list: Vec::new(), }; let filler_1 = ItemFiller{ maybe_field_int: Some(7), list: Vec::new() }; item.apply(filler_1); assert_eq!(item.maybe_field_int, Some(7)); let filler_2 = ItemFiller{ maybe_field_int: Some(100), list: Vec::new() }; // The field is not empty, so the filler has not effect. item.apply(filler_2); assert_eq!(item.maybe_field_int, Some(7)); let filler_3 = ItemFiller{ maybe_field_int: Some(100), list: vec![7] }; item.apply(filler_3); assert_eq!(item.maybe_field_int, Some(7)); assert_eq!(item.list, vec![7]); ``` ## Documentation and Examples Also, you can modify the patch structure by defining `#[patch(...)]` or `#[filler(...)]` attributes on the original struct or fields. Struct attributes: - `#[patch(name = "...")]`: change the name of the generated patch struct. - `#[patch(attribute(...))]`: add attributes to the generated patch struct. - `#[patch(attribute(derive(...)))]`: add derives to the generated patch struct. Field attributes: - `#[patch(skip)]`: skip the field in the generated patch struct. - `#[patch(name = "...")]`: change the type of the field in the generated patch struct. - `#[patch(attribute(...))]`: add attributes to the field in the generated patch struct. - `#[patch(attribute(derive(...)))]`: add derives to the field in the generated patch struct. - `#[filler(extendable)]`: use the struct of field for Filler, the struct needs implement `Default`, `Extend`, `IntoIterator` and `is_empty`. Please check the [traits][doc-traits] of document to learn more. The [examples][examples] demo following scenarios. - diff two instance for a patch - create a patch from json string - rename the patch structure - check a patch is empty or not - add attribute to patch struct - show option field behavior - show operators about patches - show example with serde crates, ex: `humantime_serde` for duration - show filler with all possible types ## Features This crate also includes the following optional features: - `status`(default): implements the `Status` trait for the patch struct, which provides the `is_empty` method. - `op` (default): provide operators `<<` between instance and patch, and `+` for patches - default: when there is a field conflict between patches, `+` will add together if the `#[patch(addable)]` or `#[patch(add=fn)]` is provided, else it will panic. - `merge` (optional): implements the `Merge` trait for the patch struct, which provides the `merge` method, and `<<` between patches. - `std`(optional): - `box`: implements the `Patch>` trait for `T` where `T` implements `Patch

`. This let you patch a boxed (or not) struct with a boxed patch. - `option`: implements the `Patch>` trait for `Option` where `T` implements `Patch

`, please take a look at the example to learn more. - default: `T` needs to implement `From

`. When patching on None, it will based on `from

` to cast T, and this let you patch structs containing fields with optional values. - `none_as_default`: `T` needs to implement `Default`. When patching on None, it will patch on a default instance, and this also let you patch structs containing fields with optional values. - `keep_none`: When patching on None, it is still None. [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/ [doc-traits]: https://docs.rs/struct-patch/latest/struct_patch/traits/trait.Patch.html#container-attributes [examples]: /struct-patch/examples struct-patch-0.9.3/examples/diff.rs000064400000000000000000000010511046102023000153470ustar 00000000000000use struct_patch::Patch; #[derive(Default, Patch)] #[patch(attribute(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: ItemPatch = 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.9.3/examples/filler.rs000064400000000000000000000073411046102023000157240ustar 00000000000000use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; use std::iter::{Extend, IntoIterator, Iterator}; use struct_patch::Filler; #[cfg(feature = "status")] use struct_patch::Status; // NOTE: Default, Extend, IntoIterator, is_empty are required for extendable type #[derive(Debug, Default)] struct WrapVec { inner: Vec, } impl Extend for WrapVec { fn extend>(&mut self, iter: WrapVec) { self.inner.extend(iter.into_iter()); } } impl IntoIterator for WrapVec { type Item = usize; type IntoIter = Box>; fn into_iter(self) -> Self::IntoIter { Box::new(self.inner.into_iter()) } } impl WrapVec { pub fn is_empty(&self) -> bool { self.inner.is_empty() } } #[derive(Default, Filler)] #[filler(attribute(derive(Debug, Default)))] struct Item { field_complete: bool, // Will check the field is equal to the value to define the field is empty or not #[filler(empty_value = 0)] field_int: usize, field_string: String, maybe_field_int: Option, maybe_field_string: Option, list: Vec, _deque: VecDeque, _linked_list: LinkedList, _map: HashMap, _bmap: BTreeMap, _set: HashSet, _bset: BTreeSet, _heap: BinaryHeap, #[filler(extendable)] _wrap: WrapVec, } // Generated by Filler derive macro // // #[derive(Debug, Default)] // pass by filler(attribute(...)) // struct ItemFiller { // maybe_field_int: Option, // maybe_field_string: Option, // list: Vec, // _deque: VecDeque, // _linked_list: LinkedList, // _map: HashMap, // _bmap: BTreeMap, // _set: HashSet, // _bset: BTreeSet, // _heap: BinaryHeap, // } fn main() { let mut item = Item::default(); let mut filler: ItemFiller = Item::new_empty_filler(); #[cfg(feature = "status")] assert!(filler.is_empty()); // provided by Status filler.maybe_field_int = Some(7); #[cfg(feature = "status")] assert!(!filler.is_empty()); assert_eq!( format!("{filler:?}"), "ItemFiller { field_int: 0, maybe_field_int: Some(7), maybe_field_string: None, list: [], _deque: [], _linked_list: [], _set: {}, _bset: {}, _heap: [], _wrap: WrapVec { inner: [] } }" ); item.apply(filler); assert!(!item.field_complete); assert_eq!(item.field_int, 0); assert_eq!(item.field_string, ""); assert_eq!(item.maybe_field_int, Some(7)); assert_eq!(item.maybe_field_string, None); assert_eq!(item.list.len(), 0); let mut filler: ItemFiller = Item::new_empty_filler(); filler.maybe_field_int = Some(100); filler.maybe_field_string = Some("Something".into()); item.apply(filler); assert!(!item.field_complete); assert_eq!(item.field_int, 0); assert_eq!(item.field_string, ""); assert_eq!(item.maybe_field_int, Some(7)); assert_eq!(item.maybe_field_string, Some("Something".into())); assert_eq!(item.list.len(), 0); let mut filler: ItemFiller = Item::new_empty_filler(); filler.list = vec![1, 2]; item.apply(filler); assert_eq!(item.list, vec![1, 2]); let mut filler: ItemFiller = Item::new_empty_filler(); filler.list = vec![3, 4]; item.apply(filler); assert_eq!(item.list, vec![1, 2]); let mut filler: ItemFiller = Item::new_empty_filler(); filler.field_int = 7; item.apply(filler); assert_eq!(item.field_int, 7); let mut filler: ItemFiller = Item::new_empty_filler(); filler.field_int = 5; item.apply(filler); assert_eq!(item.field_int, 7); } struct-patch-0.9.3/examples/instance.rs000064400000000000000000000031141046102023000162450ustar 00000000000000use struct_patch::Patch; #[derive(Default, Patch)] #[patch(attribute(derive(Debug, Default)))] struct Item { field_complete: bool, field_int: usize, field_string: String, } // Generated by Patch derive macro // // #[derive(Debug, Default)] // pass by patch(attribute(...)) // struct ItemPatch { // field_complete: Option, // field_int: Option, // field_string: Option, // } fn main() { let mut item = Item::default(); let mut patch: ItemPatch = Item::new_empty_patch(); patch.field_int = Some(7); assert_eq!( format!("{patch:?}"), "ItemPatch { field_complete: None, field_int: Some(7), field_string: None }" ); item.apply(patch); assert!(!item.field_complete); assert_eq!(item.field_int, 7); assert_eq!(item.field_string, ""); #[cfg(feature = "op")] { let another_patch = ItemPatch { field_complete: None, field_int: None, field_string: Some("from another patch".into()), }; let new_item = item << another_patch; assert!(!new_item.field_complete); assert_eq!(new_item.field_int, 7); assert_eq!(new_item.field_string, "from another patch"); let the_other_patch = ItemPatch { field_complete: Some(true), field_int: None, field_string: None, }; let final_item = new_item << the_other_patch; assert!(final_item.field_complete); assert_eq!(final_item.field_int, 7); assert_eq!(final_item.field_string, "from another patch"); } } struct-patch-0.9.3/examples/json.rs000064400000000000000000000017501046102023000154160ustar 00000000000000use serde::Deserialize; use struct_patch::Patch; #[derive(Default, Debug, PartialEq, Patch)] #[patch(attribute(derive(Debug, Default, Deserialize)))] struct Item { field_bool: bool, field_int: usize, field_string: String, sub: SubItem, } #[derive(Default, Debug, PartialEq, Patch, Deserialize)] #[patch(attribute(derive(Debug, Default, Deserialize)))] struct SubItem { inner_int: usize, } fn main() { let mut item = Item { field_bool: true, field_int: 42, field_string: String::from("hello"), sub: SubItem { inner_int: 0 }, }; let data = r#"{ "field_int": 7, "sub": { "inner_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"), sub: SubItem { inner_int: 7 }, } ); } struct-patch-0.9.3/examples/op.rs000064400000000000000000000051341046102023000150630ustar 00000000000000#[cfg(feature = "op")] fn str_concat(a: String, b: String) -> String { format!("{}, {}", a, b) } #[cfg(feature = "op")] fn main() { use struct_patch::Patch; #[derive(Clone, Debug, Default, Patch, PartialEq)] #[patch(attribute(derive(Clone, Debug, Default)))] struct Item { field_complete: bool, #[patch(addable)] field_int: usize, #[patch(add=str_concat)] field_string: String, } let mut item = Item::default(); let mut patch: ItemPatch = Item::new_empty_patch(); patch.field_int = Some(7); assert_eq!( format!("{patch:?}"), "ItemPatch { field_complete: None, field_int: Some(7), field_string: None }" ); item.apply(patch); assert!(!item.field_complete); assert_eq!(item.field_int, 7); assert_eq!(item.field_string, ""); let another_patch = ItemPatch { field_complete: None, field_int: None, field_string: Some("from another patch".into()), }; let conflict_patch = ItemPatch { field_complete: None, field_int: Some(1), field_string: Some("from conflict patch".into()), }; let the_other_patch = ItemPatch { field_complete: Some(true), field_int: Some(2), field_string: Some("the other patch".into()), }; // NOTE: The values of #[patch(addable)] can be added together. let final_item_from_conflict = item.clone() << (conflict_patch.clone() + the_other_patch.clone()); assert_eq!(final_item_from_conflict.field_int, 3); assert_eq!( final_item_from_conflict.field_string, "from conflict patch, the other patch" ); let final_item_without_bracket = item.clone() << conflict_patch.clone() << the_other_patch.clone(); assert_eq!(final_item_without_bracket.field_int, 2); #[cfg(feature = "merge")] { let final_item_with_bracket = item.clone() << (conflict_patch.clone() << the_other_patch.clone()); assert_eq!(final_item_with_bracket, final_item_without_bracket); assert_eq!(final_item_with_bracket.field_int, 2); } let final_item_from_merge = item.clone() << (another_patch.clone() + the_other_patch.clone()); assert_eq!( final_item_from_merge.field_string, "from another patch, the other patch" ); assert!(final_item_from_merge.field_complete); let final_item_series_patch = item << another_patch << the_other_patch; assert_eq!(final_item_series_patch.field_string, "the other patch"); assert!(final_item_series_patch.field_complete); } #[cfg(not(feature = "op"))] fn main() {} struct-patch-0.9.3/examples/option.rs000064400000000000000000000107301046102023000157530ustar 00000000000000#[cfg(feature = "option")] use struct_patch::Patch; #[cfg(all( feature = "option", not(feature = "keep_none"), not(feature = "none_as_default") ))] fn pure_none_feature() { #[derive(Debug, PartialEq, Patch)] #[patch(attribute(derive(Debug)))] struct User { name: String, #[patch(name = "Option")] address: Option

, } #[derive(Debug, PartialEq, Patch)] #[patch(attribute(derive(Debug)))] struct Address { street: Option, country: String, } // NOTE: we need impl the From trait. // When patch on None, the patch will convert into the instance base on From implementation impl From for Address { fn from(patch: AddressPatch) -> Self { let mut address = Address { street: None, country: "France".to_string(), }; address.apply(patch); address } } let mut user = User { name: String::from("Thomas"), address: None, }; let mut patch: UserPatch = User::new_empty_patch(); patch.address = Some(Some(AddressPatch { street: Some(Some("Av. Gustave Eiffel, 75007 Paris".to_string())), country: None, })); user.apply(patch); assert_eq!( user, User { name: String::from("Thomas"), address: Some(Address { street: Some(String::from("Av. Gustave Eiffel, 75007 Paris")), country: String::from("France"), }), } ); } #[cfg(feature = "none_as_default")] fn none_as_default_feature() { #[derive(Debug, PartialEq, Patch)] #[patch(attribute(derive(Debug)))] struct User { name: String, #[patch(name = "Option")] address: Option
, } #[derive(Debug, PartialEq, Patch)] #[patch(attribute(derive(Debug)))] struct Address { street: Option, country: String, } // NOTE: we need impl the Default trait // When patch on None, the patch will patch on a Default instance impl Default for Address { fn default() -> Self { Self { country: "France".to_string(), street: None, } } } let mut user = User { name: String::from("Thomas"), address: None, }; let mut patch: UserPatch = User::new_empty_patch(); patch.address = Some(Some(AddressPatch { street: Some(Some("Av. Gustave Eiffel, 75007 Paris".to_string())), country: None, })); user.apply(patch); assert_eq!( user, User { name: String::from("Thomas"), address: Some(Address { street: Some(String::from("Av. Gustave Eiffel, 75007 Paris")), country: String::from("France"), }), } ); } #[cfg(feature = "keep_none")] fn keep_none_feature() { #[derive(Debug, PartialEq, Patch)] #[patch(attribute(derive(Debug)))] struct User { name: String, #[patch(name = "Option")] address: Option
, } #[derive(Debug, PartialEq, Patch)] #[patch(attribute(derive(Debug)))] struct Address { street: Option, country: String, } let mut user = User { name: String::from("Thomas"), address: None, }; let mut patch: UserPatch = User::new_empty_patch(); patch.address = Some(Some(AddressPatch { street: Some(Some("Av. Gustave Eiffel, 75007 Paris".to_string())), country: None, })); user.apply(patch); assert_eq!( user, User { name: String::from("Thomas"), address: None } ); } #[cfg(feature = "option")] fn main() { // NOTE: // The `pure_none_feature` and `none_as_default_feature` are the same logic, // but the former uses `From` trait and the later uses `Default` trait. // You can base on your need to use `option` feature or `none_as_default` feature #[cfg(all(not(feature = "keep_none"), not(feature = "none_as_default")))] pure_none_feature(); #[cfg(feature = "none_as_default")] none_as_default_feature(); // NOTE: // In the feature, the patch do not allow to apply on None #[cfg(feature = "keep_none")] keep_none_feature(); } #[cfg(not(feature = "option"))] fn main() { println!("Please enable the 'option' feature to run this example"); } struct-patch-0.9.3/examples/patch-attr.rs000064400000000000000000000014431046102023000165130ustar 00000000000000use serde_with::skip_serializing_none; use struct_patch::Patch; #[derive(Default, Patch)] #[patch(attribute(derive(serde::Serialize, Debug, Default)))] #[patch(attribute(skip_serializing_none))] struct Item { field_bool: bool, field_int: usize, field_string: String, } // Generated by Patch derive macro // // #[derive(Debug, Default)] // pass by patch(attribute(...)) // #[skip_serializing_none] // pass by patch(attribute(...)) // struct ItemPatch { // pass by patch(name = ...) // field_bool: Option, // field_int: Option, // field_string: Option, // } fn main() { let patch: ItemPatch = Item::new_empty_patch(); assert_eq!( format!("{patch:?}"), "ItemPatch { field_bool: None, field_int: None, field_string: None }" ); } struct-patch-0.9.3/examples/rename-patch-struct.rs000064400000000000000000000012471046102023000203340ustar 00000000000000use struct_patch::Patch; #[derive(Default, Patch)] #[patch(attribute(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(attribute(...)) // struct ItemOverlay { // pass by patch(name = ...) // field_bool: Option, // field_int: Option, // field_string: Option, // } fn main() { let patch: ItemOverlay = Item::new_empty_patch(); assert_eq!( format!("{patch:?}"), "ItemOverlay { field_bool: None, field_int: None, field_string: None }" ); } struct-patch-0.9.3/examples/status.rs000064400000000000000000000007461046102023000157740ustar 00000000000000use struct_patch::Patch; #[cfg(feature = "status")] use struct_patch::Status; #[derive(Default, Patch)] #[patch(attribute(derive(Debug, Default)))] struct Item { field_bool: bool, field_int: usize, field_string: String, } fn main() { let mut patch: ItemPatch = Item::new_empty_patch(); #[cfg(feature = "status")] assert!(patch.is_empty()); // provided by Status patch.field_int = Some(7); #[cfg(feature = "status")] assert!(!patch.is_empty()); } struct-patch-0.9.3/examples/time.rs000064400000000000000000000017101046102023000153770ustar 00000000000000use serde::Deserialize; use std::time::Duration; use struct_patch::Patch; #[derive(Deserialize, Clone, Debug, Patch)] #[patch(name = "FileConfig", attribute(derive(Deserialize, Debug)))] struct Config { #[serde(with = "humantime_serde")] #[patch(attribute(serde(with = "humantime_serde", default)))] // NOTE: // We need extra default parameter for Option. // https://github.com/jean-airoldie/humantime-serde/issues/13#issuecomment-2388437558 time: Duration, } fn main() { let config = Config { time: Duration::from_millis(500), }; let patch: FileConfig = toml::from_str("time = \"200ms\"").unwrap(); let mut patched = config.clone(); patched.apply(patch); assert_eq!(patched.time, Duration::from_millis(200)); let empty_patch: FileConfig = toml::from_str("").unwrap(); let mut patched = config.clone(); patched.apply(empty_patch); assert_eq!(patched.time, Duration::from_millis(500)); } struct-patch-0.9.3/src/lib.rs000064400000000000000000000307331046102023000141670ustar 00000000000000//! This crate provides the [`Patch`] and [`Filler`] traits and 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(attribute(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`] //! //! Deriving [`Filler`] on a struct will generate a struct similar to the original one with the //! field with `Option`, `BTreeMap`, `BTreeSet`, `BinaryHeap`,`HashMap`, `HashSet`, `LinkedList`, //! `VecDeque `or `Vec`. //! Any struct implement `Default`, `Extend`, `IntoIterator`, `is_empty` can be used with //! `#[filler(extenable)]`. //! Unlike [`Patch`], the [`Filler`] only work on the empty fields of instance. //! //! ```rust //! use struct_patch::Filler; //! //! #[derive(Filler)] //! struct Item { //! field_int: usize, //! maybe_field_int: Option, //! } //! let mut item = Item { //! field_int: 0, //! maybe_field_int: None, //! }; //! //! let filler_1 = ItemFiller{ maybe_field_int: Some(7), }; //! item.apply(filler_1); //! assert_eq!(item.maybe_field_int, Some(7)); //! //! let filler_2 = ItemFiller{ maybe_field_int: Some(100), }; //! item.apply(filler_2); //! assert_eq!(item.maybe_field_int, Some(7)); //! ``` #![cfg_attr(not(any(test, feature = "box", feature = "option")), no_std)] #[doc(hidden)] pub use struct_patch_derive::Filler; #[doc(hidden)] pub use struct_patch_derive::Patch; #[cfg(any(feature = "box", feature = "option"))] pub mod std; pub mod traits; pub use traits::*; #[cfg(test)] mod tests { use serde::Deserialize; #[cfg(feature = "merge")] use struct_patch::Merge; use struct_patch::Patch; #[cfg(feature = "status")] use struct_patch::Status; 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(attribute(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() { #[allow(dead_code)] #[derive(Patch)] #[patch(attribute(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_nullable() { #[derive(Patch, Debug, PartialEq)] struct Item { field: Option, other: Option, } let mut item = Item { field: Some(1), other: Some(String::from("hello")), }; let patch = ItemPatch { field: None, other: Some(None), }; item.apply(patch); assert_eq!( item, Item { field: Some(1), other: None } ); } #[test] fn test_skip() { #[derive(Patch, PartialEq, Debug)] #[patch(attribute(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(attribute(derive(PartialEq, Debug, Deserialize)))] struct B { c: u32, d: u32, } #[derive(PartialEq, Debug, Patch, Deserialize)] #[patch(attribute(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 } } ); } #[test] fn test_generic() { #[derive(Patch)] struct Item where T: PartialEq, { pub field: T, } let patch = ItemPatch { field: Some(String::from("hello")), }; let mut item = Item { field: String::new(), }; item.apply(patch); assert_eq!(item.field, "hello"); } #[test] fn test_named_generic() { #[derive(Patch)] #[patch(name = "PatchItem")] struct Item where T: PartialEq, { pub field: T, } let patch = PatchItem { field: Some(String::from("hello")), }; let mut item = Item { field: String::new(), }; item.apply(patch); } #[test] fn test_nested_generic() { #[derive(PartialEq, Debug, Patch, Deserialize)] #[patch(attribute(derive(PartialEq, Debug, Deserialize)))] struct B where T: PartialEq, { c: T, d: T, } #[derive(PartialEq, Debug, Patch, Deserialize)] #[patch(attribute(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(); a.apply(patch); assert_eq!( a, A { b: B { c: 1, d: 0 } } ); } #[cfg(feature = "op")] #[test] fn test_shl() { #[derive(Patch, Debug, PartialEq)] struct Item { field: u32, other: String, } let item = Item { field: 1, other: String::from("hello"), }; let patch = ItemPatch { field: None, other: Some(String::from("bye")), }; assert_eq!( item << patch, Item { field: 1, other: String::from("bye") } ); } #[cfg(all(feature = "op", feature = "merge"))] #[test] fn test_shl_on_patch() { #[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")), }; let patch2 = ItemPatch { field: Some(2), other: None, }; let new_patch = patch << patch2; item.apply(new_patch); assert_eq!( item, Item { field: 2, other: String::from("bye") } ); } #[cfg(feature = "op")] #[test] fn test_add_patches() { #[derive(Patch)] #[patch(attribute(derive(Debug, PartialEq)))] struct Item { field: u32, other: String, } let patch = ItemPatch { field: Some(1), other: None, }; let patch2 = ItemPatch { field: None, other: Some(String::from("hello")), }; let overall_patch = patch + patch2; assert_eq!( overall_patch, ItemPatch { field: Some(1), other: Some(String::from("hello")), } ); } #[cfg(feature = "op")] #[test] #[should_panic] fn test_add_conflict_patches_panic() { #[derive(Patch, Debug, PartialEq)] struct Item { field: u32, } let patch = ItemPatch { field: Some(1) }; let patch2 = ItemPatch { field: Some(2) }; let _overall_patch = patch + patch2; } #[cfg(feature = "merge")] #[test] fn test_merge() { #[derive(Patch)] #[patch(attribute(derive(PartialEq, Debug)))] struct Item { a: u32, b: u32, c: u32, d: u32, } let patch = ItemPatch { a: None, b: Some(2), c: Some(0), d: None, }; let patch2 = ItemPatch { a: Some(1), b: None, c: Some(3), d: None, }; let merged_patch = patch.merge(patch2); assert_eq!( merged_patch, ItemPatch { a: Some(1), b: Some(2), c: Some(3), d: None, } ); } #[cfg(feature = "merge")] #[test] fn test_merge_nested() { #[derive(Patch, PartialEq, Debug)] #[patch(attribute(derive(PartialEq, Debug, Clone)))] struct B { c: u32, d: u32, e: u32, f: u32, } #[derive(Patch)] #[patch(attribute(derive(PartialEq, Debug)))] struct A { a: u32, #[patch(name = "BPatch")] b: B, } let patches = vec![ APatch { a: Some(1), b: Some(BPatch { c: None, d: Some(2), e: Some(0), f: None, }), }, APatch { a: Some(0), b: Some(BPatch { c: Some(1), d: None, e: Some(3), f: None, }), }, ]; let merged_patch = patches.into_iter().reduce(Merge::merge).unwrap(); assert_eq!( merged_patch, APatch { a: Some(0), b: Some(BPatch { c: Some(1), d: Some(2), e: Some(3), f: None, }), } ); } } struct-patch-0.9.3/src/std.rs000064400000000000000000000230051046102023000142050ustar 00000000000000#[cfg(all(feature = "merge", feature = "option"))] use crate::Merge; #[cfg(any(feature = "box", feature = "option"))] use crate::Patch; #[cfg(feature = "box")] use std::boxed::Box; #[cfg(feature = "box")] impl Patch> for T where T: Patch

, { fn apply(&mut self, patch: Box

) { self.apply(*patch); } fn into_patch(self) -> Box

{ Box::new(self.into_patch()) } fn into_patch_by_diff(self, previous_struct: Self) -> Box

{ Box::new(self.into_patch_by_diff(previous_struct)) } fn new_empty_patch() -> Box

{ Box::new(T::new_empty_patch()) } } #[cfg(feature = "option")] /// Patch implementation for Option /// This implementation is used to apply a patch to an optional field /// The default behavior when patching on `None`, will use the `From` trait to convert the patch to /// the struct type. /// Else, /// The feature `none_as_default`, will patch on default instance when patching on `None`. /// The feature `keep_none` will keep none when patching on none. #[cfg(all(not(feature = "keep_none"), not(feature = "none_as_default")))] impl Patch> for Option where T: Patch

+ From

, { fn apply(&mut self, patch: Option

) { if let Some(patch) = patch { if let Some(self_) = self { self_.apply(patch); } else { *self = Some(patch.into()); } } else { *self = None; } } fn into_patch(self) -> Option

{ self.map(|x| x.into_patch()) } fn into_patch_by_diff(self, previous_struct: Self) -> Option

{ match (self, previous_struct) { (Some(self_), Some(previous_struct_)) => { Some(self_.into_patch_by_diff(previous_struct_)) } (Some(self_), None) => Some(self_.into_patch()), (None, _) => None, } } fn new_empty_patch() -> Option

{ Some(T::new_empty_patch()) } } #[cfg(feature = "keep_none")] impl Patch> for Option where T: Patch

, { fn apply(&mut self, patch: Option

) { if let Some(patch) = patch { if let Some(self_) = self { self_.apply(patch); return; } } *self = None; } fn into_patch(self) -> Option

{ self.map(|x| x.into_patch()) } fn into_patch_by_diff(self, previous_struct: Self) -> Option

{ match (self, previous_struct) { (Some(self_), Some(previous_struct_)) => { Some(self_.into_patch_by_diff(previous_struct_)) } (Some(self_), None) => Some(self_.into_patch()), (None, _) => None, } } fn new_empty_patch() -> Option

{ Some(T::new_empty_patch()) } } #[cfg(feature = "none_as_default")] impl Patch> for Option where T: Patch

+ Default, { fn apply(&mut self, patch: Option

) { if let Some(patch) = patch { if let Some(self_) = self { self_.apply(patch); } else { let mut instance = T::default(); instance.apply(patch); *self = Some(instance); } } else { *self = None; } } fn into_patch(self) -> Option

{ self.map(|x| x.into_patch()) } fn into_patch_by_diff(self, previous_struct: Self) -> Option

{ match (self, previous_struct) { (Some(self_), Some(previous_struct_)) => { Some(self_.into_patch_by_diff(previous_struct_)) } (Some(self_), None) => Some(self_.into_patch()), (None, _) => None, } } fn new_empty_patch() -> Option

{ Some(T::new_empty_patch()) } } #[cfg(all(feature = "option", feature = "merge"))] impl Merge for Option where T: Merge, { fn merge(self, other: Self) -> Self { if let Some(other) = other { let mut self_ = self; if let Some(self_) = self_.take() { Some(self_.merge(other)) } else { Some(other) } } else { None } } } #[cfg(test)] mod tests { use crate as struct_patch; use crate::Patch; // Tests for Patch> implementation #[cfg(feature = "box")] mod patch_box { use super::*; #[test] fn test_patch_box_simple() { #[derive(Patch, Debug, PartialEq)] struct Item { field: u32, other: String, } let mut item = Item { field: 1, other: String::from("hello"), }; let patch = Box::new(ItemPatch { field: None, other: Some(String::from("bye")), }); item.apply(patch); assert_eq!( item, Item { field: 1, other: String::from("bye") } ); } } // Test for Patch> implementation #[cfg(feature = "option")] mod patch_option { use super::*; #[test] fn test_patch_option() { #[derive(Patch, Debug, PartialEq)] struct Item { field: u32, other: String, } impl From for Item { fn from(patch: ItemPatch) -> Self { Item { field: patch.field.unwrap_or_default(), other: patch.other.unwrap_or_default(), } } } let mut item = Some(Item { field: 1, other: String::from("hello"), }); let patch = Some(ItemPatch { field: None, other: Some(String::from("bye")), }); item.apply(patch); assert_eq!( item, Some(Item { field: 1, other: String::from("bye") }) ); } /// Tests for nested optional fields /// See https://stackoverflow.com/questions/44331037/how-can-i-distinguish-between-a-deserialized-field-that-is-missing-and-one-that /// and https://github.com/serde-rs/serde/issues/1042 /// To understand how to manage optional fields in patch with serde mod nested { use super::*; use serde::Deserialize; use serde::Deserializer; #[derive(PartialEq, Debug, Patch, Deserialize)] #[patch(attribute(derive(PartialEq, Debug, Deserialize)))] struct B { c: u32, d: u32, } #[derive(PartialEq, Debug, Patch, Deserialize)] #[patch(attribute(derive(PartialEq, Debug, Deserialize)))] struct A { #[patch( name = "Option", attribute(serde(deserialize_with = "deserialize_optional_field", default)) )] b: Option, } impl From for B { fn from(patch: BPatch) -> Self { B { c: patch.c.unwrap_or_default(), d: patch.d.unwrap_or_default(), } } } fn deserialize_optional_field<'de, T, D>( deserializer: D, ) -> Result>, D::Error> where D: Deserializer<'de>, T: Deserialize<'de>, { Ok(Some(Option::deserialize(deserializer)?)) } #[test] fn test_optional_nested_present() { let mut a = A { b: Some(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(Some(BPatch { c: Some(1), d: None })) } ); a.apply(patch); assert_eq!( a, A { b: Some(B { c: 1, d: 0 }) } ); } #[test] fn test_optional_nested_absent() { let mut a = A { b: Some(B { c: 0, d: 0 }), }; let data = r#"{ }"#; let patch: APatch = serde_json::from_str(data).unwrap(); assert_eq!(patch, APatch { b: None }); a.apply(patch); assert_eq!( a, A { b: Some(B { c: 0, d: 0 }) } ); } #[test] fn test_optional_nested_null() { let mut a = A { b: Some(B { c: 0, d: 0 }), }; let data = r#"{ "b": null }"#; let patch: APatch = serde_json::from_str(data).unwrap(); assert_eq!(patch, APatch { b: Some(None) }); a.apply(patch); assert_eq!(a, A { b: None }); } } } } struct-patch-0.9.3/src/traits.rs000064400000000000000000000061201046102023000147200ustar 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(attribute(derive(...)))]` /// Use this attribute to derive traits on the generated patch struct /// ```rust /// # use struct_patch::Patch; /// # use serde::{Serialize, Deserialize}; /// #[derive(Patch)] /// #[patch(attribute(derive(Debug, Default, Deserialize, Serialize)))] /// struct Item; /// /// // Generated struct /// // #[derive(Debug, Default, Deserialize, Serialize)] /// // struct ItemPatch {} /// ``` /// /// ### `#[patch(attribute(...))]` /// Use this attribute to pass the attributes on the generated patch struct /// ```compile_fail /// // This example need `serde` and `serde_with` crates /// # use struct_patch::Patch; /// #[derive(Patch, Debug)] /// #[patch(attribute(derive(Serialize, Deserialize, Default)))] /// #[patch(attribute(skip_serializing_none))] /// struct Item; /// /// // Generated struct /// // #[derive(Default, Deserialize, Serialize)] /// // #[skip_serializing_none] /// // 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; } pub trait Filler { /// Apply a filler fn apply(&mut self, filler: F); /// Get an empty filler instance fn new_empty_filler() -> F; } #[cfg(feature = "status")] /// A patch struct with extra status information pub trait Status { /// Returns `true` if all fields are `None`, `false` otherwise. fn is_empty(&self) -> bool; } #[cfg(feature = "merge")] /// A patch struct that can be merged to another one pub trait Merge { fn merge(self, other: Self) -> Self; }