Skip to content

Commit 3b3870c

Browse files
committed
Simplify flatten
No longer use it for internally-tagged enums. Instead, use a private helper that adds the tag property.
1 parent 342cd5f commit 3b3870c

File tree

6 files changed

+62
-76
lines changed

6 files changed

+62
-76
lines changed

schemars/src/_private.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::gen::SchemaGenerator;
22
use crate::JsonSchema;
33
use crate::Schema;
44
use serde::Serialize;
5+
use serde_json::json;
56
use serde_json::Map;
67
use serde_json::Value;
78

@@ -73,6 +74,45 @@ pub fn new_externally_tagged_enum(variant: &str, sub_schema: Schema) -> Schema {
7374
})
7475
}
7576

77+
pub fn apply_internal_enum_tag(
78+
schema: &mut Schema,
79+
tag_name: &str,
80+
variant: &str,
81+
deny_unknown_fields: bool,
82+
) {
83+
let obj = schema.ensure_object();
84+
let is_unit = obj.get("type").is_some_and(|t| t.as_str() == Some("null"));
85+
86+
obj.insert("type".to_owned(), "object".into());
87+
88+
if let Some(properties) = obj
89+
.entry("properties")
90+
.or_insert(Value::Object(Map::new()))
91+
.as_object_mut()
92+
{
93+
properties.insert(
94+
tag_name.to_string(),
95+
json!({
96+
"type": "string",
97+
// TODO switch from single-valued "enum" to "const"
98+
"enum": [variant]
99+
}),
100+
);
101+
}
102+
103+
if let Some(required) = obj
104+
.entry("required")
105+
.or_insert(Value::Array(Vec::new()))
106+
.as_array_mut()
107+
{
108+
required.insert(0, tag_name.into());
109+
}
110+
111+
if deny_unknown_fields && is_unit {
112+
obj.entry("additionalProperties").or_insert(false.into());
113+
}
114+
}
115+
76116
/// Create a schema for an internally tagged enum
77117
pub fn new_internally_tagged_enum(
78118
tag_name: &str,

schemars/src/flatten.rs

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,9 @@ impl Schema {
99
/// It should not be considered part of the public API.
1010
#[doc(hidden)]
1111
pub fn flatten(mut self, other: Self) -> Schema {
12-
// This special null-type-schema handling is here for backward-compatibility, but needs reviewing.
13-
// I think it's only needed to make internally-tagged enum unit variants behave correctly, but that
14-
// should be handled entirely within schemars_derive.
15-
if other
16-
.as_object()
17-
.and_then(|o| o.get("type"))
18-
.and_then(|t| t.as_str())
19-
== Some("null")
20-
{
21-
return self;
22-
}
23-
24-
if let Value::Object(mut obj2) = other.to_value() {
12+
if let Value::Object(obj2) = other.to_value() {
2513
let obj1 = self.ensure_object();
2614

27-
let ap2 = obj2.remove("additionalProperties");
28-
if let Entry::Occupied(mut ap1) = obj1.entry("additionalProperties") {
29-
match ap2 {
30-
Some(ap2) => {
31-
flatten_additional_properties(ap1.get_mut(), ap2);
32-
}
33-
None => {
34-
ap1.remove();
35-
}
36-
}
37-
}
38-
3915
for (key, value2) in obj2 {
4016
match obj1.entry(key) {
4117
Entry::Vacant(vacant) => {
@@ -93,19 +69,3 @@ impl Schema {
9369
self
9470
}
9571
}
96-
97-
// TODO validate behaviour when flattening a normal struct into a struct with deny_unknown_fields
98-
fn flatten_additional_properties(v1: &mut Value, v2: Value) {
99-
match (v1, v2) {
100-
(v1, Value::Bool(true)) => {
101-
*v1 = Value::Bool(true);
102-
}
103-
(v1 @ Value::Bool(false), v2) => {
104-
*v1 = v2;
105-
}
106-
(Value::Object(o1), Value::Object(o2)) => {
107-
o1.extend(o2);
108-
}
109-
_ => {}
110-
}
111-
}

schemars/tests/expected/enum-internal-duf.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,7 @@
112112
"additionalProperties": false
113113
},
114114
{
115-
"type": [
116-
"object",
117-
"integer"
118-
],
115+
"type": "object",
119116
"format": "int32",
120117
"required": [
121118
"typeProperty"

schemars/tests/expected/enum-internal.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
"StringMap"
2929
]
3030
}
31+
},
32+
"additionalProperties": {
33+
"type": "string"
3134
}
3235
},
3336
{
@@ -105,10 +108,7 @@
105108
}
106109
},
107110
{
108-
"type": [
109-
"object",
110-
"integer"
111-
],
111+
"type": "object",
112112
"format": "int32",
113113
"required": [
114114
"typeProperty"

schemars/tests/expected/schema_with-enum-internal.json

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@
2121
}
2222
},
2323
{
24-
"type": [
25-
"object",
26-
"boolean"
27-
],
24+
"type": "object",
2825
"required": [
2926
"typeProperty"
3027
],
@@ -38,10 +35,7 @@
3835
}
3936
},
4037
{
41-
"type": [
42-
"object",
43-
"boolean"
44-
],
38+
"type": "object",
4539
"required": [
4640
"typeProperty"
4741
],

schemars_derive/src/schema_exprs.rs

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -231,19 +231,14 @@ fn expr_for_internal_tagged_enum<'a>(
231231

232232
let name = variant.name();
233233

234-
let mut tag_schema = quote! {
235-
schemars::_private::new_internally_tagged_enum(#tag_name, #name, #deny_unknown_fields)
236-
};
237-
238-
variant.attrs.as_metadata().apply_to_schema(&mut tag_schema);
239-
240-
if let Some(variant_schema) =
241-
expr_for_untagged_enum_variant_for_flatten(variant, deny_unknown_fields)
242-
{
243-
tag_schema.extend(quote!(.flatten(#variant_schema)))
244-
}
245-
246-
tag_schema
234+
let mut schema_expr = expr_for_internal_tagged_enum_variant(variant, deny_unknown_fields);
235+
variant.attrs.as_metadata().apply_to_schema(&mut schema_expr);
236+
237+
quote!({
238+
let mut schema = #schema_expr;
239+
schemars::_private::apply_internal_enum_tag(&mut schema, #tag_name, #name, #deny_unknown_fields);
240+
schema
241+
})
247242
})
248243
.collect();
249244

@@ -383,10 +378,10 @@ fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool)
383378
}
384379
}
385380

386-
fn expr_for_untagged_enum_variant_for_flatten(
381+
fn expr_for_internal_tagged_enum_variant(
387382
variant: &Variant,
388383
deny_unknown_fields: bool,
389-
) -> Option<TokenStream> {
384+
) -> TokenStream {
390385
if let Some(with_attr) = &variant.attrs.with {
391386
let (ty, type_def) = type_for_schema(with_attr);
392387
let gen = quote!(gen);
@@ -395,15 +390,15 @@ fn expr_for_untagged_enum_variant_for_flatten(
395390
};
396391

397392
prepend_type_def(type_def, &mut schema_expr);
398-
return Some(schema_expr);
393+
return schema_expr;
399394
}
400395

401-
Some(match variant.style {
402-
Style::Unit => return None,
396+
match variant.style {
397+
Style::Unit => expr_for_unit_struct(),
403398
Style::Newtype => expr_for_field(&variant.fields[0], false),
404399
Style::Tuple => expr_for_tuple_struct(&variant.fields),
405400
Style::Struct => expr_for_struct(&variant.fields, &SerdeDefault::None, deny_unknown_fields),
406-
})
401+
}
407402
}
408403

409404
fn expr_for_unit_struct() -> TokenStream {

0 commit comments

Comments
 (0)