Refactor out add_schema_as_property

This commit is contained in:
Graham Esau 2021-04-16 10:42:03 +01:00
parent 1a2dafc1a5
commit 60a9869448
6 changed files with 89 additions and 101 deletions

View file

@ -24,40 +24,12 @@ pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
schema schema
} }
// Helper for generating schemas for `Option` fields. pub fn apply_metadata(schema: Schema, metadata: Metadata) -> Schema {
pub fn add_schema_as_property<T: ?Sized + JsonSchema>( if metadata == Metadata::default() {
gen: &mut SchemaGenerator, schema
parent: &mut SchemaObject,
name: String,
metadata: Option<Metadata>,
required: Option<bool>,
) {
let is_type_option = T::_schemars_private_is_option();
let required = required.unwrap_or(!is_type_option);
let mut schema = if required && is_type_option {
T::_schemars_private_non_optional_json_schema(gen)
} else { } else {
gen.subschema_for::<T>()
};
schema = apply_metadata(schema, metadata);
let object = parent.object();
if required {
object.required.insert(name.clone());
}
object.properties.insert(name, schema);
}
pub fn apply_metadata(schema: Schema, metadata: Option<Metadata>) -> Schema {
match metadata {
None => schema,
Some(ref metadata) if *metadata == Metadata::default() => schema,
Some(metadata) => {
let mut schema_obj = schema.into_object(); let mut schema_obj = schema.into_object();
schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata); schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata);
Schema::Object(schema_obj) Schema::Object(schema_obj)
} }
}
} }

View file

@ -17,7 +17,16 @@
], ],
"definitions": { "definitions": {
"InnerStruct": { "InnerStruct": {
"type": "object" "type": "object",
"required": [
"x"
],
"properties": {
"x": {
"type": "integer",
"format": "int32"
}
}
} }
} }
} }

View file

@ -56,7 +56,9 @@ build_enum!(
#[derive(Debug, JsonSchema)] #[derive(Debug, JsonSchema)]
OuterEnum { OuterEnum {
#[derive(Debug, JsonSchema)] #[derive(Debug, JsonSchema)]
InnerStruct {} InnerStruct {
x: i32
}
} }
); );

View file

