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

@ -1,7 +1,6 @@
use crate::flatten::Merge;
use crate::gen::SchemaGenerator;
use crate::schema::{Metadata, Schema, SchemaObject};
use crate::JsonSchema;
use crate::schema::{InstanceType, ObjectValidation, Schema, SchemaObject};
use crate::{JsonSchema, Map, Set};
use serde::Serialize;
use serde_json::Value;
@ -25,16 +24,6 @@ pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
schema
}
pub fn apply_metadata(schema: Schema, metadata: Metadata) -> Schema {
if metadata == Metadata::default() {
schema
} else {
let mut schema_obj = schema.into_object();
schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata);
Schema::Object(schema_obj)
}
}
/// Hack to simulate specialization:
/// `MaybeSerializeWrapper(x).maybe_to_value()` will resolve to either
/// - The inherent method `MaybeSerializeWrapper::maybe_to_value(...)` if x is `Serialize`
@ -65,3 +54,118 @@ impl<T: Serialize> MaybeSerializeWrapper<T> {
serde_json::value::to_value(self.0).ok()
}
}
/// Create a schema for a unit enum
pub fn new_unit_enum(variant: &str) -> Schema {
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(vec![variant.into()]),
..SchemaObject::default()
})
}
/// Create a schema for an externally tagged enum
pub fn new_externally_tagged_enum(variant: &str, sub_schema: Schema) -> Schema {
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::Object.into()),
object: Some(Box::new(ObjectValidation {
properties: {
let mut props = Map::new();
props.insert(variant.to_owned(), sub_schema);
props
},
required: {
let mut required = Set::new();
required.insert(variant.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()
})),
..SchemaObject::default()
})
}
/// Create a schema for an internally tagged enum
pub fn new_internally_tagged_enum(
tag_name: &str,
variant: &str,
deny_unknown_fields: bool,
) -> Schema {
let tag_schema = Schema::Object(SchemaObject {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(vec![variant.into()]),
..Default::default()
});
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::Object.into()),
object: Some(Box::new(ObjectValidation {
properties: {
let mut props = Map::new();
props.insert(tag_name.to_owned(), tag_schema);
props
},
required: {
let mut required = Set::new();
required.insert(tag_name.to_owned());
required
},
additional_properties: deny_unknown_fields.then(|| Box::new(false.into())),
..Default::default()
})),
..SchemaObject::default()
})
}
pub fn insert_object_property<T: ?Sized + JsonSchema>(
obj: &mut ObjectValidation,
key: &str,
has_default: bool,
required: bool,
schema: Schema,
) {
obj.properties.insert(key.to_owned(), schema);
if required || !(has_default || T::_schemars_private_is_option()) {
obj.required.insert(key.to_owned());
}
}
pub mod metadata {
use crate::Schema;
use serde_json::Value;
macro_rules! add_metadata_fn {
($method:ident, $name:ident, $ty:ty) => {
pub fn $method(schema: Schema, $name: impl Into<$ty>) -> Schema {
let value = $name.into();
if value == <$ty>::default() {
schema
} else {
let mut schema_obj = schema.into_object();
schema_obj.metadata().$name = value.into();
Schema::Object(schema_obj)
}
}
};
}
add_metadata_fn!(add_description, description, String);
add_metadata_fn!(add_id, id, String);
add_metadata_fn!(add_title, title, String);
add_metadata_fn!(add_deprecated, deprecated, bool);
add_metadata_fn!(add_read_only, read_only, bool);
add_metadata_fn!(add_write_only, write_only, bool);
add_metadata_fn!(add_default, default, Value);
pub fn add_examples<I: IntoIterator<Item = Value>>(schema: Schema, examples: I) -> Schema {
let mut schema_obj = schema.into_object();
schema_obj.metadata().examples.extend(examples);
Schema::Object(schema_obj)
}
}