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
}
// Helper for generating schemas for `Option` fields.
pub fn add_schema_as_property<T: ?Sized + JsonSchema>(
gen: &mut SchemaGenerator,
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)
pub fn apply_metadata(schema: Schema, metadata: Metadata) -> Schema {
if metadata == Metadata::default() {
schema
} 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();
schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata);
Schema::Object(schema_obj)
}
}
}

View file

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

View file

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

View file

@ -120,9 +120,7 @@ impl ValidationAttrs {
self
}
pub fn validation_statements(&self, field_name: &str) -> Option<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.
pub fn apply_to_schema(&self, schema_expr: TokenStream) -> TokenStream {
let mut array_validation = Vec::new();
let mut number_validation = Vec::new();
let mut object_validation = Vec::new();
@ -187,7 +185,7 @@ impl ValidationAttrs {
let format = self.format.as_ref().map(|f| {
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()
|| format.is_some()
{
Some(quote! {
if let Some(schemars::schema::Schema::Object(prop_schema_object)) = schema_object
.object
.as_mut()
.and_then(|o| o.properties.get_mut(#field_name))
quote! {
{
let mut schema = #schema_expr;
if let schemars::schema::Schema::Object(schema_object) = &mut schema
{
#array_validation
#number_validation
@ -214,9 +211,11 @@ impl ValidationAttrs {
#string_validation
#format
}
})
schema
}
}
} else {
None
schema_expr
}
}
}
@ -226,8 +225,8 @@ fn wrap_array_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
None
} else {
Some(quote! {
if prop_schema_object.has_type(schemars::schema::InstanceType::Array) {
let validation = prop_schema_object.array();
if schema_object.has_type(schemars::schema::InstanceType::Array) {
let validation = schema_object.array();
#(#v)*
}
})
@ -239,9 +238,9 @@ fn wrap_number_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
None
} else {
Some(quote! {
if prop_schema_object.has_type(schemars::schema::InstanceType::Integer)
|| prop_schema_object.has_type(schemars::schema::InstanceType::Number) {
let validation = prop_schema_object.number();
if schema_object.has_type(schemars::schema::InstanceType::Integer)
|| schema_object.has_type(schemars::schema::InstanceType::Number) {
let validation = schema_object.number();
#(#v)*
}
})
@ -253,8 +252,8 @@ fn wrap_object_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
None
} else {
Some(quote! {
if prop_schema_object.has_type(schemars::schema::InstanceType::Object) {
let validation = prop_schema_object.object();
if schema_object.has_type(schemars::schema::InstanceType::Object) {
let validation = schema_object.object();
#(#v)*
}
})
@ -266,8 +265,8 @@ fn wrap_string_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
None
} else {
Some(quote! {
if prop_schema_object.has_type(schemars::schema::InstanceType::String) {
let validation = prop_schema_object.string();
if schema_object.has_type(schemars::schema::InstanceType::String) {
let validation = schema_object.string();
#(#v)*
}
})

View file

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

View file

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