Reduce memory footprint of SchemaObject.

Nested validation structs are now wrapped in Option<Box<_>>, reducing size of JsonSchema (depending on system) from 688 to 424 bytes.
This commit is contained in:
Graham Esau 2019-10-13 20:42:29 +01:00
parent 5a82498e28
commit 72629a3c37
12 changed files with 87 additions and 38 deletions

View file

@ -13,10 +13,10 @@ impl<T> JsonSchema for [T; 0] {
fn json_schema(_: &mut SchemaGenerator) -> Result { fn json_schema(_: &mut SchemaGenerator) -> Result {
Ok(SchemaObject { Ok(SchemaObject {
instance_type: Some(InstanceType::Array.into()), instance_type: Some(InstanceType::Array.into()),
array: ArrayValidation { array: Some(Box::new(ArrayValidation {
max_items: Some(0), max_items: Some(0),
..Default::default() ..Default::default()
}, })),
..Default::default() ..Default::default()
} }
.into()) .into())
@ -36,12 +36,12 @@ macro_rules! array_impls {
fn json_schema(gen: &mut SchemaGenerator) -> Result { fn json_schema(gen: &mut SchemaGenerator) -> Result {
Ok(SchemaObject { Ok(SchemaObject {
instance_type: Some(InstanceType::Array.into()), instance_type: Some(InstanceType::Array.into()),
array: ArrayValidation { array: Some(Box::new(ArrayValidation {
items: Some(gen.subschema_for::<T>()?.into()), items: Some(gen.subschema_for::<T>()?.into()),
max_items: Some($len), max_items: Some($len),
min_items: Some($len), min_items: Some($len),
..Default::default() ..Default::default()
}, })),
..Default::default() ..Default::default()
}.into()) }.into())
} }
@ -70,12 +70,13 @@ mod tests {
schema.instance_type, schema.instance_type,
Some(SingleOrVec::from(InstanceType::Array)) Some(SingleOrVec::from(InstanceType::Array))
); );
let array_validation = schema.array.unwrap();
assert_eq!( assert_eq!(
schema.array.items, array_validation.items,
Some(SingleOrVec::from(schema_for::<i32>())) Some(SingleOrVec::from(schema_for::<i32>()))
); );
assert_eq!(schema.array.max_items, Some(8)); assert_eq!(array_validation.max_items, Some(8));
assert_eq!(schema.array.min_items, Some(8)); assert_eq!(array_validation.min_items, Some(8));
} }
// SomeStruct does not implement JsonSchema // SomeStruct does not implement JsonSchema
@ -88,6 +89,7 @@ mod tests {
schema.instance_type, schema.instance_type,
Some(SingleOrVec::from(InstanceType::Array)) 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));
} }
} }

View file

@ -40,6 +40,10 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
}; };
Ok(schema) 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 { fn with_null_type(mut obj: SchemaObject) -> SchemaObject {

View file

@ -27,10 +27,10 @@ macro_rules! map_impl {
}; };
Ok(SchemaObject { Ok(SchemaObject {
instance_type: Some(InstanceType::Object.into()), instance_type: Some(InstanceType::Object.into()),
object: ObjectValidation { object: Some(Box::new(ObjectValidation {
additional_properties: Some(Box::new(additional_properties)), additional_properties: Some(Box::new(additional_properties)),
..Default::default() ..Default::default()
}, })),
..Default::default() ..Default::default()
}.into()) }.into())
} }
@ -62,6 +62,7 @@ mod tests {
Some(SingleOrVec::from(InstanceType::Object)) Some(SingleOrVec::from(InstanceType::Object))
); );
let additional_properties = schema.object let additional_properties = schema.object
.unwrap()
.additional_properties .additional_properties
.expect("additionalProperties field present"); .expect("additionalProperties field present");
assert_eq!(*additional_properties, Schema::Bool(true)); assert_eq!(*additional_properties, Schema::Bool(true));
@ -80,6 +81,7 @@ mod tests {
Some(SingleOrVec::from(InstanceType::Object)) Some(SingleOrVec::from(InstanceType::Object))
); );
let additional_properties = schema.object let additional_properties = schema.object
.unwrap()
.additional_properties .additional_properties
.expect("additionalProperties field present"); .expect("additionalProperties field present");
assert_eq!(*additional_properties, Schema::Object(Default::default())); assert_eq!(*additional_properties, Schema::Object(Default::default()));
@ -102,6 +104,7 @@ mod tests {
Some(SingleOrVec::from(InstanceType::Object)) Some(SingleOrVec::from(InstanceType::Object))
); );
let additional_properties = schema.object let additional_properties = schema.object
.unwrap()
.additional_properties .additional_properties
.expect("additionalProperties field present"); .expect("additionalProperties field present");
assert_eq!(*additional_properties, schema_for::<i32>()); assert_eq!(*additional_properties, schema_for::<i32>());

