Repository: GREsau/schemars (rust)

Issue:
could certain schemas derived from enums more precisely use oneOf?
When writing an SDK generator, we noticed that schemas derived from enums use `anyOf` for complex variants (and `enum` for unit variants). This seems necessary in some cases, for example:

```rust
#[derive(JsonSchema)]
#[serde(untagged)]
pub enum E {
    ThingA { a: String },
    ThingB { a: String },
}
```

i.e. where the variants literally cannot be disambiguated. In such a case I don't believe that `oneOf` would be satisfiable since two subschemas would be identical.

However with tagged enums (external, internal, or adjacent) would it be reasonable to change `anyOf` to `oneOf`? For example in this output of the type above, removing the `untagged` directive:

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "E",
  "anyOf": [
    {
      "type": "object",
      "required": [
        "ThingA"
      ],
      "properties": {
        "ThingA": {
          "type": "object",
          "required": [
            "a"
          ],
          "properties": {
            "a": {
              "type": "string"
            }
          }
        }
      },
      "additionalProperties": false
    },
    {
      "type": "object",
      "required": [
        "ThingB"
      ],
      "properties": {
        "ThingB": {
          "type": "object",
          "required": [
            "a"
          ],
          "properties": {
            "a": {
              "type": "string"
            }
          }
        }
      },
      "additionalProperties": false
    }
  ]
}
```

In this case, where each subschema is mutually exclusive, I believe that `anyOf` and `oneOf` are equivalent. It would, however, be more explicit to use `oneOf` and, as noted earlier, it would simplify our particular use case of SDK generation.

If this seems like a reasonable change I'd be happy to submit a PR, but I wanted to check in first.

Pull request description:
Use oneOf for enums when possible
Fixes #98 

I did some minor clean up to the test infra to make the output easier to understand

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5792dd3..7198d6b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## [0.8.5] - 2021-09-20
+### Fixed:
+- Allow fields with plain `#[validate]` attributes (https://github.com/GREsau/schemars/issues/109)
+
 ## [0.8.4] - 2021-09-19
 ### Added:
 - `#[schemars(schema_with = "...")]` attribute can now be set on enum variants.
diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml
index af7059e..3a384e6 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.4"
+version = "0.8.5"
 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.4", optional = true, path = "../schemars_derive" }
+schemars_derive = { version = "=0.8.5", optional = true, path = "../schemars_derive" }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 dyn-clone = "1.0"
diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml
index d92827e..7216a30 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.4"
+version = "0.8.5"
 authors = ["Graham Esau <gesau@hotmail.co.uk>"]
 edition = "2018"
 license = "MIT"
