From 38bcc7d01ac405c17f392b6aa20fcef827f2e497 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 6 Sep 2024 17:29:24 -0400 Subject: [PATCH] Test merging happy path --- src/meta/mod.rs | 16 ++++--- src/meta/tests.rs | 109 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 5 deletions(-) diff --git a/src/meta/mod.rs b/src/meta/mod.rs index f2cd748..1f13c83 100644 --- a/src/meta/mod.rs +++ b/src/meta/mod.rs @@ -298,20 +298,26 @@ impl TryFrom for Meta { } } -impl TryFrom<&[Value]> for Meta { +impl TryFrom<&[&Value]> for Meta { type Error = Box; - fn try_from(meta: &[Value]) -> Result { + // Merge multiple spec values into a single Meta object. The first value + // in `meta` should be the primary metadata, generally included in a + // distribution. Subsequent values will be merged into that first value + // via the [RFC 7396] merge pattern. + // + // [RFC 7396]: https://www.rfc-editor.org/rfc/rfc7396.html + fn try_from(meta: &[&Value]) -> Result { if meta.is_empty() { return Err(Box::from("meta contains no values")); } // Find the version of the first doc. let version = - util::get_version(&meta[0]).ok_or("no spec version found in first meta value")?; + util::get_version(meta[0]).ok_or("no spec version found in first meta value")?; // Convert the first doc to v2 if necessary. let mut v2 = match version { - 1 => v1::to_v2(&meta[0])?, + 1 => v1::to_v2(meta[0])?, 2 => meta[0].clone(), _ => return Err(Box::from(format!("Unknown meta version {version}"))), }; @@ -324,7 +330,7 @@ impl TryFrom<&[Value]> for Meta { // Validate the patched doc and return. let mut validator = crate::valid::Validator::new(); validator.validate(&v2).map_err(|e| e.to_string())?; - Meta::from_version(version, v2) + Meta::from_version(2, v2) } } diff --git a/src/meta/tests.rs b/src/meta/tests.rs index cf5b0b5..8a742dd 100644 --- a/src/meta/tests.rs +++ b/src/meta/tests.rs @@ -105,3 +105,112 @@ fn test_bad_corpus() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_try_merge_v1() -> Result<(), Box> { + // Load a v1 META file. + let dir: PathBuf = [env!("CARGO_MANIFEST_DIR"), "corpus"].iter().collect(); + let widget_file = dir.join("v1").join("widget.json"); + let contents: Value = serde_json::from_reader(File::open(&widget_file)?)?; + + // expect maps a JSON pointer to an expected value. + for (name, patch, expect) in [ + ( + "license", + json!({"license": "MIT"}), + json!({"/license": "MIT"}), + ), + ( + "tle", + json!({"contents": {"extensions": {"widget": {"tle": true}}}}), + json!({"/contents/extensions/widget": { + "control": "widget.control", + "sql": "sql/widget.sql.in", + "tle": true, + }}), + ), + ( + "categories", + json!({"classifications": {"categories": ["Analytics", "Connectors"]}}), + json!({"/classifications/categories": ["Analytics", "Connectors"]}), + ), + ( + "tags", + json!({"classifications": {"tags": ["hi", "go", "ick"]}}), + json!({"/classifications/tags": ["hi", "go", "ick"]}), + ), + ( + "resources", + json!({"resources": { + "issues": "https://example.com/issues", + "repository": "https://example.com/repo", + }}), + json!({"/resources": { + "homepage": "http://widget.example.org/", + "issues": "https://example.com/issues", + "repository": "https://example.com/repo", + }}), + ), + ] { + run_merge_case(name, &contents, &patch, &expect)?; + } + + Ok(()) +} + +#[test] +fn test_try_merge_v2() -> Result<(), Box> { + // Load a v2 META file. + let dir: PathBuf = [env!("CARGO_MANIFEST_DIR"), "corpus"].iter().collect(); + let widget_file = dir.join("v2").join("minimal.json"); + let contents: Value = serde_json::from_reader(File::open(&widget_file)?)?; + + // expect maps a JSON pointer to an expected value. + for (name, patch, expect) in [ + ( + "license", + json!({"license": "MIT"}), + json!({"/license": "MIT"}), + ), + ( + "tle", + json!({"contents": {"extensions": {"pair": {"tle": true}}}}), + json!({"/contents/extensions/pair": { + "sql": "sql/pair.sql", + "control": "pair.control", + "tle": true, + }}), + ), + ] { + run_merge_case(name, &contents, &patch, &expect)?; + } + + Ok(()) +} + +fn run_merge_case( + name: &str, + orig: &Value, + patch: &Value, + expect: &Value, +) -> Result<(), Box> { + let meta = vec![orig, patch]; + match Meta::try_from(meta.as_slice()) { + Err(e) => panic!("widget.json patching {name} failed: {e}"), + Ok(m) => { + // Convert the Meta object to JSON. + let output: Result> = m.try_into(); + match output { + Err(e) => panic!("widget.json {name} serialization failed: {e}"), + Ok(val) => { + // Compare expected values at pointers. + for (p, v) in expect.as_object().unwrap() { + assert_eq!(v, val.pointer(p).unwrap()) + } + } + } + } + } + + Ok(()) +}