From c78d721fc58bc993620ff8831a690c05081e0e2b Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 13 Oct 2019 22:38:20 +0100 Subject: [PATCH] Further reduce memory footprint of SchemaObject. More fields are now wrapped in Option>, reducing size of JsonSchema (depending on system) from 424 to 240 bytes. --- schemars/src/flatten.rs | 13 ++++-- schemars/src/gen.rs | 36 ++++++--------- schemars/src/json_schema_impls/core.rs | 13 ++++-- schemars/src/schema.rs | 63 ++++++++++++++++---------- schemars/tests/util/mod.rs | 6 +-- schemars_derive/src/lib.rs | 15 ++++-- 6 files changed, 87 insertions(+), 59 deletions(-) diff --git a/schemars/src/flatten.rs b/schemars/src/flatten.rs index 90b15c6..82f452c 100644 --- a/schemars/src/flatten.rs +++ b/schemars/src/flatten.rs @@ -36,9 +36,16 @@ macro_rules! impl_merge { impl_merge!(SchemaObject { merge: definitions extensions instance_type enum_values - number string array object, - or: schema id title description format const_value all_of any_of one_of not - if_schema then_schema else_schema reference, + metadata subschemas number string array object, + or: format const_value reference, +}); + +impl_merge!(Metadata { + or: schema id title description, +}); + +impl_merge!(SubschemaValidation { + or: all_of any_of one_of not if_schema then_schema else_schema, }); impl_merge!(NumberValidation { diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 6fbe574..f51d34d 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -124,30 +124,22 @@ impl SchemaGenerator { self.definitions } - pub fn root_schema_for(&mut self) -> Result { - let schema = T::json_schema(self)?; - Ok(match schema { - Schema::Object(mut o) => { - o.schema = Some("http://json-schema.org/draft-07/schema#".to_owned()); - o.title = Some(T::schema_name()); - o.definitions.extend(self.definitions().clone()); - Schema::Object(o) - } - schema => schema, - }) + pub fn root_schema_for(&mut self) -> Result { + let mut schema: SchemaObject = T::json_schema(self)?.into(); + let metadata = schema.metadata.get_or_insert_with(Default::default); + metadata.schema = Some("http://json-schema.org/draft-07/schema#".to_owned()); + metadata.title = Some(T::schema_name()); + schema.definitions.extend(self.definitions().clone()); + Ok(schema) } - pub fn into_root_schema_for(mut self) -> Result { - let schema = T::json_schema(&mut self)?; - Ok(match schema { - Schema::Object(mut o) => { - o.schema = Some("http://json-schema.org/draft-07/schema#".to_owned()); - o.title = Some(T::schema_name()); - o.definitions.extend(self.into_definitions()); - Schema::Object(o) - } - schema => schema, - }) + pub fn into_root_schema_for(mut self) -> Result { + let mut schema: SchemaObject = T::json_schema(&mut self)?.into(); + let metadata = schema.metadata.get_or_insert_with(Default::default); + metadata.schema = Some("http://json-schema.org/draft-07/schema#".to_owned()); + metadata.title = Some(T::schema_name()); + schema.definitions.extend(self.into_definitions()); + Ok(schema) } pub fn dereference_once(&self, schema: Schema) -> Result { diff --git a/schemars/src/json_schema_impls/core.rs b/schemars/src/json_schema_impls/core.rs index e72253d..27f07a6 100644 --- a/schemars/src/json_schema_impls/core.rs +++ b/schemars/src/json_schema_impls/core.rs @@ -27,7 +27,10 @@ impl JsonSchema for Option { }, ) => Schema::Object(with_null_type(obj)), schema => SchemaObject { - any_of: Some(vec![schema, <()>::json_schema(gen)?]), + subschemas: Some(Box::new(SubschemaValidation { + any_of: Some(vec![schema, <()>::json_schema(gen)?]), + ..Default::default() + })), ..Default::default() } .into(), @@ -99,7 +102,7 @@ mod tests { Some(vec![InstanceType::Integer, InstanceType::Null].into()) ); assert_eq!(schema.extensions.get("nullable"), None); - assert_eq!(schema.any_of.is_none(), true); + assert_eq!(schema.subschemas.is_none(), true); } #[test] @@ -111,8 +114,8 @@ mod tests { let schema = schema_object_for::>(); assert_eq!(schema.instance_type, None); assert_eq!(schema.extensions.get("nullable"), None); - assert_eq!(schema.any_of.is_some(), true); - let any_of = schema.any_of.unwrap(); + assert_eq!(schema.subschemas.is_some(), true); + let any_of = schema.subschemas.unwrap().any_of.unwrap(); assert_eq!(any_of.len(), 2); assert_eq!(any_of[0], Schema::new_ref("#/definitions/Foo".to_string())); assert_eq!(any_of[1], schema_for::<()>()); @@ -131,6 +134,6 @@ mod tests { Some(SingleOrVec::from(InstanceType::Integer)) ); assert_eq!(schema.extensions.get("nullable"), Some(&json!(true))); - assert_eq!(schema.any_of.is_none(), true); + assert_eq!(schema.subschemas.is_none(), true); } } diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index 1e3ae81..eb19372 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -38,14 +38,8 @@ impl From for Schema { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[serde(rename_all = "camelCase", default)] pub struct SchemaObject { - #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")] - pub schema: Option, - #[serde(rename = "$id", skip_serializing_if = "Option::is_none")] - pub id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, + #[serde(flatten, deserialize_with = "skip_if_default")] + pub metadata: Option>, #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub instance_type: Option>, #[serde(skip_serializing_if = "Option::is_none")] @@ -54,23 +48,11 @@ pub struct SchemaObject { pub enum_values: Option>, #[serde(rename = "const", skip_serializing_if = "Option::is_none")] pub const_value: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub all_of: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub any_of: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub one_of: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub not: Option>, - #[serde(rename = "if", skip_serializing_if = "Option::is_none")] - pub if_schema: Option>, - #[serde(rename = "then", skip_serializing_if = "Option::is_none")] - pub then_schema: Option>, - #[serde(rename = "else", skip_serializing_if = "Option::is_none")] - pub else_schema: Option>, #[serde(alias = "$defs", skip_serializing_if = "Map::is_empty")] pub definitions: Map, #[serde(flatten, deserialize_with = "skip_if_default")] + pub subschemas: Option>, + #[serde(flatten, deserialize_with = "skip_if_default")] pub number: Option>, #[serde(flatten, deserialize_with = "skip_if_default")] pub string: Option>, @@ -123,13 +105,48 @@ impl From for SchemaObject { Schema::Object(o) => o, Schema::Bool(true) => SchemaObject::default(), Schema::Bool(false) => SchemaObject { - not: Some(Schema::Object(Default::default()).into()), + subschemas: Some(Box::new(SubschemaValidation { + not: Some(Schema::Object(Default::default()).into()), + ..Default::default() + })), ..Default::default() }, } } } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] +#[serde(rename_all = "camelCase", default)] +pub struct Metadata { + #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")] + pub schema: Option, + #[serde(rename = "$id", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] +#[serde(rename_all = "camelCase", default)] +pub struct SubschemaValidation { + #[serde(skip_serializing_if = "Option::is_none")] + pub all_of: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub any_of: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub one_of: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub not: Option>, + #[serde(rename = "if", skip_serializing_if = "Option::is_none")] + pub if_schema: Option>, + #[serde(rename = "then", skip_serializing_if = "Option::is_none")] + pub then_schema: Option>, + #[serde(rename = "else", skip_serializing_if = "Option::is_none")] + pub else_schema: Option>, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[serde(rename_all = "camelCase", default)] pub struct NumberValidation { diff --git a/schemars/tests/util/mod.rs b/schemars/tests/util/mod.rs index c6d5ed4..47b1d0f 100644 --- a/schemars/tests/util/mod.rs +++ b/schemars/tests/util/mod.rs @@ -1,5 +1,5 @@ use pretty_assertions::assert_eq; -use schemars::{gen::SchemaSettings, schema::Schema, schema_for, JsonSchema}; +use schemars::{gen::SchemaSettings, schema::SchemaObject, schema_for, JsonSchema}; use std::error::Error; use std::fs; use std::panic; @@ -18,7 +18,7 @@ pub fn test_default_generated_schema(file: &str) -> TestResult { test_schema(&actual, file) } -fn test_schema(actual: &Schema, file: &str) -> TestResult { +fn test_schema(actual: &SchemaObject, file: &str) -> TestResult { let expected_json = match fs::read_to_string(format!("tests/expected/{}.json", file)) { Ok(j) => j, Err(e) => { @@ -36,7 +36,7 @@ fn test_schema(actual: &Schema, file: &str) -> TestResult { Ok(()) } -fn write_actual_to_file(schema: &Schema, file: &str) -> TestResult { +fn write_actual_to_file(schema: &SchemaObject, file: &str) -> TestResult { let actual_json = serde_json::to_string_pretty(&schema)?; fs::write(format!("tests/actual/{}.json", file), actual_json)?; Ok(()) diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index 8733141..4a01342 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -159,7 +159,10 @@ fn schema_for_external_tagged_enum<'a>( })); wrap_schema_fields(quote! { - any_of: Some(vec![#(#schemas),*]), + subschemas: Some(Box::new(schemars::schema::SubschemaValidation { + any_of: Some(vec![#(#schemas),*]), + ..Default::default() + })), }) } @@ -208,7 +211,10 @@ fn schema_for_internal_tagged_enum<'a>( }); wrap_schema_fields(quote! { - any_of: Some(vec![#(#schemas),*]), + subschemas: Some(Box::new(schemars::schema::SubschemaValidation { + any_of: Some(vec![#(#schemas),*]), + ..Default::default() + })), }) } @@ -219,7 +225,10 @@ fn schema_for_untagged_enum<'a>( let schemas = variants.map(|v| schema_for_untagged_enum_variant(v, cattrs)); wrap_schema_fields(quote! { - any_of: Some(vec![#(#schemas),*]), + subschemas: Some(Box::new(schemars::schema::SubschemaValidation { + any_of: Some(vec![#(#schemas),*]), + ..Default::default() + })), }) }