From 0a1200baac8e8136c2f9db4f1d85c06f9f7086b5 Mon Sep 17 00:00:00 2001 From: Adam Leventhal Date: Sun, 26 Sep 2021 10:02:44 -0700 Subject: [PATCH] Use oneOf for enums when possible (#108) --- schemars/tests/expected/deprecated-enum.json | 10 +- .../tests/expected/doc_comments_enum.json | 2 +- .../expected/enum-adjacent-tagged-duf.json | 76 ++--- .../tests/expected/enum-adjacent-tagged.json | 76 ++--- .../tests/expected/enum-external-duf.json | 20 +- schemars/tests/expected/enum-external.json | 20 +- .../tests/expected/enum-internal-duf.json | 30 +- schemars/tests/expected/enum-internal.json | 30 +- schemars/tests/expected/macro_built_enum.json | 2 +- .../schema_with-enum-adjacent-tagged.json | 38 +-- .../expected/schema_with-enum-external.json | 2 +- .../expected/schema_with-enum-internal.json | 8 +- .../tests/expected/skip_enum_variants.json | 2 +- schemars/tests/util/mod.rs | 4 +- schemars_derive/src/schema_exprs.rs | 264 ++++++++++-------- 15 files changed, 305 insertions(+), 279 deletions(-) diff --git a/schemars/tests/expected/deprecated-enum.json b/schemars/tests/expected/deprecated-enum.json index f869be21..074310f3 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 edcb3391..0adc3801 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 011b3782..c5b54c81 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 4e0ca123..efe19dc8 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 4bbe7e42..b6b7b99a 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 cdc2fe45..cc721dfd 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 8b6fef85..deb39664 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 f3c15331..b43b779a 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 8a14a4b3..d030787d 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 f464511b..3ecba4c8 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 92fb5a63..dea02199 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 ea39afe7..7ede7e6e 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 4acb2eb4..ba1bf23c 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 9f365d69..e83671f2 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_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index f2c76e90..4c463dc8 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>, 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::::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>, 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 { + 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 {