diff --git a/schemars/tests/enum.rs b/schemars/tests/enum.rs index c271196..774082a 100644 --- a/schemars/tests/enum.rs +++ b/schemars/tests/enum.rs @@ -94,5 +94,5 @@ pub enum Adjacent { #[test] fn enum_adjacent_tagged() -> TestResult { - test_default_generated_schema::("enum_adjacent_tagged-untagged") + test_default_generated_schema::("enum-adjacent-tagged") } diff --git a/schemars/tests/expected/enum_adjacent_tagged-untagged.json b/schemars/tests/expected/enum-adjacent-tagged.json similarity index 86% rename from schemars/tests/expected/enum_adjacent_tagged-untagged.json rename to schemars/tests/expected/enum-adjacent-tagged.json index 4c4f401..b7c32c4 100644 --- a/schemars/tests/expected/enum_adjacent_tagged-untagged.json +++ b/schemars/tests/expected/enum-adjacent-tagged.json @@ -45,7 +45,7 @@ ], "properties": { "c": { - "type": "null" + "$ref": "#/definitions/UnitStruct" }, "t": { "type": "string", @@ -63,20 +63,7 @@ ], "properties": { "c": { - "type": "object", - "required": [ - "bar", - "foo" - ], - "properties": { - "bar": { - "type": "boolean" - }, - "foo": { - "type": "integer", - "format": "int32" - } - } + "$ref": "#/definitions/Struct" }, "t": { "type": "string", @@ -179,5 +166,26 @@ } } } - ] + ], + "definitions": { + "Struct": { + "type": "object", + "required": [ + "bar", + "foo" + ], + "properties": { + "bar": { + "type": "boolean" + }, + "foo": { + "type": "integer", + "format": "int32" + } + } + }, + "UnitStruct": { + "type": "null" + } + } } \ No newline at end of file diff --git a/schemars_derive/src/ast/mod.rs b/schemars_derive/src/ast/mod.rs index b67850e..14b9959 100644 --- a/schemars_derive/src/ast/mod.rs +++ b/schemars_derive/src/ast/mod.rs @@ -54,10 +54,21 @@ impl<'a> Variant<'a> { pub fn name(&self) -> String { self.serde_attrs.name().deserialize_name() } + + pub fn is_unit(&self) -> bool { + match self.style { + serde_ast::Style::Unit => true, + _ => false, + } + } } impl<'a> Field<'a> { pub fn name(&self) -> String { self.serde_attrs.name().deserialize_name() } + + pub fn type_for_schema(&self) -> &syn::Type { + self.with.as_ref().unwrap_or(self.ty) + } } diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index 6b69957..2e768d4 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -7,14 +7,10 @@ extern crate proc_macro; mod ast; mod attr; mod metadata; +mod schema_exprs; use ast::*; -use metadata::*; use proc_macro2::TokenStream; -use quote::ToTokens; -use serde_derive_internals::ast::Style; -use serde_derive_internals::attr::{self as serde_attr, Default as SerdeDefault, TagType}; -use syn::spanned::Spanned; #[proc_macro_derive(JsonSchema, attributes(schemars, serde))] pub fn derive_json_schema_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -34,15 +30,7 @@ fn derive_json_schema(mut input: syn::DeriveInput) -> TokenStream { Err(e) => return compile_error(&e), }; - let schema_expr = match &cont.data { - Data::Struct(Style::Unit, _) => schema_for_unit_struct(), - Data::Struct(Style::Newtype, fields) => schema_for_newtype_struct(&fields[0]), - Data::Struct(Style::Tuple, fields) => schema_for_tuple_struct(fields), - Data::Struct(Style::Struct, fields) => schema_for_struct(fields, Some(&cont.serde_attrs)), - Data::Enum(variants) => schema_for_enum(variants, &cont.serde_attrs), - }; - let doc_metadata = SchemaMetadata::from_doc_attrs(&cont.original.attrs); - let schema_expr = doc_metadata.apply_to_schema(schema_expr); + let schema_expr = schema_exprs::expr_for_container(&cont); let type_name = &cont.ident; let type_params: Vec<_> = cont.generics.type_params().map(|ty| &ty.ident).collect(); @@ -103,397 +91,9 @@ fn add_trait_bounds(generics: &mut syn::Generics) { } } -fn wrap_schema_fields(schema_contents: TokenStream) -> TokenStream { - quote! { - schemars::schema::Schema::Object( - schemars::schema::SchemaObject { - #schema_contents - ..Default::default() - }) - } -} - fn compile_error<'a>(errors: impl IntoIterator) -> TokenStream { let compile_errors = errors.into_iter().map(syn::Error::to_compile_error); quote! { #(#compile_errors)* } } - -fn is_unit_variant(v: &Variant) -> bool { - match v.style { - Style::Unit => v.with.is_none(), - _ => false, - } -} - -fn schema_for_enum(variants: &[Variant], cattrs: &serde_attr::Container) -> TokenStream { - let variants = variants - .iter() - .filter(|v| !v.serde_attrs.skip_deserializing()); - match cattrs.tag() { - TagType::External => schema_for_external_tagged_enum(variants), - TagType::None => schema_for_untagged_enum(variants), - TagType::Internal { tag } => schema_for_internal_tagged_enum(variants, tag), - TagType::Adjacent { tag, content } => { - schema_for_adjacent_tagged_enum(variants, tag, content) - } - } -} - -fn schema_for_external_tagged_enum<'a>( - variants: impl Iterator>, -) -> TokenStream { - let (unit_variants, complex_variants): (Vec<_>, Vec<_>) = - variants.partition(|v| is_unit_variant(v)); - let unit_count = unit_variants.len(); - - let unit_names = unit_variants.into_iter().map(|v| v.name()); - let unit_schema = wrap_schema_fields(quote! { - enum_values: Some(vec![#(#unit_names.into()),*]), - }); - - if complex_variants.is_empty() { - return unit_schema; - } - - let mut schemas = Vec::new(); - if unit_count > 0 { - schemas.push(unit_schema); - } - - schemas.extend(complex_variants.into_iter().map(|variant| { - let name = variant.name(); - let sub_schema = schema_for_untagged_enum_variant(variant); - let schema_expr = wrap_schema_fields(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(#name.to_owned(), #sub_schema); - props - }, - required: { - let mut required = schemars::Set::new(); - required.insert(#name.to_owned()); - required - }, - ..Default::default() - })), - }); - let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs); - doc_metadata.apply_to_schema(schema_expr) - })); - - wrap_schema_fields(quote! { - subschemas: Some(Box::new(schemars::schema::SubschemaValidation { - any_of: Some(vec![#(#schemas),*]), - ..Default::default() - })), - }) -} - -fn schema_for_internal_tagged_enum<'a>( - variants: impl Iterator>, - tag_name: &str, -) -> TokenStream { - let schemas = variants.map(|variant| { - let name = variant.name(); - let type_schema = wrap_schema_fields(quote! { - instance_type: Some(schemars::schema::InstanceType::String.into()), - enum_values: Some(vec![#name.into()]), - }); - - let tag_schema = wrap_schema_fields(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(), #type_schema); - props - }, - required: { - let mut required = schemars::Set::new(); - required.insert(#tag_name.to_owned()); - required - }, - ..Default::default() - })), - }); - let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs); - let tag_schema = doc_metadata.apply_to_schema(tag_schema); - - // FIXME this match block is duplicated in schema_for_adjacent_tagged_enum, - // and is extremely similar to schema_for_untagged_enum_variant - let variant_schema = match (&variant.with, &variant.style) { - (Some(with), _) => { - quote_spanned! {variant.original.span()=> - <#with>::json_schema(gen) - } - } - (None, Style::Unit) => return tag_schema, - (None, Style::Newtype) => { - let field = &variant.fields[0]; - let ty = get_json_schema_type(field); - quote_spanned! {field.original.span()=> - <#ty>::json_schema(gen) - } - } - (None, Style::Struct) => schema_for_struct(&variant.fields, None), - (None, Style::Tuple) => schema_for_tuple_struct(&variant.fields), - }; - quote! { - #tag_schema.flatten(#variant_schema) - } - }); - - wrap_schema_fields(quote! { - subschemas: Some(Box::new(schemars::schema::SubschemaValidation { - any_of: Some(vec![#(#schemas),*]), - ..Default::default() - })), - }) -} - -fn schema_for_untagged_enum<'a>(variants: impl Iterator>) -> TokenStream { - let schemas = variants.map(|variant| { - let schema_expr = schema_for_untagged_enum_variant(variant); - let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs); - doc_metadata.apply_to_schema(schema_expr) - }); - - wrap_schema_fields(quote! { - subschemas: Some(Box::new(schemars::schema::SubschemaValidation { - any_of: Some(vec![#(#schemas),*]), - ..Default::default() - })), - }) -} - -fn schema_for_untagged_enum_variant(variant: &Variant) -> TokenStream { - if let Some(with) = &variant.with { - return quote_spanned! {variant.original.span()=> - gen.subschema_for::<#with>() - }; - } - - match variant.style { - Style::Unit => schema_for_unit_struct(), - Style::Newtype => schema_for_newtype_struct(&variant.fields[0]), - Style::Tuple => schema_for_tuple_struct(&variant.fields), - Style::Struct => schema_for_struct(&variant.fields, None), - } -} - -fn schema_for_adjacent_tagged_enum<'a>( - variants: impl Iterator>, - tag_name: &str, - content_name: &str, -) -> TokenStream { - let schemas = variants.map(|variant| { - let content_schema = match (&variant.with, &variant.style) { - (Some(with), _) => Some(quote_spanned! {variant.original.span()=> - <#with>::json_schema(gen) - }), - (None, Style::Unit) => None, - (None, Style::Newtype) => { - let field = &variant.fields[0]; - let ty = get_json_schema_type(field); - Some(quote_spanned! {field.original.span()=> - <#ty>::json_schema(gen) - }) - } - (None, Style::Struct) => Some(schema_for_struct(&variant.fields, None)), - (None, Style::Tuple) => Some(schema_for_tuple_struct(&variant.fields)), - }; - - let (add_content_property, add_content_required) = content_schema - .map(|content_schema| { - ( - quote!(props.insert(#content_name.to_owned(), #content_schema);), - quote!(required.insert(#content_name.to_owned());), - ) - }) - .unwrap_or_default(); - - let name = variant.name(); - let tag_schema = wrap_schema_fields(quote! { - instance_type: Some(schemars::schema::InstanceType::String.into()), - enum_values: Some(vec![#name.into()]), - }); - - let outer_schema = wrap_schema_fields(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); - #add_content_property - props - }, - required: { - let mut required = schemars::Set::new(); - required.insert(#tag_name.to_owned()); - #add_content_required - required - }, - ..Default::default() - })), - }); - - let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs); - doc_metadata.apply_to_schema(outer_schema) - }); - - wrap_schema_fields(quote! { - subschemas: Some(Box::new(schemars::schema::SubschemaValidation { - any_of: Some(vec![#(#schemas),*]), - ..Default::default() - })), - }) -} - -fn schema_for_unit_struct() -> TokenStream { - quote! { - gen.subschema_for::<()>() - } -} - -fn schema_for_newtype_struct(field: &Field) -> TokenStream { - let ty = get_json_schema_type(field); - quote_spanned! {field.original.span()=> - gen.subschema_for::<#ty>() - } -} - -fn schema_for_tuple_struct(fields: &[Field]) -> TokenStream { - let types = fields - .iter() - .filter(|f| !f.serde_attrs.skip_deserializing()) - .map(get_json_schema_type); - quote! { - gen.subschema_for::<(#(#types),*)>() - } -} - -fn schema_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) -> TokenStream { - let (flattened_fields, property_fields): (Vec<_>, Vec<_>) = fields - .iter() - .filter(|f| !f.serde_attrs.skip_deserializing() || !f.serde_attrs.skip_serializing()) - .partition(|f| f.serde_attrs.flatten()); - - let set_container_default = match cattrs.map_or(&SerdeDefault::None, |c| c.default()) { - SerdeDefault::None => None, - SerdeDefault::Default => Some(quote!(let container_default = Self::default();)), - SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)), - }; - - let properties = property_fields.iter().map(|field| { - let name = field.name(); - let default = field_default_expr(field, set_container_default.is_some()); - - let required = match default { - Some(_) => quote!(false), - None => quote!(true), - }; - - let metadata = &SchemaMetadata { - read_only: field.serde_attrs.skip_deserializing(), - write_only: field.serde_attrs.skip_serializing(), - default, - ..SchemaMetadata::from_doc_attrs(&field.original.attrs) - }; - - let ty = get_json_schema_type(field); - let span = field.original.span(); - - quote_spanned! {span=> - <#ty>::add_schema_as_property(gen, &mut schema_object, #name.to_owned(), #metadata, #required); - } - }); - - let flattens = flattened_fields.iter().map(|field| { - let ty = get_json_schema_type(field); - let span = field.original.span(); - - quote_spanned! {span=> - .flatten(<#ty>::json_schema_for_flatten(gen)) - } - }); - - quote! { - { - #set_container_default - let mut schema_object = schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::Object.into()), - ..Default::default() - }; - #(#properties)* - schemars::schema::Schema::Object(schema_object) - #(#flattens)* - } - } -} - -fn field_default_expr(field: &Field, container_has_default: bool) -> Option { - let field_default = field.serde_attrs.default(); - if field.serde_attrs.skip_serializing() || (field_default.is_none() && !container_has_default) { - return None; - } - - let ty = field.ty; - let default_expr = match field_default { - SerdeDefault::None => { - let member = &field.member; - quote!(container_default.#member) - } - SerdeDefault::Default => quote!(<#ty>::default()), - SerdeDefault::Path(path) => quote!(#path()), - }; - - let default_expr = match field.serde_attrs.skip_serializing_if() { - Some(skip_if) => { - quote! { - { - let default = #default_expr; - if #skip_if(&default) { - None - } else { - Some(default) - } - } - } - } - None => quote!(Some(#default_expr)), - }; - - Some(if let Some(ser_with) = field.serde_attrs.serialize_with() { - quote! { - { - struct _SchemarsDefaultSerialize(T); - - impl serde::Serialize for _SchemarsDefaultSerialize<#ty> - { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer - { - #ser_with(&self.0, serializer) - } - } - - #default_expr.map(|d| _SchemarsDefaultSerialize(d)) - } - } - } else { - default_expr - }) -} - -fn get_json_schema_type(field: &Field) -> TokenStream { - // TODO support [schemars(schema_with= "...")] or equivalent - field - .with - .as_ref() - .map_or_else(|| field.ty.to_token_stream(), |w| w.to_token_stream()) -} diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs new file mode 100644 index 0000000..13ea944 --- /dev/null +++ b/schemars_derive/src/schema_exprs.rs @@ -0,0 +1,383 @@ +use crate::{ast::*, metadata::SchemaMetadata}; +use proc_macro2::TokenStream; +use serde_derive_internals::ast::Style; +use serde_derive_internals::attr::{self as serde_attr, Default as SerdeDefault, TagType}; +use syn::spanned::Spanned; + +pub fn expr_for_container(cont: &Container) -> TokenStream { + let schema_expr = match &cont.data { + Data::Struct(Style::Unit, _) => expr_for_unit_struct(), + Data::Struct(Style::Newtype, fields) => expr_for_newtype_struct(&fields[0]), + Data::Struct(Style::Tuple, fields) => expr_for_tuple_struct(fields), + Data::Struct(Style::Struct, fields) => expr_for_struct(fields, Some(&cont.serde_attrs)), + Data::Enum(variants) => expr_for_enum(variants, &cont.serde_attrs), + }; + + let doc_metadata = SchemaMetadata::from_doc_attrs(&cont.original.attrs); + doc_metadata.apply_to_schema(schema_expr) +} + +fn expr_for_enum(variants: &[Variant], cattrs: &serde_attr::Container) -> TokenStream { + let variants = variants + .iter() + .filter(|v| !v.serde_attrs.skip_deserializing()); + match cattrs.tag() { + TagType::External => expr_for_external_tagged_enum(variants), + TagType::None => expr_for_untagged_enum(variants), + TagType::Internal { tag } => expr_for_internal_tagged_enum(variants, tag), + TagType::Adjacent { tag, content } => expr_for_adjacent_tagged_enum(variants, tag, content), + } +} + +fn expr_for_external_tagged_enum<'a>( + variants: impl Iterator>, +) -> TokenStream { + let (unit_variants, complex_variants): (Vec<_>, Vec<_>) = + variants.partition(|v| v.is_unit() && v.with.is_none()); + + let unit_names = unit_variants.iter().map(|v| v.name()); + let unit_schema = schema_object(quote! { + enum_values: Some(vec![#(#unit_names.into()),*]), + }); + + if complex_variants.is_empty() { + return unit_schema; + } + + let mut schemas = Vec::new(); + if unit_variants.len() > 0 { + schemas.push(unit_schema); + } + + schemas.extend(complex_variants.into_iter().map(|variant| { + let name = variant.name(); + let sub_schema = expr_for_untagged_enum_variant(variant); + let schema_expr = 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(#name.to_owned(), #sub_schema); + props + }, + required: { + let mut required = schemars::Set::new(); + required.insert(#name.to_owned()); + required + }, + ..Default::default() + })), + }); + let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs); + doc_metadata.apply_to_schema(schema_expr) + })); + + schema_object(quote! { + subschemas: Some(Box::new(schemars::schema::SubschemaValidation { + any_of: Some(vec![#(#schemas),*]), + ..Default::default() + })), + }) +} + +fn expr_for_internal_tagged_enum<'a>( + variants: impl Iterator>, + tag_name: &str, +) -> TokenStream { + let variant_schemas = variants.map(|variant| { + let name = variant.name(); + let type_schema = schema_object(quote! { + instance_type: Some(schemars::schema::InstanceType::String.into()), + enum_values: Some(vec![#name.into()]), + }); + + let tag_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(), #type_schema); + props + }, + required: { + let mut required = schemars::Set::new(); + required.insert(#tag_name.to_owned()); + required + }, + ..Default::default() + })), + }); + let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs); + let tag_schema = doc_metadata.apply_to_schema(tag_schema); + + match expr_for_untagged_enum_variant_for_flatten(&variant) { + Some(variant_schema) => quote! { + #tag_schema.flatten(#variant_schema) + }, + None => tag_schema, + } + }); + + schema_object(quote! { + subschemas: Some(Box::new(schemars::schema::SubschemaValidation { + any_of: Some(vec![#(#variant_schemas),*]), + ..Default::default() + })), + }) +} + +fn expr_for_untagged_enum<'a>(variants: impl Iterator>) -> TokenStream { + let schemas = variants.map(|variant| { + let schema_expr = expr_for_untagged_enum_variant(variant); + let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs); + doc_metadata.apply_to_schema(schema_expr) + }); + + schema_object(quote! { + subschemas: Some(Box::new(schemars::schema::SubschemaValidation { + any_of: Some(vec![#(#schemas),*]), + ..Default::default() + })), + }) +} + +fn expr_for_adjacent_tagged_enum<'a>( + variants: impl Iterator>, + tag_name: &str, + content_name: &str, +) -> TokenStream { + let schemas = variants.map(|variant| { + let content_schema = if variant.is_unit() && variant.with.is_none() { + None + } else { + Some(expr_for_untagged_enum_variant(variant)) + }; + + 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());), + ) + }) + .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 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); + #add_content_to_props + props + }, + required: { + let mut required = schemars::Set::new(); + required.insert(#tag_name.to_owned()); + #add_content_to_required + required + }, + ..Default::default() + })), + }); + + let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs); + doc_metadata.apply_to_schema(outer_schema) + }); + + schema_object(quote! { + subschemas: Some(Box::new(schemars::schema::SubschemaValidation { + any_of: Some(vec![#(#schemas),*]), + ..Default::default() + })), + }) +} + +fn expr_for_untagged_enum_variant(variant: &Variant) -> TokenStream { + if let Some(with) = &variant.with { + return quote_spanned! {variant.original.span()=> + gen.subschema_for::<#with>() + }; + } + + match variant.style { + Style::Unit => expr_for_unit_struct(), + Style::Newtype => expr_for_newtype_struct(&variant.fields[0]), + Style::Tuple => expr_for_tuple_struct(&variant.fields), + Style::Struct => expr_for_struct(&variant.fields, None), + } +} + +fn expr_for_untagged_enum_variant_for_flatten(variant: &Variant) -> Option { + if let Some(with) = &variant.with { + return Some(quote_spanned! {variant.original.span()=> + <#with>::json_schema(gen) + }); + } + + Some(match variant.style { + Style::Unit => return None, + Style::Newtype => { + let field = &variant.fields[0]; + let ty = field.type_for_schema(); + quote_spanned! {field.original.span()=> + <#ty>::json_schema(gen) + } + } + Style::Tuple => expr_for_tuple_struct(&variant.fields), + Style::Struct => expr_for_struct(&variant.fields, None), + }) +} + +fn expr_for_unit_struct() -> TokenStream { + quote! { + gen.subschema_for::<()>() + } +} + +fn expr_for_newtype_struct(field: &Field) -> TokenStream { + let ty = field.type_for_schema(); + quote_spanned! {field.original.span()=> + gen.subschema_for::<#ty>() + } +} + +fn expr_for_tuple_struct(fields: &[Field]) -> TokenStream { + let types = fields + .iter() + .filter(|f| !f.serde_attrs.skip_deserializing()) + .map(Field::type_for_schema); + quote! { + gen.subschema_for::<(#(#types),*)>() + } +} + +fn expr_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) -> TokenStream { + let (flattened_fields, property_fields): (Vec<_>, Vec<_>) = fields + .iter() + .filter(|f| !f.serde_attrs.skip_deserializing() || !f.serde_attrs.skip_serializing()) + .partition(|f| f.serde_attrs.flatten()); + + let set_container_default = cattrs.and_then(|c| match c.default() { + SerdeDefault::None => None, + SerdeDefault::Default => Some(quote!(let container_default = Self::default();)), + SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)), + }); + + let properties = property_fields.iter().map(|field| { + let name = field.name(); + let default = field_default_expr(field, set_container_default.is_some()); + + let required = match default { + Some(_) => quote!(false), + None => quote!(true), + }; + + let metadata = &SchemaMetadata { + read_only: field.serde_attrs.skip_deserializing(), + write_only: field.serde_attrs.skip_serializing(), + default, + ..SchemaMetadata::from_doc_attrs(&field.original.attrs) + }; + + let ty = field.type_for_schema(); + let span = field.original.span(); + + quote_spanned! {span=> + <#ty>::add_schema_as_property(gen, &mut schema_object, #name.to_owned(), #metadata, #required); + } + }); + + let flattens = flattened_fields.iter().map(|field| { + let ty = field.type_for_schema(); + let span = field.original.span(); + + quote_spanned! {span=> + .flatten(<#ty>::json_schema_for_flatten(gen)) + } + }); + + quote! { + { + #set_container_default + let mut schema_object = schemars::schema::SchemaObject { + instance_type: Some(schemars::schema::InstanceType::Object.into()), + ..Default::default() + }; + #(#properties)* + schemars::schema::Schema::Object(schema_object) + #(#flattens)* + } + } +} + +fn field_default_expr(field: &Field, container_has_default: bool) -> Option { + let field_default = field.serde_attrs.default(); + if field.serde_attrs.skip_serializing() || (field_default.is_none() && !container_has_default) { + return None; + } + + let ty = field.ty; + let default_expr = match field_default { + SerdeDefault::None => { + let member = &field.member; + quote!(container_default.#member) + } + SerdeDefault::Default => quote!(<#ty>::default()), + SerdeDefault::Path(path) => quote!(#path()), + }; + + let default_expr = match field.serde_attrs.skip_serializing_if() { + Some(skip_if) => { + quote! { + { + let default = #default_expr; + if #skip_if(&default) { + None + } else { + Some(default) + } + } + } + } + None => quote!(Some(#default_expr)), + }; + + Some(if let Some(ser_with) = field.serde_attrs.serialize_with() { + quote! { + { + struct _SchemarsDefaultSerialize(T); + + impl serde::Serialize for _SchemarsDefaultSerialize<#ty> + { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer + { + #ser_with(&self.0, serializer) + } + } + + #default_expr.map(|d| _SchemarsDefaultSerialize(d)) + } + } + } else { + default_expr + }) +} + +fn schema_object(properties: TokenStream) -> TokenStream { + quote! { + schemars::schema::Schema::Object( + schemars::schema::SchemaObject { + #properties + ..Default::default() + }) + } +}