From 30a9a384e2997d12c2fa04bc5721661b3fd01ddf Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 19 Aug 2024 12:08:47 +0100 Subject: [PATCH] Improve `flatten` behavioure with `additionalProperties` --- schemars/src/_private.rs | 23 ++++++-- ..._flattened_struct_deny_unknown_fields.json | 52 +++++++++++++++++ schemars/tests/flatten.rs | 57 +++++++++++++++++++ 3 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 schemars/tests/expected/test_flattened_struct_deny_unknown_fields.json diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index 43a46db..a40cc2b 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -185,17 +185,23 @@ pub fn flatten(schema: &mut Schema, other: Schema) { Err(true) => { schema .ensure_object() - .entry("additionalProperties") - .or_insert(true.into()); + .insert("additionalProperties".to_owned(), 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::Vacant(vacant) => match vacant.key().as_str() { + "additionalProperties" | "unevaluatedProperties" => { + if value2 != Value::Bool(false) { + vacant.insert(value2); + } + } + _ => { + vacant.insert(value2); + } + }, Entry::Occupied(occupied) => { match occupied.key().as_str() { "required" => { @@ -212,6 +218,13 @@ pub fn flatten(schema: &mut Schema, other: Schema) { } } } + "additionalProperties" | "unevaluatedProperties" => { + // Even if an outer type has `deny_unknown_fields`, unknown fields + // may be accepted by the flattened type + if occupied.get() == &Value::Bool(false) { + *occupied.into_mut() = value2; + } + } _ => { // leave the original value as it is (don't modify `schema`) } diff --git a/schemars/tests/expected/test_flattened_struct_deny_unknown_fields.json b/schemars/tests/expected/test_flattened_struct_deny_unknown_fields.json new file mode 100644 index 0000000..1ac94b5 --- /dev/null +++ b/schemars/tests/expected/test_flattened_struct_deny_unknown_fields.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Tuple_of_OuterAllowUnknownFields_and_MiddleDenyUnknownFields", + "type": "array", + "prefixItems": [ + { + "$ref": "#/$defs/OuterAllowUnknownFields" + }, + { + "$ref": "#/$defs/MiddleDenyUnknownFields" + } + ], + "minItems": 2, + "maxItems": 2, + "$defs": { + "OuterAllowUnknownFields": { + "type": "object", + "properties": { + "outer_field": { + "type": "boolean" + }, + "middle_field": { + "type": "boolean" + }, + "inner_field": { + "type": "boolean" + } + }, + "required": [ + "outer_field", + "middle_field", + "inner_field" + ] + }, + "MiddleDenyUnknownFields": { + "type": "object", + "properties": { + "middle_field": { + "type": "boolean" + }, + "inner_field": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "middle_field", + "inner_field" + ] + } + } +} \ No newline at end of file diff --git a/schemars/tests/flatten.rs b/schemars/tests/flatten.rs index 321c419..ffd8d1f 100644 --- a/schemars/tests/flatten.rs +++ b/schemars/tests/flatten.rs @@ -76,6 +76,24 @@ struct FlattenMap { value: BTreeMap, } +#[allow(dead_code)] +#[derive(JsonSchema)] +#[schemars(rename = "FlattenValue", deny_unknown_fields)] +struct FlattenValueDenyUnknownFields { + flag: bool, + #[serde(flatten)] + value: Value, +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +#[schemars(rename = "FlattenValue", deny_unknown_fields)] +struct FlattenMapDenyUnknownFields { + flag: bool, + #[serde(flatten)] + value: BTreeMap, +} + #[test] fn test_flattened_value() -> TestResult { test_default_generated_schema::("flattened_value") @@ -86,3 +104,42 @@ 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") } + +#[test] +fn test_flattened_value_deny_unknown_fields() -> TestResult { + // intentionally using the same file as test_flattened_value, as the schema should be identical + test_default_generated_schema::("flattened_value") +} + +#[test] +fn test_flattened_map_deny_unknown_fields() -> TestResult { + // intentionally using the same file as test_flattened_value, as the schema should be identical + test_default_generated_schema::("flattened_value") +} + +#[derive(JsonSchema)] +pub struct OuterAllowUnknownFields { + pub outer_field: bool, + #[serde(flatten)] + pub middle: MiddleDenyUnknownFields, +} + +#[derive(JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct MiddleDenyUnknownFields { + pub middle_field: bool, + #[serde(flatten)] + pub inner: InnerAllowUnknownFields, +} + +#[derive(JsonSchema)] +pub struct InnerAllowUnknownFields { + pub inner_field: bool, +} + +#[test] +fn test_flattened_struct_deny_unknown_fields() -> TestResult { + test_default_generated_schema::<(OuterAllowUnknownFields, MiddleDenyUnknownFields)>( + "test_flattened_struct_deny_unknown_fields", + ) +}