diff --git a/Cargo.lock b/Cargo.lock index b07f8af..494dc73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2075,6 +2075,7 @@ dependencies = [ "semver", "serde", "serde_json", + "serde_yaml", "sha1", "sha2", "tempfile", @@ -3237,6 +3238,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3923,6 +3937,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/mcmpmgr/Cargo.toml b/mcmpmgr/Cargo.toml index 0761b3f..492896d 100644 --- a/mcmpmgr/Cargo.toml +++ b/mcmpmgr/Cargo.toml @@ -14,6 +14,7 @@ reqwest = { version = "0.12.5", features = ["json"] } semver = { version = "1.0.23", features = ["serde"] } serde = { version = "1.0.207", features = ["derive"] } serde_json = "1.0.128" +serde_yaml = "0.9.34" sha1 = "0.10.6" sha2 = "0.10.8" tempfile = "3.12.0" diff --git a/mcmpmgr/src/file_merge.rs b/mcmpmgr/src/file_merge.rs index b462962..03efaa9 100644 --- a/mcmpmgr/src/file_merge.rs +++ b/mcmpmgr/src/file_merge.rs @@ -1,4 +1,4 @@ -use std::{any::Any, default, str::FromStr}; +use std::str::FromStr; #[derive(Debug, Clone, Copy)] pub enum FileType { @@ -81,50 +81,164 @@ fn test_merge_json() { assert!( merged_overwrite["b"]["y"]["test"] == "thing", - "//b/y/test wasn't overwritten with \"thing\". src={}, dst={}", + "//b/y/test wasn't overwritten with \"thing\". src={:#?}, dst={:#?}", src, merged_overwrite ); assert!( merged_overwrite["a"] == 3, - "//a was not set to 3. src={}, dst={}", + "//a was not set to 3. src={:#?}, dst={:#?}", src, merged_overwrite ); assert!( merged_overwrite["b"]["x"].is_object(), - "//b/x is not an object. src={}, dst={}", + "//b/x is not an object. src={:#?}, dst={:#?}", src, merged_overwrite ); assert!( merged_overwrite["c"]["foo"] == "bar", - "//c/foo != bar. src={}, dst={}", + "//c/foo != bar. src={:#?}, dst={:#?}", src, merged_overwrite ); assert!( merged_retained["b"]["y"]["test"] == "something", - "//b/y/test was overwritten. src={}, dst={}", + "//b/y/test was overwritten. src={:#?}, dst={:#?}", src, merged_retained ); assert!( merged_retained["a"] == 3, - "//a was not set to 3. src={}, dst={}", + "//a was not set to 3. src={:#?}, dst={:#?}", src, merged_retained ); assert!( merged_retained["b"]["x"].is_object(), - "//b/x is not an object. src={}, dst={}", + "//b/x is not an object. src={:#?}, dst={:#?}", src, merged_retained ); assert!( merged_retained["c"]["foo"] == "bar", - "//c/foo != bar. src={}, dst={}", + "//c/foo != bar. src={:#?}, dst={:#?}", + src, + merged_retained + ); +} + +fn merge_yaml( + src: &serde_yaml::Value, + dst: &mut serde_yaml::Value, + overwrite_existing: bool, +) -> anyhow::Result<()> { + if src.is_mapping() && dst.is_mapping() { + let src = src.as_mapping().unwrap(); + let dst = dst.as_mapping_mut().unwrap(); + + for (k, v) in src.iter() { + if v.is_mapping() { + let dst_v = dst.entry(k.clone()).or_insert(serde_yaml::from_str("{}")?); + merge_yaml(v, dst_v, overwrite_existing)?; + } else { + if overwrite_existing || !dst.contains_key(k) { + dst.insert(k.clone(), v.clone()); + } + } + } + } else { + // TODO: Keep track of path for better errors + anyhow::bail!("Cannot merge non-objects: {src:#?} and {dst:#?}") + } + Ok(()) +} + +#[test] +fn test_merge_yaml() { + let src = serde_yaml::from_str( + r#"{ + "a": 3, + "b": { + "x": { + + }, + "y": { + "test": "thing" + } + }, + "c": {} + }"#, + ) + .unwrap(); + + let dst: serde_yaml::Value = serde_yaml::from_str( + r#"{ + "b": { + "y": { + "test": "something" + } + }, + "c": { + "foo": "bar" + } + }"#, + ) + .unwrap(); + + let mut merged_overwrite = dst.clone(); + let mut merged_retained = dst.clone(); + merge_yaml(&src, &mut merged_overwrite, true).unwrap(); + merge_yaml(&src, &mut merged_retained, false).unwrap(); + + assert!( + merged_overwrite["b"]["y"]["test"] == "thing", + "//b/y/test wasn't overwritten with \"thing\". src={:#?}, dst={:#?}", + src, + merged_overwrite + ); + assert!( + merged_overwrite["a"] == 3, + "//a was not set to 3. src={:#?}, dst={:#?}", + src, + merged_overwrite + ); + assert!( + merged_overwrite["b"]["x"].is_mapping(), + "//b/x is not a mapping. src={:#?}, dst={:#?}", + src, + merged_overwrite + ); + assert!( + merged_overwrite["c"]["foo"] == "bar", + "//c/foo != bar. src={:#?}, dst={:#?}", + src, + merged_overwrite + ); + + assert!( + merged_retained["b"]["y"]["test"] == "something", + "//b/y/test was overwritten. src={:#?}, dst={:#?}", + src, + merged_retained + ); + assert!( + merged_retained["a"] == 3, + "//a was not set to 3. src={:#?}, dst={:#?}", + src, + merged_retained + ); + assert!( + merged_retained["b"]["x"].is_mapping(), + "//b/x is not a mapping. src={:#?}, dst={:#?}", + src, + merged_retained + ); + assert!( + merged_retained["c"]["foo"] == "bar", + "//c/foo != bar. src={:#?}, dst={:#?}", src, merged_retained ); @@ -144,7 +258,12 @@ fn merge_files( merge_json(&src_val, &mut dst_val, overwrite_existing)?; dst_val.to_string() } - FileType::Yaml => todo!(), + FileType::Yaml => { + let src_val = serde_yaml::Value::from(src); + let mut dst_val = serde_yaml::Value::from(dst); + merge_yaml(&src_val, &mut dst_val, overwrite_existing)?; + serde_yaml::to_string(&dst_val)? + } FileType::Toml => todo!(), }) }