From 26c4099bbef228fdf728fa38c4fcfa214ce98e2b Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 9 Dec 2019 12:34:28 +0000 Subject: [PATCH] Do not serialize schema default if it would be skipped by `skip_serializing_if` attribute --- schemars/src/schema.rs | 20 +++++++++++-- schemars/tests/expected/schema-openapi3.json | 13 -------- schemars/tests/expected/schema.json | 13 -------- schemars_derive/src/lib.rs | 6 ++-- schemars_derive/src/metadata.rs | 31 ++++++++++++-------- schemars_derive/src/preprocess.rs | 1 + 6 files changed, 42 insertions(+), 42 deletions(-) diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index 796e3df..b194ea4 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -103,7 +103,11 @@ pub struct SchemaObject { /// The `const` keyword. /// /// See [JSON Schema Validation 6.1.3. "const"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3) - #[serde(rename = "const", skip_serializing_if = "Option::is_none")] + #[serde( + rename = "const", + skip_serializing_if = "Option::is_none", + deserialize_with = "allow_null" + )] pub const_value: Option, /// Properties of the [`SchemaObject`] which define validation assertions in terms of other schemas. #[serde(flatten, deserialize_with = "skip_if_default")] @@ -130,6 +134,15 @@ pub struct SchemaObject { pub extensions: Map, } +// Deserializing "null" to `Option` directly results in `None`, +// this function instead makes it deserialize to `Some(Value::Null)`. +fn allow_null<'de, D>(de: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + Value::deserialize(de).map(Option::Some) +} + fn skip_if_default<'de, D, T>(deserializer: D) -> Result>, D::Error> where D: serde::Deserializer<'de>, @@ -226,7 +239,10 @@ pub struct Metadata { /// The `default` keyword. /// /// See [JSON Schema Validation 9.2. "default"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "allow_null" + )] pub default: Option, /// The `deprecated` keyword. /// diff --git a/schemars/tests/expected/schema-openapi3.json b/schemars/tests/expected/schema-openapi3.json index 925f4ae..7833836 100644 --- a/schemars/tests/expected/schema-openapi3.json +++ b/schemars/tests/expected/schema-openapi3.json @@ -72,7 +72,6 @@ }, "definitions": { "description": "The `$defs` keyword.\n\nThis is currently serialized as `definitions` for backwards compatibility.\n\nSee [JSON Schema 8.2.5. Schema Re-Use With \"$defs\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.5).", - "default": {}, "type": "object", "additionalProperties": { "$ref": "#/components/schemas/Schema" @@ -80,7 +79,6 @@ }, "deprecated": { "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "default": false, "type": "boolean" }, "description": { @@ -222,7 +220,6 @@ }, "patternProperties": { "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "default": {}, "type": "object", "additionalProperties": { "$ref": "#/components/schemas/Schema" @@ -230,7 +227,6 @@ }, "properties": { "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "default": {}, "type": "object", "additionalProperties": { "$ref": "#/components/schemas/Schema" @@ -247,12 +243,10 @@ }, "readOnly": { "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "default": false, "type": "boolean" }, "required": { "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "default": [], "type": "array", "items": { "type": "string" @@ -288,7 +282,6 @@ }, "writeOnly": { "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "default": false, "type": "boolean" } }, @@ -390,7 +383,6 @@ }, "deprecated": { "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "default": false, "type": "boolean" }, "description": { @@ -532,7 +524,6 @@ }, "patternProperties": { "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "default": {}, "type": "object", "additionalProperties": { "$ref": "#/components/schemas/Schema" @@ -540,7 +531,6 @@ }, "properties": { "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "default": {}, "type": "object", "additionalProperties": { "$ref": "#/components/schemas/Schema" @@ -557,12 +547,10 @@ }, "readOnly": { "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "default": false, "type": "boolean" }, "required": { "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "default": [], "type": "array", "items": { "type": "string" @@ -598,7 +586,6 @@ }, "writeOnly": { "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "default": false, "type": "boolean" } }, diff --git a/schemars/tests/expected/schema.json b/schemars/tests/expected/schema.json index 3a1f2e4..4982d5a 100644 --- a/schemars/tests/expected/schema.json +++ b/schemars/tests/expected/schema.json @@ -86,7 +86,6 @@ }, "definitions": { "description": "The `$defs` keyword.\n\nThis is currently serialized as `definitions` for backwards compatibility.\n\nSee [JSON Schema 8.2.5. Schema Re-Use With \"$defs\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.5).", - "default": {}, "type": "object", "additionalProperties": { "$ref": "#/definitions/Schema" @@ -94,7 +93,6 @@ }, "deprecated": { "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "default": false, "type": "boolean" }, "description": { @@ -276,7 +274,6 @@ }, "patternProperties": { "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "default": {}, "type": "object", "additionalProperties": { "$ref": "#/definitions/Schema" @@ -284,7 +281,6 @@ }, "properties": { "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "default": {}, "type": "object", "additionalProperties": { "$ref": "#/definitions/Schema" @@ -303,12 +299,10 @@ }, "readOnly": { "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "default": false, "type": "boolean" }, "required": { "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "default": [], "type": "array", "items": { "type": "string" @@ -352,7 +346,6 @@ }, "writeOnly": { "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "default": false, "type": "boolean" } }, @@ -466,7 +459,6 @@ }, "deprecated": { "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "default": false, "type": "boolean" }, "description": { @@ -648,7 +640,6 @@ }, "patternProperties": { "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "default": {}, "type": "object", "additionalProperties": { "$ref": "#/definitions/Schema" @@ -656,7 +647,6 @@ }, "properties": { "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "default": {}, "type": "object", "additionalProperties": { "$ref": "#/definitions/Schema" @@ -675,12 +665,10 @@ }, "readOnly": { "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "default": false, "type": "boolean" }, "required": { "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "default": [], "type": "array", "items": { "type": "string" @@ -724,7 +712,6 @@ }, "writeOnly": { "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "default": false, "type": "boolean" } }, diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index a795f6a..446703f 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -294,7 +294,9 @@ fn schema_for_struct(fields: &[Field], cattrs: &attr::Container) -> TokenStream let ty = field.ty; let default = match field.attrs.default() { - SerdeDefault::None if set_container_default.is_some() => { + _ if field.attrs.skip_serializing() => None, + SerdeDefault::None if set_container_default.is_none() => None, + SerdeDefault::None => { let field_ident = field .original .ident @@ -302,7 +304,6 @@ fn schema_for_struct(fields: &[Field], cattrs: &attr::Container) -> TokenStream .expect("This is not a tuple struct, so field should be named"); Some(quote!(cdefault.#field_ident)) } - SerdeDefault::None => None, SerdeDefault::Default => Some(quote!(<#ty>::default())), SerdeDefault::Path(path) => Some(quote!(#path())), } @@ -341,6 +342,7 @@ fn schema_for_struct(fields: &[Field], cattrs: &attr::Container) -> TokenStream read_only: field.attrs.skip_deserializing(), write_only: field.attrs.skip_serializing(), default, + skip_default_if: field.attrs.skip_serializing_if().cloned(), ..get_metadata_from_docs(&field.original.attrs) }; let schema_expr = set_metadata_on_schema(schema_expr, &metadata); diff --git a/schemars_derive/src/metadata.rs b/schemars_derive/src/metadata.rs index b652eb9..f2b2b8e 100644 --- a/schemars_derive/src/metadata.rs +++ b/schemars_derive/src/metadata.rs @@ -1,6 +1,6 @@ use crate::doc_attrs; use proc_macro2::TokenStream; -use syn::Attribute; +use syn::{Attribute, ExprPath}; #[derive(Debug, Clone, Default)] pub struct SchemaMetadata { @@ -9,6 +9,7 @@ pub struct SchemaMetadata { pub read_only: bool, pub write_only: bool, pub default: Option, + pub skip_default_if: Option, } pub fn set_metadata_on_schema_from_docs( @@ -34,32 +35,38 @@ pub fn set_metadata_on_schema(schema_expr: TokenStream, metadata: &SchemaMetadat if let Some(title) = &metadata.title { setters.push(quote! { metadata.title = Some(#title.to_owned()); - }) + }); } if let Some(description) = &metadata.description { setters.push(quote! { metadata.description = Some(#description.to_owned()); - }) + }); } if metadata.read_only { setters.push(quote! { metadata.read_only = true; - }) + }); } if metadata.write_only { setters.push(quote! { metadata.write_only = true; - }) + }); } - if let Some(default) = &metadata.default { - setters.push(quote! { - metadata.default = match serde_json::value::to_value(#default) { - Ok(serde_json::value::Value::Null) | Err(_) => None, - Ok(d) => Some(d), - }; - }) + match (&metadata.default, &metadata.skip_default_if) { + (Some(default), Some(skip_if)) => setters.push(quote! { + { + let default = #default; + if !#skip_if(&default) { + metadata.default = serde_json::value::to_value(default).ok(); + } + } + }), + (Some(default), None) => setters.push(quote! { + metadata.default = serde_json::value::to_value(#default).ok(); + }), + _ => {} } if setters.is_empty() { diff --git a/schemars_derive/src/preprocess.rs b/schemars_derive/src/preprocess.rs index ed5a61a..008b605 100644 --- a/schemars_derive/src/preprocess.rs +++ b/schemars_derive/src/preprocess.rs @@ -18,6 +18,7 @@ static SERDE_KEYWORDS: &[&str] = &[ "alias", "skip", "skip_serializing", + "skip_serializing_if", "skip_deserializing", "other", "flatten",