diff --git a/schemars_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs
index cc81a92..b1b9f04 100644
--- a/schemars_derive/src/attr/mod.rs
+++ b/schemars_derive/src/attr/mod.rs
@@ -104,7 +104,7 @@ impl Attrs {
 
         for meta_item in attrs
             .iter()
-            .flat_map(|attr| get_meta_items(attr, attr_type, errors))
+            .flat_map(|attr| get_meta_items(attr, attr_type, errors, ignore_errors))
             .flatten()
         {
             match &meta_item {
@@ -201,6 +201,7 @@ fn get_meta_items(
     attr: &syn::Attribute,
     attr_type: &'static str,
     errors: &Ctxt,
+    ignore_errors: bool,
 ) -> Result<Vec<syn::NestedMeta>, ()> {
     if !attr.path.is_ident(attr_type) {
         return Ok(Vec::new());
@@ -209,11 +210,15 @@ fn get_meta_items(
     match attr.parse_meta() {
         Ok(List(meta)) => Ok(meta.nested.into_iter().collect()),
         Ok(other) => {
-            errors.error_spanned_by(other, format!("expected #[{}(...)]", attr_type));
+            if !ignore_errors {
+                errors.error_spanned_by(other, format!("expected #[{}(...)]", attr_type))
+            }
             Err(())
         }
         Err(err) => {
-            errors.error_spanned_by(attr, err);
+            if !ignore_errors {
+                errors.error_spanned_by(attr, err)
+            }
             Err(())
         }
     }
diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs
index 398fa59..1a93a2a 100644
--- a/schemars_derive/src/attr/validation.rs
+++ b/schemars_derive/src/attr/validation.rs
@@ -99,7 +99,7 @@ impl ValidationAttrs {
 
         for meta_item in attrs
             .iter()
-            .flat_map(|attr| get_meta_items(attr, attr_type, errors))
+            .flat_map(|attr| get_meta_items(attr, attr_type, errors, ignore_errors))
             .flatten()
         {
             match &meta_item {
diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs
index f2c76e9..4c463dc 100644
--- a/schemars_derive/src/schema_exprs.rs
+++ b/schemars_derive/src/schema_exprs.rs
@@ -1,3 +1,5 @@
+use std::collections::HashSet;
+
 use crate::{ast::*, attr::WithAttr, metadata::SchemaMetadata};
 use proc_macro2::{Span, TokenStream};
 use serde_derive_internals::ast::Style;
@@ -140,8 +142,14 @@ 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());
+    let mut unique_names = HashSet::<String>::new();
+    let mut count = 0;
+    let (unit_variants, complex_variants): (Vec<_>, Vec<_>) = variants
+        .inspect(|v| {
+            unique_names.insert(v.name());
+            count += 1;
+        })
+        .partition(|v| v.is_unit() && v.attrs.with.is_none());
 
     let unit_names = unit_variants.iter().map(|v| v.name());
     let unit_schema = schema_object(quote! {
@@ -188,12 +196,7 @@ fn expr_for_external_tagged_enum<'a>(
         schema_expr
     }));
 
-    schema_object(quote! {
-        subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
-            any_of: Some(vec![#(#schemas),*]),
-            ..Default::default()
-        })),
-    })
+    variant_subschemas(unique_names.len() == count, schemas)
 }
 
 fn expr_for_internal_tagged_enum<'a>(
@@ -201,70 +204,71 @@ fn expr_for_internal_tagged_enum<'a>(
     tag_name: &str,
     deny_unknown_fields: bool,
 ) -> TokenStream {
-    let variant_schemas = variants.map(|variant| {
-        let name = variant.name();
-        let type_schema = schema_object(quote! {
-            instance_type: Some(schemars::schema::InstanceType::String.into()),
-            enum_values: Some(vec![#name.into()]),
-        });
-
-        let mut tag_schema = schema_object(quote! {
-            instance_type: Some(schemars::schema::InstanceType::Object.into()),
-            object: Some(Box::new(schemars::schema::ObjectValidation {
-                properties: {
-                    let mut props = schemars::Map::new();
-                    props.insert(#tag_name.to_owned(), #type_schema);
-                    props
-                },
-                required: {
-                    let mut required = schemars::Set::new();
-                    required.insert(#tag_name.to_owned());
-                    required
-                },
-                ..Default::default()
-            })),
-        });
-
-        variant.attrs.as_metadata().apply_to_schema(&mut tag_schema);
-
-        if let Some(variant_schema) =
-            expr_for_untagged_enum_variant_for_flatten(&variant, deny_unknown_fields)
-        {
-            tag_schema.extend(quote!(.flatten(#variant_schema)))
-        }
+    let mut unique_names = HashSet::new();
+    let mut count = 0;
+    let variant_schemas = variants
+        .map(|variant| {
+            unique_names.insert(variant.name());
+            count += 1;
+
+            let name = variant.name();
+            let type_schema = schema_object(quote! {
+                instance_type: Some(schemars::schema::InstanceType::String.into()),
+                enum_values: Some(vec![#name.into()]),
+            });
+
+            let mut tag_schema = schema_object(quote! {
+                instance_type: Some(schemars::schema::InstanceType::Object.into()),
+                object: Some(Box::new(schemars::schema::ObjectValidation {
+                    properties: {
+                        let mut props = schemars::Map::new();
+                        props.insert(#tag_name.to_owned(), #type_schema);
+                        props
+                    },
+                    required: {
+                        let mut required = schemars::Set::new();
+                        required.insert(#tag_name.to_owned());
+                        required
+                    },
+                    ..Default::default()
+                })),
+            });
+
+            variant.attrs.as_metadata().apply_to_schema(&mut tag_schema);
+
+            if let Some(variant_schema) =
+                expr_for_untagged_enum_variant_for_flatten(variant, deny_unknown_fields)
+            {
+                tag_schema.extend(quote!(.flatten(#variant_schema)))
+            }
 
-        tag_schema
-    });
+            tag_schema
+        })
+        .collect();
 
-    schema_object(quote! {
-        subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
-            any_of: Some(vec![#(#variant_schemas),*]),
-            ..Default::default()
-        })),
-    })
+    variant_subschemas(unique_names.len() == count, variant_schemas)
 }
 
 fn expr_for_untagged_enum<'a>(
     variants: impl Iterator<Item = &'a Variant<'a>>,
     deny_unknown_fields: bool,
 ) -> TokenStream {
-    let schemas = variants.map(|variant| {
-        let mut schema_expr = expr_for_untagged_enum_variant(variant, deny_unknown_fields);
+    let schemas = variants
+        .map(|variant| {
+            let mut schema_expr = expr_for_untagged_enum_variant(variant, deny_unknown_fields);
 
-        variant
-            .attrs
-            .as_metadata()
-            .apply_to_schema(&mut schema_expr);
+            variant
+                .attrs
+                .as_metadata()
+                .apply_to_schema(&mut schema_expr);
 
-        schema_expr
-    });
+            schema_expr
+        })
+        .collect();
 
-    schema_object(quote! {
-        subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
-            any_of: Some(vec![#(#schemas),*]),
-            ..Default::default()
-        })),
-    })
+    // Untagged enums can easily have variants whose schemas overlap; rather
+    // that checking the exclusivity of each subschema we simply us `any_of`.
+    variant_subschemas(false, schemas)
 }
 
 fn expr_for_adjacent_tagged_enum<'a>(
@@ -273,70 +277,92 @@ fn expr_for_adjacent_tagged_enum<'a>(
     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, deny_unknown_fields))
-        };
+    let mut unique_names = HashSet::new();
+    let mut count = 0;
+    let schemas = variants
+        .map(|variant| {
+            unique_names.insert(variant.name());
+            count += 1;
+
+            let content_schema = if variant.is_unit() && variant.attrs.with.is_none() {
+                None
+            } else {
+                Some(expr_for_untagged_enum_variant(variant, deny_unknown_fields))
+            };
 
-        let (add_content_to_props, add_content_to_required) = content_schema
-            .map(|content_schema| {
-                (
-                    quote!(props.insert(#content_name.to_owned(), #content_schema);),
-                    quote!(required.insert(#content_name.to_owned());),
-                )
-            })
-            .unwrap_or_default();
+            let (add_content_to_props, add_content_to_required) = content_schema
+                .map(|content_schema| {
+                    (
+                        quote!(props.insert(#content_name.to_owned(), #content_schema);),
+                        quote!(required.insert(#content_name.to_owned());),
+                    )
+                })
+                .unwrap_or_default();
+
+            let name = variant.name();
+            let tag_schema = schema_object(quote! {
+                instance_type: Some(schemars::schema::InstanceType::String.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 name = variant.name();
-        let tag_schema = schema_object(quote! {
-            instance_type: Some(schemars::schema::InstanceType::String.into()),
-            enum_values: Some(vec![#name.into()]),
-        });
+            let mut outer_schema = schema_object(quote! {
+                instance_type: Some(schemars::schema::InstanceType::Object.into()),
+                object: Some(Box::new(schemars::schema::ObjectValidation {
+                    properties: {
+                        let mut props = schemars::Map::new();
+                        props.insert(#tag_name.to_owned(), #tag_schema);
+                        #add_content_to_props
+                        props
+                    },
+                    required: {
+                        let mut required = schemars::Set::new();
+                        required.insert(#tag_name.to_owned());
+                        #add_content_to_required
+                        required
+                    },
+                    #set_additional_properties
+                    ..Default::default()
+                })),
+            });
+
+            variant
+                .attrs
+                .as_metadata()
+                .apply_to_schema(&mut outer_schema);
+
+            outer_schema
+        })
+        .collect();
 
-        let set_additional_properties = if deny_unknown_fields {
-            quote! {
-                additional_properties: Some(Box::new(false.into())),
-            }
-        } else {
-            TokenStream::new()
-        };
+    variant_subschemas(unique_names.len() == count, schemas)
+}
 
-        let mut outer_schema = schema_object(quote! {
-            instance_type: Some(schemars::schema::InstanceType::Object.into()),
-            object: Some(Box::new(schemars::schema::ObjectValidation {
-                properties: {
-                    let mut props = schemars::Map::new();
-                    props.insert(#tag_name.to_owned(), #tag_schema);
-                    #add_content_to_props
-                    props
-                },
-                required: {
-                    let mut required = schemars::Set::new();
-                    required.insert(#tag_name.to_owned());
-                    #add_content_to_required
-                    required
-                },
-                #set_additional_properties
+/// Callers must determine if all subschemas are mutually exclusive. This can
+/// be done for most tagging regimes by checking that all tag names are unique.
+fn variant_subschemas(unique: bool, schemas: Vec<TokenStream>) -> TokenStream {
+    if unique {
+        schema_object(quote! {
+            subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
+                one_of: Some(vec![#(#schemas),*]),
                 ..Default::default()
             })),
-        });
-
-        variant
-            .attrs
-            .as_metadata()
-            .apply_to_schema(&mut outer_schema);
-
-        outer_schema
-    });
-
-    schema_object(quote! {
-        subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
-            any_of: Some(vec![#(#schemas),*]),
-            ..Default::default()
-        })),
-    })
+        })
+    } else {
+        schema_object(quote! {
+            subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
+                any_of: Some(vec![#(#schemas),*]),
+                ..Default::default()
+            })),
+        })
+    }
 }
 
 fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool) -> TokenStream {


Test patch:
diff --git a/schemars/tests/expected/deprecated-enum.json b/schemars/tests/expected/deprecated-enum.json
index f869be2..074310f 100644
--- a/schemars/tests/expected/deprecated-enum.json
+++ b/schemars/tests/expected/deprecated-enum.json
@@ -2,7 +2,7 @@
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "DeprecatedEnum",
   "deprecated": true,
-  "anyOf": [
+  "oneOf": [
     {
       "type": "string",
       "enum": [
@@ -24,13 +24,13 @@
             "foo"
           ],
           "properties": {
-            "foo": {
-              "type": "integer",
-              "format": "int32"
-            },
             "deprecated_field": {
               "deprecated": true,
               "type": "boolean"
+            },
+            "foo": {
+              "type": "integer",
+              "format": "int32"
             }
           }
         }
diff --git a/schemars/tests/expected/doc_comments_enum.json b/schemars/tests/expected/doc_comments_enum.json
index edcb339..0adc380 100644
--- a/schemars/tests/expected/doc_comments_enum.json
+++ b/schemars/tests/expected/doc_comments_enum.json
@@ -2,7 +2,7 @@
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "This is the enum's title",
   "description": "This is the enum's description.",
-  "anyOf": [
+  "oneOf": [
     {
       "type": "string",
       "enum": [
diff --git a/schemars/tests/expected/enum-adjacent-tagged-duf.json b/schemars/tests/expected/enum-adjacent-tagged-duf.json
index 011b378..c5b54c8 100644
--- a/schemars/tests/expected/enum-adjacent-tagged-duf.json
+++ b/schemars/tests/expected/enum-adjacent-tagged-duf.json
@@ -1,7 +1,7 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "Adjacent",
-  "anyOf": [
+  "oneOf": [
     {
       "type": "object",
       "required": [
@@ -24,17 +24,17 @@
         "t"
       ],
       "properties": {
-        "t": {
-          "type": "string",
-          "enum": [
-            "StringMap"
-          ]
-        },
         "c": {
           "type": "object",
           "additionalProperties": {
             "type": "string"
           }
+        },
+        "t": {
+          "type": "string",
+          "enum": [
+            "StringMap"
+          ]
         }
       },
       "additionalProperties": false
@@ -46,14 +46,14 @@
         "t"
       ],
       "properties": {
+        "c": {
+          "$ref": "#/definitions/UnitStruct"
+        },
         "t": {
           "type": "string",
           "enum": [
             "UnitStructNewType"
           ]
-        },
-        "c": {
-          "$ref": "#/definitions/UnitStruct"
         }
       },
       "additionalProperties": false
@@ -65,14 +65,14 @@
         "t"
       ],
       "properties": {
+        "c": {
+          "$ref": "#/definitions/Struct"
+        },
         "t": {
           "type": "string",
           "enum": [
             "StructNewType"
           ]
-        },
-        "c": {
-          "$ref": "#/definitions/Struct"
         }
       },
       "additionalProperties": false
@@ -84,12 +84,6 @@
         "t"
       ],
       "properties": {
-        "t": {
-          "type": "string",
-          "enum": [
-            "Struct"
-          ]
-        },
         "c": {
           "type": "object",
           "required": [
@@ -97,15 +91,21 @@
             "foo"
           ],
           "properties": {
+            "bar": {
+              "type": "boolean"
+            },
             "foo": {
               "type": "integer",
               "format": "int32"
-            },
-            "bar": {
-              "type": "boolean"
             }
           },
           "additionalProperties": false
+        },
+        "t": {
+          "type": "string",
+          "enum": [
+            "Struct"
+          ]
         }
       },
       "additionalProperties": false
@@ -117,12 +117,6 @@
         "t"
       ],
       "properties": {
-        "t": {
-          "type": "string",
-          "enum": [
-            "Tuple"
-          ]
-        },
         "c": {
           "type": "array",
           "items": [
@@ -136,6 +130,12 @@
           ],
           "maxItems": 2,
           "minItems": 2
+        },
+        "t": {
+          "type": "string",
+          "enum": [
+            "Tuple"
+          ]
         }
       },
       "additionalProperties": false
@@ -162,24 +162,21 @@
         "t"
       ],
       "properties": {
+        "c": {
+          "type": "integer",
+          "format": "int32"
+        },
         "t": {
           "type": "string",
           "enum": [
             "WithInt"
           ]
-        },
-        "c": {
-          "type": "integer",
-          "format": "int32"
         }
       },
       "additionalProperties": false
     }
   ],
   "definitions": {
-    "UnitStruct": {
-      "type": "null"
-    },
     "Struct": {
       "type": "object",
       "required": [
@@ -187,14 +184,17 @@
         "foo"
       ],
       "properties": {
+        "bar": {
+          "type": "boolean"
+        },
         "foo": {
           "type": "integer",
           "format": "int32"
-        },
-        "bar": {
-          "type": "boolean"
         }
       }
+    },
+    "UnitStruct": {
+      "type": "null"
     }
   }
 }
\ No newline at end of file
diff --git a/schemars/tests/expected/enum-adjacent-tagged.json b/schemars/tests/expected/enum-adjacent-tagged.json
index 4e0ca12..efe19dc 100644
--- a/schemars/tests/expected/enum-adjacent-tagged.json
+++ b/schemars/tests/expected/enum-adjacent-tagged.json
@@ -1,7 +1,7 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "Adjacent",
-  "anyOf": [
+  "oneOf": [
     {
       "type": "object",
       "required": [
@@ -23,17 +23,17 @@
         "t"
       ],
       "properties": {
-        "t": {
-          "type": "string",
-          "enum": [
-            "StringMap"
-          ]
-        },
         "c": {
           "type": "object",
           "additionalProperties": {
             "type": "string"
           }
+        },
+        "t": {
+          "type": "string",
+          "enum": [
+            "StringMap"
+          ]
         }
       }
     },
@@ -44,14 +44,14 @@
         "t"
       ],
       "properties": {
+        "c": {
+          "$ref": "#/definitions/UnitStruct"
+        },
         "t": {
           "type": "string",
           "enum": [
             "UnitStructNewType"
           ]
-        },
-        "c": {
-          "$ref": "#/definitions/UnitStruct"
         }
       }
     },
@@ -62,14 +62,14 @@
         "t"
       ],
       "properties": {
+        "c": {
+          "$ref": "#/definitions/Struct"
+        },
         "t": {
           "type": "string",
           "enum": [
             "StructNewType"
           ]
-        },
-        "c": {
-          "$ref": "#/definitions/Struct"
         }
       }
     },
@@ -80,12 +80,6 @@
         "t"
       ],
       "properties": {
-        "t": {
-          "type": "string",
-          "enum": [
-            "Struct"
-          ]
-        },
         "c": {
           "type": "object",
           "required": [
@@ -93,14 +87,20 @@
             "foo"
           ],
           "properties": {
+            "bar": {
+              "type": "boolean"
+            },
             "foo": {
               "type": "integer",
               "format": "int32"
-            },
-            "bar": {
-              "type": "boolean"
             }
           }
+        },
+        "t": {
+          "type": "string",
+          "enum": [
+            "Struct"
+          ]
         }
       }
     },
@@ -111,12 +111,6 @@
         "t"
       ],
       "properties": {
-        "t": {
-          "type": "string",
-          "enum": [
-            "Tuple"
-          ]
-        },
         "c": {
           "type": "array",
           "items": [
@@ -130,6 +124,12 @@
           ],
           "maxItems": 2,
           "minItems": 2
+        },
+        "t": {
+          "type": "string",
+          "enum": [
+            "Tuple"
+          ]
         }
       }
     },
@@ -154,23 +154,20 @@
         "t"
       ],
       "properties": {
+        "c": {
+          "type": "integer",
+          "format": "int32"
+        },
         "t": {
           "type": "string",
           "enum": [
             "WithInt"
           ]
-        },
-        "c": {
-          "type": "integer",
-          "format": "int32"
         }
       }
     }
   ],
   "definitions": {
-    "UnitStruct": {
-      "type": "null"
-    },
     "Struct": {
       "type": "object",
       "required": [
@@ -178,14 +175,17 @@
         "foo"
       ],
       "properties": {
+        "bar": {
+          "type": "boolean"
+        },
         "foo": {
           "type": "integer",
           "format": "int32"
-        },
-        "bar": {
-          "type": "boolean"
         }
       }
+    },
+    "UnitStruct": {
+      "type": "null"
     }
   }
 }
\ No newline at end of file
diff --git a/schemars/tests/expected/enum-external-duf.json b/schemars/tests/expected/enum-external-duf.json
index 4bbe7e4..b6b7b99 100644
--- a/schemars/tests/expected/enum-external-duf.json
+++ b/schemars/tests/expected/enum-external-duf.json
@@ -1,7 +1,7 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "External",
-  "anyOf": [
+  "oneOf": [
     {
       "type": "string",
       "enum": [
@@ -61,12 +61,12 @@
             "foo"
           ],
           "properties": {
+            "bar": {
+              "type": "boolean"
+            },
             "foo": {
               "type": "integer",
               "format": "int32"
-            },
-            "bar": {
-              "type": "boolean"
             }
           },
           "additionalProperties": false
@@ -112,9 +112,6 @@
     }
   ],
   "definitions": {
-    "UnitStruct": {
-      "type": "null"
-    },
     "Struct": {
       "type": "object",
       "required": [
@@ -122,14 +119,17 @@
         "foo"
       ],
       "properties": {
+        "bar": {
+          "type": "boolean"
+        },
         "foo": {
           "type": "integer",
           "format": "int32"
-        },
-        "bar": {
-          "type": "boolean"
         }
       }
+    },
+    "UnitStruct": {
+      "type": "null"
     }
   }
 }
\ No newline at end of file
diff --git a/schemars/tests/expected/enum-external.json b/schemars/tests/expected/enum-external.json
index cdc2fe4..cc721df 100644
--- a/schemars/tests/expected/enum-external.json
+++ b/schemars/tests/expected/enum-external.json
@@ -1,7 +1,7 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "External",
-  "anyOf": [
+  "oneOf": [
     {
       "type": "string",
       "enum": [
@@ -61,12 +61,12 @@
             "foo"
           ],
           "properties": {
+            "bar": {
+              "type": "boolean"
+            },
             "foo": {
               "type": "integer",
               "format": "int32"
-            },
-            "bar": {
-              "type": "boolean"
             }
           }
         }
@@ -111,9 +111,6 @@
     }
   ],
   "definitions": {
-    "UnitStruct": {
-      "type": "null"
-    },
     "Struct": {
       "type": "object",
       "required": [
@@ -121,14 +118,17 @@
         "foo"
       ],
       "properties": {
+        "bar": {
+          "type": "boolean"
+        },
         "foo": {
           "type": "integer",
           "format": "int32"
-        },
-        "bar": {
-          "type": "boolean"
         }
       }
+    },
+    "UnitStruct": {
+      "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 8b6fef8..deb3966 100644
--- a/schemars/tests/expected/enum-internal-duf.json
+++ b/schemars/tests/expected/enum-internal-duf.json
@@ -1,7 +1,7 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "Internal",
-  "anyOf": [
+  "oneOf": [
     {
       "type": "object",
       "required": [
@@ -55,18 +55,18 @@
         "typeProperty"
       ],
       "properties": {
-        "typeProperty": {
-          "type": "string",
-          "enum": [
-            "StructNewType"
-          ]
+        "bar": {
+          "type": "boolean"
         },
         "foo": {
           "type": "integer",
           "format": "int32"
         },
-        "bar": {
-          "type": "boolean"
+        "typeProperty": {
+          "type": "string",
+          "enum": [
+            "StructNewType"
+          ]
         }
       }
     },
@@ -78,18 +78,18 @@
         "typeProperty"
       ],
       "properties": {
-        "typeProperty": {
-          "type": "string",
-          "enum": [
-            "Struct"
-          ]
+        "bar": {
+          "type": "boolean"
         },
         "foo": {
           "type": "integer",
           "format": "int32"
         },
-        "bar": {
-          "type": "boolean"
+        "typeProperty": {
+          "type": "string",
+          "enum": [
+            "Struct"
+          ]
         }
       },
       "additionalProperties": false
diff --git a/schemars/tests/expected/enum-internal.json b/schemars/tests/expected/enum-internal.json
index f3c1533..b43b779 100644
--- a/schemars/tests/expected/enum-internal.json
+++ b/schemars/tests/expected/enum-internal.json
@@ -1,7 +1,7 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "Internal",
-  "anyOf": [
+  "oneOf": [
     {
       "type": "object",
       "required": [
@@ -55,18 +55,18 @@
         "typeProperty"
       ],
       "properties": {
-        "typeProperty": {
-          "type": "string",
-          "enum": [
-            "StructNewType"
-          ]
+        "bar": {
+          "type": "boolean"
         },
         "foo": {
           "type": "integer",
           "format": "int32"
         },
-        "bar": {
-          "type": "boolean"
+        "typeProperty": {
+          "type": "string",
+          "enum": [
+            "StructNewType"
+          ]
         }
       }
     },
@@ -78,18 +78,18 @@
         "typeProperty"
       ],
       "properties": {
-        "typeProperty": {
-          "type": "string",
-          "enum": [
-            "Struct"
-          ]
+        "bar": {
+          "type": "boolean"
         },
         "foo": {
           "type": "integer",
           "format": "int32"
         },
-        "bar": {
-          "type": "boolean"
+        "typeProperty": {
+          "type": "string",
+          "enum": [
+            "Struct"
+          ]
         }
       }
     },
diff --git a/schemars/tests/expected/macro_built_enum.json b/schemars/tests/expected/macro_built_enum.json
index 8a14a4b..d030787 100644
--- a/schemars/tests/expected/macro_built_enum.json
+++ b/schemars/tests/expected/macro_built_enum.json
@@ -1,7 +1,7 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "OuterEnum",
-  "anyOf": [
+  "oneOf": [
     {
       "type": "object",
       "required": [
diff --git a/schemars/tests/expected/schema_with-enum-adjacent-tagged.json b/schemars/tests/expected/schema_with-enum-adjacent-tagged.json
index f464511..3ecba4c 100644
--- a/schemars/tests/expected/schema_with-enum-adjacent-tagged.json
+++ b/schemars/tests/expected/schema_with-enum-adjacent-tagged.json
@@ -1,7 +1,7 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "Adjacent",
-  "anyOf": [
+  "oneOf": [
     {
       "type": "object",
       "required": [
@@ -9,12 +9,6 @@
         "t"
       ],
       "properties": {
-        "t": {
-          "type": "string",
-          "enum": [
-            "Struct"
-          ]
-        },
         "c": {
           "type": "object",
           "required": [
@@ -25,6 +19,12 @@
               "type": "boolean"
             }
           }
+        },
+        "t": {
+          "type": "string",
+          "enum": [
+            "Struct"
+          ]
         }
       }
     },
@@ -35,14 +35,14 @@
         "t"
       ],
       "properties": {
+        "c": {
+          "type": "boolean"
+        },
         "t": {
           "type": "string",
           "enum": [
             "NewType"
           ]
-        },
-        "c": {
-          "type": "boolean"
         }
       }
     },
@@ -53,12 +53,6 @@
         "t"
       ],
       "properties": {
-        "t": {
-          "type": "string",
-          "enum": [
-            "Tuple"
-          ]
-        },
         "c": {
           "type": "array",
           "items": [
@@ -72,6 +66,12 @@
           ],
           "maxItems": 2,
           "minItems": 2
+        },
+        "t": {
+          "type": "string",
+          "enum": [
+            "Tuple"
+          ]
         }
       }
     },
@@ -82,14 +82,14 @@
         "t"
       ],
       "properties": {
+        "c": {
+          "type": "boolean"
+        },
         "t": {
           "type": "string",
           "enum": [
             "Unit"
           ]
-        },
-        "c": {
-          "type": "boolean"
         }
       }
     }
diff --git a/schemars/tests/expected/schema_with-enum-external.json b/schemars/tests/expected/schema_with-enum-external.json
index 92fb5a6..dea0219 100644
--- a/schemars/tests/expected/schema_with-enum-external.json
+++ b/schemars/tests/expected/schema_with-enum-external.json
@@ -1,7 +1,7 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "External",
-  "anyOf": [
+  "oneOf": [
     {
       "type": "object",
       "required": [
diff --git a/schemars/tests/expected/schema_with-enum-internal.json b/schemars/tests/expected/schema_with-enum-internal.json
index ea39afe..7ede7e6 100644
--- a/schemars/tests/expected/schema_with-enum-internal.json
+++ b/schemars/tests/expected/schema_with-enum-internal.json
@@ -1,7 +1,7 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "Internal",
-  "anyOf": [
+  "oneOf": [
     {
       "type": "object",
       "required": [
@@ -9,14 +9,14 @@
         "typeProperty"
       ],
       "properties": {
+        "foo": {
+          "type": "boolean"
+        },
         "typeProperty": {
           "type": "string",
           "enum": [
             "Struct"
           ]
-        },
-        "foo": {
-          "type": "boolean"
         }
       }
     },
diff --git a/schemars/tests/expected/skip_enum_variants.json b/schemars/tests/expected/skip_enum_variants.json
index 4acb2eb..ba1bf23 100644
--- a/schemars/tests/expected/skip_enum_variants.json
+++ b/schemars/tests/expected/skip_enum_variants.json
@@ -1,7 +1,7 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "MyEnum",
-  "anyOf": [
+  "oneOf": [
     {
       "type": "string",
       "enum": [
diff --git a/schemars/tests/util/mod.rs b/schemars/tests/util/mod.rs
index 9f365d6..e83671f 100644
--- a/schemars/tests/util/mod.rs
+++ b/schemars/tests/util/mod.rs
@@ -22,7 +22,7 @@ pub fn test_schema(actual: &RootSchema, file: &str) -> TestResult {
     let expected_json = match fs::read_to_string(format!("tests/expected/{}.json", file)) {
         Ok(j) => j,
         Err(e) => {
-            write_actual_to_file(&actual, file)?;
+            write_actual_to_file(actual, file)?;
             return Err(Box::from(e));
         }
     };
@@ -32,7 +32,7 @@ pub fn test_schema(actual: &RootSchema, file: &str) -> TestResult {
         write_actual_to_file(actual, file)?;
     }
 
-    assert_eq!(actual, expected);
+    assert_eq!(expected, actual);
     Ok(())
 }
 
diff --git a/schemars/tests/validate.rs b/schemars/tests/validate.rs
index c2060e0..c8b6809 100644
--- a/schemars/tests/validate.rs
+++ b/schemars/tests/validate.rs
@@ -42,6 +42,7 @@ pub struct Struct {
     #[validate(required)]
     required_option: Option<bool>,
     #[validate(required)]
+    #[validate]
     #[serde(flatten)]
     required_flattened: Option<Inner>,
 }