merge-struct-0.1.0/.cargo_vcs_info.json0000644000000001520000000000100134510ustar { "git": { "sha1": "048268cb2422f5882098f990be7f7376c7e2a0f1" }, "path_in_vcs": "merge-struct" }merge-struct-0.1.0/Cargo.toml0000644000000020470000000000100114540ustar # 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 = "merge-struct" version = "0.1.0" authors = ["Dotan Nahum "] description = "Deep merge for serializable structs" documentation = "https://docs.rs/merge-struct" readme = "README.md" keywords = [ "serde", "json", ] categories = ["encoding"] license = "Apache-2.0" repository = "https://github.com/jondot/merge-struct" [dependencies.serde] version = "1" [dependencies.serde_json] version = "1" [dev-dependencies.insta] version = "1.17.1" features = [ "backtrace", "redactions", ] [dev-dependencies.pretty_assertions] version = "1" merge-struct-0.1.0/Cargo.toml.orig000064400000000000000000000010721046102023000151320ustar 00000000000000[package] name = "merge-struct" version = "0.1.0" edition = "2021" description = "Deep merge for serializable structs" authors = ["Dotan Nahum "] documentation = "https://docs.rs/merge-struct" repository = "https://github.com/jondot/merge-struct" categories = ["encoding"] keywords = ["serde", "json"] license = "Apache-2.0" readme = "../README.md" [dependencies] serde = "1" serde_json = "1" [dev-dependencies] insta = { version = "1.17.1", features = ["backtrace", "redactions"] } pretty_assertions = "1" # rstest = "^0.14.0" # serial_test = "0.4.0" merge-struct-0.1.0/README.md000064400000000000000000000033641046102023000135300ustar 00000000000000Merge Struct ============ [github](https://github.com/jondot/merge-struct) [crates.io](https://crates.io/crates/merge-struct) [docs.rs](https://docs.rs/merge-struct) [build status](https://github.com/jondot/merge-struct/actions?query=branch%3Amaster) This is a Rust library deep merges two serializable structs. ## Dependency ```toml [dependencies] merge-struct = "0.1.0" ``` For most recent version see [crates.io](https://crates.io/crates/merge-struct) ## Usage ```rust use std::collections::BTreeMap; use serde_json; use serde::{Deserialize, Serialize}; use merge_struct::merge; let left: Data = serde_json::from_str( r###" { "is_root": false, "entries": { "/var/log/f2": { "name":"f2", "size": 5 } }, "folders": [ { "name": "/var/log", "num_files": 20 } ] } "###, ) .unwrap(); let right: Data = serde_json::from_str( r###" { "folders":[], "entries": { "/var/log/f1": { "name":"f1", "size": 12 } } } "###, ).unwrap(); let res = merge(&left, &right); ``` # Copyright Copyright (c) 2022 [@jondot](http://twitter.com/jondot). See [LICENSE](LICENSE.txt) for further details. merge-struct-0.1.0/src/lib.rs000064400000000000000000000133261046102023000141530ustar 00000000000000#![warn(missing_docs)] //! //! `merge-struct` is a simple library for deep merging two serializable structs. //! //! //! ## Examples //! Deserialize two structs and merge them //! //! ```no_run //! use std::collections::BTreeMap; //! use serde_json; //! use serde::{Deserialize, Serialize}; //! use merge_struct::merge; //! //! #[derive(Serialize, Deserialize)] //! struct Data { //! is_root: Option, //! folders: Vec, //! entries: Option>, // btree so test results will be ordered and stable between runs //! } //! #[derive(Serialize, Deserialize)] //! struct Folder { //! name: String, //! num_files: Option, //! } //! #[derive(Serialize, Deserialize)] //! struct Entry { //! name: String, //! size: u32, //! } //! //! let left: Data = serde_json::from_str( //! r###" //! { //! "is_root": false, //! "entries": { //! "/var/log/f2": { //! "name":"f2", //! "size": 5 //! } //! }, //! "folders": [ //! { //! "name": "/var/log", //! "num_files": 20 //! } //! ] //! } //! "###, //! ) //! .unwrap(); //! let right: Data = serde_json::from_str( //! r###" //! { //! "folders":[], //! "entries": { //! "/var/log/f1": { //! "name":"f1", //! "size": 12 //! } //! } //! } //! "###, //! ) //! .unwrap(); //! let res = merge(&left, &right); //!``` //! use serde_json::Error; use serde_json::Value; fn to_value(value: &T) -> Result { serde_json::to_value(value) } fn from_value( value: serde_json::Value, ) -> Result { serde_json::from_value(value) } fn merge_value(a: &mut Value, b: &Value) { match (a, b) { (Value::Object(ref mut a), &Value::Object(ref b)) => { for (k, v) in b { merge_value(a.entry(k).or_insert(Value::Null), v); } } (Value::Array(ref mut a), &Value::Array(ref b)) => { a.extend(b.clone()); } (Value::Array(ref mut a), &Value::Object(ref b)) => { a.extend([Value::Object(b.clone())]); } (_, Value::Null) => {} // do nothing (a, b) => { *a = b.clone(); } } } /// /// deep merge two structs that are serializable. /// based on turning them into json::Value and merging that. /// /// # Errors /// Will return an error if serialization fails /// pub fn merge( base: &T, overrides: &T, ) -> Result { let mut left = to_value(base)?; let right = to_value(overrides)?; merge_value(&mut left, &right); from_value(left) } #[cfg(test)] mod tests { use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; use super::*; use insta::assert_yaml_snapshot; #[derive(Serialize, Deserialize)] struct Data { is_root: Option, folders: Vec, entries: Option>, // btree so test results will be ordered and stable between runs } #[derive(Serialize, Deserialize)] struct Folder { name: String, num_files: Option, } #[derive(Serialize, Deserialize)] struct Entry { name: String, size: u32, } #[test] fn test_merge_left_empty() { let left: Data = serde_json::from_str( r###" { "is_root": false, "folders": [] } "###, ) .unwrap(); let right: Data = serde_json::from_str( r###" { "is_root": true, "folders":[ { "name": "/var/log", "num_files": 20 } ], "entries": { "/var/log/f1": { "name":"f1", "size": 12 } } } "###, ) .unwrap(); assert_yaml_snapshot!(merge(&left, &right).unwrap()); } #[test] fn test_merge_right_empty() { let right: Data = serde_json::from_str( r###" { "is_root": false, "folders": [] } "###, ) .unwrap(); let left: Data = serde_json::from_str( r###" { "is_root": true, "folders":[ { "name": "/var/log", "num_files": 20 } ], "entries": { "/var/log/f1": { "name":"f1", "size": 12 } } } "###, ) .unwrap(); assert_yaml_snapshot!(merge(&left, &right).unwrap()); } #[test] fn test_merge() { let left: Data = serde_json::from_str( r###" { "is_root": false, "entries": { "/var/log/f2": { "name":"f2", "size": 5 } }, "folders": [ { "name": "/var/log", "num_files": 20 } ] } "###, ) .unwrap(); let right: Data = serde_json::from_str( r###" { "folders":[], "entries": { "/var/log/f1": { "name":"f1", "size": 12 } } } "###, ) .unwrap(); assert_yaml_snapshot!(merge(&left, &right).unwrap()); } } merge-struct-0.1.0/src/snapshots/merge_struct__tests__merge.snap000064400000000000000000000003541046102023000233430ustar 00000000000000--- source: merge-struct/src/lib.rs expression: "merge(&left, &right).unwrap()" --- is_root: false folders: - name: /var/log num_files: 20 entries: /var/log/f1: name: f1 size: 12 /var/log/f2: name: f2 size: 5 merge-struct-0.1.0/src/snapshots/merge_struct__tests__merge_left_empty.snap000064400000000000000000000003031046102023000255650ustar 00000000000000--- source: merge-struct/src/lib.rs expression: "merge(&left, &right).unwrap()" --- is_root: true folders: - name: /var/log num_files: 20 entries: /var/log/f1: name: f1 size: 12 merge-struct-0.1.0/src/snapshots/merge_struct__tests__merge_right_empty.snap000064400000000000000000000003041046102023000257510ustar 00000000000000--- source: merge-struct/src/lib.rs expression: "merge(&left, &right).unwrap()" --- is_root: false folders: - name: /var/log num_files: 20 entries: /var/log/f1: name: f1 size: 12