Enable deriving JsonSchema on adjacent tagged enums

Issue #4
This commit is contained in:
Graham Esau 2020-04-30 14:21:04 +01:00
parent 8207892fa6
commit 11b7a09c93
4 changed files with 235 additions and 3 deletions

View file

@ -59,3 +59,19 @@ pub enum Untagged {
fn enum_untagged() -> TestResult {
test_default_generated_schema::<Untagged>("enum-untagged")
}
#[derive(Debug, JsonSchema)]
#[schemars(tag = "t", content = "c")]
pub enum Adjacent {
UnitOne,
StringMap(Map<String, String>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct { foo: i32, bar: bool },
Tuple(i32, bool),
}
#[test]
fn enum_adjacent_tagged() -> TestResult {
test_default_generated_schema::<Adjacent>("enum_adjacent_tagged-untagged")
}

View file

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

View file

@ -11,8 +11,7 @@ static SERDE_KEYWORDS: &[&str] = &[
// TODO: for structs with `deny_unknown_fields`, set schema's `additionalProperties` to false.
// "deny_unknown_fields",
"tag",
// TODO: support adjecently tagged enums (https://github.com/GREsau/schemars/issues/4)
// "content",
"content",
"untagged",
"default",
"skip",

View file

@ -131,7 +131,9 @@ fn schema_for_enum(variants: &[Variant], cattrs: &serde_attr::Container) -> Toke
TagType::External => schema_for_external_tagged_enum(variants),
TagType::None => schema_for_untagged_enum(variants),
TagType::Internal { tag } => schema_for_internal_tagged_enum(variants, tag),
TagType::Adjacent { .. } => unimplemented!("Adjacent tagged enums not yet supported."),
TagType::Adjacent { tag, content } => {
schema_for_adjacent_tagged_enum(variants, tag, content)
}
}
}
@ -268,6 +270,71 @@ fn schema_for_untagged_enum_variant(variant: &Variant) -> TokenStream {
}
}
fn schema_for_adjacent_tagged_enum<'a>(
variants: impl Iterator<Item = &'a Variant<'a>>,
tag_name: &str,
content_name: &str,
) -> TokenStream {
let schemas = variants.map(|variant| {
let content_schema = match variant.style {
Style::Unit => None,
Style::Newtype => {
let field = &variant.fields[0];
let ty = get_json_schema_type(field);
Some(quote_spanned! {field.original.span()=>
<#ty>::json_schema(gen)
})
}
Style::Struct => Some(schema_for_struct(&variant.fields, None)),
Style::Tuple => Some(schema_for_tuple_struct(&variant.fields)),
};
let (add_content_property, add_content_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.attrs.name().deserialize_name();
let tag_schema = wrap_schema_fields(quote! {
instance_type: Some(schemars::schema::InstanceType::String.into()),
enum_values: Some(vec![#name.into()]),
});
let outer_schema = wrap_schema_fields(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_property
props
},
required: {
let mut required = schemars::Set::new();
required.insert(#tag_name.to_owned());
#add_content_required
required
},
..Default::default()
})),
});
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
doc_metadata.apply_to_schema(outer_schema)
});
wrap_schema_fields(quote! {
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
any_of: Some(vec![#(#schemas),*]),
..Default::default()
})),
})
}
fn schema_for_unit_struct() -> TokenStream {
quote! {
gen.subschema_for::<()>()