Set additionalProperties to false on enums where appropriate

This commit is contained in:
Graham Esau 2021-03-20 18:45:29 +00:00
parent 3a7d7ad905
commit 6a3bba1e86
12 changed files with 727 additions and 31 deletions

View file

@ -1,5 +1,9 @@
# Changelog # Changelog
## **In-dev** - [0.8.1]
### Changed:
- Deriving JsonSchema on enums now sets `additionalProperties` to false on generated schemas wherever serde doesn't accept unknown properties. This includes non-unit variants of externally tagged enums, and struct-style variants of all enums that have the `deny_unknown_fields` attribute.
## [0.8.0] - 2020-09-27 ## [0.8.0] - 2020-09-27
### Added: ### Added:
- `visit::Visitor`, a trait for updating a schema and all schemas it contains recursively. A `SchemaSettings` can now contain a list of visitors. - `visit::Visitor`, a trait for updating a schema and all schemas it contains recursively. A `SchemaSettings` can now contain a list of visitors.

View file

@ -0,0 +1,106 @@
mod util;
use schemars::{JsonSchema, Map};
use util::*;
// Ensure that schemars_derive uses the full path to std::string::String
pub struct String;
#[derive(Debug, JsonSchema)]
pub struct UnitStruct;
#[derive(Debug, JsonSchema)]
pub struct Struct {
foo: i32,
bar: bool,
}
// Outer container should always have additionalPropreties: false
// `Struct` variant should have additionalPropreties: false
#[derive(Debug, JsonSchema)]
#[schemars(rename_all = "camelCase", deny_unknown_fields)]
pub enum External {
UnitOne,
StringMap(Map<&'static str, &'static str>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
UnitTwo,
Tuple(i32, bool),
#[schemars(with = "i32")]
WithInt,
}
#[test]
fn enum_external_tag() -> TestResult {
test_default_generated_schema::<External>("enum-external-duf")
}
// Only `Struct` variant should have additionalPropreties: false
#[derive(Debug, JsonSchema)]
#[schemars(tag = "typeProperty", deny_unknown_fields)]
pub enum Internal {
UnitOne,
StringMap(Map<&'static str, &'static str>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
UnitTwo,
#[schemars(with = "i32")]
WithInt,
}
#[test]
fn enum_internal_tag() -> TestResult {
test_default_generated_schema::<Internal>("enum-internal-duf")
}
// Only `Struct` variant should have additionalPropreties: false
#[derive(Debug, JsonSchema)]
#[schemars(untagged, deny_unknown_fields)]
pub enum Untagged {
UnitOne,
StringMap(Map<&'static str, &'static str>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
Tuple(i32, bool),
#[schemars(with = "i32")]
WithInt,
}
#[test]
fn enum_untagged() -> TestResult {
test_default_generated_schema::<Untagged>("enum-untagged-duf")
}
// Outer container and `Struct` variant should have additionalPropreties: false
#[derive(Debug, JsonSchema)]
#[schemars(tag = "t", content = "c", deny_unknown_fields)]
pub enum Adjacent {
UnitOne,
StringMap(Map<&'static str, &'static str>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
Tuple(i32, bool),
UnitTwo,
#[schemars(with = "i32")]
WithInt,
}
#[test]
fn enum_adjacent_tagged() -> TestResult {
test_default_generated_schema::<Adjacent>("enum-adjacent-tagged-duf")
}

View file

@ -34,7 +34,8 @@
} }
} }
} }
} },
"additionalProperties": false
} }
] ]
} }

View file

@ -31,7 +31,8 @@
} }
} }
} }
} },
"additionalProperties": false
} }
] ]
} }

View file