View file

@ -58,11 +58,11 @@ impl JsonSchema for char {
fn json_schema(_: &mut SchemaGenerator) -> Result { fn json_schema(_: &mut SchemaGenerator) -> Result {
Ok(SchemaObject { Ok(SchemaObject {
instance_type: Some(InstanceType::String.into()), instance_type: Some(InstanceType::String.into()),
string: StringValidation { string: Some(Box::new(StringValidation {
min_length: Some(1), min_length: Some(1),
max_length: Some(1), max_length: Some(1),
..Default::default() ..Default::default()
}, })),
..Default::default() ..Default::default()
} }
.into()) .into())

View file

@ -17,10 +17,10 @@ macro_rules! seq_impl {
fn json_schema(gen: &mut SchemaGenerator) -> Result { fn json_schema(gen: &mut SchemaGenerator) -> Result {
Ok(SchemaObject { Ok(SchemaObject {
instance_type: Some(InstanceType::Array.into()), instance_type: Some(InstanceType::Array.into()),
array: ArrayValidation { array: Some(Box::new(ArrayValidation {
items: Some(gen.subschema_for::<T>()?.into()), items: Some(gen.subschema_for::<T>()?.into()),
..Default::default() ..Default::default()
}, })),
..Default::default() ..Default::default()
}.into()) }.into())
} }

View file

@ -18,12 +18,12 @@ macro_rules! tuple_impls {
]; ];
Ok(SchemaObject { Ok(SchemaObject {
instance_type: Some(InstanceType::Array.into()), instance_type: Some(InstanceType::Array.into()),
array: ArrayValidation { array: Some(Box::new(ArrayValidation {
items: Some(items.into()), items: Some(items.into()),
max_items: Some($len), max_items: Some($len),
min_items: Some($len), min_items: Some($len),
..Default::default() ..Default::default()
}, })),
..Default::default() ..Default::default()
}.into()) }.into())
} }
@ -64,14 +64,15 @@ mod tests {
schema.instance_type, schema.instance_type,
Some(SingleOrVec::from(InstanceType::Array)) Some(SingleOrVec::from(InstanceType::Array))
); );
let array_validation = schema.array.unwrap();
assert_eq!( assert_eq!(
schema.array.items, array_validation.items,
Some(SingleOrVec::Vec(vec![ Some(SingleOrVec::Vec(vec![
schema_for::<i32>(), schema_for::<i32>(),
schema_for::<bool>() schema_for::<bool>()
])) ]))
); );
assert_eq!(schema.array.max_items, Some(2)); assert_eq!(array_validation.max_items, Some(2));
assert_eq!(schema.array.min_items, Some(2)); assert_eq!(array_validation.min_items, Some(2));
} }
} }

View file

