From 705aba1cef4ad4de3436a707b3e0644cd5c977c5 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 12 Aug 2024 21:39:41 +0100 Subject: [PATCH] Fix flattening of `serde_json::Value` It should behave similarly to flattening a `Map` in that it allows any properties --- schemars/src/_private.rs | 77 ++++++++------------ schemars/tests/expected/flattened_value.json | 14 ++++ schemars/tests/flatten.rs | 31 ++++++++ 3 files changed, 76 insertions(+), 46 deletions(-) create mode 100644 schemars/tests/expected/flattened_value.json diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index c507c5f..f641c02 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -176,58 +176,43 @@ pub fn apply_inner_validation(schema: &mut Schema, f: fn(&mut Schema) -> ()) { } pub fn flatten(schema: &mut Schema, other: Schema) { - if let Value::Object(obj2) = other.to_value() { - let obj1 = schema.ensure_object(); + match other.try_to_object() { + Err(false) => {} + Err(true) => { + schema + .ensure_object() + .entry("additionalProperties") + .or_insert(true.into()); + } + Ok(obj2) => { + let obj1 = schema.ensure_object(); - for (key, value2) in obj2 { - match obj1.entry(key) { - Entry::Vacant(vacant) => { - vacant.insert(value2); - } - Entry::Occupied(mut occupied) => { - match occupied.key().as_str() { - // This special "type" handling can probably be removed once the enum variant `with`/`schema_with` behaviour is fixed - "type" => match (occupied.get_mut(), value2) { - (Value::Array(a1), Value::Array(mut a2)) => { - a2.retain(|v2| !a1.contains(v2)); - a1.extend(a2); - } - (v1, Value::Array(mut a2)) => { - if !a2.contains(v1) { - a2.push(std::mem::take(v1)); - *occupied.get_mut() = Value::Array(a2); + for (key, value2) in obj2 { + match obj1.entry(key) { + Entry::Vacant(vacant) => { + vacant.insert(value2); + } + Entry::Occupied(occupied) => { + match occupied.key().as_str() { + "required" => { + if let Value::Array(a1) = occupied.into_mut() { + if let Value::Array(a2) = value2 { + a1.extend(a2); + } } } - (Value::Array(a1), v2) => { - if !a1.contains(&v2) { - a1.push(v2); + "properties" | "patternProperties" => { + if let Value::Object(o1) = occupied.into_mut() { + if let Value::Object(o2) = value2 { + o1.extend(o2); + } } } - (v1, v2) => { - if v1 != &v2 { - *occupied.get_mut() = - Value::Array(vec![std::mem::take(v1), v2]); - } + _ => { + // leave the original value as it is (don't modify `schema`) } - }, - "required" => { - if let Value::Array(a1) = occupied.into_mut() { - if let Value::Array(a2) = value2 { - a1.extend(a2); - } - } - } - "properties" | "patternProperties" => { - if let Value::Object(o1) = occupied.into_mut() { - if let Value::Object(o2) = value2 { - o1.extend(o2); - } - } - } - _ => { - // leave the original value as it is (don't modify `schema`) - } - }; + }; + } } } } diff --git a/schemars/tests/expected/flattened_value.json b/schemars/tests/expected/flattened_value.json new file mode 100644 index 0000000..fe79d42 --- /dev/null +++ b/schemars/tests/expected/flattened_value.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "FlattenValue", + "type": "object", + "properties": { + "flag": { + "type": "boolean" + } + }, + "required": [ + "flag" + ], + "additionalProperties": true +} \ No newline at end of file diff --git a/schemars/tests/flatten.rs b/schemars/tests/flatten.rs index dcbf186..321c419 100644 --- a/schemars/tests/flatten.rs +++ b/schemars/tests/flatten.rs @@ -1,5 +1,7 @@ mod util; use schemars::JsonSchema; +use serde_json::Value; +use std::collections::BTreeMap; use util::*; #[allow(dead_code)] @@ -53,5 +55,34 @@ fn test_flat_schema() -> TestResult { #[test] fn test_flattened_schema() -> TestResult { + // intentionally using the same file as test_flat_schema, as the schema should be identical test_default_generated_schema::("flatten") } + +#[allow(dead_code)] +#[derive(JsonSchema)] +struct FlattenValue { + flag: bool, + #[serde(flatten)] + value: Value, +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +#[schemars(rename = "FlattenValue")] +struct FlattenMap { + flag: bool, + #[serde(flatten)] + value: BTreeMap, +} + +#[test] +fn test_flattened_value() -> TestResult { + test_default_generated_schema::("flattened_value") +} + +#[test] +fn test_flattened_map() -> TestResult { + // intentionally using the same file as test_flattened_value, as the schema should be identical + test_default_generated_schema::("flattened_value") +}