Define Schema as a newtype around serde_json::Value (#289)

This commit is contained in:
Graham Esau 2024-05-12 19:23:54 +01:00 committed by GitHub
parent 7f6a7b7e32
commit 342cd5fd09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
79 changed files with 1410 additions and 2394 deletions

View file

@ -325,119 +325,85 @@ impl ValidationAttrs {
}
pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) {
if let Some(apply_expr) = self.apply_to_schema_expr() {
*schema_expr = quote! {
{
let mut schema = #schema_expr;
#apply_expr
schema
}
}
let setters = self.make_setters(quote!(&mut schema));
if !setters.is_empty() {
*schema_expr = quote!({
let mut schema = #schema_expr;
#(#setters)*
schema
});
}
}
fn apply_to_schema_expr(&self) -> Option<TokenStream> {
let mut array_validation = Vec::new();
let mut number_validation = Vec::new();
let mut object_validation = Vec::new();
let mut string_validation = Vec::new();
fn make_setters(&self, mut_schema: impl ToTokens) -> Vec<TokenStream> {
let mut result = Vec::new();
if let Some(length_min) = self.length_min.as_ref().or(self.length_equal.as_ref()) {
string_validation.push(quote! {
validation.min_length = Some(#length_min as u32);
result.push(quote! {
schemars::_private::insert_validation_property(#mut_schema, "string", "minLength", #length_min);
});
array_validation.push(quote! {
validation.min_items = Some(#length_min as u32);
result.push(quote! {
schemars::_private::insert_validation_property(#mut_schema, "array", "minItems", #length_min);
});
}
if let Some(length_max) = self.length_max.as_ref().or(self.length_equal.as_ref()) {
string_validation.push(quote! {
validation.max_length = Some(#length_max as u32);
result.push(quote! {
schemars::_private::insert_validation_property(#mut_schema, "string", "maxLength", #length_max);
});
array_validation.push(quote! {
validation.max_items = Some(#length_max as u32);
result.push(quote! {
schemars::_private::insert_validation_property(#mut_schema, "array", "maxItems", #length_max);
});
}
if let Some(range_min) = &self.range_min {
number_validation.push(quote! {
validation.minimum = Some(#range_min as f64);
result.push(quote! {
schemars::_private::insert_validation_property(#mut_schema, "number", "minimum", #range_min);
});
}
if let Some(range_max) = &self.range_max {
number_validation.push(quote! {
validation.maximum = Some(#range_max as f64);
result.push(quote! {
schemars::_private::insert_validation_property(#mut_schema, "number", "maximum", #range_max);
});
}
if let Some(regex) = &self.regex {
string_validation.push(quote! {
validation.pattern = Some(#regex.to_string());
result.push(quote! {
schemars::_private::insert_validation_property(#mut_schema, "string", "pattern", #regex);
});
}
if let Some(contains) = &self.contains {
object_validation.push(quote! {
validation.required.insert(#contains.to_string());
result.push(quote! {
schemars::_private::append_required(#mut_schema, #contains);
});
if self.regex.is_none() {
let pattern = crate::regex_syntax::escape(contains);
string_validation.push(quote! {
validation.pattern = Some(#pattern.to_string());
result.push(quote! {
schemars::_private::insert_validation_property(#mut_schema, "string", "pattern", #pattern);
});
}
}
let format = self.format.as_ref().map(|f| {
let f = f.schema_str();
quote! {
schema_object.format = Some(#f.to_string());
}
});
let inner_validation = self
.inner
.as_deref()
.and_then(|inner| inner.apply_to_schema_expr())
.map(|apply_expr| {
quote! {
if schema_object.has_type(schemars::schema::InstanceType::Array) {
if let Some(schemars::schema::SingleOrVec::Single(inner_schema)) = &mut schema_object.array().items {
let mut schema = &mut **inner_schema;
#apply_expr
}
}
}
});
let array_validation = wrap_array_validation(array_validation);
let number_validation = wrap_number_validation(number_validation);
let object_validation = wrap_object_validation(object_validation);
let string_validation = wrap_string_validation(string_validation);
if array_validation.is_some()
|| number_validation.is_some()
|| object_validation.is_some()
|| string_validation.is_some()
|| format.is_some()
|| inner_validation.is_some()
{
Some(quote! {
if let schemars::schema::Schema::Object(schema_object) = &mut schema {
#array_validation
#number_validation
#object_validation
#string_validation
#format
#inner_validation
}
if let Some(format) = &self.format {
let f = format.schema_str();
result.push(quote! {
schema.ensure_object().insert("format".to_owned(), #f.into());
})
} else {
None
};
if let Some(inner) = &self.inner {
let inner_setters = inner.make_setters(quote!(schema));
if !inner_setters.is_empty() {
result.push(quote! {
schemars::_private::apply_inner_validation(#mut_schema, |schema| { #(#inner_setters)* });
})
}
}
result
}
}
@ -456,59 +422,6 @@ fn parse_lit_into_expr_path(
})
}
fn wrap_array_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
if v.is_empty() {
None
} else {
Some(quote! {
if schema_object.has_type(schemars::schema::InstanceType::Array) {
let validation = schema_object.array();
#(#v)*
}
})
}
}
fn wrap_number_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
if v.is_empty() {
None
} else {
Some(quote! {
if schema_object.has_type(schemars::schema::InstanceType::Integer)
|| schema_object.has_type(schemars::schema::InstanceType::Number) {
let validation = schema_object.number();
#(#v)*
}
})
}
}
fn wrap_object_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
if v.is_empty() {
None
} else {
Some(quote! {
if schema_object.has_type(schemars::schema::InstanceType::Object) {
let validation = schema_object.object();
#(#v)*
}
})
}
}
fn wrap_string_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
if v.is_empty() {
None
} else {
Some(quote! {
if schema_object.has_type(schemars::schema::InstanceType::String) {
let validation = schema_object.string();
#(#v)*
}
})
}
}
fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, expr: Expr) -> Option<Expr> {
// this odd double-parsing is to make `-10` parsed as an Lit instead of an Expr::Unary
let lit: Lit = match syn::parse2(expr.to_token_stream()) {

View file

@ -68,11 +68,11 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result<To
<#ty as schemars::JsonSchema>::schema_id()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
<#ty as schemars::JsonSchema>::json_schema(gen)
}
fn _schemars_private_non_optional_json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
fn _schemars_private_non_optional_json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
<#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(gen)
}
@ -182,7 +182,7 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result<To
#schema_id
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
#schema_expr
}
};

View file

@ -13,32 +13,46 @@ 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 mut schema = #schema_expr;
let obj = schema.ensure_object();
#(#setters)*
schema
}}
}
}
fn make_setters(&self) -> Vec<TokenStream> {
let mut setters = Vec::<TokenStream>::new();
if let Some(title) = &self.title {
*schema_expr = quote! {
schemars::_private::metadata::add_title(#schema_expr, #title)
};
setters.push(quote! {
obj.insert("title".to_owned(), #title.into());
});
}
if let Some(description) = &self.description {
*schema_expr = quote! {
schemars::_private::metadata::add_description(#schema_expr, #description)
};
setters.push(quote! {
obj.insert("description".to_owned(), #description.into());
});
}
if self.deprecated {
*schema_expr = quote! {
schemars::_private::metadata::add_deprecated(#schema_expr, true)
};
setters.push(quote! {
obj.insert("deprecated".to_owned(), true.into());
});
}
if self.read_only {
*schema_expr = quote! {
schemars::_private::metadata::add_read_only(#schema_expr, true)
};
setters.push(quote! {
obj.insert("readOnly".to_owned(), true.into());
});
}
if self.write_only {
*schema_expr = quote! {
schemars::_private::metadata::add_write_only(#schema_expr, true)
};
setters.push(quote! {
obj.insert("writeOnly".to_owned(), true.into());
});
}
if !self.examples.is_empty() {
@ -47,16 +61,19 @@ impl<'a> SchemaMetadata<'a> {
schemars::_serde_json::value::to_value(#eg())
}
});
*schema_expr = quote! {
schemars::_private::metadata::add_examples(#schema_expr, [#(#examples),*].into_iter().flatten())
};
setters.push(quote! {
obj.insert("examples".to_owned(), schemars::_serde_json::Value::Array([#(#examples),*].into_iter().flatten().collect()));
});
}
if let Some(default) = &self.default {
*schema_expr = quote! {
schemars::_private::metadata::add_default(#schema_expr, #default.and_then(|d| schemars::_schemars_maybe_to_value!(d)))
};
setters.push(quote! {
if let Some(default) = #default.and_then(|d| schemars::_schemars_maybe_to_value!(d)) {
obj.insert("default".to_owned(), default);
}
});
}
setters
}
}

View file

@ -49,9 +49,18 @@ pub fn expr_for_repr(cont: &Container) -> Result<TokenStream, syn::Error> {
let enum_ident = &cont.ident;
let variant_idents = variants.iter().map(|v| &v.ident);
let mut schema_expr = schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::Integer.into()),
enum_values: Some(vec![#((#enum_ident::#variant_idents as #repr_type).into()),*]),
let mut schema_expr = quote!({
let mut map = schemars::_serde_json::Map::new();
map.insert("type".to_owned(), "integer".into());
map.insert(
"enum".to_owned(),
schemars::_serde_json::Value::Array({
let mut enum_values = Vec::new();
#(enum_values.push((#enum_ident::#variant_idents as #repr_type).into());)*
enum_values
}),
);
schemars::Schema::from(map)
});
cont.attrs.as_metadata().apply_to_schema(&mut schema_expr);
@ -118,7 +127,7 @@ fn type_for_schema(with_attr: &WithAttr) -> (syn::Type, Option<TokenStream>) {
))
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
#fun(gen)
}
}
@ -160,9 +169,18 @@ fn expr_for_external_tagged_enum<'a>(
})
.partition(|v| v.is_unit() && v.attrs.is_default());
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()),*]),
let unit_schema = quote!({
let mut map = schemars::_serde_json::Map::new();
map.insert("type".to_owned(), "string".into());
map.insert(
"enum".to_owned(),
schemars::_serde_json::Value::Array({
let mut enum_values = Vec::new();
#(enum_values.push((#unit_names).into());)*
enum_values
}),
);
schemars::Schema::from(map)
});
if complex_variants.is_empty() {
@ -276,47 +294,44 @@ fn expr_for_adjacent_tagged_enum<'a>(
let (add_content_to_props, add_content_to_required) = content_schema
.map(|content_schema| {
(
quote!(props.insert(#content_name.to_owned(), #content_schema);),
quote!(required.insert(#content_name.to_owned());),
quote!(#content_name: (#content_schema),),
quote!(#content_name,),
)
})
.unwrap_or_default();
let name = variant.name();
let tag_schema = schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::String.into()),
enum_values: Some(vec![#name.into()]),
});
let tag_schema = quote! {
schemars::json_schema!({
"type": "string",
"enum": [#name],
})
};
let set_additional_properties = if deny_unknown_fields {
quote! {
additional_properties: Some(Box::new(false.into())),
"additionalProperties": false,
}
} else {
TokenStream::new()
};
let mut outer_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(), #tag_schema);
let mut outer_schema = quote! {
schemars::json_schema!({
"type": "object",
"properties": {
#tag_name: (#tag_schema),
#add_content_to_props
props
},
required: {
let mut required = schemars::Set::new();
required.insert(#tag_name.to_owned());
"required": [
#tag_name,
#add_content_to_required
required
},
],
// As we're creating a "wrapper" object, we can honor the
// disposition of deny_unknown_fields.
#set_additional_properties
..Default::default()
})),
});
})
};
variant
.attrs
@ -333,21 +348,19 @@ fn expr_for_adjacent_tagged_enum<'a>(
/// Callers must determine if all subschemas are mutually exclusive. This can
/// be done for most tagging regimes by checking that all tag names are unique.
fn variant_subschemas(unique: bool, schemas: Vec<TokenStream>) -> TokenStream {
if unique {
schema_object(quote! {
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
one_of: Some(vec![#(#schemas),*]),
..Default::default()
})),
})
} else {
schema_object(quote! {
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
any_of: Some(vec![#(#schemas),*]),
..Default::default()
})),
})
}
let keyword = if unique { "oneOf" } else { "anyOf" };
quote!({
let mut map = schemars::_serde_json::Map::new();
map.insert(
#keyword.to_owned(),
schemars::_serde_json::Value::Array({
let mut enum_values = Vec::new();
#(enum_values.push(#schemas.to_value());)*
enum_values
}),
);
schemars::Schema::from(map)
})
}
fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool) -> TokenStream {
@ -412,16 +425,11 @@ fn expr_for_tuple_struct(fields: &[Field]) -> TokenStream {
let len = fields.len() as u32;
quote! {
schemars::schema::Schema::Object(
schemars::schema::SchemaObject {
instance_type: Some(schemars::schema::InstanceType::Array.into()),
array: Some(Box::new(schemars::schema::ArrayValidation {
items: Some(vec![#(#fields),*].into()),
max_items: Some(#len),
min_items: Some(#len),
..Default::default()
})),
..Default::default()
schemars::json_schema!({
"type": "array",
"items": [#((#fields)),*],
"minItems": #len,
"maxItems": #len,
})
}
}
@ -477,7 +485,7 @@ fn expr_for_struct(
quote! {
{
#type_def
schemars::_private::insert_object_property::<#ty>(object_validation, #name, #has_default, #required, #schema_expr);
schemars::_private::insert_object_property::<#ty>(&mut schema, #name, #has_default, #required, #schema_expr);
}
}
})
@ -502,7 +510,7 @@ fn expr_for_struct(
let set_additional_properties = if deny_unknown_fields {
quote! {
object_validation.additional_properties = Some(Box::new(false.into()));
"additionalProperties": false,
}
} else {
TokenStream::new()
@ -510,15 +518,12 @@ fn expr_for_struct(
quote! {
{
#set_container_default
let mut schema_object = schemars::schema::SchemaObject {
instance_type: Some(schemars::schema::InstanceType::Object.into()),
..Default::default()
};
let object_validation = schema_object.object();
#set_additional_properties
let mut schema = schemars::json_schema!({
"type": "object",
#set_additional_properties
});
#(#properties)*
schemars::schema::Schema::Object(schema_object)
#(.flatten(#flattens))*
schema #(.flatten(#flattens))*
}
}
}
@ -578,16 +583,6 @@ fn field_default_expr(field: &Field, container_has_default: bool) -> Option<Toke
})
}
fn schema_object(properties: TokenStream) -> TokenStream {
quote! {
schemars::schema::Schema::Object(
schemars::schema::SchemaObject {
#properties
..Default::default()
})
}
}
fn prepend_type_def(type_def: Option<TokenStream>, schema_expr: &mut TokenStream) {
if let Some(type_def) = type_def {
*schema_expr = quote! {