Simplify generated enum code (#286)

* simplify the code generated for unit enums
* simplify generated code for validating object properties
* optimize internal and externally tagged enums

---------

Co-authored-by: Robin Appelman <robin@icewind.nl>
This commit is contained in:
Graham Esau 2024-05-06 13:54:13 +01:00 committed by GitHub
parent e5ef0f8d7b
commit d04c17bda4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 152 additions and 124 deletions

View file

@ -13,47 +13,32 @@ pub struct SchemaMetadata<'a> {
impl<'a> SchemaMetadata<'a> {
pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) {
let setters = self.make_setters();
if !setters.is_empty() {
*schema_expr = quote! {{
let schema = #schema_expr;
schemars::_private::apply_metadata(schema, schemars::schema::Metadata {
#(#setters)*
..Default::default()
})
}}
}
}
fn make_setters(&self) -> Vec<TokenStream> {
let mut setters = Vec::<TokenStream>::new();
if let Some(title) = &self.title {
setters.push(quote! {
title: Some(#title.to_owned()),
});
*schema_expr = quote! {
schemars::_private::metadata::add_title(#schema_expr, #title)
};
}
if let Some(description) = &self.description {
setters.push(quote! {
description: Some(#description.to_owned()),
});
*schema_expr = quote! {
schemars::_private::metadata::add_description(#schema_expr, #description)
};
}
if self.deprecated {
setters.push(quote! {
deprecated: true,
});
*schema_expr = quote! {
schemars::_private::metadata::add_deprecated(#schema_expr, true)
};
}
if self.read_only {
setters.push(quote! {
read_only: true,
});
*schema_expr = quote! {
schemars::_private::metadata::add_read_only(#schema_expr, true)
};
}
if self.write_only {
setters.push(quote! {
write_only: true,
});
*schema_expr = quote! {
schemars::_private::metadata::add_write_only(#schema_expr, true)
};
}
if !self.examples.is_empty() {
@ -62,17 +47,16 @@ impl<'a> SchemaMetadata<'a> {
schemars::_serde_json::value::to_value(#eg())
}
});
setters.push(quote! {
examples: vec![#(#examples),*].into_iter().flatten().collect(),
});
*schema_expr = quote! {
schemars::_private::metadata::add_examples(#schema_expr, [#(#examples),*].into_iter().flatten())
};
}
if let Some(default) = &self.default {
setters.push(quote! {
default: #default.and_then(|d| schemars::_schemars_maybe_to_value!(d)),
});
*schema_expr = quote! {
schemars::_private::metadata::add_default(#schema_expr, #default.and_then(|d| schemars::_schemars_maybe_to_value!(d)))
};
}
setters
}
}

View file

@ -162,7 +162,7 @@ fn expr_for_external_tagged_enum<'a>(
let unit_names = unit_variants.iter().map(|v| v.name());
let unit_schema = schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::String.into()),
enum_values: Some(vec![#(#unit_names.into()),*]),
enum_values: Some([#(#unit_names),*].into_iter().map(|v| v.into()).collect()),
});
if complex_variants.is_empty() {
@ -178,35 +178,14 @@ fn expr_for_external_tagged_enum<'a>(
let name = variant.name();
let mut schema_expr = if variant.is_unit() && variant.attrs.with.is_none() {
schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::String.into()),
enum_values: Some(vec![#name.into()]),
})
quote! {
schemars::_private::new_unit_enum(#name)
}
} else {
let sub_schema = expr_for_untagged_enum_variant(variant, deny_unknown_fields);
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(#name.to_owned(), #sub_schema);
props
},
required: {
let mut required = schemars::Set::new();
required.insert(#name.to_owned());
required
},
// Externally tagged variants must prohibit additional
// properties irrespective of the disposition of
// `deny_unknown_fields`. If additional properties were allowed
// one could easily construct an object that validated against
// multiple variants since here it's the properties rather than
// the values of a property that distingish between variants.
additional_properties: Some(Box::new(false.into())),
..Default::default()
})),
})
quote! {
schemars::_private::new_externally_tagged_enum(#name, #sub_schema)
}
};
variant
@ -227,43 +206,16 @@ fn expr_for_internal_tagged_enum<'a>(
) -> TokenStream {
let mut unique_names = HashSet::new();
let mut count = 0;
let set_additional_properties = if deny_unknown_fields {
quote! {
additional_properties: Some(Box::new(false.into())),
}
} else {
TokenStream::new()
};
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
},
// As we're creating a "wrapper" object, we can honor the
// disposition of deny_unknown_fields.
#set_additional_properties
..Default::default()
})),
});
let mut tag_schema = quote! {
schemars::_private::new_internally_tagged_enum(#tag_name, #name, #deny_unknown_fields)
};
variant.attrs.as_metadata().apply_to_schema(&mut tag_schema);
@ -498,19 +450,8 @@ fn expr_for_struct(
let (ty, type_def) = type_for_field_schema(field);
let maybe_insert_required = match (&default, field.validation_attrs.required()) {
(Some(_), _) => TokenStream::new(),
(None, false) => {
quote! {
if !<#ty as schemars::JsonSchema>::_schemars_private_is_option() {
object_validation.required.insert(#name.to_owned());
}
}
}
(None, true) => quote! {
object_validation.required.insert(#name.to_owned());
},
};
let has_default = default.is_some();
let required = field.validation_attrs.required();
let metadata = SchemaMetadata {
read_only: field.serde_attrs.skip_deserializing(),
@ -536,8 +477,7 @@ fn expr_for_struct(
quote! {
{
#type_def
object_validation.properties.insert(#name.to_owned(), #schema_expr);
#maybe_insert_required
schemars::_private::insert_object_property::<#ty>(object_validation, #name, #has_default, #required, #schema_expr);
}
}
})