From 60a98694482d1a11afc629aa6c7a93f193e7177e Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Fri, 16 Apr 2021 10:42:03 +0100 Subject: [PATCH] Refactor out `add_schema_as_property` --- schemars/src/_private.rs | 40 +++----------- schemars/tests/expected/macro_built_enum.json | 11 +++- schemars/tests/macro.rs | 4 +- schemars_derive/src/attr/validation.rs | 49 +++++++++-------- schemars_derive/src/metadata.rs | 34 ++++-------- schemars_derive/src/schema_exprs.rs | 52 +++++++++++++------ 6 files changed, 89 insertions(+), 101 deletions(-) diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index 8428370..1799f41 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -24,40 +24,12 @@ pub fn json_schema_for_flatten( schema } -// Helper for generating schemas for `Option` fields. -pub fn add_schema_as_property( - gen: &mut SchemaGenerator, - parent: &mut SchemaObject, - name: String, - metadata: Option, - required: Option, -) { - 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::() - }; - - 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) -> 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) - } + let mut schema_obj = schema.into_object(); + schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata); + Schema::Object(schema_obj) } } diff --git a/schemars/tests/expected/macro_built_enum.json b/schemars/tests/expected/macro_built_enum.json index 8564ef7..8a14a4b 100644 --- a/schemars/tests/expected/macro_built_enum.json +++ b/schemars/tests/expected/macro_built_enum.json @@ -17,7 +17,16 @@ ], "definitions": { "InnerStruct": { - "type": "object" + "type": "object", + "required": [ + "x" + ], + "properties": { + "x": { + "type": "integer", + "format": "int32" + } + } } } } \ No newline at end of file diff --git a/schemars/tests/macro.rs b/schemars/tests/macro.rs index 9991494..ca7dee8 100644 --- a/schemars/tests/macro.rs +++ b/schemars/tests/macro.rs @@ -56,7 +56,9 @@ build_enum!( #[derive(Debug, JsonSchema)] OuterEnum { #[derive(Debug, JsonSchema)] - InnerStruct {} + InnerStruct { + x: i32 + } } ); diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs index 043cccb..c1f0ca7 100644 --- a/schemars_derive/src/attr/validation.rs +++ b/schemars_derive/src/attr/validation.rs @@ -120,9 +120,7 @@ impl ValidationAttrs { self } - pub fn validation_statements(&self, field_name: &str) -> Option { - // 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,21 +200,22 @@ 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! { { - #array_validation - #number_validation - #object_validation - #string_validation - #format + let mut schema = #schema_expr; + if let schemars::schema::Schema::Object(schema_object) = &mut schema + { + #array_validation + #number_validation + #object_validation + #string_validation + #format + } + schema } - }) + } } else { - None + schema_expr } } } @@ -226,8 +225,8 @@ fn wrap_array_validation(v: Vec) -> Option { 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) -> Option { 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) -> Option { 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) -> Option { 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)* } }) diff --git a/schemars_derive/src/metadata.rs b/schemars_derive/src/metadata.rs index a84deca..05e5faf 100644 --- a/schemars_derive/src/metadata.rs +++ b/schemars_derive/src/metadata.rs @@ -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, } -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 { - quote! { - { - let schema = #schema_expr; - schemars::_private::apply_metadata(schema, #self) + let setters = self.make_setters(); + if setters.is_empty() { + schema_expr + } else { + quote! { + schemars::_private::apply_metadata(#schema_expr, schemars::schema::Metadata { + #(#setters)* + ..Default::default() + }) } } } diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 5c68c52..d8740e4 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -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)