Respect with attributes on enum variants

This commit is contained in:
Graham Esau 2020-05-10 17:17:04 +01:00
parent 1e17c46803
commit 08886799bb
8 changed files with 161 additions and 25 deletions

View file

@ -1,5 +1,9 @@
# Changelog # Changelog
## In-dev - version TBC
### Fixed
- `#[serde(with = "...")]`/`#[schemars(with = "...")]` attributes on enum variants are now respected
## [0.7.2] - 2020-04-30 ## [0.7.2] - 2020-04-30
### Added: ### Added:
- Enable deriving JsonSchema on adjacent tagged enums (https://github.com/GREsau/schemars/issues/4) - Enable deriving JsonSchema on adjacent tagged enums (https://github.com/GREsau/schemars/issues/4)

View file

@ -18,9 +18,14 @@ pub enum External {
StringMap(Map<String, String>), StringMap(Map<String, String>),
UnitStructNewType(UnitStruct), UnitStructNewType(UnitStruct),
StructNewType(Struct), StructNewType(Struct),
Struct { foo: i32, bar: bool }, Struct {
foo: i32,
bar: bool,
},
UnitTwo, UnitTwo,
Tuple(i32, bool), Tuple(i32, bool),
#[schemars(with = "i32")]
WithInt,
} }
#[test] #[test]
@ -35,8 +40,13 @@ pub enum Internal {
StringMap(Map<String, String>), StringMap(Map<String, String>),
UnitStructNewType(UnitStruct), UnitStructNewType(UnitStruct),
StructNewType(Struct), StructNewType(Struct),
Struct { foo: i32, bar: bool }, Struct {
foo: i32,
bar: bool,
},
UnitTwo, UnitTwo,
#[schemars(with = "i32")]
WithInt,
} }
#[test] #[test]
@ -51,8 +61,13 @@ pub enum Untagged {
StringMap(Map<String, String>), StringMap(Map<String, String>),
UnitStructNewType(UnitStruct), UnitStructNewType(UnitStruct),
StructNewType(Struct), StructNewType(Struct),
Struct { foo: i32, bar: bool }, Struct {
foo: i32,
bar: bool,
},
Tuple(i32, bool), Tuple(i32, bool),
#[schemars(with = "i32")]
WithInt,
} }
#[test] #[test]
@ -67,8 +82,14 @@ pub enum Adjacent {
StringMap(Map<String, String>), StringMap(Map<String, String>),
UnitStructNewType(UnitStruct), UnitStructNewType(UnitStruct),
StructNewType(Struct), StructNewType(Struct),
Struct { foo: i32, bar: bool }, Struct {
foo: i32,
bar: bool,
},
Tuple(i32, bool), Tuple(i32, bool),
UnitTwo,
#[schemars(with = "i32")]
WithInt,
} }
#[test] #[test]

View file

@ -89,6 +89,18 @@
"minItems": 2 "minItems": 2
} }
} }
},
{
"type": "object",
"required": [
"withInt"
],
"properties": {
"withInt": {
"type": "integer",
"format": "int32"
}
}
} }
], ],
"definitions": { "definitions": {

View file

@ -106,6 +106,24 @@
] ]
} }
} }
},
{
"type": [
"object",
"integer"
],
"format": "int32",
"required": [
"typeProperty"
],
"properties": {
"typeProperty": {
"type": "string",
"enum": [
"WithInt"
]
}
}
} }
] ]
} }

View file

@ -46,6 +46,10 @@
], ],
"maxItems": 2, "maxItems": 2,
"minItems": 2 "minItems": 2
},
{
"type": "integer",
"format": "int32"
} }
], ],
"definitions": { "definitions": {

View file

@ -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"
]
}
}
} }
] ]
} }

View file