@ -0,0 +1,200 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Adjacent",
"anyOf": [
{
"type": "object",
"required": [
"t"
],
"properties": {
"t": {
"type": "string",
"enum": [
"UnitOne"
]
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"c",
"t"
],
"properties": {
"t": {
"type": "string",
"enum": [
"StringMap"
]
},
"c": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"c",
"t"
],
"properties": {
"t": {
"type": "string",
"enum": [
"UnitStructNewType"
]
},
"c": {
"$ref": "#/definitions/UnitStruct"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"c",
"t"
],
"properties": {
"t": {
"type": "string",
"enum": [
"StructNewType"
]
},
"c": {
"$ref": "#/definitions/Struct"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"c",
"t"
],
"properties": {
"t": {
"type": "string",
"enum": [
"Struct"
]
},
"c": {
"type": "object",
"required": [
"bar",
"foo"
],
"properties": {
"foo": {
"type": "integer",
"format": "int32"
},
"bar": {
"type": "boolean"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"c",
"t"
],
"properties": {
"t": {
"type": "string",
"enum": [
"Tuple"
]
},
"c": {
"type": "array",
"items": [
{
"type": "integer",
"format": "int32"
},
{
"type": "boolean"
}
],
"maxItems": 2,
"minItems": 2
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"t"
],
"properties": {
"t": {
"type": "string",
"enum": [
"UnitTwo"
]
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"c",
"t"
],
"properties": {
"t": {
"type": "string",
"enum": [
"WithInt"
]
},
"c": {
"type": "integer",
"format": "int32"
}
},
"additionalProperties": false
}
],
"definitions": {
"UnitStruct": {
"type": "null"
},
"Struct": {
"type": "object",
"required": [
"bar",
"foo"
],
"properties": {
"foo": {
"type": "integer",
"format": "int32"
},
"bar": {
"type": "boolean"
}
}
}
}
}

View file

@ -0,0 +1,135 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "External",
"anyOf": [
{
"type": "string",
"enum": [
"unitOne",
"unitTwo"
]
},
{
"type": "object",
"required": [
"stringMap"
],
"properties": {
"stringMap": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"unitStructNewType"
],
"properties": {
"unitStructNewType": {
"$ref": "#/definitions/UnitStruct"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"structNewType"
],
"properties": {
"structNewType": {
"$ref": "#/definitions/Struct"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"struct"
],
"properties": {
"struct": {
"type": "object",
"required": [
"bar",
"foo"
],
"properties": {
"foo": {
"type": "integer",
"format": "int32"
},
"bar": {
"type": "boolean"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"tuple"
],
"properties": {
"tuple": {
"type": "array",
"items": [
{
"type": "integer",
"format": "int32"
},
{
"type": "boolean"
}
],
"maxItems": 2,
"minItems": 2
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"withInt"
],
"properties": {
"withInt": {
"type": "integer",
"format": "int32"
}
},
"additionalProperties": false
}
],
"definitions": {
"UnitStruct": {
"type": "null"
},
"Struct": {
"type": "object",
"required": [
"bar",
"foo"
],
"properties": {
"foo": {
"type": "integer",
"format": "int32"
},
"bar": {
"type": "boolean"
}
}
}
}
}

View file

@ -21,7 +21,8 @@
"type": "string" "type": "string"
} }
} }
} },
"additionalProperties": false
}, },
{ {
"type": "object", "type": "object",
@ -32,7 +33,8 @@
"unitStructNewType": { "unitStructNewType": {
"$ref": "#/definitions/UnitStruct" "$ref": "#/definitions/UnitStruct"
} }
} },
"additionalProperties": false
}, },
{ {
"type": "object", "type": "object",
@ -43,7 +45,8 @@
"structNewType": { "structNewType": {
"$ref": "#/definitions/Struct" "$ref": "#/definitions/Struct"
} }
} },
"additionalProperties": false
}, },
{ {
"type": "object", "type": "object",
@ -67,7 +70,8 @@
} }
} }
} }
} },
"additionalProperties": false
}, },
{ {
"type": "object", "type": "object",
@ -89,7 +93,8 @@
"maxItems": 2, "maxItems": 2,
"minItems": 2 "minItems": 2
} }
} },
"additionalProperties": false
}, },
{ {
"type": "object", "type": "object",
@ -101,7 +106,8 @@
"type": "integer", "type": "integer",
"format": "int32" "format": "int32"
} }
} },
"additionalProperties": false
} }
], ],
"definitions": { "definitions": {

View file

@ -0,0 +1,130 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Internal",
"anyOf": [
{
"type": "object",
"required": [
"typeProperty"
],
"properties": {
"typeProperty": {
"type": "string",
"enum": [
"UnitOne"
]
}
}
},
{
"type": "object",
"required": [
"typeProperty"
],
"properties": {
"typeProperty": {
"type": "string",
"enum": [
"StringMap"
]
}
},
"additionalProperties": {
"type": "string"
}
},
{
"type": "object",
"required": [
"typeProperty"
],
"properties": {
"typeProperty": {
"type": "string",
"enum": [
"UnitStructNewType"
]
}
}
},
{
"type": "object",
"required": [
"bar",
"foo",
"typeProperty"
],
"properties": {
"typeProperty": {
"type": "string",
"enum": [
"StructNewType"
]
},
"foo": {
"type": "integer",
"format": "int32"
},
"bar": {
"type": "boolean"
}
}
},
{
"type": "object",
"required": [
"bar",
"foo",
"typeProperty"
],
"properties": {
"typeProperty": {
"type": "string",
"enum": [
"Struct"
]
},
"foo": {
"type": "integer",
"format": "int32"
},
"bar": {
"type": "boolean"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"typeProperty"
],
"properties": {
"typeProperty": {
"type": "string",
"enum": [
"UnitTwo"
]
}
}
},
{
"type": [
"object",
"integer"
],
"format": "int32",
"required": [
"typeProperty"
],
"properties": {
"typeProperty": {
"type": "string",
"enum": [
"WithInt"
]
}
}
}
]
}

View file

@ -0,0 +1,77 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Untagged",
"anyOf": [
{
"type": "null"
},
{
"type": "object",
"additionalProperties": {
"type": "string"
}
},
{
"$ref": "#/definitions/UnitStruct"
},
{
"$ref": "#/definitions/Struct"
},
{
"type": "object",
"required": [
"bar",
"foo"
],
"properties": {
"foo": {
"type": "integer",
"format": "int32"
},
"bar": {
"type": "boolean"
}
},
"additionalProperties": false
},
{
"type": "array",
"items": [
{
"type": "integer",
"format": "int32"
},
{
"type": "boolean"
}
],
"maxItems": 2,
"minItems": 2
},
{
"type": "integer",
"format": "int32"
}
],
"definitions": {
"UnitStruct": {
"type": "null"
},
"Struct": {
"type": "object",
"required": [
"bar",
"foo"
],
"properties": {
"foo": {
"type": "integer",
"format": "int32"
},
"bar": {
"type": "boolean"
}
}
}
}
}

View file

@ -19,7 +19,8 @@
} }
} }
} }
} },
"additionalProperties": false
}, },
{ {
"type": "object", "type": "object",
@ -30,7 +31,8 @@
"newType": { "newType": {
"type": "boolean" "type": "boolean"
} }
} },
"additionalProperties": false
}, },
{ {
"type": "object", "type": "object",
@ -52,7 +54,8 @@
"maxItems": 2, "maxItems": 2,
"minItems": 2 "minItems": 2
} }
} },
"additionalProperties": false
} }
] ]
} }

