diff --git a/serde/src/private/de.rs b/serde/src/private/de.rs index 33aecb4e..61bbf566 100644 --- a/serde/src/private/de.rs +++ b/serde/src/private/de.rs @@ -8,7 +8,7 @@ use lib::*; -use de::{Deserialize, Deserializer, Error, Visitor}; +use de::{Deserialize, Deserializer, IntoDeserializer, Error, Visitor}; #[cfg(any(feature = "std", feature = "collections"))] use de::Unexpected; @@ -1740,3 +1740,110 @@ mod content { } } } + +//////////////////////////////////////////////////////////////////////////////// + +// Like `IntoDeserializer` but also implemented for `&[u8]`. This is used for +// the newtype fallthrough case of `field_identifier`. +// +// #[derive(Deserialize)] +// #[serde(field_identifier)] +// enum F { +// A, +// B, +// Other(String), // deserialized using IdentifierDeserializer +// } +pub trait IdentifierDeserializer<'de, E: Error> { + type Deserializer: Deserializer<'de, Error = E>; + + fn from(self) -> Self::Deserializer; +} + +impl<'de, E> IdentifierDeserializer<'de, E> for u32 +where + E: Error, +{ + type Deserializer = >::Deserializer; + + fn from(self) -> Self::Deserializer { + self.into_deserializer() + } +} + +pub struct StrDeserializer<'a, E> { + value: &'a str, + marker: PhantomData, +} + +impl<'a, E> IdentifierDeserializer<'a, E> for &'a str +where + E: Error, +{ + type Deserializer = StrDeserializer<'a, E>; + + fn from(self) -> Self::Deserializer { + StrDeserializer { + value: self, + marker: PhantomData, + } + } +} + +impl<'de, 'a, E> Deserializer<'de> for StrDeserializer<'a, E> +where + E: Error, +{ + type Error = E; + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_str(self.value) + } + + forward_to_deserialize_any! { + bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes + byte_buf option unit unit_struct newtype_struct seq seq_fixed_size + tuple tuple_struct map struct identifier enum ignored_any + } +} + +pub struct BytesDeserializer<'a, E> { + value: &'a [u8], + marker: PhantomData, +} + +impl<'a, E> IdentifierDeserializer<'a, E> for &'a [u8] +where + E: Error, +{ + type Deserializer = BytesDeserializer<'a, E>; + + fn from(self) -> Self::Deserializer { + BytesDeserializer { + value: self, + marker: PhantomData, + } + } +} + +impl<'de, 'a, E> Deserializer<'de> for BytesDeserializer<'a, E> +where + E: Error, +{ + type Error = E; + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_bytes(self.value) + } + + forward_to_deserialize_any! { + bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes + byte_buf option unit unit_struct newtype_struct seq seq_fixed_size + tuple tuple_struct map struct identifier enum ignored_any + } +} diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index e9526494..79304878 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -186,7 +186,7 @@ fn borrowed_lifetimes(cont: &Container) -> BTreeSet { fn deserialize_body(cont: &Container, params: &Parameters) -> Fragment { if let Some(from_type) = cont.attrs.from_type() { deserialize_from(from_type) - } else { + } else if let attr::Identifier::No = cont.attrs.identifier() { match cont.body { Body::Enum(ref variants) => deserialize_enum(params, variants, &cont.attrs), Body::Struct(Style::Struct, ref fields) => { @@ -204,6 +204,13 @@ fn deserialize_body(cont: &Container, params: &Parameters) -> Fragment { } Body::Struct(Style::Unit, _) => deserialize_unit_struct(params, &cont.attrs), } + } else { + match cont.body { + Body::Enum(ref variants) => { + deserialize_custom_identifier(params, variants, &cont.attrs) + } + Body::Struct(_, _) => unreachable!("checked in serde_derive_internals"), + } } } @@ -608,7 +615,7 @@ fn deserialize_externally_tagged_enum( } }; - let variant_visitor = Stmts(deserialize_field_visitor(variant_names_idents, cattrs, true),); + let variant_visitor = Stmts(deserialize_generated_identifier(variant_names_idents, cattrs, true),); // Match arms to extract a variant from a string let variant_arms = @@ -703,7 +710,7 @@ fn deserialize_internally_tagged_enum( } }; - let variant_visitor = Stmts(deserialize_field_visitor(variant_names_idents, cattrs, true),); + let variant_visitor = Stmts(deserialize_generated_identifier(variant_names_idents, cattrs, true),); // Match arms to extract a variant from a string let variant_arms = variants.iter() @@ -763,7 +770,7 @@ fn deserialize_adjacently_tagged_enum( } }; - let variant_visitor = Stmts(deserialize_field_visitor(variant_names_idents, cattrs, true),); + let variant_visitor = Stmts(deserialize_generated_identifier(variant_names_idents, cattrs, true),); let ref variant_arms: Vec<_> = variants .iter() @@ -1200,67 +1207,23 @@ fn deserialize_untagged_newtype_variant( } } -fn deserialize_field_visitor( +fn deserialize_generated_identifier( fields: Vec<(String, Ident)>, cattrs: &attr::Container, is_variant: bool, ) -> Fragment { - let field_strs = fields.iter().map(|&(ref name, _)| name); - let field_bytes = fields.iter().map(|&(ref name, _)| quote::ByteStr(name)); + let this = quote!(__Field); let field_idents: &Vec<_> = &fields.iter().map(|&(_, ref ident)| ident).collect(); - let ignore_variant = if is_variant || cattrs.deny_unknown_fields() { - None + let (ignore_variant, fallthrough) = if is_variant || cattrs.deny_unknown_fields() { + (None, None) } else { - Some(quote!(__ignore,)) + let ignore_variant = quote!(__ignore,); + let fallthrough = quote!(_serde::export::Ok(__Field::__ignore)); + (Some(ignore_variant), Some(fallthrough)) }; - let visit_index = if is_variant { - let variant_indices = 0u32..; - let fallthrough_msg = format!("variant index 0 <= i < {}", fields.len()); - Some( - quote! { - fn visit_u32<__E>(self, __value: u32) -> _serde::export::Result<__Field, __E> - where __E: _serde::de::Error - { - match __value { - #( - #variant_indices => _serde::export::Ok(__Field::#field_idents), - )* - _ => _serde::export::Err(_serde::de::Error::invalid_value( - _serde::de::Unexpected::Unsigned(__value as u64), - &#fallthrough_msg)) - } - } - }, - ) - } else { - None - }; - - let fallthrough_arm = if is_variant { - quote! { - _serde::export::Err(_serde::de::Error::unknown_variant(__value, VARIANTS)) - } - } else if cattrs.deny_unknown_fields() { - quote! { - _serde::export::Err(_serde::de::Error::unknown_field(__value, FIELDS)) - } - } else { - quote! { - _serde::export::Ok(__Field::__ignore) - } - }; - - let bytes_to_str = if is_variant || cattrs.deny_unknown_fields() { - Some( - quote! { - let __value = &_serde::export::from_utf8_lossy(__value); - }, - ) - } else { - None - }; + let visitor_impl = Stmts(deserialize_identifier(this, &fields, is_variant, fallthrough)); quote_block! { #[allow(non_camel_case_types)] @@ -1269,54 +1232,204 @@ fn deserialize_field_visitor( #ignore_variant } + struct __FieldVisitor; + + impl<'de> _serde::de::Visitor<'de> for __FieldVisitor { + type Value = __Field; + + #visitor_impl + } + impl<'de> _serde::Deserialize<'de> for __Field { #[inline] - fn deserialize<__D>(__deserializer: __D) -> _serde::export::Result<__Field, __D::Error> + fn deserialize<__D>(__deserializer: __D) -> _serde::export::Result where __D: _serde::Deserializer<'de> { - struct __FieldVisitor; - - impl<'de> _serde::de::Visitor<'de> for __FieldVisitor { - type Value = __Field; - - fn expecting(&self, formatter: &mut _serde::export::Formatter) -> _serde::export::fmt::Result { - _serde::export::Formatter::write_str(formatter, "field name") - } - - #visit_index - - fn visit_str<__E>(self, __value: &str) -> _serde::export::Result<__Field, __E> - where __E: _serde::de::Error - { - match __value { - #( - #field_strs => _serde::export::Ok(__Field::#field_idents), - )* - _ => #fallthrough_arm - } - } - - fn visit_bytes<__E>(self, __value: &[u8]) -> _serde::export::Result<__Field, __E> - where __E: _serde::de::Error - { - match __value { - #( - #field_bytes => _serde::export::Ok(__Field::#field_idents), - )* - _ => { - #bytes_to_str - #fallthrough_arm - } - } - } - } - _serde::Deserializer::deserialize_identifier(__deserializer, __FieldVisitor) } } } } +fn deserialize_custom_identifier( + params: &Parameters, + variants: &[Variant], + cattrs: &attr::Container, +) -> Fragment { + let is_variant = match cattrs.identifier() { + attr::Identifier::Variant => true, + attr::Identifier::Field => false, + attr::Identifier::No => unreachable!(), + }; + + let this = ¶ms.this; + let this = quote!(#this); + + let (ordinary, fallthrough) = if let Some(last) = variants.last() { + let last_ident = &last.ident; + if last.attrs.other() { + let ordinary = &variants[..variants.len() - 1]; + let fallthrough = quote!(_serde::export::Ok(#this::#last_ident)); + (ordinary, Some(fallthrough)) + } else if let Style::Newtype = last.style { + let ordinary = &variants[..variants.len() - 1]; + let deserializer = quote!(_serde::private::de::IdentifierDeserializer::from(__value)); + let fallthrough = quote! { + _serde::export::Result::map( + _serde::Deserialize::deserialize(#deserializer), + #this::#last_ident) + }; + (ordinary, Some(fallthrough)) + } else { + (variants, None) + } + } else { + (variants, None) + }; + + let names_idents: Vec<_> = ordinary + .iter() + .map(|variant| (variant.attrs.name().deserialize_name(), variant.ident.clone())) + .collect(); + + let names = names_idents.iter().map(|&(ref name, _)| name); + + let names_const = if fallthrough.is_some() { + None + } else if is_variant { + let variants = quote! { + const VARIANTS: &'static [&'static str] = &[ #(#names),* ]; + }; + Some(variants) + } else { + let fields = quote! { + const FIELDS: &'static [&'static str] = &[ #(#names),* ]; + }; + Some(fields) + }; + + let (de_impl_generics, de_ty_generics, ty_generics, where_clause) = split_with_de_lifetime(params); + let visitor_impl = Stmts(deserialize_identifier(this.clone(), &names_idents, is_variant, fallthrough)); + + quote_block! { + #names_const + + struct __FieldVisitor #de_impl_generics #where_clause { + marker: _serde::export::PhantomData<#this #ty_generics>, + lifetime: _serde::export::PhantomData<&'de ()>, + } + + impl #de_impl_generics _serde::de::Visitor<'de> for __FieldVisitor #de_ty_generics #where_clause { + type Value = #this #ty_generics; + + #visitor_impl + } + + let __visitor = __FieldVisitor { + marker: _serde::export::PhantomData::<#this #ty_generics>, + lifetime: _serde::export::PhantomData, + }; + _serde::Deserializer::deserialize_identifier(__deserializer, __visitor) + } +} + +fn deserialize_identifier( + this: Tokens, + fields: &[(String, Ident)], + is_variant: bool, + fallthrough: Option, +) -> Fragment { + let field_strs = fields.iter().map(|&(ref name, _)| name); + let field_bytes = fields.iter().map(|&(ref name, _)| quote::ByteStr(name)); + + let constructors: &Vec<_> = &fields + .iter() + .map(|&(_, ref ident)| quote!(#this::#ident)) + .collect(); + + let expecting = if is_variant { + "variant identifier" + } else { + "field identifier" + }; + + let visit_index = if is_variant { + let variant_indices = 0u32..; + let fallthrough_msg = format!("variant index 0 <= i < {}", fields.len()); + let visit_index = quote! { + fn visit_u32<__E>(self, __value: u32) -> _serde::export::Result + where __E: _serde::de::Error + { + match __value { + #( + #variant_indices => _serde::export::Ok(#constructors), + )* + _ => _serde::export::Err(_serde::de::Error::invalid_value( + _serde::de::Unexpected::Unsigned(__value as u64), + &#fallthrough_msg)) + } + } + }; + Some(visit_index) + } else { + None + }; + + let bytes_to_str = if fallthrough.is_some() { + None + } else { + let conversion = quote! { + let __value = &_serde::export::from_utf8_lossy(__value); + }; + Some(conversion) + }; + + let fallthrough_arm = if let Some(fallthrough) = fallthrough { + fallthrough + } else if is_variant { + quote! { + _serde::export::Err(_serde::de::Error::unknown_variant(__value, VARIANTS)) + } + } else { + quote! { + _serde::export::Err(_serde::de::Error::unknown_field(__value, FIELDS)) + } + }; + + quote_block! { + fn expecting(&self, formatter: &mut _serde::export::Formatter) -> _serde::export::fmt::Result { + _serde::export::Formatter::write_str(formatter, #expecting) + } + + #visit_index + + fn visit_str<__E>(self, __value: &str) -> _serde::export::Result + where __E: _serde::de::Error + { + match __value { + #( + #field_strs => _serde::export::Ok(#constructors), + )* + _ => #fallthrough_arm + } + } + + fn visit_bytes<__E>(self, __value: &[u8]) -> _serde::export::Result + where __E: _serde::de::Error + { + match __value { + #( + #field_bytes => _serde::export::Ok(#constructors), + )* + _ => { + #bytes_to_str + #fallthrough_arm + } + } + } + } +} + fn deserialize_struct_visitor( struct_path: Tokens, params: &Parameters, @@ -1337,7 +1450,7 @@ fn deserialize_struct_visitor( } }; - let field_visitor = deserialize_field_visitor(field_names_idents, cattrs, false); + let field_visitor = deserialize_generated_identifier(field_names_idents, cattrs, false); let visit_map = deserialize_map(struct_path, params, fields, cattrs); diff --git a/serde_derive/src/ser.rs b/serde_derive/src/ser.rs index f7193f18..087702a3 100644 --- a/serde_derive/src/ser.rs +++ b/serde_derive/src/ser.rs @@ -12,13 +12,14 @@ use quote::Tokens; use bound; use fragment::{Fragment, Stmts, Match}; use internals::ast::{Body, Container, Field, Style, Variant}; -use internals::{self, attr}; +use internals::{attr, Ctxt}; use std::u32; pub fn expand_derive_serialize(input: &syn::DeriveInput) -> Result { - let ctxt = internals::Ctxt::new(); + let ctxt = Ctxt::new(); let cont = Container::from_ast(&ctxt, input); + precondition(&ctxt, &cont); try!(ctxt.check()); let ident = &cont.ident; @@ -61,6 +62,18 @@ pub fn expand_derive_serialize(input: &syn::DeriveInput) -> Result {} + attr::Identifier::Field => { + cx.error("field identifiers cannot be serialized"); + } + attr::Identifier::Variant => { + cx.error("variant identifiers cannot be serialized"); + } + } +} + struct Parameters { /// Variable holding the value being serialized. Either `self` for local /// types or `__self` for remote types. diff --git a/serde_derive_internals/src/ast.rs b/serde_derive_internals/src/ast.rs index c34b0e3f..7882334c 100644 --- a/serde_derive_internals/src/ast.rs +++ b/serde_derive_internals/src/ast.rs @@ -36,6 +36,7 @@ pub struct Field<'a> { pub ty: &'a syn::Ty, } +#[derive(Copy, Clone)] pub enum Style { Struct, Tuple, diff --git a/serde_derive_internals/src/attr.rs b/serde_derive_internals/src/attr.rs index 1af148d0..fd43527b 100644 --- a/serde_derive_internals/src/attr.rs +++ b/serde_derive_internals/src/attr.rs @@ -111,6 +111,7 @@ pub struct Container { from_type: Option, into_type: Option, remote: Option, + identifier: Identifier, } /// Styles of representing an enum. @@ -145,6 +146,23 @@ pub enum EnumTag { None, } +/// Whether this enum represents the fields of a struct or the variants of an +/// enum. +#[derive(Copy, Clone, Debug)] +pub enum Identifier { + /// It does not. + No, + + /// This enum represents the fields of a struct. All of the variants must be + /// unit variants, except possibly one which is annotated with + /// `#[serde(other)]` and is a newtype variant. + Field, + + /// This enum represents the variants of an enum. All of the variants must + /// be unit variants. + Variant, +} + impl Container { /// Extract out the `#[serde(...)]` attributes from an item. pub fn from_ast(cx: &Ctxt, item: &syn::MacroInput) -> Self { @@ -161,6 +179,8 @@ impl Container { let mut from_type = Attr::none(cx, "from"); let mut into_type = Attr::none(cx, "into"); let mut remote = Attr::none(cx, "remote"); + let mut field_identifier = BoolAttr::none(cx, "field_identifier"); + let mut variant_identifier = BoolAttr::none(cx, "variant_identifier"); for meta_items in item.attrs.iter().filter_map(get_serde_meta_items) { for meta_item in meta_items { @@ -307,6 +327,16 @@ impl Container { } } + // Parse `#[serde(field_identifier)]` + MetaItem(Word(ref name)) if name == "field_identifier" => { + field_identifier.set_true(); + } + + // Parse `#[serde(variant_identifier)]` + MetaItem(Word(ref name)) if name == "variant_identifier" => { + variant_identifier.set_true(); + } + MetaItem(ref meta_item) => { cx.error(format!("unknown serde container attribute `{}`", meta_item.name())); @@ -333,6 +363,7 @@ impl Container { from_type: from_type.get(), into_type: into_type.get(), remote: remote.get(), + identifier: decide_identifier(cx, item, field_identifier, variant_identifier), } } @@ -375,6 +406,10 @@ impl Container { pub fn remote(&self) -> Option<&syn::Path> { self.remote.as_ref() } + + pub fn identifier(&self) -> Identifier { + self.identifier + } } fn decide_tag( @@ -431,6 +466,31 @@ fn decide_tag( } } +fn decide_identifier( + cx: &Ctxt, + item: &syn::MacroInput, + field_identifier: BoolAttr, + variant_identifier: BoolAttr, +) -> Identifier { + match (&item.body, field_identifier.get(), variant_identifier.get()) { + (_, false, false) => Identifier::No, + (_, true, true) => { + cx.error("`field_identifier` and `variant_identifier` cannot both be set"); + Identifier::No + } + (&syn::Body::Struct(_), true, false) => { + cx.error("`field_identifier` can only be used on an enum"); + Identifier::No + } + (&syn::Body::Struct(_), false, true) => { + cx.error("`variant_identifier` can only be used on an enum"); + Identifier::No + } + (&syn::Body::Enum(_), true, false) => Identifier::Field, + (&syn::Body::Enum(_), false, true) => Identifier::Variant, + } +} + /// Represents variant attribute information #[derive(Debug)] pub struct Variant { @@ -440,6 +500,7 @@ pub struct Variant { rename_all: RenameRule, skip_deserializing: bool, skip_serializing: bool, + other: bool, } impl Variant { @@ -449,6 +510,7 @@ impl Variant { let mut skip_deserializing = BoolAttr::none(cx, "skip_deserializing"); let mut skip_serializing = BoolAttr::none(cx, "skip_serializing"); let mut rename_all = Attr::none(cx, "rename_all"); + let mut other = BoolAttr::none(cx, "other"); for meta_items in variant.attrs.iter().filter_map(get_serde_meta_items) { for meta_item in meta_items { @@ -487,11 +549,17 @@ impl Variant { MetaItem(Word(ref name)) if name == "skip_deserializing" => { skip_deserializing.set_true(); } + // Parse `#[serde(skip_serializing)]` MetaItem(Word(ref name)) if name == "skip_serializing" => { skip_serializing.set_true(); } + // Parse `#[serde(other)]` + MetaItem(Word(ref name)) if name == "other" => { + other.set_true(); + } + MetaItem(ref meta_item) => { cx.error(format!("unknown serde variant attribute `{}`", meta_item.name())); } @@ -517,6 +585,7 @@ impl Variant { rename_all: rename_all.get().unwrap_or(RenameRule::None), skip_deserializing: skip_deserializing.get(), skip_serializing: skip_serializing.get(), + other: other.get(), } } @@ -544,6 +613,10 @@ impl Variant { pub fn skip_serializing(&self) -> bool { self.skip_serializing } + + pub fn other(&self) -> bool { + self.other + } } /// Represents field attribute information diff --git a/serde_derive_internals/src/check.rs b/serde_derive_internals/src/check.rs index 941b2d4a..5a58fbaa 100644 --- a/serde_derive_internals/src/check.rs +++ b/serde_derive_internals/src/check.rs @@ -6,23 +6,88 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use ast::{Body, Container}; +use ast::{Body, Container, Style}; +use attr::Identifier; use Ctxt; /// Cross-cutting checks that require looking at more than a single attrs /// object. Simpler checks should happen when parsing and building the attrs. -pub fn check(cx: &Ctxt, item: &Container) { - match item.body { +pub fn check(cx: &Ctxt, cont: &Container) { + check_getter(cx, cont); + check_identifier(cx, cont); +} + +/// Getters are only allowed inside structs (not enums) with the `remote` +/// attribute. +fn check_getter(cx: &Ctxt, cont: &Container) { + match cont.body { Body::Enum(_) => { - if item.body.has_getter() { + if cont.body.has_getter() { cx.error("#[serde(getter = \"...\")] is not allowed in an enum"); } } Body::Struct(_, _) => { - if item.body.has_getter() && item.attrs.remote().is_none() { + if cont.body.has_getter() && cont.attrs.remote().is_none() { cx.error("#[serde(getter = \"...\")] can only be used in structs \ that have #[serde(remote = \"...\")]"); } } } } + +/// The `other` attribute must be used at most once and it must be the last +/// variant of an enum that has the `field_identifier` attribute. +/// +/// Inside a `variant_identifier` all variants must be unit variants. Inside a +/// `field_identifier` all but possibly one variant must be unit variants. The +/// last variant may be a newtype variant which is an implicit "other" case. +fn check_identifier(cx: &Ctxt, cont: &Container) { + let variants = match cont.body { + Body::Enum(ref variants) => variants, + Body::Struct(_, _) => { + return; + } + }; + + for (i, variant) in variants.iter().enumerate() { + match (variant.style, cont.attrs.identifier(), variant.attrs.other()) { + // The `other` attribute may only be used in a field_identifier. + (_, Identifier::Variant, true) | (_, Identifier::No, true) => { + cx.error("#[serde(other)] may only be used inside a field_identifier"); + } + + // Variant with `other` attribute must be the last one. + (Style::Unit, Identifier::Field, true) => { + if i < variants.len() - 1 { + cx.error("#[serde(other)] must be the last variant"); + } + } + + // Variant with `other` attribute must be a unit variant. + (_, Identifier::Field, true) => { + cx.error("#[serde(other)] must be on a unit variant"); + } + + // Any sort of variant is allowed if this is not an identifier. + (_, Identifier::No, false) => {} + + // Unit variant without `other` attribute is always fine. + (Style::Unit, _, false) => {} + + // The last field is allowed to be a newtype catch-all. + (Style::Newtype, Identifier::Field, false) => { + if i < variants.len() - 1 { + cx.error(format!("`{}` must be the last variant", variant.ident)); + } + } + + (_, Identifier::Field, false) => { + cx.error("field_identifier may only contain unit variants"); + } + + (_, Identifier::Variant, false) => { + cx.error("variant_identifier may only contain unit variants"); + } + } + } +} diff --git a/test_suite/tests/compile-fail/identifier/both.rs b/test_suite/tests/compile-fail/identifier/both.rs new file mode 100644 index 00000000..13a8d917 --- /dev/null +++ b/test_suite/tests/compile-fail/identifier/both.rs @@ -0,0 +1,19 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked +#[serde(field_identifier, variant_identifier)] //~^ HELP: `field_identifier` and `variant_identifier` cannot both be set +enum F { + A, + B, +} + +fn main() {} diff --git a/test_suite/tests/compile-fail/identifier/field_struct.rs b/test_suite/tests/compile-fail/identifier/field_struct.rs new file mode 100644 index 00000000..79b380d9 --- /dev/null +++ b/test_suite/tests/compile-fail/identifier/field_struct.rs @@ -0,0 +1,16 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked +#[serde(field_identifier)] +struct S; //~^^ HELP: `field_identifier` can only be used on an enum + +fn main() {} diff --git a/test_suite/tests/compile-fail/identifier/field_tuple.rs b/test_suite/tests/compile-fail/identifier/field_tuple.rs new file mode 100644 index 00000000..7b26885d --- /dev/null +++ b/test_suite/tests/compile-fail/identifier/field_tuple.rs @@ -0,0 +1,19 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked +#[serde(field_identifier)] +enum F { + A, + B(u8, u8), //~^^^^ HELP: field_identifier may only contain unit variants +} + +fn main() {} diff --git a/test_suite/tests/compile-fail/identifier/newtype_not_last.rs b/test_suite/tests/compile-fail/identifier/newtype_not_last.rs new file mode 100644 index 00000000..f601be3e --- /dev/null +++ b/test_suite/tests/compile-fail/identifier/newtype_not_last.rs @@ -0,0 +1,20 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked +#[serde(field_identifier)] +enum F { + A, + Other(String), //~^^^^ HELP: `Other` must be the last variant + B, +} + +fn main() {} diff --git a/test_suite/tests/compile-fail/identifier/not_identifier.rs b/test_suite/tests/compile-fail/identifier/not_identifier.rs new file mode 100644 index 00000000..3dd6d65f --- /dev/null +++ b/test_suite/tests/compile-fail/identifier/not_identifier.rs @@ -0,0 +1,19 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked +enum F { + A, + #[serde(other)] //~^^^ HELP: #[serde(other)] may only be used inside a field_identifier + B, +} + +fn main() {} diff --git a/test_suite/tests/compile-fail/identifier/not_unit.rs b/test_suite/tests/compile-fail/identifier/not_unit.rs new file mode 100644 index 00000000..dfc65df2 --- /dev/null +++ b/test_suite/tests/compile-fail/identifier/not_unit.rs @@ -0,0 +1,20 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked +#[serde(field_identifier)] +enum F { + A, + #[serde(other)] //~^^^^ HELP: #[serde(other)] must be on a unit variant + Other(u8, u8), +} + +fn main() {} diff --git a/test_suite/tests/compile-fail/identifier/other_not_last.rs b/test_suite/tests/compile-fail/identifier/other_not_last.rs new file mode 100644 index 00000000..481f4764 --- /dev/null +++ b/test_suite/tests/compile-fail/identifier/other_not_last.rs @@ -0,0 +1,21 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked +#[serde(field_identifier)] +enum F { + A, + #[serde(other)] //~^^^^ HELP: #[serde(other)] must be the last variant + Other, + B, +} + +fn main() {} diff --git a/test_suite/tests/compile-fail/identifier/serialize.rs b/test_suite/tests/compile-fail/identifier/serialize.rs new file mode 100644 index 00000000..1c928b3f --- /dev/null +++ b/test_suite/tests/compile-fail/identifier/serialize.rs @@ -0,0 +1,19 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Serialize)] //~ ERROR: proc-macro derive panicked +#[serde(field_identifier)] //~^ HELP: field identifiers cannot be serialized +enum F { + A, + B, +} + +fn main() {} diff --git a/test_suite/tests/compile-fail/identifier/variant_struct.rs b/test_suite/tests/compile-fail/identifier/variant_struct.rs new file mode 100644 index 00000000..aeb37f9a --- /dev/null +++ b/test_suite/tests/compile-fail/identifier/variant_struct.rs @@ -0,0 +1,16 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked +#[serde(variant_identifier)] +struct S; //~^^ HELP: `variant_identifier` can only be used on an enum + +fn main() {} diff --git a/test_suite/tests/compile-fail/identifier/variant_tuple.rs b/test_suite/tests/compile-fail/identifier/variant_tuple.rs new file mode 100644 index 00000000..9ea2ee2e --- /dev/null +++ b/test_suite/tests/compile-fail/identifier/variant_tuple.rs @@ -0,0 +1,19 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked +#[serde(variant_identifier)] +enum F { + A, + B(u8, u8), //~^^^^ HELP: variant_identifier may only contain unit variants +} + +fn main() {} diff --git a/test_suite/tests/test_identifier.rs b/test_suite/tests/test_identifier.rs new file mode 100644 index 00000000..76f1cffa --- /dev/null +++ b/test_suite/tests/test_identifier.rs @@ -0,0 +1,82 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +extern crate serde; + +extern crate serde_test; +use serde_test::{Token, assert_de_tokens}; + +#[test] +fn test_variant_identifier() { + #[derive(Deserialize, Debug, PartialEq)] + #[serde(variant_identifier)] + enum V { + Aaa, + Bbb, + } + + assert_de_tokens(&V::Aaa, &[Token::U32(0)]); + assert_de_tokens(&V::Aaa, &[Token::Str("Aaa")]); + assert_de_tokens(&V::Aaa, &[Token::Bytes(b"Aaa")]); +} + +#[test] +fn test_field_identifier() { + #[derive(Deserialize, Debug, PartialEq)] + #[serde(field_identifier, rename_all = "snake_case")] + enum F { + Aaa, + Bbb, + } + + assert_de_tokens(&F::Aaa, &[Token::Str("aaa")]); + assert_de_tokens(&F::Aaa, &[Token::Bytes(b"aaa")]); +} + +#[test] +fn test_unit_fallthrough() { + #[derive(Deserialize, Debug, PartialEq)] + #[serde(field_identifier, rename_all = "snake_case")] + enum F { + Aaa, + Bbb, + #[serde(other)] + Other, + } + + assert_de_tokens(&F::Other, &[Token::Str("x")]); +} + +#[test] +fn test_newtype_fallthrough() { + #[derive(Deserialize, Debug, PartialEq)] + #[serde(field_identifier, rename_all = "snake_case")] + enum F { + Aaa, + Bbb, + Other(String), + } + + assert_de_tokens(&F::Other("x".to_owned()), &[Token::Str("x")]); +} + +#[test] +fn test_newtype_fallthrough_generic() { + #[derive(Deserialize, Debug, PartialEq)] + #[serde(field_identifier, rename_all = "snake_case")] + enum F { + Aaa, + Bbb, + Other(T), + } + + assert_de_tokens(&F::Other("x".to_owned()), &[Token::Str("x")]); +}