@ -120,9 +120,7 @@ impl ValidationAttrs {
self self
} }
pub fn validation_statements(&self, field_name: &str) -> Option<TokenStream> { pub fn apply_to_schema(&self, schema_expr: TokenStream) -> TokenStream {
// Assume that the result will be interpolated in a context with the local variable
// `schema_object` - the SchemaObject for the struct that contains this field.
let mut array_validation = Vec::new(); let mut array_validation = Vec::new();
let mut number_validation = Vec::new(); let mut number_validation = Vec::new();
let mut object_validation = Vec::new(); let mut object_validation = Vec::new();
@ -187,7 +185,7 @@ impl ValidationAttrs {
let format = self.format.as_ref().map(|f| { let format = self.format.as_ref().map(|f| {
quote! { quote! {
prop_schema_object.format = Some(#f.to_string()); schema_object.format = Some(#f.to_string());
} }
}); });
@ -202,11 +200,10 @@ impl ValidationAttrs {
|| string_validation.is_some() || string_validation.is_some()
|| format.is_some() || format.is_some()
{ {
Some(quote! { quote! {
if let Some(schemars::schema::Schema::Object(prop_schema_object)) = schema_object {
.object let mut schema = #schema_expr;
.as_mut() if let schemars::schema::Schema::Object(schema_object) = &mut schema
.and_then(|o| o.properties.get_mut(#field_name))
{ {
#array_validation #array_validation
#number_validation #number_validation
@ -214,9 +211,11 @@ impl ValidationAttrs {
#string_validation #string_validation
#format #format
} }
}) schema
}
}
} else { } else {
None schema_expr
} }
} }
} }
@ -226,8 +225,8 @@ fn wrap_array_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
None None
} else { } else {
Some(quote! { Some(quote! {
if prop_schema_object.has_type(schemars::schema::InstanceType::Array) { if schema_object.has_type(schemars::schema::InstanceType::Array) {
let validation = prop_schema_object.array(); let validation = schema_object.array();
#(#v)* #(#v)*
} }
}) })
@ -239,9 +238,9 @@ fn wrap_number_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
None None
} else { } else {
Some(quote! { Some(quote! {
if prop_schema_object.has_type(schemars::schema::InstanceType::Integer) if schema_object.has_type(schemars::schema::InstanceType::Integer)
|| prop_schema_object.has_type(schemars::schema::InstanceType::Number) { || schema_object.has_type(schemars::schema::InstanceType::Number) {
let validation = prop_schema_object.number(); let validation = schema_object.number();
#(#v)* #(#v)*
} }
}) })
@ -253,8 +252,8 @@ fn wrap_object_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
None None
} else { } else {
Some(quote! { Some(quote! {
if prop_schema_object.has_type(schemars::schema::InstanceType::Object) { if schema_object.has_type(schemars::schema::InstanceType::Object) {
let validation = prop_schema_object.object(); let validation = schema_object.object();
#(#v)* #(#v)*
} }
}) })
@ -266,8 +265,8 @@ fn wrap_string_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
None None
} else { } else {
Some(quote! { Some(quote! {
if prop_schema_object.has_type(schemars::schema::InstanceType::String) { if schema_object.has_type(schemars::schema::InstanceType::String) {
let validation = prop_schema_object.string(); let validation = schema_object.string();
#(#v)* #(#v)*
} }
}) })

View file

@ -1,7 +1,6 @@
use crate::attr; use crate::attr;
use attr::Attrs; use attr::Attrs;
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::TokenStream;
use quote::{ToTokens, TokenStreamExt};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SchemaMetadata<'a> { pub struct SchemaMetadata<'a> {
@ -14,24 +13,6 @@ pub struct SchemaMetadata<'a> {
pub default: Option<TokenStream>, pub default: Option<TokenStream>,
} }
impl ToTokens for SchemaMetadata<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let setters = self.make_setters();
if setters.is_empty() {
tokens.append(Ident::new("None", Span::call_site()))
} else {
tokens.extend(quote! {
Some({
schemars::schema::Metadata {
#(#setters)*
..Default::default()
}
})
})
}
}
}
impl<'a> SchemaMetadata<'a> { impl<'a> SchemaMetadata<'a> {
pub fn from_attrs(attrs: &'a Attrs) -> Self { pub fn from_attrs(attrs: &'a Attrs) -> Self {
SchemaMetadata { SchemaMetadata {
@ -46,10 +27,15 @@ impl<'a> SchemaMetadata<'a> {
} }
pub fn apply_to_schema(&self, schema_expr: TokenStream) -> TokenStream { pub fn apply_to_schema(&self, schema_expr: TokenStream) -> TokenStream {
let setters = self.make_setters();
if setters.is_empty() {
schema_expr
} else {
quote! { quote! {
{ schemars::_private::apply_metadata(#schema_expr, schemars::schema::Metadata {
let schema = #schema_expr; #(#setters)*
schemars::_private::apply_metadata(schema, #self) ..Default::default()
})
} }
} }
} }

View file

@ -413,30 +413,49 @@ fn expr_for_struct(
let name = field.name(); let name = field.name();
let default = field_default_expr(field, set_container_default.is_some()); let default = field_default_expr(field, set_container_default.is_some());
let required = match (&default, field.validation_attrs.required) { let (ty, type_def) = type_for_field_schema(field, type_defs.len());
(Some(_), _) => quote!(Some(false)), if let Some(type_def) = type_def {
(None, false) => quote!(None), type_defs.push(type_def);
(None, true) => quote!(Some(true)), }
let gen = quote!(gen);
let schema_expr = if field.validation_attrs.required {
quote_spanned! {ty.span()=>
<#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#gen)
}
} else {
quote_spanned! {ty.span()=>
#gen.subschema_for::<#ty>()
}
}; };
let metadata = &SchemaMetadata { 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 metadata = SchemaMetadata {
read_only: field.serde_attrs.skip_deserializing(), read_only: field.serde_attrs.skip_deserializing(),
write_only: field.serde_attrs.skip_serializing(), write_only: field.serde_attrs.skip_serializing(),
default, default,
..SchemaMetadata::from_attrs(&field.attrs) ..SchemaMetadata::from_attrs(&field.attrs)
}; };
let (ty, type_def) = type_for_field_schema(field, type_defs.len()); let schema_expr = metadata.apply_to_schema(schema_expr);
if let Some(type_def) = type_def { let schema_expr = field.validation_attrs.apply_to_schema(schema_expr);
type_defs.push(type_def);
}
let args = quote!(gen, &mut schema_object, #name.to_owned(), #metadata, #required); quote! {
let validation = field.validation_attrs.validation_statements(&name); object_validation.properties.insert(#name.to_owned(), #schema_expr);
#maybe_insert_required
quote_spanned! {ty.span()=>
schemars::_private::add_schema_as_property::<#ty>(#args);
#validation
} }
}) })
.collect(); .collect();
@ -464,7 +483,7 @@ fn expr_for_struct(
let set_additional_properties = if deny_unknown_fields { let set_additional_properties = if deny_unknown_fields {
quote! { quote! {
schema_object.object().additional_properties = Some(Box::new(false.into())); object_validation.additional_properties = Some(Box::new(false.into()));
} }
} else { } else {
TokenStream::new() TokenStream::new()
@ -477,6 +496,7 @@ fn expr_for_struct(
instance_type: Some(schemars::schema::InstanceType::Object.into()), instance_type: Some(schemars::schema::InstanceType::Object.into()),
..Default::default() ..Default::default()
}; };
let object_validation = schema_object.object();
#set_additional_properties #set_additional_properties
#(#properties)* #(#properties)*
schemars::schema::Schema::Object(schema_object) schemars::schema::Schema::Object(schema_object)