Skip to content

Commit 11b7a09

Browse files
committed
Enable deriving JsonSchema on adjacent tagged enums
Issue #4
1 parent 8207892 commit 11b7a09

File tree

4 files changed

+235
-3
lines changed

4 files changed

+235
-3
lines changed

schemars/tests/enum.rs

+16
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,19 @@ pub enum Untagged {
5959
fn enum_untagged() -> TestResult {
6060
test_default_generated_schema::<Untagged>("enum-untagged")
6161
}
62+
63+
#[derive(Debug, JsonSchema)]
64+
#[schemars(tag = "t", content = "c")]
65+
pub enum Adjacent {
66+
UnitOne,
67+
StringMap(Map<String, String>),
68+
UnitStructNewType(UnitStruct),
69+
StructNewType(Struct),
70+
Struct { foo: i32, bar: bool },
71+
Tuple(i32, bool),
72+
}
73+
74+
#[test]
75+
fn enum_adjacent_tagged() -> TestResult {
76+
test_default_generated_schema::<Adjacent>("enum_adjacent_tagged-untagged")
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"title": "Adjacent",
4+
"anyOf": [
5+
{
6+
"type": "object",
7+
"required": [
8+
"t"
9+
],
10+
"properties": {
11+
"t": {
12+
"type": "string",
13+
"enum": [
14+
"UnitOne"
15+
]
16+
}
17+
}
18+
},
19+
{
20+
"type": "object",
21+
"required": [
22+
"c",
23+
"t"
24+
],
25+
"properties": {
26+
"c": {
27+
"type": "object",
28+
"additionalProperties": {
29+
"type": "string"
30+
}
31+
},
32+
"t": {
33+
"type": "string",
34+
"enum": [
35+
"StringMap"
36+
]
37+
}
38+
}
39+
},
40+
{
41+
"type": "object",
42+
"required": [
43+
"c",
44+
"t"
45+
],
46+
"properties": {
47+
"c": {
48+
"type": "null"
49+
},
50+
"t": {
51+
"type": "string",
52+
"enum": [
53+
"UnitStructNewType"
54+
]
55+
}
56+
}
57+
},
58+
{
59+
"type": "object",
60+
"required": [
61+
"c",
62+
"t"
63+
],
64+
"properties": {
65+
"c": {
66+
"type": "object",
67+
"required": [
68+
"bar",
69+
"foo"
70+
],
71+
"properties": {
72+
"bar": {
73+
"type": "boolean"
74+
},
75+
"foo": {
76+
"type": "integer",
77+
"format": "int32"
78+
}
79+
}
80+
},
81+
"t": {
82+
"type": "string",
83+
"enum": [
84+
"StructNewType"
85+
]
86+
}
87+
}
88+
},
89+
{
90+
"type": "object",
91+
"required": [
92+
"c",
93+
"t"
94+
],
95+
"properties": {
96+
"c": {
97+
"type": "object",
98+
"required": [
99+
"bar",
100+
"foo"
101+
],
102+
"properties": {
103+
"bar": {
104+
"type": "boolean"
105+
},
106+
"foo": {
107+
"type": "integer",
108+
"format": "int32"
109+
}
110+
}
111+
},
112+
"t": {
113+
"type": "string",
114+
"enum": [
115+
"Struct"
116+
]
117+
}
118+
}
119+
},
120+
{
121+
"type": "object",
122+
"required": [
123+
"c",
124+
"t"
125+
],
126+
"properties": {
127+
"c": {
128+
"type": "array",
129+
"items": [
130+
{
131+
"type": "integer",
132+
"format": "int32"
133+
},
134+
{
135+
"type": "boolean"
136+
}
137+
],
138+
"maxItems": 2,
139+
"minItems": 2
140+
},
141+
"t": {
142+
"type": "string",
143+
"enum": [
144+
"Tuple"
145+
]
146+
}
147+
}
148+
}
149+
]
150+
}

schemars_derive/src/attr/schemars_to_serde.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ static SERDE_KEYWORDS: &[&str] = &[
1111
// TODO: for structs with `deny_unknown_fields`, set schema's `additionalProperties` to false.
1212
// "deny_unknown_fields",
1313
"tag",
14-
// TODO: support adjecently tagged enums (https://github.com/GREsau/schemars/issues/4)
15-
// "content",
14+
"content",
1615
"untagged",
1716
"default",
1817
"skip",

schemars_derive/src/lib.rs

+68-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ fn schema_for_enum(variants: &[Variant], cattrs: &serde_attr::Container) -> Toke
131131
TagType::External => schema_for_external_tagged_enum(variants),
132132
TagType::None => schema_for_untagged_enum(variants),
133133
TagType::Internal { tag } => schema_for_internal_tagged_enum(variants, tag),
134-
TagType::Adjacent { .. } => unimplemented!("Adjacent tagged enums not yet supported."),
134+
TagType::Adjacent { tag, content } => {
135+
schema_for_adjacent_tagged_enum(variants, tag, content)
136+
}
135137
}
136138
}
137139

@@ -268,6 +270,71 @@ fn schema_for_untagged_enum_variant(variant: &Variant) -> TokenStream {
268270
}
269271
}
270272

273+
fn schema_for_adjacent_tagged_enum<'a>(
274+
variants: impl Iterator<Item = &'a Variant<'a>>,
275+
tag_name: &str,
276+
content_name: &str,
277+
) -> TokenStream {
278+
let schemas = variants.map(|variant| {
279+
let content_schema = match variant.style {
280+
Style::Unit => None,
281+
Style::Newtype => {
282+
let field = &variant.fields[0];
283+
let ty = get_json_schema_type(field);
284+
Some(quote_spanned! {field.original.span()=>
285+
<#ty>::json_schema(gen)
286+
})
287+
}
288+
Style::Struct => Some(schema_for_struct(&variant.fields, None)),
289+
Style::Tuple => Some(schema_for_tuple_struct(&variant.fields)),
290+
};
291+
292+
let (add_content_property, add_content_required) = content_schema
293+
.map(|content_schema| {
294+
(
295+
quote!(props.insert(#content_name.to_owned(), #content_schema);),
296+
quote!(required.insert(#content_name.to_owned());),
297+
)
298+
})
299+
.unwrap_or_default();
300+
301+
let name = variant.attrs.name().deserialize_name();
302+
let tag_schema = wrap_schema_fields(quote! {
303+
instance_type: Some(schemars::schema::InstanceType::String.into()),
304+
enum_values: Some(vec![#name.into()]),
305+
});
306+
307+
let outer_schema = wrap_schema_fields(quote! {
308+
instance_type: Some(schemars::schema::InstanceType::Object.into()),
309+
object: Some(Box::new(schemars::schema::ObjectValidation {
310+
properties: {
311+
let mut props = schemars::Map::new();
312+
props.insert(#tag_name.to_owned(), #tag_schema);
313+
#add_content_property
314+
props
315+
},
316+
required: {
317+
let mut required = schemars::Set::new();
318+
required.insert(#tag_name.to_owned());
319+
#add_content_required
320+
required
321+
},
322+
..Default::default()
323+
})),
324+
});
325+
326+
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
327+
doc_metadata.apply_to_schema(outer_schema)
328+
});
329+
330+
wrap_schema_fields(quote! {
331+
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
332+
any_of: Some(vec![#(#schemas),*]),
333+
..Default::default()
334+
})),
335+
})
336+
}
337+
271338
fn schema_for_unit_struct() -> TokenStream {
272339
quote! {
273340
gen.subschema_for::<()>()

0 commit comments

Comments
 (0)