@ -23,6 +23,10 @@ pub trait JsonSchema {
fn schema_name() -> String; fn schema_name() -> String;
fn json_schema(gen: &mut gen::SchemaGenerator) -> Result; fn json_schema(gen: &mut gen::SchemaGenerator) -> Result;
fn json_schema_non_null(gen: &mut gen::SchemaGenerator) -> Result {
Self::json_schema(gen)
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -70,20 +70,33 @@ pub struct SchemaObject {
pub else_schema: Option<Box<Schema>>, pub else_schema: Option<Box<Schema>>,
#[serde(alias = "$defs", skip_serializing_if = "Map::is_empty")] #[serde(alias = "$defs", skip_serializing_if = "Map::is_empty")]
pub definitions: Map<String, Schema>, pub definitions: Map<String, Schema>,
#[serde(flatten)] #[serde(flatten, deserialize_with = "skip_if_default")]
pub number: NumberValidation, pub number: Option<Box<NumberValidation>>,
#[serde(flatten)] #[serde(flatten, deserialize_with = "skip_if_default")]
pub string: StringValidation, pub string: Option<Box<StringValidation>>,
#[serde(flatten)] #[serde(flatten, deserialize_with = "skip_if_default")]
pub array: ArrayValidation, pub array: Option<Box<ArrayValidation>>,
#[serde(flatten)] #[serde(flatten, deserialize_with = "skip_if_default")]
pub object: ObjectValidation, pub object: Option<Box<ObjectValidation>>,
#[serde(rename = "$ref", skip_serializing_if = "Option::is_none")] #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
pub reference: Option<String>, pub reference: Option<String>,
#[serde(flatten)] #[serde(flatten)]
pub extensions: Map<String, Value>, pub extensions: Map<String, Value>,
} }
fn skip_if_default<'de, D, T>(deserializer: D) -> Result<Option<Box<T>>, 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 { impl SchemaObject {
pub fn new_ref(reference: String) -> Self { pub fn new_ref(reference: String) -> Self {
SchemaObject { SchemaObject {

View file

@ -4,7 +4,16 @@
"type": "object", "type": "object",
"definitions": { "definitions": {
"another-new-name": { "another-new-name": {
"type": "object" "type": "object",
"required": [
"foo"
],
"properties": {
"foo": {
"type": "integer",
"format": "int32"
}
}
} }
}, },
"required": [ "required": [

View file

@ -4,7 +4,16 @@
"type": "object", "type": "object",
"definitions": { "definitions": {
"MySimpleStruct": { "MySimpleStruct": {
"type": "object" "type": "object",
"required": [
"foo"
],
"properties": {
"foo": {
"type": "integer",
"format": "int32"
}
}
} }
}, },
"required": [ "required": [

View file

@ -12,7 +12,9 @@ struct MyStruct<T, U, V, W> {
} }
#[derive(Debug, JsonSchema)] #[derive(Debug, JsonSchema)]
struct MySimpleStruct {} struct MySimpleStruct {
foo: i32,
}
#[test] #[test]
fn default_name_multiple_type_params() -> TestResult { fn default_name_multiple_type_params() -> TestResult {
@ -33,7 +35,9 @@ struct MyRenamedStruct<T, U, V, W> {
#[derive(Debug, JsonSchema)] #[derive(Debug, JsonSchema)]
#[serde(rename = "this-attribute-is-ignored")] #[serde(rename = "this-attribute-is-ignored")]
#[schemars(rename = "another-new-name")] #[schemars(rename = "another-new-name")]
struct MySimpleRenamedStruct {} struct MySimpleRenamedStruct {
foo: i32,
}
#[test] #[test]
fn overriden_with_rename_multiple_type_params() -> TestResult { fn overriden_with_rename_multiple_type_params() -> TestResult {

View file

@ -142,7 +142,7 @@ fn schema_for_external_tagged_enum<'a>(
let sub_schema = schema_for_untagged_enum_variant(variant, cattrs); let sub_schema = schema_for_untagged_enum_variant(variant, cattrs);
wrap_schema_fields(quote! { wrap_schema_fields(quote! {
instance_type: Some(schemars::schema::InstanceType::Object.into()), instance_type: Some(schemars::schema::InstanceType::Object.into()),
object: schemars::schema::ObjectValidation { object: Some(Box::new(schemars::schema::ObjectValidation {
properties: { properties: {
let mut props = schemars::Map::new(); let mut props = schemars::Map::new();
props.insert(#name.to_owned(), #sub_schema); props.insert(#name.to_owned(), #sub_schema);
@ -154,7 +154,7 @@ fn schema_for_external_tagged_enum<'a>(
required required
}, },
..Default::default() ..Default::default()
}, })),
}) })
})); }));
@ -176,7 +176,7 @@ fn schema_for_internal_tagged_enum<'a>(
}); });
let tag_schema = wrap_schema_fields(quote! { let tag_schema = wrap_schema_fields(quote! {
instance_type: Some(schemars::schema::InstanceType::Object.into()), instance_type: Some(schemars::schema::InstanceType::Object.into()),
object: schemars::schema::ObjectValidation { object: Some(Box::new(schemars::schema::ObjectValidation {
properties: { properties: {
let mut props = schemars::Map::new(); let mut props = schemars::Map::new();
props.insert(#tag_name.to_owned(), #type_schema); props.insert(#tag_name.to_owned(), #type_schema);
@ -188,7 +188,7 @@ fn schema_for_internal_tagged_enum<'a>(
required required
}, },
..Default::default() ..Default::default()
}, })),
}); });
let variant_schema = match variant.style { let variant_schema = match variant.style {
Style::Unit => return tag_schema, 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! { let schema = wrap_schema_fields(quote! {
instance_type: Some(schemars::schema::InstanceType::Object.into()), instance_type: Some(schemars::schema::InstanceType::Object.into()),
object: schemars::schema::ObjectValidation { object: Some(Box::new(schemars::schema::ObjectValidation {
properties: { properties: {
let mut props = schemars::Map::new(); let mut props = schemars::Map::new();
#(#recurse)* #(#recurse)*
@ -287,13 +287,13 @@ fn schema_for_struct(fields: &[Field], cattrs: &attr::Container) -> TokenStream
required required
}, },
..Default::default() ..Default::default()
}, })),
}); });
let flattens = flat.iter().map(|field| { let flattens = flat.iter().map(|field| {
let ty = get_json_schema_type(field); let ty = get_json_schema_type(field);
quote_spanned! {field.original.span()=> quote_spanned! {field.original.span()=>
.flatten(<#ty>::json_schema(gen)?)? .flatten(<#ty>::json_schema_non_null(gen)?)?
} }
}); });