Repository: GREsau/schemars
Pull request #113 (2021-10-08 06:36:44)

Fixes for internally tagged enums and flattening additional_properties
Flattening has some surprising outcomes when it comes to the `additional_properties` field. Consider that `None` is equivalent to `Some(Schema::Bool(true))`; merge currently prefers any `Some` to any `None` irrespective of the contents. Given the context of merge/flatten, the result of merging two `additional_field` values should be the union of the two schemas i.e. the schema that validates either.

This is particularly relevant for internally tagged schemas where currently additional properties are allowed even when `deny_unknown_fields` is set *and* can be constrained e.g. when a variant such as `StringMap(Map<&'static str, &'static str>),` is present.


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 90f4f72..b27b983 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,7 +15,7 @@ jobs:
         include:
           - rust: 1.37.0
             # exclude ui_test as the output is slightly different in rustc 1.37
-            test_features: "--features impl_json_schema,chrono,indexmap,either,uuid,smallvec,arrayvec"
+            test_features: "--features impl_json_schema,chrono,indexmap,either,uuid,smallvec,arrayvec,enumset"
             allow_failure: false
           - rust: stable
             test_features: "--all-features"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce09bc1..5b9f451 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## [0.8.7] - 2021-11-14
+### Added:
+- Implement `JsonSchema` for `EnumSet` (https://github.com/GREsau/schemars/pull/92)
+
+### Fixed:
+- Do not cause compile error when using a default value that doesn't implement `Serialize` (https://github.com/GREsau/schemars/issues/115)
+
 ## [0.8.6] - 2021-09-26
 ### Changed:
 - Use `oneOf` instead of `anyOf` for enums when possible (https://github.com/GREsau/schemars/issues/108)
diff --git a/README.md b/README.md
index 571045d..dbdcf8b 100644
--- a/README.md
+++ b/README.md
@@ -274,3 +274,13 @@ Schemars can implement `JsonSchema` on types from several popular crates, enable
 - [`arrayvec`](https://crates.io/crates/arrayvec) (^0.5)
 - [`url`](https://crates.io/crates/url) (^2.0)
 - [`bytes`](https://crates.io/crates/bytes) (^1.0)
+- [`enumset`](https://crates.io/crates/enumset) (^1.0)
+- [`rust_decimal`](https://crates.io/crates/rust_decimal) (^1.0)
+- [`bigdecimal`](https://crates.io/crates/bigdecimal) (^0.3)
+
+For example, to implement `JsonSchema` on types from `chrono`, enable it as a feature in the `schemars` dependency in your `Cargo.toml` like so:
+
+```toml
+[dependencies]
+schemars = { version = "0.8", features = ["chrono"] }
+```
diff --git a/docs/4-features.md b/docs/4-features.md
index d080908..a6aee04 100644
--- a/docs/4-features.md
+++ b/docs/4-features.md
@@ -28,3 +28,6 @@ Schemars can implement `JsonSchema` on types from several popular crates, enable
 - [`arrayvec`](https://crates.io/crates/arrayvec) (^0.5)
 - [`url`](https://crates.io/crates/url) (^2.0)
 - [`bytes`](https://crates.io/crates/bytes) (^1.0)
+- [`enumset`](https://crates.io/crates/enumset) (^1.0)
+- [`rust_decimal`](https://crates.io/crates/rust_decimal) (^1.0)
+- [`bigdecimal`](https://crates.io/crates/bigdecimal) (^0.3)
diff --git a/docs/_includes/examples/custom_settings.schema.json b/docs/_includes/examples/custom_settings.schema.json
index 59939be..12ac7d5 100644
--- a/docs/_includes/examples/custom_settings.schema.json
+++ b/docs/_includes/examples/custom_settings.schema.json
@@ -25,7 +25,7 @@
   },
   "definitions": {
     "MyEnum": {
-      "anyOf": [
+      "oneOf": [
         {
           "type": "object",
           "required": [
diff --git a/docs/_includes/examples/doc_comments.schema.json b/docs/_includes/examples/doc_comments.schema.json
index 0f3405c..121cdb4 100644
--- a/docs/_includes/examples/doc_comments.schema.json
+++ b/docs/_includes/examples/doc_comments.schema.json
@@ -33,7 +33,7 @@
   "definitions": {
     "MyEnum": {
       "title": "My Amazing Enum",
-      "anyOf": [
+      "oneOf": [
         {
           "description": "A wrapper around a `String`",
           "type": "object",
diff --git a/docs/_includes/examples/main.schema.json b/docs/_includes/examples/main.schema.json
index 737bcbc..ddbd9d3 100644
--- a/docs/_includes/examples/main.schema.json
+++ b/docs/_includes/examples/main.schema.json
@@ -27,7 +27,7 @@
   },
   "definitions": {
     "MyEnum": {
-      "anyOf": [
+      "oneOf": [
         {
           "type": "object",
           "required": [
diff --git a/docs/_includes/examples/validate.schema.json b/docs/_includes/examples/validate.schema.json
index e8ed35e..1e45a96 100644
--- a/docs/_includes/examples/validate.schema.json
+++ b/docs/_includes/examples/validate.schema.json
@@ -18,7 +18,7 @@
       "minimum": 1.0
     },
     "my_nullable_enum": {
-      "anyOf": [
+      "oneOf": [
         {
           "type": "object",
           "required": [
diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml
index 53a6584..013c0d6 100644
--- a/schemars/Cargo.toml
+++ b/schemars/Cargo.toml
@@ -3,7 +3,7 @@ name = "schemars"
 description = "Generate JSON Schemas from Rust code"
 homepage = "https://graham.cool/schemars/"
 repository = "https://github.com/GREsau/schemars"
-version = "0.8.6"
+version = "0.8.7"
 authors = ["Graham Esau <gesau@hotmail.co.uk>"]
 edition = "2018"
 license = "MIT"
@@ -13,7 +13,7 @@ categories = ["encoding"]
 build = "build.rs"
 
 [dependencies]
-schemars_derive = { version = "=0.8.6", optional = true, path = "../schemars_derive" }
+schemars_derive = { version = "=0.8.7", optional = true, path = "../schemars_derive" }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 dyn-clone = "1.0"
@@ -26,6 +26,9 @@ smallvec = { version = "1.0", optional = true }
 arrayvec = { version = "0.5", default-features = false, optional = true }
 url = { version = "2.0", default-features = false, optional = true }
 bytes = { version = "1.0", optional = true }
+rust_decimal = { version = "1", default-features = false, optional = true }
+bigdecimal = { version = "0.3", default-features = false, optional = true }
+enumset = { version = "1.0", optional = true }
 
 [dev-dependencies]
 pretty_assertions = "0.6.1"
@@ -87,5 +90,9 @@ required-features = ["ui_test"]
 name = "url"
 required-features = ["url"]
 
+[[test]]
+name = "enumset"
+required-features = ["enumset"]
+
 [package.metadata.docs.rs]
 all-features = true
diff --git a/schemars/examples/custom_settings.schema.json b/schemars/examples/custom_settings.schema.json
index 59939be..12ac7d5 100644
--- a/schemars/examples/custom_settings.schema.json
+++ b/schemars/examples/custom_settings.schema.json
@@ -25,7 +25,7 @@
   },
   "definitions": {
     "MyEnum": {
-      "anyOf": [
+      "oneOf": [
         {
           "type": "object",
           "required": [
diff --git a/schemars/examples/doc_comments.schema.json b/schemars/examples/doc_comments.schema.json
index 0f3405c..121cdb4 100644
--- a/schemars/examples/doc_comments.schema.json
+++ b/schemars/examples/doc_comments.schema.json
@@ -33,7 +33,7 @@
   "definitions": {
     "MyEnum": {
       "title": "My Amazing Enum",
-      "anyOf": [
+      "oneOf": [
         {
           "description": "A wrapper around a `String`",
           "type": "object",
diff --git a/schemars/examples/main.schema.json b/schemars/examples/main.schema.json
index 737bcbc..ddbd9d3 100644
--- a/schemars/examples/main.schema.json
+++ b/schemars/examples/main.schema.json
@@ -27,7 +27,7 @@
   },
   "definitions": {
     "MyEnum": {
-      "anyOf": [
+      "oneOf": [
         {
           "type": "object",
           "required": [
diff --git a/schemars/examples/validate.schema.json b/schemars/examples/validate.schema.json
index e8ed35e..1e45a96 100644
--- a/schemars/examples/validate.schema.json
+++ b/schemars/examples/validate.schema.json
@@ -18,7 +18,7 @@
       "minimum": 1.0
     },
     "my_nullable_enum": {
-      "anyOf": [
+      "oneOf": [
         {
           "type": "object",
           "required": [
diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs
index b914699..aa6b570 100644
--- a/schemars/src/_private.rs
+++ b/schemars/src/_private.rs
@@ -2,6 +2,8 @@ use crate::flatten::Merge;
 use crate::gen::SchemaGenerator;
 use crate::schema::{Metadata, Schema, SchemaObject};
 use crate::JsonSchema;
+use serde::Serialize;
+use serde_json::Value;
 
 // Helper for generating schemas for flattened `Option` fields.
 pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
@@ -32,3 +34,34 @@ pub fn apply_metadata(schema: Schema, metadata: Metadata) -> Schema {
         Schema::Object(schema_obj)
     }
 }
+
+/// Hack to simulate specialization:
+/// `MaybeSerializeWrapper(x).maybe_to_value()` will resolve to either
+/// - The inherent method `MaybeSerializeWrapper::maybe_to_value(...)` if x is `Serialize`
+/// - The trait method `NoSerialize::maybe_to_value(...)` from the blanket impl otherwise
+#[doc(hidden)]
+#[macro_export]
+macro_rules! _schemars_maybe_to_value {
+    ($expression:expr) => {{
+        #[allow(unused_imports)]
+        use $crate::_private::{MaybeSerializeWrapper, NoSerialize as _};
+
+        MaybeSerializeWrapper($expression).maybe_to_value()
+    }};
+}
+
+pub struct MaybeSerializeWrapper<T>(pub T);
+
+pub trait NoSerialize: Sized {
+    fn maybe_to_value(self) -> Option<Value> {
+        None
+    }
+}
+
+impl<T> NoSerialize for T {}
+
+impl<T: Serialize> MaybeSerializeWrapper<T> {
+    pub fn maybe_to_value(self) -> Option<Value> {
+        serde_json::value::to_value(self.0).ok()
+    }
+}
diff --git a/schemars/src/flatten.rs b/schemars/src/flatten.rs
index 646f614..35a734f 100644
--- a/schemars/src/flatten.rs
+++ b/schemars/src/flatten.rs
@@ -38,6 +38,31 @@ macro_rules! impl_merge {
     };
 }
 
+// For ObjectValidation::additional_properties.
+impl Merge for Option<Box<Schema>> {
+    fn merge(self, other: Self) -> Self {
+        match (self.map(|x| *x), other.map(|x| *x)) {
+            // Perfer permissive schemas.
+            (Some(Schema::Bool(true)), _) => Some(Box::new(true.into())),
+            (_, Some(Schema::Bool(true))) => Some(Box::new(true.into())),
+            (None, _) => None,
+            (_, None) => None,
+
+            // Merge if we have two non-trivial schemas.
+            (Some(Schema::Object(s1)), Some(Schema::Object(s2))) => {
+                Some(Box::new(Schema::Object(s1.merge(s2))))
+            }
+
+            // Perfer the more permissive schema.
+            (Some(s1 @ Schema::Object(_)), Some(Schema::Bool(false))) => Some(Box::new(s1)),
+            (Some(Schema::Bool(false)), Some(s2 @ Schema::Object(_))) => Some(Box::new(s2)),
+
+            // Default to the null schema.
+            (Some(Schema::Bool(false)), Some(Schema::Bool(false))) => Some(Box::new(false.into())),
+        }
+    }
+}
+
 impl_merge!(SchemaObject {
     merge: extensions instance_type enum_values
         metadata subschemas number string array object,
@@ -76,8 +101,8 @@ impl_merge!(ArrayValidation {
 });
 
 impl_merge!(ObjectValidation {
-    merge: required properties pattern_properties,
-    or: max_properties min_properties additional_properties property_names,
+    merge: required properties pattern_properties additional_properties,
+    or: max_properties min_properties property_names,
 });
 
 impl<T: Merge> Merge for Option<T> {
diff --git a/schemars/src/json_schema_impls/decimal.rs b/schemars/src/json_schema_impls/decimal.rs
new file mode 100644
index 0000000..6058a7f
--- /dev/null
+++ b/schemars/src/json_schema_impls/decimal.rs
@@ -0,0 +1,31 @@
+use crate::gen::SchemaGenerator;
+use crate::schema::*;
+use crate::JsonSchema;
+
+macro_rules! decimal_impl {
+    ($type:ty) => {
+        decimal_impl!($type => Number, "Number");
+    };
+    ($type:ty => $instance_type:ident, $name:expr) => {
+        impl JsonSchema for $type {
+            no_ref_schema!();
+
+            fn schema_name() -> String {
+                $name.to_owned()
+            }
+
+            fn json_schema(_: &mut SchemaGenerator) -> Schema {
+                SchemaObject {
+                    instance_type: Some(InstanceType::$instance_type.into()),
+                    ..Default::default()
+                }
+                .into()
+            }
+        }
+    };
+}
+
+#[cfg(feature="rust_decimal")]
+decimal_impl!(rust_decimal::Decimal);
+#[cfg(feature="bigdecimal")]
+decimal_impl!(bigdecimal::BigDecimal);
diff --git a/schemars/src/json_schema_impls/enumset.rs b/schemars/src/json_schema_impls/enumset.rs
new file mode 100644
index 0000000..22a3ebc
--- /dev/null
+++ b/schemars/src/json_schema_impls/enumset.rs
@@ -0,0 +1,6 @@
+use crate::gen::SchemaGenerator;
+use crate::schema::*;
+use crate::JsonSchema;
+use enumset::{EnumSet, EnumSetType};
+
+forward_impl!((<T> JsonSchema for EnumSet<T> where T: EnumSetType + JsonSchema) => std::collections::BTreeSet<T>);
diff --git a/schemars/src/json_schema_impls/mod.rs b/schemars/src/json_schema_impls/mod.rs
index ff2e1e7..950f0f7 100644
--- a/schemars/src/json_schema_impls/mod.rs
+++ b/schemars/src/json_schema_impls/mod.rs
@@ -45,8 +45,12 @@ mod bytes;
 #[cfg(feature = "chrono")]
 mod chrono;
 mod core;
+#[cfg(any(feature = "rust_decimal", feature="bigdecimal"))]
+mod decimal;
 #[cfg(feature = "either")]
 mod either;
+#[cfg(feature = "enumset")]
+mod enumset;
 mod ffi;
 #[cfg(feature = "indexmap")]
 mod indexmap;
diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs
index e400ccf..5ef0bdf 100644
--- a/schemars/src/lib.rs
+++ b/schemars/src/lib.rs
@@ -269,6 +269,16 @@ Schemars can implement `JsonSchema` on types from several popular crates, enable
 - [`arrayvec`](https://crates.io/crates/arrayvec) (^0.5)
 - [`url`](https://crates.io/crates/url) (^2.0)
 - [`bytes`](https://crates.io/crates/bytes) (^1.0)
+- [`enumset`](https://crates.io/crates/enumset) (^1.0)
+- [`rust_decimal`](https://crates.io/crates/rust_decimal) (^1.0)
+- [`bigdecimal`](https://crates.io/crates/bigdecimal) (^0.3)
+
+For example, to implement `JsonSchema` on types from `chrono`, enable it as a feature in the `schemars` dependency in your `Cargo.toml` like so:
+
+```toml
+[dependencies]
+schemars = { version = "0.8", features = ["chrono"] }
+```
 */
 
 /// The map type used by schemars types.
diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml
index d4be61a..4e08ee0 100644
--- a/schemars_derive/Cargo.toml
+++ b/schemars_derive/Cargo.toml
@@ -3,7 +3,7 @@ name = "schemars_derive"
 description = "Macros for #[derive(JsonSchema)], for use with schemars"
 homepage = "https://graham.cool/schemars/"
 repository = "https://github.com/GREsau/schemars"
-version = "0.8.6"
+version = "0.8.7"
 authors = ["Graham Esau <gesau@hotmail.co.uk>"]
 edition = "2018"
 license = "MIT"
diff --git a/schemars_derive/src/metadata.rs b/schemars_derive/src/metadata.rs
index aefe243..32dbf67 100644
--- a/schemars_derive/src/metadata.rs
+++ b/schemars_derive/src/metadata.rs
@@ -68,7 +68,7 @@ impl<'a> SchemaMetadata<'a> {
 
         if let Some(default) = &self.default {
             setters.push(quote! {
-                default: #default.and_then(|d| schemars::_serde_json::value::to_value(d).ok()),
+                default: #default.and_then(|d| schemars::_schemars_maybe_to_value!(d)),
             });
         }
 
diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs
index 4c463dc..c5b1672 100644
--- a/schemars_derive/src/schema_exprs.rs
+++ b/schemars_derive/src/schema_exprs.rs
@@ -183,6 +183,12 @@ fn expr_for_external_tagged_enum<'a>(
                     required.insert(#name.to_owned());
                     required
                 },
+                // Externally tagged variants must prohibit additional
+                // properties irrespective of the disposition of
+                // `deny_unknown_fields`. If additional properties were allowed
+                // one could easily construct an object that validated against
+                // multiple variants since here it's the properties rather than
+                // the values of a property that distingish between variants.
                 additional_properties: Some(Box::new(false.into())),
                 ..Default::default()
             })),
@@ -206,6 +212,13 @@ fn expr_for_internal_tagged_enum<'a>(
 ) -> TokenStream {
     let mut unique_names = HashSet::new();
     let mut count = 0;
+    let set_additional_properties = if deny_unknown_fields {
+        quote! {
+            additional_properties: Some(Box::new(false.into())),
+        }
+    } else {
+        TokenStream::new()
+    };
     let variant_schemas = variants
         .map(|variant| {
             unique_names.insert(variant.name());
@@ -230,6 +243,9 @@ fn expr_for_internal_tagged_enum<'a>(
                         required.insert(#tag_name.to_owned());
                         required
                     },
+                    // As we're creating a "wrapper" object, we can honor the
+                    // disposition of deny_unknown_fields.
+                    #set_additional_properties
                     ..Default::default()
                 })),
             });
@@ -328,6 +344,8 @@ fn expr_for_adjacent_tagged_enum<'a>(
                         #add_content_to_required
                         required
                     },
+                    // As we're creating a "wrapper" object, we can honor the
+                    // disposition of deny_unknown_fields.
                     #set_additional_properties
                     ..Default::default()
                 })),


Test patch:
diff --git a/schemars/tests/default.rs b/schemars/tests/default.rs
index d91585a..0d68d0e 100644
--- a/schemars/tests/default.rs
+++ b/schemars/tests/default.rs
@@ -1,6 +1,5 @@
 mod util;
 use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
 use util::*;
 
 fn is_default<T: Default + PartialEq>(value: &T) -> bool {
@@ -25,7 +24,7 @@ where
     ser.collect_str(&format_args!("i:{} b:{}", value.my_int, value.my_bool))
 }
 
-#[derive(Default, Deserialize, Serialize, JsonSchema, Debug)]
+#[derive(Default, JsonSchema, Debug)]
 #[serde(default)]
 pub struct MyStruct {
     pub my_int: i32,
@@ -37,9 +36,10 @@ pub struct MyStruct {
         skip_serializing_if = "is_default"
     )]
     pub my_struct2_default_skipped: MyStruct2,
+    pub not_serialize: NotSerialize,
 }
 
-#[derive(Default, Deserialize, Serialize, JsonSchema, Debug, PartialEq)]
+#[derive(Default, JsonSchema, Debug, PartialEq)]
 #[serde(default = "ten_and_true")]
 pub struct MyStruct2 {
     #[serde(default = "six")]
@@ -47,6 +47,9 @@ pub struct MyStruct2 {
     pub my_bool: bool,
 }
 
+#[derive(Default, JsonSchema, Debug)]
+pub struct NotSerialize;
+
 #[test]
 fn schema_default_values() -> TestResult {
     test_default_generated_schema::<MyStruct>("default")
diff --git a/schemars/tests/enum.rs b/schemars/tests/enum.rs
index ea34d06..48e0d34 100644
--- a/schemars/tests/enum.rs
+++ b/schemars/tests/enum.rs
@@ -99,3 +99,16 @@ pub enum Adjacent {
 fn enum_adjacent_tagged() -> TestResult {
     test_default_generated_schema::<Adjacent>("enum-adjacent-tagged")
 }
+
+#[derive(Debug, JsonSchema)]
+#[schemars(tag = "typeProperty")]
+pub enum SimpleInternal {
+    A,
+    B,
+    C,
+}
+
+#[test]
+fn enum_simple_internal_tag() -> TestResult {
+    test_default_generated_schema::<SimpleInternal>("enum-simple-internal")
+}
diff --git a/schemars/tests/enum_deny_unknown_fields.rs b/schemars/tests/enum_deny_unknown_fields.rs
index bf0f400..8720b72 100644
--- a/schemars/tests/enum_deny_unknown_fields.rs
+++ b/schemars/tests/enum_deny_unknown_fields.rs
@@ -104,3 +104,16 @@ pub enum Adjacent {
 fn enum_adjacent_tagged() -> TestResult {
     test_default_generated_schema::<Adjacent>("enum-adjacent-tagged-duf")
 }
+
+#[derive(Debug, JsonSchema)]
+#[schemars(tag = "typeProperty", deny_unknown_fields)]
+pub enum SimpleInternal {
+    A,
+    B,
+    C,
+}
+
+#[test]
+fn enum_simple_internal_tag() -> TestResult {
+    test_default_generated_schema::<SimpleInternal>("enum-simple-internal-duf")
+}
diff --git a/schemars/tests/enumset.rs b/schemars/tests/enumset.rs
new file mode 100644
index 0000000..27f2030
--- /dev/null
+++ b/schemars/tests/enumset.rs
@@ -0,0 +1,15 @@
+mod util;
+use enumset::{EnumSet, EnumSetType};
+use schemars::JsonSchema;
+use util::*;
+
+#[derive(EnumSetType, JsonSchema)]
+enum Foo {
+    Bar,
+    Baz,
+}
+
+#[test]
+fn enumset() -> TestResult {
+    test_default_generated_schema::<EnumSet<Foo>>("enumset")
+}
diff --git a/schemars/tests/expected/default.json b/schemars/tests/expected/default.json
index 6210986..aefef83 100644
--- a/schemars/tests/expected/default.json
+++ b/schemars/tests/expected/default.json
@@ -22,6 +22,9 @@
     },
     "my_struct2_default_skipped": {
       "$ref": "#/definitions/MyStruct2"
+    },
+    "not_serialize": {
+      "$ref": "#/definitions/NotSerialize"
     }
   },
   "definitions": {
@@ -38,6 +41,9 @@
           "type": "boolean"
         }
       }
+    },
+    "NotSerialize": {
+      "type": "null"
     }
   }
 }
\ No newline at end of file
diff --git a/schemars/tests/expected/enum-internal-duf.json b/schemars/tests/expected/enum-internal-duf.json
index deb3966..fc36644 100644
--- a/schemars/tests/expected/enum-internal-duf.json
+++ b/schemars/tests/expected/enum-internal-duf.json
@@ -14,7 +14,8 @@
             "UnitOne"
           ]
         }
-      }
+      },
+      "additionalProperties": false
     },
     {
       "type": "object",
@@ -45,7 +46,8 @@
             "UnitStructNewType"
           ]
         }
-      }
+      },
+      "additionalProperties": false
     },
     {
       "type": "object",
@@ -106,7 +108,8 @@
             "UnitTwo"
           ]
         }
-      }
+      },
+      "additionalProperties": false
     },
     {
       "type": [
@@ -124,7 +127,8 @@
             "WithInt"
           ]
         }
-      }
+      },
+      "additionalProperties": false
     }
   ]
 }
\ No newline at end of file
diff --git a/schemars/tests/expected/enum-internal.json b/schemars/tests/expected/enum-internal.json
index b43b779..37739b0 100644
--- a/schemars/tests/expected/enum-internal.json
+++ b/schemars/tests/expected/enum-internal.json
@@ -28,9 +28,6 @@
             "StringMap"
           ]
         }
-      },
-      "additionalProperties": {
-        "type": "string"
       }
     },
     {
diff --git a/schemars/tests/expected/enum-simple-internal-duf.json b/schemars/tests/expected/enum-simple-internal-duf.json
new file mode 100644
index 0000000..833f7b7
--- /dev/null
+++ b/schemars/tests/expected/enum-simple-internal-duf.json
@@ -0,0 +1,51 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "SimpleInternal",
+  "oneOf": [
+    {
+      "type": "object",
+      "required": [
+        "typeProperty"
+      ],
+      "properties": {
+        "typeProperty": {
+          "type": "string",
+          "enum": [
+            "A"
+          ]
+        }
+      },
+      "additionalProperties": false
+    },
+    {
+      "type": "object",
+      "required": [
+        "typeProperty"
+      ],
+      "properties": {
+        "typeProperty": {
+          "type": "string",
+          "enum": [
+            "B"
+          ]
+        }
+      },
+      "additionalProperties": false
+    },
+    {
+      "type": "object",
+      "required": [
+        "typeProperty"
+      ],
+      "properties": {
+        "typeProperty": {
+          "type": "string",
+          "enum": [
+            "C"
+          ]
+        }
+      },
+      "additionalProperties": false
+    }
+  ]
+}
\ No newline at end of file
diff --git a/schemars/tests/expected/enum-simple-internal.json b/schemars/tests/expected/enum-simple-internal.json
new file mode 100644
index 0000000..50cd62c
--- /dev/null
+++ b/schemars/tests/expected/enum-simple-internal.json
@@ -0,0 +1,48 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "SimpleInternal",
+  "oneOf": [
+    {
+      "type": "object",
+      "required": [
+        "typeProperty"
+      ],
+      "properties": {
+        "typeProperty": {
+          "type": "string",
+          "enum": [
+            "A"
+          ]
+        }
+      }
+    },
+    {
+      "type": "object",
+      "required": [
+        "typeProperty"
+      ],
+      "properties": {
+        "typeProperty": {
+          "type": "string",
+          "enum": [
+            "B"
+          ]
+        }
+      }
+    },
+    {
+      "type": "object",
+      "required": [
+        "typeProperty"
+      ],
+      "properties": {
+        "typeProperty": {
+          "type": "string",
+          "enum": [
+            "C"
+          ]
+        }
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/schemars/tests/expected/enumset.json b/schemars/tests/expected/enumset.json
new file mode 100644
index 0000000..4950c93
--- /dev/null
+++ b/schemars/tests/expected/enumset.json
@@ -0,0 +1,18 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "Set_of_Foo",
+  "type": "array",
+  "items": {
+    "$ref": "#/definitions/Foo"
+  },
+  "uniqueItems": true,
+  "definitions": {
+    "Foo": {
+      "type": "string",
+      "enum": [
+        "Bar",
+        "Baz"
+      ]
+    }
+  }
+}