View file

@ -18,7 +18,8 @@
"type": "number", "type": "number",
"format": "float" "format": "float"
} }
} },
"additionalProperties": false
} }
] ]
} }

View file

@ -9,7 +9,11 @@ pub fn expr_for_container(cont: &Container) -> TokenStream {
Data::Struct(Style::Unit, _) => expr_for_unit_struct(), Data::Struct(Style::Unit, _) => expr_for_unit_struct(),
Data::Struct(Style::Newtype, fields) => expr_for_newtype_struct(&fields[0]), Data::Struct(Style::Newtype, fields) => expr_for_newtype_struct(&fields[0]),
Data::Struct(Style::Tuple, fields) => expr_for_tuple_struct(fields), Data::Struct(Style::Tuple, fields) => expr_for_tuple_struct(fields),
Data::Struct(Style::Struct, fields) => expr_for_struct(fields, Some(&cont.serde_attrs)), Data::Struct(Style::Struct, fields) => expr_for_struct(
fields,
cont.serde_attrs.default(),
cont.serde_attrs.deny_unknown_fields(),
),
Data::Enum(variants) => expr_for_enum(variants, &cont.serde_attrs), Data::Enum(variants) => expr_for_enum(variants, &cont.serde_attrs),
}; };
@ -70,19 +74,26 @@ pub fn type_for_schema(field: &Field, local_id: usize) -> (syn::Type, Option<Tok
} }
fn expr_for_enum(variants: &[Variant], cattrs: &serde_attr::Container) -> TokenStream { fn expr_for_enum(variants: &[Variant], cattrs: &serde_attr::Container) -> TokenStream {
let deny_unknown_fields = cattrs.deny_unknown_fields();
let variants = variants let variants = variants
.iter() .iter()
.filter(|v| !v.serde_attrs.skip_deserializing()); .filter(|v| !v.serde_attrs.skip_deserializing());
match cattrs.tag() { match cattrs.tag() {
TagType::External => expr_for_external_tagged_enum(variants), TagType::External => expr_for_external_tagged_enum(variants, deny_unknown_fields),
TagType::None => expr_for_untagged_enum(variants), TagType::None => expr_for_untagged_enum(variants, deny_unknown_fields),
TagType::Internal { tag } => expr_for_internal_tagged_enum(variants, tag), TagType::Internal { tag } => {
TagType::Adjacent { tag, content } => expr_for_adjacent_tagged_enum(variants, tag, content), expr_for_internal_tagged_enum(variants, tag, deny_unknown_fields)
}
TagType::Adjacent { tag, content } => {
expr_for_adjacent_tagged_enum(variants, tag, content, deny_unknown_fields)
}
} }
} }
fn expr_for_external_tagged_enum<'a>( fn expr_for_external_tagged_enum<'a>(
variants: impl Iterator<Item = &'a Variant<'a>>, variants: impl Iterator<Item = &'a Variant<'a>>,
deny_unknown_fields: bool,
) -> TokenStream { ) -> TokenStream {
let (unit_variants, complex_variants): (Vec<_>, Vec<_>) = let (unit_variants, complex_variants): (Vec<_>, Vec<_>) =
variants.partition(|v| v.is_unit() && v.attrs.with.is_none()); variants.partition(|v| v.is_unit() && v.attrs.with.is_none());
@ -104,7 +115,8 @@ fn expr_for_external_tagged_enum<'a>(
schemas.extend(complex_variants.into_iter().map(|variant| { schemas.extend(complex_variants.into_iter().map(|variant| {
let name = variant.name(); let name = variant.name();
let sub_schema = expr_for_untagged_enum_variant(variant); let sub_schema = expr_for_untagged_enum_variant(variant, deny_unknown_fields);
let schema_expr = schema_object(quote! { let schema_expr = schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::Object.into()), instance_type: Some(schemars::schema::InstanceType::Object.into()),
object: Some(Box::new(schemars::schema::ObjectValidation { object: Some(Box::new(schemars::schema::ObjectValidation {
@ -118,6 +130,7 @@ fn expr_for_external_tagged_enum<'a>(
required.insert(#name.to_owned()); required.insert(#name.to_owned());
required required
}, },
additional_properties: Some(Box::new(false.into())),
..Default::default() ..Default::default()
})), })),
}); });
@ -136,6 +149,7 @@ fn expr_for_external_tagged_enum<'a>(
fn expr_for_internal_tagged_enum<'a>( fn expr_for_internal_tagged_enum<'a>(
variants: impl Iterator<Item = &'a Variant<'a>>, variants: impl Iterator<Item = &'a Variant<'a>>,
tag_name: &str, tag_name: &str,
deny_unknown_fields: bool,
) -> TokenStream { ) -> TokenStream {
let variant_schemas = variants.map(|variant| { let variant_schemas = variants.map(|variant| {
let name = variant.name(); let name = variant.name();
@ -163,7 +177,7 @@ fn expr_for_internal_tagged_enum<'a>(
let doc_metadata = SchemaMetadata::from_attrs(&variant.attrs); let doc_metadata = SchemaMetadata::from_attrs(&variant.attrs);
let tag_schema = doc_metadata.apply_to_schema(tag_schema); let tag_schema = doc_metadata.apply_to_schema(tag_schema);
match expr_for_untagged_enum_variant_for_flatten(&variant) { match expr_for_untagged_enum_variant_for_flatten(&variant, deny_unknown_fields) {
Some(variant_schema) => quote! { Some(variant_schema) => quote! {
#tag_schema.flatten(#variant_schema) #tag_schema.flatten(#variant_schema)
}, },
@ -179,9 +193,12 @@ fn expr_for_internal_tagged_enum<'a>(
}) })
} }
fn expr_for_untagged_enum<'a>(variants: impl Iterator<Item = &'a Variant<'a>>) -> TokenStream { fn expr_for_untagged_enum<'a>(
variants: impl Iterator<Item = &'a Variant<'a>>,
deny_unknown_fields: bool,
) -> TokenStream {
let schemas = variants.map(|variant| { let schemas = variants.map(|variant| {
let schema_expr = expr_for_untagged_enum_variant(variant); let schema_expr = expr_for_untagged_enum_variant(variant, deny_unknown_fields);
let doc_metadata = SchemaMetadata::from_attrs(&variant.attrs); let doc_metadata = SchemaMetadata::from_attrs(&variant.attrs);
doc_metadata.apply_to_schema(schema_expr) doc_metadata.apply_to_schema(schema_expr)
}); });
@ -198,12 +215,13 @@ fn expr_for_adjacent_tagged_enum<'a>(
variants: impl Iterator<Item = &'a Variant<'a>>, variants: impl Iterator<Item = &'a Variant<'a>>,
tag_name: &str, tag_name: &str,
content_name: &str, content_name: &str,
deny_unknown_fields: bool,
) -> TokenStream { ) -> TokenStream {
let schemas = variants.map(|variant| { let schemas = variants.map(|variant| {
let content_schema = if variant.is_unit() && variant.attrs.with.is_none() { let content_schema = if variant.is_unit() && variant.attrs.with.is_none() {
None None
} else { } else {
Some(expr_for_untagged_enum_variant(variant)) Some(expr_for_untagged_enum_variant(variant, deny_unknown_fields))
}; };
let (add_content_to_props, add_content_to_required) = content_schema let (add_content_to_props, add_content_to_required) = content_schema
@ -221,6 +239,14 @@ fn expr_for_adjacent_tagged_enum<'a>(
enum_values: Some(vec![#name.into()]), enum_values: Some(vec![#name.into()]),
}); });
let set_additional_properties = if deny_unknown_fields {
quote! {
additional_properties: Some(Box::new(false.into())),
}
} else {
TokenStream::new()
};
let outer_schema = schema_object(quote! { let outer_schema = schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::Object.into()), instance_type: Some(schemars::schema::InstanceType::Object.into()),
object: Some(Box::new(schemars::schema::ObjectValidation { object: Some(Box::new(schemars::schema::ObjectValidation {
@ -236,6 +262,7 @@ fn expr_for_adjacent_tagged_enum<'a>(
#add_content_to_required #add_content_to_required
required required
}, },
#set_additional_properties
..Default::default() ..Default::default()
})), })),
}); });
@ -252,7 +279,7 @@ fn expr_for_adjacent_tagged_enum<'a>(
}) })
} }
fn expr_for_untagged_enum_variant(variant: &Variant) -> TokenStream { fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool) -> TokenStream {
if let Some(WithAttr::Type(with)) = &variant.attrs.with { if let Some(WithAttr::Type(with)) = &variant.attrs.with {
return quote_spanned! {variant.original.span()=> return quote_spanned! {variant.original.span()=>
gen.subschema_for::<#with>() gen.subschema_for::<#with>()
@ -263,11 +290,14 @@ fn expr_for_untagged_enum_variant(variant: &Variant) -> TokenStream {
Style::Unit => expr_for_unit_struct(), Style::Unit => expr_for_unit_struct(),
Style::Newtype => expr_for_field(&variant.fields[0], true), Style::Newtype => expr_for_field(&variant.fields[0], true),
Style::Tuple => expr_for_tuple_struct(&variant.fields), Style::Tuple => expr_for_tuple_struct(&variant.fields),
Style::Struct => expr_for_struct(&variant.fields, None), Style::Struct => expr_for_struct(&variant.fields, &SerdeDefault::None, deny_unknown_fields),
} }
} }
fn expr_for_untagged_enum_variant_for_flatten(variant: &Variant) -> Option<TokenStream> { fn expr_for_untagged_enum_variant_for_flatten(
variant: &Variant,
deny_unknown_fields: bool,
) -> Option<TokenStream> {
if let Some(WithAttr::Type(with)) = &variant.attrs.with { if let Some(WithAttr::Type(with)) = &variant.attrs.with {
return Some(quote_spanned! {variant.original.span()=> return Some(quote_spanned! {variant.original.span()=>
<#with>::json_schema(gen) <#with>::json_schema(gen)
@ -278,7 +308,7 @@ fn expr_for_untagged_enum_variant_for_flatten(variant: &Variant) -> Option<Token
Style::Unit => return None, Style::Unit => return None,
Style::Newtype => expr_for_field(&variant.fields[0], false), Style::Newtype => expr_for_field(&variant.fields[0], false),
Style::Tuple => expr_for_tuple_struct(&variant.fields), Style::Tuple => expr_for_tuple_struct(&variant.fields),
Style::Struct => expr_for_struct(&variant.fields, None), Style::Struct => expr_for_struct(&variant.fields, &SerdeDefault::None, deny_unknown_fields),
}) })
} }
@ -307,17 +337,21 @@ fn expr_for_tuple_struct(fields: &[Field]) -> TokenStream {
} }
} }
fn expr_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) -> TokenStream { fn expr_for_struct(
fields: &[Field],
default: &SerdeDefault,
deny_unknown_fields: bool,
) -> TokenStream {
let (flattened_fields, property_fields): (Vec<_>, Vec<_>) = fields let (flattened_fields, property_fields): (Vec<_>, Vec<_>) = fields
.iter() .iter()
.filter(|f| !f.serde_attrs.skip_deserializing() || !f.serde_attrs.skip_serializing()) .filter(|f| !f.serde_attrs.skip_deserializing() || !f.serde_attrs.skip_serializing())
.partition(|f| f.serde_attrs.flatten()); .partition(|f| f.serde_attrs.flatten());
let set_container_default = cattrs.and_then(|c| match c.default() { let set_container_default = match default {
SerdeDefault::None => None, SerdeDefault::None => None,
SerdeDefault::Default => Some(quote!(let container_default = Self::default();)), SerdeDefault::Default => Some(quote!(let container_default = Self::default();)),
SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)), SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)),
}); };
let mut type_defs = Vec::new(); let mut type_defs = Vec::new();
@ -362,7 +396,6 @@ fn expr_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) ->
}) })
.collect(); .collect();
let deny_unknown_fields = cattrs.map_or(false, |attrs| attrs.deny_unknown_fields());
let set_additional_properties = if deny_unknown_fields { let set_additional_properties = if deny_unknown_fields {
quote! { quote! {
schema_object.object().additional_properties = Some(Box::new(false.into())); schema_object.object().additional_properties = Some(Box::new(false.into()));
@ -370,7 +403,6 @@ fn expr_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) ->
} else { } else {
TokenStream::new() TokenStream::new()
}; };
quote! { quote! {
{ {
#(#type_defs)* #(#type_defs)*