diff --git a/CHANGELOG.md b/CHANGELOG.md index a1492f3..9deeacf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## In-dev - version TBC +### Fixed +- `#[serde(with = "...")]`/`#[schemars(with = "...")]` attributes on enum variants are now respected + ## [0.7.2] - 2020-04-30 ### Added: - Enable deriving JsonSchema on adjacent tagged enums (https://github.com/GREsau/schemars/issues/4) diff --git a/schemars/tests/enum.rs b/schemars/tests/enum.rs index fd9a8e2..c271196 100644 --- a/schemars/tests/enum.rs +++ b/schemars/tests/enum.rs @@ -18,9 +18,14 @@ pub enum External { StringMap(Map), UnitStructNewType(UnitStruct), StructNewType(Struct), - Struct { foo: i32, bar: bool }, + Struct { + foo: i32, + bar: bool, + }, UnitTwo, Tuple(i32, bool), + #[schemars(with = "i32")] + WithInt, } #[test] @@ -35,8 +40,13 @@ pub enum Internal { StringMap(Map), UnitStructNewType(UnitStruct), StructNewType(Struct), - Struct { foo: i32, bar: bool }, + Struct { + foo: i32, + bar: bool, + }, UnitTwo, + #[schemars(with = "i32")] + WithInt, } #[test] @@ -51,8 +61,13 @@ pub enum Untagged { StringMap(Map), UnitStructNewType(UnitStruct), StructNewType(Struct), - Struct { foo: i32, bar: bool }, + Struct { + foo: i32, + bar: bool, + }, Tuple(i32, bool), + #[schemars(with = "i32")] + WithInt, } #[test] @@ -67,8 +82,14 @@ pub enum Adjacent { StringMap(Map), UnitStructNewType(UnitStruct), StructNewType(Struct), - Struct { foo: i32, bar: bool }, + Struct { + foo: i32, + bar: bool, + }, Tuple(i32, bool), + UnitTwo, + #[schemars(with = "i32")] + WithInt, } #[test] diff --git a/schemars/tests/expected/enum-external.json b/schemars/tests/expected/enum-external.json index 91540ab..a041210 100644 --- a/schemars/tests/expected/enum-external.json +++ b/schemars/tests/expected/enum-external.json @@ -89,6 +89,18 @@ "minItems": 2 } } + }, + { + "type": "object", + "required": [ + "withInt" + ], + "properties": { + "withInt": { + "type": "integer", + "format": "int32" + } + } } ], "definitions": { diff --git a/schemars/tests/expected/enum-internal.json b/schemars/tests/expected/enum-internal.json index 003c9fd..38878f6 100644 --- a/schemars/tests/expected/enum-internal.json +++ b/schemars/tests/expected/enum-internal.json @@ -106,6 +106,24 @@ ] } } + }, + { + "type": [ + "object", + "integer" + ], + "format": "int32", + "required": [ + "typeProperty" + ], + "properties": { + "typeProperty": { + "type": "string", + "enum": [ + "WithInt" + ] + } + } } ] } \ No newline at end of file diff --git a/schemars/tests/expected/enum-untagged.json b/schemars/tests/expected/enum-untagged.json index f3de52d..4fbcf39 100644 --- a/schemars/tests/expected/enum-untagged.json +++ b/schemars/tests/expected/enum-untagged.json @@ -46,6 +46,10 @@ ], "maxItems": 2, "minItems": 2 + }, + { + "type": "integer", + "format": "int32" } ], "definitions": { diff --git a/schemars/tests/expected/enum_adjacent_tagged-untagged.json b/schemars/tests/expected/enum_adjacent_tagged-untagged.json index 196ddb0..4c4f401 100644 --- a/schemars/tests/expected/enum_adjacent_tagged-untagged.json +++ b/schemars/tests/expected/enum_adjacent_tagged-untagged.json @@ -145,6 +145,39 @@ ] } } + }, + { + "type": "object", + "required": [ + "t" + ], + "properties": { + "t": { + "type": "string", + "enum": [ + "UnitTwo" + ] + } + } + }, + { + "type": "object", + "required": [ + "c", + "t" + ], + "properties": { + "c": { + "type": "integer", + "format": "int32" + }, + "t": { + "type": "string", + "enum": [ + "WithInt" + ] + } + } } ] } \ No newline at end of file diff --git a/schemars_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs index 6330fa7..eb04f55 100644 --- a/schemars_derive/src/attr/mod.rs +++ b/schemars_derive/src/attr/mod.rs @@ -7,9 +7,8 @@ pub use schemars_to_serde::process_serde_attrs; use proc_macro2::{Group, Span, TokenStream, TokenTree}; use syn::parse::{self, Parse}; -pub fn get_with_from_attrs(field: &syn::Field) -> Option> { - field - .attrs +pub fn get_with_from_attrs(attrs: &[syn::Attribute]) -> Option> { + attrs .iter() .filter(|at| match at.path.get_ident() { // FIXME this is relying on order of attributes (schemars before serde) from schemars_to_serde.rs diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index 92ced97..8e59735 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -10,9 +10,10 @@ mod metadata; use metadata::*; use proc_macro2::TokenStream; use quote::ToTokens; -use serde_derive_internals::ast::{Container, Data, Field, Style, Variant}; +use serde_derive_internals::ast::{Container, Data, Field, Style, Variant as SerdeVariant}; use serde_derive_internals::attr::{self as serde_attr, Default as SerdeDefault, TagType}; use serde_derive_internals::{Ctxt, Derive}; +use std::ops::Deref; use syn::spanned::Spanned; #[proc_macro_derive(JsonSchema, attributes(schemars, serde))] @@ -36,7 +37,7 @@ pub fn derive_json_schema(input: proc_macro::TokenStream) -> proc_macro::TokenSt Data::Struct(Style::Newtype, ref fields) => schema_for_newtype_struct(&fields[0]), Data::Struct(Style::Tuple, ref fields) => schema_for_tuple_struct(fields), Data::Struct(Style::Struct, ref fields) => schema_for_struct(fields, Some(&cont.attrs)), - Data::Enum(ref variants) => schema_for_enum(variants, &cont.attrs), + Data::Enum(variants) => schema_for_enum(&Variant::vec_new(variants), &cont.attrs), }; let doc_metadata = SchemaMetadata::from_doc_attrs(&cont.original.attrs); let schema_expr = doc_metadata.apply_to_schema(schema_expr); @@ -120,7 +121,7 @@ fn compile_error<'a>(errors: impl IntoIterator) -> TokenS fn is_unit_variant(v: &Variant) -> bool { match v.style { - Style::Unit => true, + Style::Unit => v.with.is_none(), _ => false, } } @@ -221,17 +222,24 @@ fn schema_for_internal_tagged_enum<'a>( let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs); let tag_schema = doc_metadata.apply_to_schema(tag_schema); - let variant_schema = match variant.style { - Style::Unit => return tag_schema, - Style::Newtype => { + // 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) } } - Style::Struct => schema_for_struct(&variant.fields, None), - Style::Tuple => unreachable!("Internal tagged enum tuple variants will have caused serde_derive_internals to output a compile error already."), + (None, Style::Struct) => schema_for_struct(&variant.fields, None), + (None, Style::Tuple) => schema_for_tuple_struct(&variant.fields), }; quote! { #tag_schema.flatten(#variant_schema) @@ -262,6 +270,12 @@ fn schema_for_untagged_enum<'a>(variants: impl Iterator>) } 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]), @@ -276,17 +290,20 @@ fn schema_for_adjacent_tagged_enum<'a>( content_name: &str, ) -> TokenStream { let schemas = variants.map(|variant| { - let content_schema = match variant.style { - Style::Unit => None, - Style::Newtype => { + 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) }) } - Style::Struct => Some(schema_for_struct(&variant.fields, None)), - Style::Tuple => Some(schema_for_tuple_struct(&variant.fields)), + (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 @@ -472,11 +489,39 @@ fn field_default_expr(field: &Field, container_has_default: bool) -> Option Box { +fn get_json_schema_type(field: &Field) -> TokenStream { // TODO support [schemars(schema_with= "...")] or equivalent - match attr::get_with_from_attrs(&field.original) { - None => Box::new(field.ty.clone()), - Some(Ok(expr_path)) => Box::new(expr_path), - Some(Err(e)) => Box::new(compile_error(&[e])), + match attr::get_with_from_attrs(&field.original.attrs) { + None => field.ty.to_token_stream(), + Some(Ok(expr_path)) => expr_path.to_token_stream(), + Some(Err(e)) => compile_error(&[e]), + } +} + +struct Variant<'a> { + serde: SerdeVariant<'a>, + with: Option, +} + +impl<'a> Variant<'a> { + fn new(serde: SerdeVariant<'a>) -> Self { + let with = match attr::get_with_from_attrs(&serde.original.attrs) { + None => None, + Some(Ok(expr_path)) => Some(expr_path.to_token_stream()), + Some(Err(e)) => Some(compile_error(&[e])), + }; + Self { serde, with } + } + + fn vec_new(serdes: Vec>) -> Vec> { + serdes.into_iter().map(Self::new).collect() + } +} + +impl<'a> Deref for Variant<'a> { + type Target = SerdeVariant<'a>; + + fn deref(&self) -> &Self::Target { + &self.serde } }