diff --git a/schemars/src/flatten.rs b/schemars/src/flatten.rs new file mode 100644 index 0000000..930f8de --- /dev/null +++ b/schemars/src/flatten.rs @@ -0,0 +1,139 @@ +use crate::schema::*; +use crate::{JsonSchemaError, Map, Result, Set}; + +impl Schema { + pub fn flatten(self, other: Self) -> Result { + let s1 = ensure_flattenable(self)?; + let s2 = ensure_flattenable(other)?; + Ok(Schema::Object(s1.merge(s2))) + } +} + +trait Merge: Sized { + fn merge(self, other: Self) -> Self; +} + +macro_rules! impl_merge { + ($ty:ident { merge: $($merge_field:ident)*, or: $($or_field:ident)*, }) => { + impl Merge for $ty { + fn merge(self, other: Self) -> Self { + $ty { + $($merge_field: self.$merge_field.merge(other.$merge_field),)* + $($or_field: self.$or_field.or(other.$or_field),)* + } + } + } + }; + ($ty:ident { or: $($or_field:ident)*, }) => { + impl_merge!( $ty { merge: , or: $($or_field)*, }); + }; +} + +impl_merge!(SchemaObject { + merge: definitions extensions instance_type enum_values + number string array object, + or: schema id title description format all_of any_of one_of not + if_schema then_schema else_schema, +}); + +impl_merge!(NumberValidation { + or: multiple_of maximum exclusive_maximum minimum exclusive_minimum, +}); + +impl_merge!(StringValidation { + or: max_length min_length pattern, +}); + +impl_merge!(ArrayValidation { + or: items additional_items max_items min_items unique_items contains, +}); + +impl_merge!(ObjectValidation { + merge: required properties pattern_properties, + or: max_properties min_properties additional_properties property_names, +}); + +impl Merge for Option { + fn merge(self, other: Self) -> Self { + match (self, other) { + (Some(x), Some(y)) => Some(x.merge(y)), + (None, y) => y, + (x, None) => x, + } + } +} + +impl Merge for Box { + fn merge(mut self, other: Self) -> Self { + *self = (*self).merge(*other); + self + } +} + +impl Merge for Vec { + fn merge(mut self, other: Self) -> Self { + self.extend(other); + self + } +} + +impl Merge for Map { + fn merge(mut self, other: Self) -> Self { + self.extend(other); + self + } +} + +impl Merge for Set { + fn merge(mut self, other: Self) -> Self { + self.extend(other); + self + } +} + +impl Merge for SingleOrVec { + fn merge(self, other: Self) -> Self { + if self == other { + return self; + } + let mut vec = match (self, other) { + (SingleOrVec::Vec(v1), SingleOrVec::Vec(v2)) => v1.merge(v2), + (SingleOrVec::Vec(mut v), SingleOrVec::Single(s)) + | (SingleOrVec::Single(s), SingleOrVec::Vec(mut v)) => { + v.push(*s); + v + } + (SingleOrVec::Single(s1), SingleOrVec::Single(s2)) => vec![*s1, *s2], + }; + vec.sort(); + vec.dedup(); + SingleOrVec::Vec(vec) + } +} + +fn ensure_flattenable(schema: Schema) -> Result { + let s = match schema { + Schema::Object(s) => s, + s => { + return Err(JsonSchemaError::new( + "Only schemas with type `object` can be flattened.", + s, + )) + } + }; + match s.instance_type { + Some(SingleOrVec::Single(ref t)) if **t != InstanceType::Object => { + Err(JsonSchemaError::new( + "Only schemas with type `object` can be flattened.", + s.into(), + )) + } + Some(SingleOrVec::Vec(ref t)) if !t.contains(&InstanceType::Object) => { + Err(JsonSchemaError::new( + "Only schemas with type `object` can be flattened.", + s.into(), + )) + } + _ => Ok(s), + } +} diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index a138aa5..f38bea7 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -4,6 +4,7 @@ pub type Map = std::collections::BTreeMap; pub type Set = std::collections::BTreeSet; mod error; +mod flatten; mod json_schema_impls; #[macro_use] mod macros; diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index f6a78a2..b0bb506 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -1,5 +1,5 @@ use crate as schemars; -use crate::{JsonSchema, JsonSchemaError, Map, Result, Set}; +use crate::{JsonSchema, Map, Set}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -29,93 +29,6 @@ impl From for Schema { } } -impl Schema { - pub fn flatten(self, other: Self) -> Result { - fn extend>(mut a: E, b: impl IntoIterator) -> E { - a.extend(b); - a - } - - let s1 = self.ensure_flattenable()?; - let s2 = other.ensure_flattenable()?; - Ok(Schema::Object(SchemaObject { - schema: s1.schema.or(s2.schema), - id: s1.id.or(s2.id), - title: s1.title.or(s2.title), - description: s1.description.or(s2.description), - definitions: extend(s1.definitions, s2.definitions), - extensions: extend(s1.extensions, s2.extensions), - // TODO do the following make sense? - instance_type: s1.instance_type.or(s2.instance_type), - format: s1.format.or(s2.format), - enum_values: s1.enum_values.or(s2.enum_values), - all_of: s1.all_of.or(s2.all_of), - any_of: s1.any_of.or(s2.any_of), - one_of: s1.one_of.or(s2.one_of), - not: s1.not.or(s2.not), - if_schema: s1.if_schema.or(s2.if_schema), - then_schema: s1.then_schema.or(s2.then_schema), - else_schema: s1.else_schema.or(s2.else_schema), - number: NumberValidation { - multiple_of: s1.number.multiple_of.or(s2.number.multiple_of), - maximum: s1.number.maximum.or(s2.number.maximum), - exclusive_maximum: s1.number.exclusive_maximum.or(s2.number.exclusive_maximum), - minimum: s1.number.minimum.or(s2.number.minimum), - exclusive_minimum: s1.number.exclusive_minimum.or(s2.number.exclusive_minimum), - }, - string: StringValidation { - max_length: s1.string.max_length.or(s2.string.max_length), - min_length: s1.string.min_length.or(s2.string.min_length), - pattern: s1.string.pattern.or(s2.string.pattern), - }, - array: ArrayValidation { - items: s1.array.items.or(s2.array.items), - additional_items: s1.array.additional_items.or(s2.array.additional_items), - max_items: s1.array.max_items.or(s2.array.max_items), - min_items: s1.array.min_items.or(s2.array.min_items), - unique_items: s1.array.unique_items.or(s2.array.unique_items), - contains: s1.array.contains.or(s2.array.contains), - }, - object: ObjectValidation { - max_properties: s1.object.max_properties.or(s2.object.max_properties), - min_properties: s1.object.min_properties.or(s2.object.min_properties), - required: extend(s1.object.required, s2.object.required), - properties: extend(s1.object.properties, s2.object.properties), - pattern_properties: extend(s1.object.pattern_properties, s2.object.pattern_properties), - additional_properties: s1.object.additional_properties.or(s2.object.additional_properties), - property_names: s1.object.property_names.or(s2.object.property_names), - }, - })) - } - - fn ensure_flattenable(self) -> Result { - let s = match self { - Schema::Object(s) => s, - s => { - return Err(JsonSchemaError::new( - "Only schemas with type `object` can be flattened.", - s, - )) - } - }; - match s.instance_type { - Some(SingleOrVec::Single(ref t)) if **t != InstanceType::Object => { - Err(JsonSchemaError::new( - "Only schemas with type `object` can be flattened.", - s.into(), - )) - } - Some(SingleOrVec::Vec(ref t)) if !t.contains(&InstanceType::Object) => { - Err(JsonSchemaError::new( - "Only schemas with type `object` can be flattened.", - s.into(), - )) - } - _ => Ok(s), - } - } -} - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] pub struct Ref { #[serde(rename = "$ref")] @@ -134,10 +47,12 @@ pub struct SchemaObject { #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + // TODO Set instead of Vec pub instance_type: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub format: Option, #[serde(rename = "enum", skip_serializing_if = "Option::is_none")] + // TODO Set instead of Vec pub enum_values: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub all_of: Option>,