Further reduce memory footprint of SchemaObject.

More fields are now wrapped in Option<Box<_>>, reducing size of JsonSchema (depending on system) from 424 to 240 bytes.
This commit is contained in:
Graham Esau 2019-10-13 22:38:20 +01:00
parent 72629a3c37
commit c78d721fc5
6 changed files with 87 additions and 59 deletions

View file

@ -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 {

View file

@ -124,30 +124,22 @@ impl SchemaGenerator {
self.definitions
}
pub fn root_schema_for<T: ?Sized + JsonSchema>(&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<T: ?Sized + JsonSchema>(&mut self) -> Result<SchemaObject> {
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<T: ?Sized + JsonSchema>(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<T: ?Sized + JsonSchema>(mut self) -> Result<SchemaObject> {
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<Schema> {

View file

@ -27,8 +27,11 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
},
) => Schema::Object(with_null_type(obj)),
schema => SchemaObject {
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::<Option<Foo>>();
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);
}
}

View file

@ -38,14 +38,8 @@ impl From<bool> 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<String>,
#[serde(rename = "$id", skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(flatten, deserialize_with = "skip_if_default")]
pub metadata: Option<Box<Metadata>>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub instance_type: Option<SingleOrVec<InstanceType>>,
#[serde(skip_serializing_if = "Option::is_none")]
@ -54,23 +48,11 @@ pub struct SchemaObject {
pub enum_values: Option<Vec<Value>>,
#[serde(rename = "const", skip_serializing_if = "Option::is_none")]
pub const_value: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub all_of: Option<Vec<Schema>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub any_of: Option<Vec<Schema>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub one_of: Option<Vec<Schema>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub not: Option<Box<Schema>>,
#[serde(rename = "if", skip_serializing_if = "Option::is_none")]
pub if_schema: Option<Box<Schema>>,
#[serde(rename = "then", skip_serializing_if = "Option::is_none")]
pub then_schema: Option<Box<Schema>>,
#[serde(rename = "else", skip_serializing_if = "Option::is_none")]
pub else_schema: Option<Box<Schema>>,
#[serde(alias = "$defs", skip_serializing_if = "Map::is_empty")]
pub definitions: Map<String, Schema>,
#[serde(flatten, deserialize_with = "skip_if_default")]
pub subschemas: Option<Box<SubschemaValidation>>,
#[serde(flatten, deserialize_with = "skip_if_default")]
pub number: Option<Box<NumberValidation>>,
#[serde(flatten, deserialize_with = "skip_if_default")]
pub string: Option<Box<StringValidation>>,
@ -123,13 +105,48 @@ impl From<Schema> for SchemaObject {
Schema::Object(o) => o,
Schema::Bool(true) => SchemaObject::default(),
Schema::Bool(false) => SchemaObject {
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<String>,
#[serde(rename = "$id", skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[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<Vec<Schema>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub any_of: Option<Vec<Schema>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub one_of: Option<Vec<Schema>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub not: Option<Box<Schema>>,
#[serde(rename = "if", skip_serializing_if = "Option::is_none")]
pub if_schema: Option<Box<Schema>>,
#[serde(rename = "then", skip_serializing_if = "Option::is_none")]
pub then_schema: Option<Box<Schema>>,
#[serde(rename = "else", skip_serializing_if = "Option::is_none")]
pub else_schema: Option<Box<Schema>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[serde(rename_all = "camelCase", default)]
pub struct NumberValidation {

View file

@ -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<T: JsonSchema>(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(())

View file

@ -159,7 +159,10 @@ fn schema_for_external_tagged_enum<'a>(
}));
wrap_schema_fields(quote! {
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! {
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! {
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
any_of: Some(vec![#(#schemas),*]),
..Default::default()
})),
})
}