diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index a40cc2b..4d70bf3 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -180,6 +180,62 @@ pub fn apply_inner_validation(schema: &mut Schema, f: fn(&mut Schema) -> ()) { } pub fn flatten(schema: &mut Schema, other: Schema) { + fn flatten_property(obj1: &mut Map, key: String, value2: Value) { + match obj1.entry(key) { + 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" | "allOf" => { + 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); + } + } + } + "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; + } + } + "oneOf" | "anyOf" => { + // `OccupiedEntry` currently has no `.remove_entry()` method :( + let key = occupied.key().clone(); + let current = occupied.remove(); + flatten_property( + obj1, + "allOf".to_owned(), + json!([ + { &key: current }, + { key: value2 } + ]), + ); + } + _ => { + // leave the original value as it is (don't modify `schema`) + } + }; + } + } + } + match other.try_to_object() { Err(false) => {} Err(true) => { @@ -191,46 +247,7 @@ pub fn flatten(schema: &mut Schema, other: Schema) { let obj1 = schema.ensure_object(); for (key, value2) in obj2 { - match obj1.entry(key) { - 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" => { - 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); - } - } - } - "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`) - } - }; - } - } + flatten_property(obj1, key, value2); } } } diff --git a/schemars/src/json_schema_impls/chrono04.rs b/schemars/src/json_schema_impls/chrono04.rs index 1dfc2b1..994d4b8 100644 --- a/schemars/src/json_schema_impls/chrono04.rs +++ b/schemars/src/json_schema_impls/chrono04.rs @@ -1,7 +1,7 @@ use crate::gen::SchemaGenerator; use crate::{json_schema, JsonSchema, Schema}; -use chrono04::prelude::*; use alloc::borrow::Cow; +use chrono04::prelude::*; impl JsonSchema for Weekday { always_inline!(); diff --git a/schemars/src/json_schema_impls/semver1.rs b/schemars/src/json_schema_impls/semver1.rs index d693bd6..43e0f3a 100644 --- a/schemars/src/json_schema_impls/semver1.rs +++ b/schemars/src/json_schema_impls/semver1.rs @@ -1,7 +1,7 @@ use crate::gen::SchemaGenerator; use crate::{json_schema, JsonSchema, Schema}; -use semver1::Version; use alloc::borrow::Cow; +use semver1::Version; impl JsonSchema for Version { always_inline!(); diff --git a/schemars/tests/enum_flatten.rs b/schemars/tests/enum_flatten.rs new file mode 100644 index 0000000..3e41b30 --- /dev/null +++ b/schemars/tests/enum_flatten.rs @@ -0,0 +1,60 @@ +mod util; +use schemars::JsonSchema; +use util::*; + +#[allow(dead_code)] +#[derive(JsonSchema)] +#[schemars(rename = "Flat")] +struct Flat { + f: f32, + #[schemars(flatten)] + e1: Enum1, + #[schemars(flatten)] + e2: Enum2, + #[schemars(flatten)] + e3: Enum3, + #[schemars(flatten)] + e4: Enum4, + #[schemars(flatten)] + e5: Enum5, +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +enum Enum1 { + B(bool), + S(String), +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +enum Enum2 { + U(u32), + F(f64), +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +enum Enum3 { + B2(bool), + S2(String), +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +enum Enum4 { + U2(u32), + F2(f64), +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +enum Enum5 { + B3(bool), + S3(String), +} + +#[test] +fn test_flat_schema() -> TestResult { + test_default_generated_schema::("enum_flatten") +} diff --git a/schemars/tests/expected/enum_flatten.json b/schemars/tests/expected/enum_flatten.json new file mode 100644 index 0000000..d8eb53d --- /dev/null +++ b/schemars/tests/expected/enum_flatten.json @@ -0,0 +1,160 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Flat", + "type": "object", + "properties": { + "f": { + "type": "number", + "format": "float" + } + }, + "required": [ + "f" + ], + "allOf": [ + { + "oneOf": [ + { + "type": "object", + "properties": { + "B": { + "type": "boolean" + } + }, + "required": [ + "B" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "S": { + "type": "string" + } + }, + "required": [ + "S" + ], + "additionalProperties": false + } + ] + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "U": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "U" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "F": { + "type": "number", + "format": "double" + } + }, + "required": [ + "F" + ], + "additionalProperties": false + } + ] + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "B2": { + "type": "boolean" + } + }, + "required": [ + "B2" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "S2": { + "type": "string" + } + }, + "required": [ + "S2" + ], + "additionalProperties": false + } + ] + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "U2": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "U2" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "F2": { + "type": "number", + "format": "double" + } + }, + "required": [ + "F2" + ], + "additionalProperties": false + } + ] + } + ], + "oneOf": [ + { + "type": "object", + "properties": { + "B3": { + "type": "boolean" + } + }, + "required": [ + "B3" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "S3": { + "type": "string" + } + }, + "required": [ + "S3" + ], + "additionalProperties": false + } + ] +} \ No newline at end of file