Respect with
attributes on enum variants
This commit is contained in:
parent
1e17c46803
commit
08886799bb
8 changed files with 161 additions and 25 deletions
|
@ -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)
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -89,6 +89,18 @@
|
||||||
"minItems": 2
|
"minItems": 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"withInt"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"withInt": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
|
|
@ -106,6 +106,24 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"object",
|
||||||
|
"integer"
|
||||||
|
],
|
||||||
|
"format": "int32",
|
||||||
|
"required": [
|
||||||
|
"typeProperty"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"typeProperty": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"WithInt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -46,6 +46,10 @@
|
||||||
],
|
],
|
||||||
"maxItems": 2,
|
"maxItems": 2,
|
||||||
"minItems": 2
|
"minItems": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue