Repository: GREsau/schemars
Pull request #65 (2020-12-07 10:29:43)

Fix clippy field_reassign_with_default in generated code
In the next stable release, clippy will ship the [field_reassign_with_default](https://rust-lang.github.io/rust-clippy/master/index.html#field_reassign_with_default) lint by default. This lint is also triggered in code generated by `#[derive(JsonSchema)]` when building `schemars::schema::Metadata`. This can be avoided by constructing the type in a single expression.

Instead of generating code like this:

```rust
let mut metadata = Metadata::default();
metadata.title = Some("title".to_owned());
metadata
```

This PR will generate code like this:

```rust
Metadata {
	title: Some("title".to_owned()),
	..Default::default()
}
```

Fixes https://github.com/GREsau/schemars/issues/69

jan-auer: This lint is now enabled in `clippy::all` on Rust stable 1.49.0. @GREsau could you please take a look?

I've also briefly tried to find out why clippy is warning in generated code in the first place, but haven't been able to figure out how to suppress all checks.

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d8e5dfb..af30d0f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,12 +8,12 @@ jobs:
     strategy:
       matrix:
         rust:
-          - 1.36.0
+          - 1.37.0
           - stable
           - beta
           - nightly
         include:
-          - rust: 1.36.0
+          - rust: 1.37.0
             test_features: "--all-features"
             allow_failure: false
           - rust: stable
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4a0cd6c..b60f0d8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
 # Changelog
 
+## **In-dev** - [0.8.1]
+### Added:
+- `SchemaGenerator::definitions_mut()` which returns a mutable reference to the generator's schema definitions
+
+### Changed:
+- Minimum supported rust version is now 1.37.0
+- 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.
+- Schemas for HashSet and BTreeSet now have `uniqueItems` set to true (https://github.com/GREsau/schemars/pull/64)
+
+### Fixed
+- Fix use of `#[serde(transparent)]` in combination with `#[schemars(with = ...)]` (https://github.com/GREsau/schemars/pull/67)
+
 ## [0.8.0] - 2020-09-27
 ### Added:
 - `visit::Visitor`, a trait for updating a schema and all schemas it contains recursively. A `SchemaSettings` can now contain a list of visitors.
@@ -118,4 +130,4 @@
 - Made prepositions/conjunctions in generated schema names lowercase
     - e.g. schema name for `Result<MyStruct, Vec<String>>` has changed from "Result_Of_MyStruct_Or_Array_Of_String" to "Result_of_MyStruct_or_Array_of_String"
 - Some provided `JsonSchema` implementations with the same `type` but different `format`s (e.g. `i8` and `usize`) used the `type` as their name. They have now been updated to use `format` as their name.
-    - Previously, schema generation would incorrectly assume types such as `MyStruct<i8>` and `MyStruct<usize>` were identical, and give them a single schema definition called `MyStruct_for_Integer` despite the fact they should have different schemas. Now they will each have their own schema (`MyStruct_for_i8` and `MyStruct_for_usize` respectively).
\ No newline at end of file
+    - Previously, schema generation would incorrectly assume types such as `MyStruct<i8>` and `MyStruct<usize>` were identical, and give them a single schema definition called `MyStruct_for_Integer` despite the fact they should have different schemas. Now they will each have their own schema (`MyStruct_for_i8` and `MyStruct_for_usize` respectively).
diff --git a/README.md b/README.md
index a7946c0..8128d22 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 [![CI Build](https://img.shields.io/github/workflow/status/GREsau/schemars/CI?logo=GitHub)](https://github.com/GREsau/schemars/actions)
 [![Crates.io](https://img.shields.io/crates/v/schemars)](https://crates.io/crates/schemars)
 [![Docs](https://docs.rs/schemars/badge.svg)](https://docs.rs/schemars)
-[![rustc 1.36+](https://img.shields.io/badge/schemars-rustc_1.36+-lightgray.svg)](https://blog.rust-lang.org/2019/07/04/Rust-1.36.0.html)
+[![rustc 1.37+](https://img.shields.io/badge/schemars-rustc_1.37+-lightgray.svg)](https://blog.rust-lang.org/2019/08/15/Rust-1.37.0.html)
 
 Generate JSON Schema documents from Rust code
 
@@ -27,10 +27,8 @@ pub enum MyEnum {
     StructVariant { floats: Vec<f32> },
 }
 
-fn main() {
-    let schema = schema_for!(MyStruct);
-    println!("{}", serde_json::to_string_pretty(&schema).unwrap());
-}
+let schema = schema_for!(MyStruct);
+println!("{}", serde_json::to_string_pretty(&schema).unwrap());
 ```
 
 <details>
@@ -133,10 +131,8 @@ pub enum MyEnum {
     StructVariant { floats: Vec<f32> },
 }
 
-fn main() {
-    let schema = schema_for!(MyStruct);
-    println!("{}", serde_json::to_string_pretty(&schema).unwrap());
-}
+let schema = schema_for!(MyStruct);
+println!("{}", serde_json::to_string_pretty(&schema).unwrap());
 ```
 
 <details>
diff --git a/clippy.toml b/clippy.toml
new file mode 100644
index 0000000..3a62059
--- /dev/null
+++ b/clippy.toml
@@ -0,0 +1,1 @@
+msrv = "1.37.0"
diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs
index 5ef54b5..58d820b 100644
--- a/schemars/src/gen.rs
+++ b/schemars/src/gen.rs
@@ -231,6 +231,14 @@ impl SchemaGenerator {
         &self.definitions
     }
 
+    /// Mutably borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated.
+    ///
+    /// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas
+    /// themselves.
+    pub fn definitions_mut(&mut self) -> &mut Map<String, Schema> {
+        &mut self.definitions
+    }
+
     /// Returns the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated,
     /// leaving an empty map in its place.
     ///
@@ -335,8 +343,8 @@ impl SchemaGenerator {
     #[doc(hidden)]
     pub fn apply_metadata(&self, schema: Schema, metadata: Option<Metadata>) -> Schema {
         match metadata {
-            None => return schema,
-            Some(ref metadata) if *metadata == Metadata::default() => return schema,
+            None => schema,
+            Some(ref metadata) if *metadata == Metadata::default() => schema,
             Some(metadata) => {
                 let mut schema_obj = schema.into_object();
                 schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata);
diff --git a/schemars/src/json_schema_impls/sequences.rs b/schemars/src/json_schema_impls/sequences.rs
index 1e79210..632a01e 100644
--- a/schemars/src/json_schema_impls/sequences.rs
+++ b/schemars/src/json_schema_impls/sequences.rs
@@ -29,9 +29,38 @@ macro_rules! seq_impl {
     };
 }
 
+macro_rules! set_impl {
+    ($($desc:tt)+) => {
+        impl $($desc)+
+        where
+            T: JsonSchema,
+        {
+            no_ref_schema!();
+
+            fn schema_name() -> String {
+                format!("Set_of_{}", T::schema_name())
+            }
+
+            fn json_schema(gen: &mut SchemaGenerator) -> Schema {
+                SchemaObject {
+                    instance_type: Some(InstanceType::Array.into()),
+                    array: Some(Box::new(ArrayValidation {
+                        unique_items: Some(true),
+                        items: Some(gen.subschema_for::<T>().into()),
+                        ..Default::default()
+                    })),
+                    ..Default::default()
+                }
+                .into()
+            }
+        }
+    };
+}
+
+
 seq_impl!(<T> JsonSchema for std::collections::BinaryHeap<T>);
-seq_impl!(<T> JsonSchema for std::collections::BTreeSet<T>);
-seq_impl!(<T, H> JsonSchema for std::collections::HashSet<T, H>);
+set_impl!(<T> JsonSchema for std::collections::BTreeSet<T>);
+set_impl!(<T, H> JsonSchema for std::collections::HashSet<T, H>);
 seq_impl!(<T> JsonSchema for std::collections::LinkedList<T>);
 seq_impl!(<T> JsonSchema for Vec<T>);
 seq_impl!(<T> JsonSchema for std::collections::VecDeque<T>);
diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs
index bb189bb..907572b 100644
--- a/schemars/src/lib.rs
+++ b/schemars/src/lib.rs
@@ -22,10 +22,8 @@ pub enum MyEnum {
     StructVariant { floats: Vec<f32> },
 }
 
-fn main() {
-    let schema = schema_for!(MyStruct);
-    println!("{}", serde_json::to_string_pretty(&schema).unwrap());
-}
+let schema = schema_for!(MyStruct);
+println!("{}", serde_json::to_string_pretty(&schema).unwrap());
 ```
 
 <details>
@@ -128,10 +126,8 @@ pub enum MyEnum {
     StructVariant { floats: Vec<f32> },
 }
 
-fn main() {
-    let schema = schema_for!(MyStruct);
-    println!("{}", serde_json::to_string_pretty(&schema).unwrap());
-}
+let schema = schema_for!(MyStruct);
+println!("{}", serde_json::to_string_pretty(&schema).unwrap());
 ```
 
 <details>
diff --git a/schemars_derive/src/attr/doc.rs b/schemars_derive/src/attr/doc.rs
index a1557ba..0827dc0 100644
--- a/schemars_derive/src/attr/doc.rs
+++ b/schemars_derive/src/attr/doc.rs
@@ -52,7 +52,7 @@ fn get_doc(attrs: &[Attribute]) -> Option<String> {
         .iter()
         .flat_map(|a| a.split('\n'))
         .map(str::trim)
-        .skip_while(|s| *s == "")
+        .skip_while(|s| s.is_empty())
         .collect::<Vec<_>>();
 
     if let Some(&"") = lines.last() {
@@ -61,7 +61,7 @@ fn get_doc(attrs: &[Attribute]) -> Option<String> {
 
     // Added for backward-compatibility, but perhaps we shouldn't do this
     // https://github.com/rust-lang/rust/issues/32088
-    if lines.iter().all(|l| l.starts_with("*")) {
+    if lines.iter().all(|l| l.starts_with('*')) {
         for line in lines.iter_mut() {
             *line = line[1..].trim()
         }
diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs
index d50a890..3746771 100644
--- a/schemars_derive/src/lib.rs
+++ b/schemars_derive/src/lib.rs
@@ -38,35 +38,37 @@ fn derive_json_schema(mut input: syn::DeriveInput) -> TokenStream {
     if let Some(transparent_field) = cont.transparent_field() {
         let (ty, type_def) = schema_exprs::type_for_schema(transparent_field, 0);
         return quote! {
-            #[automatically_derived]
-            impl #impl_generics schemars::JsonSchema for #type_name #ty_generics #where_clause {
+            const _: () = {
                 #type_def
 
-                fn is_referenceable() -> bool {
-                    <#ty as schemars::JsonSchema>::is_referenceable()
-                }
-
-                fn schema_name() -> std::string::String {
-                    <#ty as schemars::JsonSchema>::schema_name()
-                }
-
-                fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
-                    <#ty as schemars::JsonSchema>::json_schema(gen)
-                }
-
-                fn json_schema_for_flatten(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
-                    <#ty as schemars::JsonSchema>::json_schema_for_flatten(gen)
-                }
-
-                fn add_schema_as_property(
-                    gen: &mut schemars::gen::SchemaGenerator,
-                    parent: &mut schemars::schema::SchemaObject,
-                    name: String,
-                    metadata: Option<schemars::schema::Metadata>,
-                    required: bool,
-                ) {
-                    <#ty as schemars::JsonSchema>::add_schema_as_property(gen, parent, name, metadata, required)
-                }
+                #[automatically_derived]
+                impl #impl_generics schemars::JsonSchema for #type_name #ty_generics #where_clause {
+                    fn is_referenceable() -> bool {
+                        <#ty as schemars::JsonSchema>::is_referenceable()
+                    }
+
+                    fn schema_name() -> std::string::String {
+                        <#ty as schemars::JsonSchema>::schema_name()
+                    }
+
+                    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
+                        <#ty as schemars::JsonSchema>::json_schema(gen)
+                    }
+
+                    fn json_schema_for_flatten(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
+                        <#ty as schemars::JsonSchema>::json_schema_for_flatten(gen)
+                    }
+
+                    fn add_schema_as_property(
+                        gen: &mut schemars::gen::SchemaGenerator,
+                        parent: &mut schemars::schema::SchemaObject,
+                        name: String,
+                        metadata: Option<schemars::schema::Metadata>,
+                        required: bool,
+                    ) {
+                        <#ty as schemars::JsonSchema>::add_schema_as_property(gen, parent, name, metadata, required)
+                    }
+                };
             };
         };
     }
diff --git a/schemars_derive/src/metadata.rs b/schemars_derive/src/metadata.rs
index 3f616ee..cdbe22d 100644
--- a/schemars_derive/src/metadata.rs
+++ b/schemars_derive/src/metadata.rs
@@ -22,9 +22,10 @@ impl ToTokens for SchemaMetadata<'_> {
         } else {
             tokens.extend(quote! {
                 Some({
-                    let mut metadata = schemars::schema::Metadata::default();
-                    #(#setters)*
-                    metadata
+                    schemars::schema::Metadata {
+                        #(#setters)*
+                        ..Default::default()
+                    }
                 })
             })
         }
@@ -58,29 +59,29 @@ impl<'a> SchemaMetadata<'a> {
 
         if let Some(title) = &self.title {
             setters.push(quote! {
-                metadata.title = Some(#title.to_owned());
+                title: Some(#title.to_owned()),
             });
         }
         if let Some(description) = &self.description {
             setters.push(quote! {
-                metadata.description = Some(#description.to_owned());
+                description: Some(#description.to_owned()),
             });
         }
 
         if self.deprecated {
             setters.push(quote! {
-                metadata.deprecated = true;
+                deprecated: true,
             });
         }
 
         if self.read_only {
             setters.push(quote! {
-                metadata.read_only = true;
+                read_only: true,
             });
         }
         if self.write_only {
             setters.push(quote! {
-                metadata.write_only = true;
+                write_only: true,
             });
         }
 
@@ -91,13 +92,13 @@ impl<'a> SchemaMetadata<'a> {
                 }
             });
             setters.push(quote! {
-                metadata.examples = vec![#(#examples),*].into_iter().flatten().collect();
+                examples: vec![#(#examples),*].into_iter().flatten().collect(),
             });
         }
 
         if let Some(default) = &self.default {
             setters.push(quote! {
-                metadata.default = #default.and_then(|d| schemars::_serde_json::value::to_value(d).ok());
+                default: #default.and_then(|d| schemars::_serde_json::value::to_value(d).ok()),
             });
         }
 
@@ -105,7 +106,7 @@ impl<'a> SchemaMetadata<'a> {
     }
 }
 
-fn none_if_empty<'a>(s: &'a String) -> Option<&'a str> {
+fn none_if_empty(s: &String) -> Option<&str> {
     if s.is_empty() {
         None
     } else {
diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs
index 6dc532d..905f67a 100644
--- a/schemars_derive/src/schema_exprs.rs
+++ b/schemars_derive/src/schema_exprs.rs
@@ -9,7 +9,11 @@ pub fn expr_for_container(cont: &Container) -> TokenStream {
         Data::Struct(Style::Unit, _) => expr_for_unit_struct(),
         Data::Struct(Style::Newtype, fields) => expr_for_newtype_struct(&fields[0]),
         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),
     };
 
@@ -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 {
+    let deny_unknown_fields = cattrs.deny_unknown_fields();
     let variants = variants
         .iter()
         .filter(|v| !v.serde_attrs.skip_deserializing());
+
     match cattrs.tag() {
-        TagType::External => expr_for_external_tagged_enum(variants),
-        TagType::None => expr_for_untagged_enum(variants),
-        TagType::Internal { tag } => expr_for_internal_tagged_enum(variants, tag),
-        TagType::Adjacent { tag, content } => expr_for_adjacent_tagged_enum(variants, tag, content),
+        TagType::External => expr_for_external_tagged_enum(variants, deny_unknown_fields),
+        TagType::None => expr_for_untagged_enum(variants, deny_unknown_fields),
+        TagType::Internal { tag } => {
+            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>(
     variants: impl Iterator<Item = &'a Variant<'a>>,
+    deny_unknown_fields: bool,
 ) -> TokenStream {
     let (unit_variants, complex_variants): (Vec<_>, Vec<_>) =
         variants.partition(|v| v.is_unit() && v.attrs.with.is_none());
@@ -98,13 +109,14 @@ fn expr_for_external_tagged_enum<'a>(
     }
 
     let mut schemas = Vec::new();
-    if unit_variants.len() > 0 {
+    if !unit_variants.is_empty() {
         schemas.push(unit_schema);
     }
 
     schemas.extend(complex_variants.into_iter().map(|variant| {
         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! {
             instance_type: Some(schemars::schema::InstanceType::Object.into()),
             object: Some(Box::new(schemars::schema::ObjectValidation {
@@ -118,6 +130,7 @@ fn expr_for_external_tagged_enum<'a>(
                     required.insert(#name.to_owned());
                     required
                 },
+                additional_properties: Some(Box::new(false.into())),
                 ..Default::default()
             })),
         });
@@ -136,6 +149,7 @@ fn expr_for_external_tagged_enum<'a>(
 fn expr_for_internal_tagged_enum<'a>(
     variants: impl Iterator<Item = &'a Variant<'a>>,
     tag_name: &str,
+    deny_unknown_fields: bool,
 ) -> TokenStream {
     let variant_schemas = variants.map(|variant| {
         let name = variant.name();
@@ -163,7 +177,7 @@ fn expr_for_internal_tagged_enum<'a>(
         let doc_metadata = SchemaMetadata::from_attrs(&variant.attrs);
         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! {
                 #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 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);
         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>>,
     tag_name: &str,
     content_name: &str,
+    deny_unknown_fields: bool,
 ) -> TokenStream {
     let schemas = variants.map(|variant| {
         let content_schema = if variant.is_unit() && variant.attrs.with.is_none() {
             None
         } 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
@@ -221,6 +239,14 @@ fn expr_for_adjacent_tagged_enum<'a>(
             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! {
             instance_type: Some(schemars::schema::InstanceType::Object.into()),
             object: Some(Box::new(schemars::schema::ObjectValidation {
@@ -236,6 +262,7 @@ fn expr_for_adjacent_tagged_enum<'a>(
                     #add_content_to_required
                     required
                 },
+                #set_additional_properties
                 ..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 {
         return quote_spanned! {variant.original.span()=>
             gen.subschema_for::<#with>()
@@ -263,11 +290,14 @@ fn expr_for_untagged_enum_variant(variant: &Variant) -> TokenStream {
         Style::Unit => expr_for_unit_struct(),
         Style::Newtype => expr_for_field(&variant.fields[0], true),
         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 {
         return Some(quote_spanned! {variant.original.span()=>
             <#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::Newtype => expr_for_field(&variant.fields[0], false),
         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
         .iter()
         .filter(|f| !f.serde_attrs.skip_deserializing() || !f.serde_attrs.skip_serializing())
         .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::Default => Some(quote!(let container_default = Self::default();)),
         SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)),
-    });
+    };
 
     let mut type_defs = Vec::new();
 
@@ -362,7 +396,6 @@ fn expr_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) ->
         })
         .collect();
 
-    let deny_unknown_fields = cattrs.map_or(false, |attrs| attrs.deny_unknown_fields());
     let set_additional_properties = if deny_unknown_fields {
         quote! {
             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 {
         TokenStream::new()
     };
-
     quote! {
         {
             #(#type_defs)*


Test patch:
diff --git a/schemars/tests/enum_deny_unknown_fields.rs b/schemars/tests/enum_deny_unknown_fields.rs
new file mode 100644
index 0000000..4fcb4bf
--- /dev/null
+++ b/schemars/tests/enum_deny_unknown_fields.rs
@@ -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")
+}
diff --git a/schemars/tests/expected/deprecated-enum.json b/schemars/tests/expected/deprecated-enum.json
index 41eaadf..f869be2 100644
--- a/schemars/tests/expected/deprecated-enum.json
+++ b/schemars/tests/expected/deprecated-enum.json
@@ -34,7 +34,8 @@
             }
           }
         }
-      }
+      },
+      "additionalProperties": false
     }
   ]
 }
\ No newline at end of file
diff --git a/schemars/tests/expected/doc_comments_enum.json b/schemars/tests/expected/doc_comments_enum.json
index 1961955..edcb339 100644
--- a/schemars/tests/expected/doc_comments_enum.json
+++ b/schemars/tests/expected/doc_comments_enum.json
@@ -31,7 +31,8 @@
             }
           }
         }
-      }
+      },
+      "additionalProperties": false
     }
   ]
 }
\ No newline at end of file
diff --git a/schemars/tests/expected/enum-adjacent-tagged-duf.json b/schemars/tests/expected/enum-adjacent-tagged-duf.json
new file mode 100644
index 0000000..011b378
--- /dev/null
+++ b/schemars/tests/expected/enum-adjacent-tagged-duf.json
@@ -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"
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/schemars/tests/expected/enum-external-duf.json b/schemars/tests/expected/enum-external-duf.json
new file mode 100644
index 0000000..4bbe7e4
--- /dev/null
+++ b/schemars/tests/expected/enum-external-duf.json
@@ -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"
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/schemars/tests/expected/enum-external.json b/schemars/tests/expected/enum-external.json
index 5800e4f..cdc2fe4 100644
--- a/schemars/tests/expected/enum-external.json
+++ b/schemars/tests/expected/enum-external.json
@@ -21,7 +21,8 @@
             "type": "string"
           }
         }
-      }
+      },
+      "additionalProperties": false
     },
     {
       "type": "object",
@@ -32,7 +33,8 @@
         "unitStructNewType": {
           "$ref": "#/definitions/UnitStruct"
         }
-      }
+      },
+      "additionalProperties": false
     },
     {
       "type": "object",
@@ -43,7 +45,8 @@
         "structNewType": {
           "$ref": "#/definitions/Struct"
         }
-      }
+      },
+      "additionalProperties": false
     },
     {
       "type": "object",
@@ -67,7 +70,8 @@
             }
           }
         }
-      }
+      },
+      "additionalProperties": false
     },
     {
       "type": "object",
@@ -89,7 +93,8 @@
           "maxItems": 2,
           "minItems": 2
         }
-      }
+      },
+      "additionalProperties": false
     },
     {
       "type": "object",
@@ -101,7 +106,8 @@
           "type": "integer",
           "format": "int32"
         }
-      }
+      },
+      "additionalProperties": false
     }
   ],
   "definitions": {
diff --git a/schemars/tests/expected/enum-internal-duf.json b/schemars/tests/expected/enum-internal-duf.json
new file mode 100644
index 0000000..8b6fef8
--- /dev/null
+++ b/schemars/tests/expected/enum-internal-duf.json
@@ -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"
+          ]
+        }
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/schemars/tests/expected/enum-untagged-duf.json b/schemars/tests/expected/enum-untagged-duf.json
new file mode 100644
index 0000000..24397bf
--- /dev/null
+++ b/schemars/tests/expected/enum-untagged-duf.json
@@ -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"
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/schemars/tests/expected/indexmap.json b/schemars/tests/expected/indexmap.json
index e9d4df4..98065d9 100644
--- a/schemars/tests/expected/indexmap.json
+++ b/schemars/tests/expected/indexmap.json
@@ -18,7 +18,8 @@
       "items": {
         "type": "integer",
         "format": "int"
-      }
+      },
+      "uniqueItems": true
     }
   }
 }
\ No newline at end of file
diff --git a/schemars/tests/expected/remote_derive_generic.json b/schemars/tests/expected/remote_derive_generic.json
index c76610c..2fea80a 100644
--- a/schemars/tests/expected/remote_derive_generic.json
+++ b/schemars/tests/expected/remote_derive_generic.json
@@ -24,7 +24,8 @@
         "type": "array",
         "items": {
           "type": "string"
-        }
+        },
+        "uniqueItems": true
       }
     }
   },
diff --git a/schemars/tests/expected/schema-2019_09.json b/schemars/tests/expected/schema-2019_09.json
index 33a063b..cf11d35 100644
--- a/schemars/tests/expected/schema-2019_09.json
+++ b/schemars/tests/expected/schema-2019_09.json
@@ -315,7 +315,8 @@
       "type": "array",
       "items": {
         "type": "string"
-      }
+      },
+      "uniqueItems": true
     },
     "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).",
@@ -670,7 +671,8 @@
           "type": "array",
           "items": {
             "type": "string"
-          }
+          },
+          "uniqueItems": true
         },
         "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).",
diff --git a/schemars/tests/expected/schema-openapi3.json b/schemars/tests/expected/schema-openapi3.json
index 78deb95..0548828 100644
--- a/schemars/tests/expected/schema-openapi3.json
+++ b/schemars/tests/expected/schema-openapi3.json
@@ -255,7 +255,8 @@
       "type": "array",
       "items": {
         "type": "string"
-      }
+      },
+      "uniqueItems": true
     },
     "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).",
@@ -552,7 +553,8 @@
           "type": "array",
           "items": {
             "type": "string"
-          }
+          },
+          "uniqueItems": true
         },
         "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).",
diff --git a/schemars/tests/expected/schema.json b/schemars/tests/expected/schema.json
index b54d0a2..6cf840b 100644
--- a/schemars/tests/expected/schema.json
+++ b/schemars/tests/expected/schema.json
@@ -315,7 +315,8 @@
       "type": "array",
       "items": {
         "type": "string"
-      }
+      },
+      "uniqueItems": true
     },
     "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).",
@@ -674,7 +675,8 @@
           "type": "array",
           "items": {
             "type": "string"
-          }
+          },
+          "uniqueItems": true
         },
         "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).",
diff --git a/schemars/tests/expected/schema_with-enum-external.json b/schemars/tests/expected/schema_with-enum-external.json
index 2e7d415..364b5f3 100644
--- a/schemars/tests/expected/schema_with-enum-external.json
+++ b/schemars/tests/expected/schema_with-enum-external.json
@@ -19,7 +19,8 @@
             }
           }
         }
-      }
+      },
+      "additionalProperties": false
     },
     {
       "type": "object",
@@ -30,7 +31,8 @@
         "newType": {
           "type": "boolean"
         }
-      }
+      },
+      "additionalProperties": false
     },
     {
       "type": "object",
@@ -52,7 +54,8 @@
           "maxItems": 2,
           "minItems": 2
         }