@ -7,9 +7,8 @@ pub use schemars_to_serde::process_serde_attrs;
use proc_macro2::{Group, Span, TokenStream, TokenTree}; use proc_macro2::{Group, Span, TokenStream, TokenTree};
use syn::parse::{self, Parse}; use syn::parse::{self, Parse};
pub fn get_with_from_attrs(field: &syn::Field) -> Option<syn::Result<syn::Type>> { pub fn get_with_from_attrs(attrs: &[syn::Attribute]) -> Option<syn::Result<syn::Type>> {
field attrs
.attrs
.iter() .iter()
.filter(|at| match at.path.get_ident() { .filter(|at| match at.path.get_ident() {
// FIXME this is relying on order of attributes (schemars before serde) from schemars_to_serde.rs // FIXME this is relying on order of attributes (schemars before serde) from schemars_to_serde.rs

View file

@ -10,9 +10,10 @@ mod metadata;
use metadata::*; use metadata::*;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::ToTokens; 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::attr::{self as serde_attr, Default as SerdeDefault, TagType};
use serde_derive_internals::{Ctxt, Derive}; use serde_derive_internals::{Ctxt, Derive};
use std::ops::Deref;
use syn::spanned::Spanned; use syn::spanned::Spanned;
#[proc_macro_derive(JsonSchema, attributes(schemars, serde))] #[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::Newtype, ref fields) => schema_for_newtype_struct(&fields[0]),
Data::Struct(Style::Tuple, ref fields) => schema_for_tuple_struct(fields), 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::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 doc_metadata = SchemaMetadata::from_doc_attrs(&cont.original.attrs);
let schema_expr = doc_metadata.apply_to_schema(schema_expr); let schema_expr = doc_metadata.apply_to_schema(schema_expr);
@ -120,7 +121,7 @@ fn compile_error<'a>(errors: impl IntoIterator<Item = &'a syn::Error>) -> TokenS
fn is_unit_variant(v: &Variant) -> bool { fn is_unit_variant(v: &Variant) -> bool {
match v.style { match v.style {
Style::Unit => true, Style::Unit => v.with.is_none(),
_ => false, _ => false,
} }
} }
@ -221,17 +222,24 @@ fn schema_for_internal_tagged_enum<'a>(
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs); let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
let tag_schema = doc_metadata.apply_to_schema(tag_schema); let tag_schema = doc_metadata.apply_to_schema(tag_schema);
let variant_schema = match variant.style { // FIXME this match block is duplicated in schema_for_adjacent_tagged_enum,
Style::Unit => return tag_schema, // and is extremely similar to schema_for_untagged_enum_variant
Style::Newtype => { 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 field = &variant.fields[0];
let ty = get_json_schema_type(field); let ty = get_json_schema_type(field);
quote_spanned! {field.original.span()=> quote_spanned! {field.original.span()=>
<#ty>::json_schema(gen) <#ty>::json_schema(gen)
} }
} }
Style::Struct => schema_for_struct(&variant.fields, None), (None, 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::Tuple) => schema_for_tuple_struct(&variant.fields),
}; };
quote! { quote! {
#tag_schema.flatten(#variant_schema) #tag_schema.flatten(#variant_schema)
@ -262,6 +270,12 @@ fn schema_for_untagged_enum<'a>(variants: impl Iterator<Item = &'a Variant<'a>>)
} }
fn schema_for_untagged_enum_variant(variant: &Variant) -> TokenStream { 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 { match variant.style {
Style::Unit => schema_for_unit_struct(), Style::Unit => schema_for_unit_struct(),
Style::Newtype => schema_for_newtype_struct(&variant.fields[0]), Style::Newtype => schema_for_newtype_struct(&variant.fields[0]),
@ -276,17 +290,20 @@ fn schema_for_adjacent_tagged_enum<'a>(
content_name: &str, content_name: &str,
) -> TokenStream { ) -> TokenStream {
let schemas = variants.map(|variant| { let schemas = variants.map(|variant| {
let content_schema = match variant.style { let content_schema = match (&variant.with, &variant.style) {
Style::Unit => None, (Some(with), _) => Some(quote_spanned! {variant.original.span()=>
Style::Newtype => { <#with>::json_schema(gen)
}),
(None, Style::Unit) => None,
(None, Style::Newtype) => {
let field = &variant.fields[0]; let field = &variant.fields[0];
let ty = get_json_schema_type(field); let ty = get_json_schema_type(field);
Some(quote_spanned! {field.original.span()=> Some(quote_spanned! {field.original.span()=>
<#ty>::json_schema(gen) <#ty>::json_schema(gen)
}) })
} }
Style::Struct => Some(schema_for_struct(&variant.fields, None)), (None, Style::Struct) => Some(schema_for_struct(&variant.fields, None)),
Style::Tuple => Some(schema_for_tuple_struct(&variant.fields)), (None, Style::Tuple) => Some(schema_for_tuple_struct(&variant.fields)),
}; };
let (add_content_property, add_content_required) = content_schema let (add_content_property, add_content_required) = content_schema
@ -472,11 +489,39 @@ fn field_default_expr(field: &Field, container_has_default: bool) -> Option<Toke
}) })
} }
fn get_json_schema_type(field: &Field) -> Box<dyn ToTokens> { fn get_json_schema_type(field: &Field) -> TokenStream {
// TODO support [schemars(schema_with= "...")] or equivalent // TODO support [schemars(schema_with= "...")] or equivalent
match attr::get_with_from_attrs(&field.original) { match attr::get_with_from_attrs(&field.original.attrs) {
None => Box::new(field.ty.clone()), None => field.ty.to_token_stream(),
Some(Ok(expr_path)) => Box::new(expr_path), Some(Ok(expr_path)) => expr_path.to_token_stream(),
Some(Err(e)) => Box::new(compile_error(&[e])), Some(Err(e)) => compile_error(&[e]),
}
}
struct Variant<'a> {
serde: SerdeVariant<'a>,
with: Option<TokenStream>,
}
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<SerdeVariant<'a>>) -> Vec<Variant<'a>> {
serdes.into_iter().map(Self::new).collect()
}
}
impl<'a> Deref for Variant<'a> {
type Target = SerdeVariant<'a>;
fn deref(&self) -> &Self::Target {
&self.serde
} }
} }