Simplify flatten
No longer use it for internally-tagged enums. Instead, use a private helper that adds the tag property.
This commit is contained in:
parent
342cd5fd09
commit
3b3870ca82
6 changed files with 61 additions and 75 deletions
|
@ -2,6 +2,7 @@ use crate::gen::SchemaGenerator;
|
||||||
use crate::JsonSchema;
|
use crate::JsonSchema;
|
||||||
use crate::Schema;
|
use crate::Schema;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use serde_json::json;
|
||||||
use serde_json::Map;
|
use serde_json::Map;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
@ -73,6 +74,45 @@ pub fn new_externally_tagged_enum(variant: &str, sub_schema: Schema) -> Schema {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_internal_enum_tag(
|
||||||
|
schema: &mut Schema,
|
||||||
|
tag_name: &str,
|
||||||
|
variant: &str,
|
||||||
|
deny_unknown_fields: bool,
|
||||||
|
) {
|
||||||
|
let obj = schema.ensure_object();
|
||||||
|
let is_unit = obj.get("type").is_some_and(|t| t.as_str() == Some("null"));
|
||||||
|
|
||||||
|
obj.insert("type".to_owned(), "object".into());
|
||||||
|
|
||||||
|
if let Some(properties) = obj
|
||||||
|
.entry("properties")
|
||||||
|
.or_insert(Value::Object(Map::new()))
|
||||||
|
.as_object_mut()
|
||||||
|
{
|
||||||
|
properties.insert(
|
||||||
|
tag_name.to_string(),
|
||||||
|
json!({
|
||||||
|
"type": "string",
|
||||||
|
// TODO switch from single-valued "enum" to "const"
|
||||||
|
"enum": [variant]
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(required) = obj
|
||||||
|
.entry("required")
|
||||||
|
.or_insert(Value::Array(Vec::new()))
|
||||||
|
.as_array_mut()
|
||||||
|
{
|
||||||
|
required.insert(0, tag_name.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if deny_unknown_fields && is_unit {
|
||||||
|
obj.entry("additionalProperties").or_insert(false.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a schema for an internally tagged enum
|
/// Create a schema for an internally tagged enum
|
||||||
pub fn new_internally_tagged_enum(
|
pub fn new_internally_tagged_enum(
|
||||||
tag_name: &str,
|
tag_name: &str,
|
||||||
|
|
|
@ -9,33 +9,9 @@ impl Schema {
|
||||||
/// It should not be considered part of the public API.
|
/// It should not be considered part of the public API.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn flatten(mut self, other: Self) -> Schema {
|
pub fn flatten(mut self, other: Self) -> Schema {
|
||||||
// This special null-type-schema handling is here for backward-compatibility, but needs reviewing.
|
if let Value::Object(obj2) = other.to_value() {
|
||||||
// I think it's only needed to make internally-tagged enum unit variants behave correctly, but that
|
|
||||||
// should be handled entirely within schemars_derive.
|
|
||||||
if other
|
|
||||||
.as_object()
|
|
||||||
.and_then(|o| o.get("type"))
|
|
||||||
.and_then(|t| t.as_str())
|
|
||||||
== Some("null")
|
|
||||||
{
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Value::Object(mut obj2) = other.to_value() {
|
|
||||||
let obj1 = self.ensure_object();
|
let obj1 = self.ensure_object();
|
||||||
|
|
||||||
let ap2 = obj2.remove("additionalProperties");
|
|
||||||
if let Entry::Occupied(mut ap1) = obj1.entry("additionalProperties") {
|
|
||||||
match ap2 {
|
|
||||||
Some(ap2) => {
|
|
||||||
flatten_additional_properties(ap1.get_mut(), ap2);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
ap1.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (key, value2) in obj2 {
|
for (key, value2) in obj2 {
|
||||||
match obj1.entry(key) {
|
match obj1.entry(key) {
|
||||||
Entry::Vacant(vacant) => {
|
Entry::Vacant(vacant) => {
|
||||||
|
@ -93,19 +69,3 @@ impl Schema {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO validate behaviour when flattening a normal struct into a struct with deny_unknown_fields
|
|
||||||
fn flatten_additional_properties(v1: &mut Value, v2: Value) {
|
|
||||||
match (v1, v2) {
|
|
||||||
(v1, Value::Bool(true)) => {
|
|
||||||
*v1 = Value::Bool(true);
|
|
||||||
}
|
|
||||||
(v1 @ Value::Bool(false), v2) => {
|
|
||||||
*v1 = v2;
|
|
||||||
}
|
|
||||||
(Value::Object(o1), Value::Object(o2)) => {
|
|
||||||
o1.extend(o2);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -112,10 +112,7 @@
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": [
|
"type": "object",
|
||||||
"object",
|
|
||||||
"integer"
|
|
||||||
],
|
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
"required": [
|
"required": [
|
||||||
"typeProperty"
|
"typeProperty"
|
||||||
|
|
|
@ -28,6 +28,9 @@
|
||||||
"StringMap"
|
"StringMap"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -105,10 +108,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": [
|
"type": "object",
|
||||||
"object",
|
|
||||||
"integer"
|
|
||||||
],
|
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
"required": [
|
"required": [
|
||||||
"typeProperty"
|
"typeProperty"
|
||||||
|
|
|
@ -21,10 +21,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": [
|
"type": "object",
|
||||||
"object",
|
|
||||||
"boolean"
|
|
||||||
],
|
|
||||||
"required": [
|
"required": [
|
||||||
"typeProperty"
|
"typeProperty"
|
||||||
],
|
],
|
||||||
|
@ -38,10 +35,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": [
|
"type": "object",
|
||||||
"object",
|
|
||||||
"boolean"
|
|
||||||
],
|
|
||||||
"required": [
|
"required": [
|
||||||
"typeProperty"
|
"typeProperty"
|
||||||
],
|
],
|
||||||
|
|
|
@ -231,19 +231,14 @@ fn expr_for_internal_tagged_enum<'a>(
|
||||||
|
|
||||||
let name = variant.name();
|
let name = variant.name();
|
||||||
|
|
||||||
let mut tag_schema = quote! {
|
let mut schema_expr = expr_for_internal_tagged_enum_variant(variant, deny_unknown_fields);
|
||||||
schemars::_private::new_internally_tagged_enum(#tag_name, #name, #deny_unknown_fields)
|
variant.attrs.as_metadata().apply_to_schema(&mut schema_expr);
|
||||||
};
|
|
||||||
|
|
||||||
variant.attrs.as_metadata().apply_to_schema(&mut tag_schema);
|
quote!({
|
||||||
|
let mut schema = #schema_expr;
|
||||||
if let Some(variant_schema) =
|
schemars::_private::apply_internal_enum_tag(&mut schema, #tag_name, #name, #deny_unknown_fields);
|
||||||
expr_for_untagged_enum_variant_for_flatten(variant, deny_unknown_fields)
|
schema
|
||||||
{
|
})
|
||||||
tag_schema.extend(quote!(.flatten(#variant_schema)))
|
|
||||||
}
|
|
||||||
|
|
||||||
tag_schema
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -383,10 +378,10 @@ fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expr_for_untagged_enum_variant_for_flatten(
|
fn expr_for_internal_tagged_enum_variant(
|
||||||
variant: &Variant,
|
variant: &Variant,
|
||||||
deny_unknown_fields: bool,
|
deny_unknown_fields: bool,
|
||||||
) -> Option<TokenStream> {
|
) -> TokenStream {
|
||||||
if let Some(with_attr) = &variant.attrs.with {
|
if let Some(with_attr) = &variant.attrs.with {
|
||||||
let (ty, type_def) = type_for_schema(with_attr);
|
let (ty, type_def) = type_for_schema(with_attr);
|
||||||
let gen = quote!(gen);
|
let gen = quote!(gen);
|
||||||
|
@ -395,15 +390,15 @@ fn expr_for_untagged_enum_variant_for_flatten(
|
||||||
};
|
};
|
||||||
|
|
||||||
prepend_type_def(type_def, &mut schema_expr);
|
prepend_type_def(type_def, &mut schema_expr);
|
||||||
return Some(schema_expr);
|
return schema_expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(match variant.style {
|
match variant.style {
|
||||||
Style::Unit => return None,
|
Style::Unit => expr_for_unit_struct(),
|
||||||
Style::Newtype => expr_for_field(&variant.fields[0], false),
|
Style::Newtype => expr_for_field(&variant.fields[0], false),
|
||||||
Style::Tuple => expr_for_tuple_struct(&variant.fields),
|
Style::Tuple => expr_for_tuple_struct(&variant.fields),
|
||||||
Style::Struct => expr_for_struct(&variant.fields, &SerdeDefault::None, deny_unknown_fields),
|
Style::Struct => expr_for_struct(&variant.fields, &SerdeDefault::None, deny_unknown_fields),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expr_for_unit_struct() -> TokenStream {
|
fn expr_for_unit_struct() -> TokenStream {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue