From 72629a3c37954d9e27fe4a2dcfab6d6d7a5f5e28 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 13 Oct 2019 20:42:29 +0100 Subject: [PATCH] Reduce memory footprint of SchemaObject. Nested validation structs are now wrapped in Option>, reducing size of JsonSchema (depending on system) from 688 to 424 bytes. --- schemars/src/json_schema_impls/array.rs | 18 +++++++----- schemars/src/json_schema_impls/core.rs | 4 +++ schemars/src/json_schema_impls/maps.rs | 7 +++-- schemars/src/json_schema_impls/primitives.rs | 4 +-- schemars/src/json_schema_impls/sequences.rs | 4 +-- schemars/src/json_schema_impls/tuple.rs | 11 +++---- schemars/src/lib.rs | 4 +++ schemars/src/schema.rs | 29 ++++++++++++++----- .../tests/expected/schema-name-custom.json | 11 ++++++- .../tests/expected/schema-name-default.json | 11 ++++++- schemars/tests/schema_name.rs | 8 +++-- schemars_derive/src/lib.rs | 14 ++++----- 12 files changed, 87 insertions(+), 38 deletions(-) diff --git a/schemars/src/json_schema_impls/array.rs b/schemars/src/json_schema_impls/array.rs index 0ed1469..289346c 100644 --- a/schemars/src/json_schema_impls/array.rs +++ b/schemars/src/json_schema_impls/array.rs @@ -13,10 +13,10 @@ impl JsonSchema for [T; 0] { fn json_schema(_: &mut SchemaGenerator) -> Result { Ok(SchemaObject { instance_type: Some(InstanceType::Array.into()), - array: ArrayValidation { + array: Some(Box::new(ArrayValidation { max_items: Some(0), ..Default::default() - }, + })), ..Default::default() } .into()) @@ -36,12 +36,12 @@ macro_rules! array_impls { fn json_schema(gen: &mut SchemaGenerator) -> Result { Ok(SchemaObject { instance_type: Some(InstanceType::Array.into()), - array: ArrayValidation { + array: Some(Box::new(ArrayValidation { items: Some(gen.subschema_for::()?.into()), max_items: Some($len), min_items: Some($len), ..Default::default() - }, + })), ..Default::default() }.into()) } @@ -70,12 +70,13 @@ mod tests { schema.instance_type, Some(SingleOrVec::from(InstanceType::Array)) ); + let array_validation = schema.array.unwrap(); assert_eq!( - schema.array.items, + array_validation.items, Some(SingleOrVec::from(schema_for::())) ); - assert_eq!(schema.array.max_items, Some(8)); - assert_eq!(schema.array.min_items, Some(8)); + assert_eq!(array_validation.max_items, Some(8)); + assert_eq!(array_validation.min_items, Some(8)); } // SomeStruct does not implement JsonSchema @@ -88,6 +89,7 @@ mod tests { schema.instance_type, Some(SingleOrVec::from(InstanceType::Array)) ); - assert_eq!(schema.array.max_items, Some(0)); + let array_validation = schema.array.unwrap(); + assert_eq!(array_validation.max_items, Some(0)); } } diff --git a/schemars/src/json_schema_impls/core.rs b/schemars/src/json_schema_impls/core.rs index e029344..e72253d 100644 --- a/schemars/src/json_schema_impls/core.rs +++ b/schemars/src/json_schema_impls/core.rs @@ -40,6 +40,10 @@ impl JsonSchema for Option { }; Ok(schema) } + + fn json_schema_non_null(gen: &mut SchemaGenerator) -> Result { + T::json_schema_non_null(gen) + } } fn with_null_type(mut obj: SchemaObject) -> SchemaObject { diff --git a/schemars/src/json_schema_impls/maps.rs b/schemars/src/json_schema_impls/maps.rs index c344b6c..db310e8 100644 --- a/schemars/src/json_schema_impls/maps.rs +++ b/schemars/src/json_schema_impls/maps.rs @@ -27,10 +27,10 @@ macro_rules! map_impl { }; Ok(SchemaObject { instance_type: Some(InstanceType::Object.into()), - object: ObjectValidation { + object: Some(Box::new(ObjectValidation { additional_properties: Some(Box::new(additional_properties)), ..Default::default() - }, + })), ..Default::default() }.into()) } @@ -62,6 +62,7 @@ mod tests { Some(SingleOrVec::from(InstanceType::Object)) ); let additional_properties = schema.object + .unwrap() .additional_properties .expect("additionalProperties field present"); assert_eq!(*additional_properties, Schema::Bool(true)); @@ -80,6 +81,7 @@ mod tests { Some(SingleOrVec::from(InstanceType::Object)) ); let additional_properties = schema.object + .unwrap() .additional_properties .expect("additionalProperties field present"); assert_eq!(*additional_properties, Schema::Object(Default::default())); @@ -102,6 +104,7 @@ mod tests { Some(SingleOrVec::from(InstanceType::Object)) ); let additional_properties = schema.object + .unwrap() .additional_properties .expect("additionalProperties field present"); assert_eq!(*additional_properties, schema_for::()); diff --git a/schemars/src/json_schema_impls/primitives.rs b/schemars/src/json_schema_impls/primitives.rs index 44b9d36..7ed27c2 100644 --- a/schemars/src/json_schema_impls/primitives.rs +++ b/schemars/src/json_schema_impls/primitives.rs @@ -58,11 +58,11 @@ impl JsonSchema for char { fn json_schema(_: &mut SchemaGenerator) -> Result { Ok(SchemaObject { instance_type: Some(InstanceType::String.into()), - string: StringValidation { + string: Some(Box::new(StringValidation { min_length: Some(1), max_length: Some(1), ..Default::default() - }, + })), ..Default::default() } .into()) diff --git a/schemars/src/json_schema_impls/sequences.rs b/schemars/src/json_schema_impls/sequences.rs index a55b812..32608f3 100644 --- a/schemars/src/json_schema_impls/sequences.rs +++ b/schemars/src/json_schema_impls/sequences.rs @@ -17,10 +17,10 @@ macro_rules! seq_impl { fn json_schema(gen: &mut SchemaGenerator) -> Result { Ok(SchemaObject { instance_type: Some(InstanceType::Array.into()), - array: ArrayValidation { + array: Some(Box::new(ArrayValidation { items: Some(gen.subschema_for::()?.into()), ..Default::default() - }, + })), ..Default::default() }.into()) } diff --git a/schemars/src/json_schema_impls/tuple.rs b/schemars/src/json_schema_impls/tuple.rs index f90dfd4..bc269b1 100644 --- a/schemars/src/json_schema_impls/tuple.rs +++ b/schemars/src/json_schema_impls/tuple.rs @@ -18,12 +18,12 @@ macro_rules! tuple_impls { ]; Ok(SchemaObject { instance_type: Some(InstanceType::Array.into()), - array: ArrayValidation { + array: Some(Box::new(ArrayValidation { items: Some(items.into()), max_items: Some($len), min_items: Some($len), ..Default::default() - }, + })), ..Default::default() }.into()) } @@ -64,14 +64,15 @@ mod tests { schema.instance_type, Some(SingleOrVec::from(InstanceType::Array)) ); + let array_validation = schema.array.unwrap(); assert_eq!( - schema.array.items, + array_validation.items, Some(SingleOrVec::Vec(vec![ schema_for::(), schema_for::() ])) ); - assert_eq!(schema.array.max_items, Some(2)); - assert_eq!(schema.array.min_items, Some(2)); + assert_eq!(array_validation.max_items, Some(2)); + assert_eq!(array_validation.min_items, Some(2)); } } diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index f38bea7..9dede78 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -23,6 +23,10 @@ pub trait JsonSchema { fn schema_name() -> String; fn json_schema(gen: &mut gen::SchemaGenerator) -> Result; + + fn json_schema_non_null(gen: &mut gen::SchemaGenerator) -> Result { + Self::json_schema(gen) + } } #[cfg(test)] diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index 7749f88..1e3ae81 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -70,20 +70,33 @@ pub struct SchemaObject { pub else_schema: Option>, #[serde(alias = "$defs", skip_serializing_if = "Map::is_empty")] pub definitions: Map, - #[serde(flatten)] - pub number: NumberValidation, - #[serde(flatten)] - pub string: StringValidation, - #[serde(flatten)] - pub array: ArrayValidation, - #[serde(flatten)] - pub object: ObjectValidation, + #[serde(flatten, deserialize_with = "skip_if_default")] + pub number: Option>, + #[serde(flatten, deserialize_with = "skip_if_default")] + pub string: Option>, + #[serde(flatten, deserialize_with = "skip_if_default")] + pub array: Option>, + #[serde(flatten, deserialize_with = "skip_if_default")] + pub object: Option>, #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")] pub reference: Option, #[serde(flatten)] pub extensions: Map, } +fn skip_if_default<'de, D, T>(deserializer: D) -> Result>, D::Error> +where + D: serde::Deserializer<'de>, + T: Deserialize<'de> + Default + PartialEq, +{ + let value = T::deserialize(deserializer)?; + if value == T::default() { + Ok(None) + } else { + Ok(Some(Box::new(value))) + } +} + impl SchemaObject { pub fn new_ref(reference: String) -> Self { SchemaObject { diff --git a/schemars/tests/expected/schema-name-custom.json b/schemars/tests/expected/schema-name-custom.json index 362c772..bc05f52 100644 --- a/schemars/tests/expected/schema-name-custom.json +++ b/schemars/tests/expected/schema-name-custom.json @@ -4,7 +4,16 @@ "type": "object", "definitions": { "another-new-name": { - "type": "object" + "type": "object", + "required": [ + "foo" + ], + "properties": { + "foo": { + "type": "integer", + "format": "int32" + } + } } }, "required": [ diff --git a/schemars/tests/expected/schema-name-default.json b/schemars/tests/expected/schema-name-default.json index 5742228..74560c5 100644 --- a/schemars/tests/expected/schema-name-default.json +++ b/schemars/tests/expected/schema-name-default.json @@ -4,7 +4,16 @@ "type": "object", "definitions": { "MySimpleStruct": { - "type": "object" + "type": "object", + "required": [ + "foo" + ], + "properties": { + "foo": { + "type": "integer", + "format": "int32" + } + } } }, "required": [ diff --git a/schemars/tests/schema_name.rs b/schemars/tests/schema_name.rs index 8b34318..bcba991 100644 --- a/schemars/tests/schema_name.rs +++ b/schemars/tests/schema_name.rs @@ -12,7 +12,9 @@ struct MyStruct { } #[derive(Debug, JsonSchema)] -struct MySimpleStruct {} +struct MySimpleStruct { + foo: i32, +} #[test] fn default_name_multiple_type_params() -> TestResult { @@ -33,7 +35,9 @@ struct MyRenamedStruct { #[derive(Debug, JsonSchema)] #[serde(rename = "this-attribute-is-ignored")] #[schemars(rename = "another-new-name")] -struct MySimpleRenamedStruct {} +struct MySimpleRenamedStruct { + foo: i32, +} #[test] fn overriden_with_rename_multiple_type_params() -> TestResult { diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index 5374f0a..8733141 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -142,7 +142,7 @@ fn schema_for_external_tagged_enum<'a>( let sub_schema = schema_for_untagged_enum_variant(variant, cattrs); wrap_schema_fields(quote! { instance_type: Some(schemars::schema::InstanceType::Object.into()), - object: schemars::schema::ObjectValidation { + object: Some(Box::new(schemars::schema::ObjectValidation { properties: { let mut props = schemars::Map::new(); props.insert(#name.to_owned(), #sub_schema); @@ -154,7 +154,7 @@ fn schema_for_external_tagged_enum<'a>( required }, ..Default::default() - }, + })), }) })); @@ -176,7 +176,7 @@ fn schema_for_internal_tagged_enum<'a>( }); let tag_schema = wrap_schema_fields(quote! { instance_type: Some(schemars::schema::InstanceType::Object.into()), - object: schemars::schema::ObjectValidation { + object: Some(Box::new(schemars::schema::ObjectValidation { properties: { let mut props = schemars::Map::new(); props.insert(#tag_name.to_owned(), #type_schema); @@ -188,7 +188,7 @@ fn schema_for_internal_tagged_enum<'a>( required }, ..Default::default() - }, + })), }); let variant_schema = match variant.style { Style::Unit => return tag_schema, @@ -275,7 +275,7 @@ fn schema_for_struct(fields: &[Field], cattrs: &attr::Container) -> TokenStream let schema = wrap_schema_fields(quote! { instance_type: Some(schemars::schema::InstanceType::Object.into()), - object: schemars::schema::ObjectValidation { + object: Some(Box::new(schemars::schema::ObjectValidation { properties: { let mut props = schemars::Map::new(); #(#recurse)* @@ -287,13 +287,13 @@ fn schema_for_struct(fields: &[Field], cattrs: &attr::Container) -> TokenStream required }, ..Default::default() - }, + })), }); let flattens = flat.iter().map(|field| { let ty = get_json_schema_type(field); quote_spanned! {field.original.span()=> - .flatten(<#ty>::json_schema(gen)?)? + .flatten(<#ty>::json_schema_non_null(gen)?)? } });