Improve flatten behavioure with additionalProperties

This commit is contained in:
Graham Esau 2024-08-19 12:08:47 +01:00
parent 85626ab3a3
commit 30a9a384e2
3 changed files with 127 additions and 5 deletions

View file

@ -185,17 +185,23 @@ pub fn flatten(schema: &mut Schema, other: Schema) {
Err(true) => { Err(true) => {
schema schema
.ensure_object() .ensure_object()
.entry("additionalProperties") .insert("additionalProperties".to_owned(), true.into());
.or_insert(true.into());
} }
Ok(obj2) => { Ok(obj2) => {
let obj1 = schema.ensure_object(); let obj1 = schema.ensure_object();
for (key, value2) in obj2 { for (key, value2) in obj2 {
match obj1.entry(key) { match obj1.entry(key) {
Entry::Vacant(vacant) => { Entry::Vacant(vacant) => match vacant.key().as_str() {
vacant.insert(value2); "additionalProperties" | "unevaluatedProperties" => {
} if value2 != Value::Bool(false) {
vacant.insert(value2);
}
}
_ => {
vacant.insert(value2);
}
},
Entry::Occupied(occupied) => { Entry::Occupied(occupied) => {
match occupied.key().as_str() { match occupied.key().as_str() {
"required" => { "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`) // leave the original value as it is (don't modify `schema`)
} }

View file

@ -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"
]
}
}
}

View file

@ -76,6 +76,24 @@ struct FlattenMap {
value: BTreeMap<String, Value>, value: BTreeMap<String, Value>,
} }
#[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<String, Value>,
}
#[test] #[test]
fn test_flattened_value() -> TestResult { fn test_flattened_value() -> TestResult {
test_default_generated_schema::<FlattenValue>("flattened_value") test_default_generated_schema::<FlattenValue>("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 // intentionally using the same file as test_flattened_value, as the schema should be identical
test_default_generated_schema::<FlattenMap>("flattened_value") test_default_generated_schema::<FlattenMap>("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::<FlattenValueDenyUnknownFields>("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::<FlattenMapDenyUnknownFields>("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",
)
}