-      }
+      },
+      "additionalProperties": false
     }
   ]
 }
\ No newline at end of file
diff --git a/schemars/tests/expected/schema_with-transparent-newtype.json b/schemars/tests/expected/schema_with-transparent-newtype.json
new file mode 100644
index 0000000..9f6afb3
--- /dev/null
+++ b/schemars/tests/expected/schema_with-transparent-newtype.json
@@ -0,0 +1,5 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "schema_fn",
+  "type": "boolean"
+}
\ No newline at end of file
diff --git a/schemars/tests/expected/skip_enum_variants.json b/schemars/tests/expected/skip_enum_variants.json
index c347df0..4acb2eb 100644
--- a/schemars/tests/expected/skip_enum_variants.json
+++ b/schemars/tests/expected/skip_enum_variants.json
@@ -18,7 +18,8 @@
           "type": "number",
           "format": "float"
         }
-      }
+      },
+      "additionalProperties": false
     }
   ]
 }
\ No newline at end of file
diff --git a/schemars/tests/schema_with_struct.rs b/schemars/tests/schema_with_struct.rs
index 1e94149..db85d80 100644
--- a/schemars/tests/schema_with_struct.rs
+++ b/schemars/tests/schema_with_struct.rs
@@ -42,3 +42,12 @@ pub struct Newtype(#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSch
 fn struct_newtype() -> TestResult {
     test_default_generated_schema::<Newtype>("schema_with-newtype")
 }
+
+#[derive(Debug, JsonSchema)]
+#[schemars(transparent)]
+pub struct TransparentNewtype(#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema);
+
+#[test]
+fn struct_transparent_newtype() -> TestResult {
+    test_default_generated_schema::<TransparentNewtype>("schema_with-transparent-newtype")
+}