mirror of
https://github.com/serde-rs/serde.git
synced 2025-10-02 15:25:38 +00:00
Macro attributes to specify From and Into trait types for structs and enums (#817)
* serde macro support for type conversions through From and Into trait * Revisions requested by dtolnay * Additional changes requested by dtolnay
This commit is contained in:
parent
c488cec641
commit
bc946e4fd7
@ -7,7 +7,7 @@ use std::borrow::Cow;
|
|||||||
use collections::borrow::Cow;
|
use collections::borrow::Cow;
|
||||||
|
|
||||||
pub use core::default::Default;
|
pub use core::default::Default;
|
||||||
pub use core::fmt;
|
pub use core::{fmt, clone, convert};
|
||||||
pub use core::marker::PhantomData;
|
pub use core::marker::PhantomData;
|
||||||
pub use core::option::Option::{self, None, Some};
|
pub use core::option::Option::{self, None, Some};
|
||||||
pub use core::result::Result::{self, Ok, Err};
|
pub use core::result::Result::{self, Ok, Err};
|
||||||
|
@ -79,7 +79,8 @@ extern crate core as actual_core;
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
mod core {
|
mod core {
|
||||||
pub use std::{ops, hash, fmt, cmp, marker, mem, i8, i16, i32, i64, u8, u16, u32, u64, isize,
|
pub use std::{ops, hash, fmt, cmp, marker, mem, i8, i16, i32, i64, u8, u16, u32, u64, isize,
|
||||||
usize, f32, f64, char, str, num, slice, iter, cell, default, result, option};
|
usize, f32, f64, char, str, num, slice, iter, cell, default, result, option,
|
||||||
|
clone, convert};
|
||||||
#[cfg(feature = "unstable")]
|
#[cfg(feature = "unstable")]
|
||||||
pub use actual_core::nonzero;
|
pub use actual_core::nonzero;
|
||||||
}
|
}
|
||||||
|
@ -98,6 +98,8 @@ pub struct Item {
|
|||||||
ser_bound: Option<Vec<syn::WherePredicate>>,
|
ser_bound: Option<Vec<syn::WherePredicate>>,
|
||||||
de_bound: Option<Vec<syn::WherePredicate>>,
|
de_bound: Option<Vec<syn::WherePredicate>>,
|
||||||
tag: EnumTag,
|
tag: EnumTag,
|
||||||
|
from_type: Option<syn::Ty>,
|
||||||
|
into_type: Option<syn::Ty>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Styles of representing an enum.
|
/// Styles of representing an enum.
|
||||||
@ -145,6 +147,8 @@ impl Item {
|
|||||||
let mut untagged = BoolAttr::none(cx, "untagged");
|
let mut untagged = BoolAttr::none(cx, "untagged");
|
||||||
let mut internal_tag = Attr::none(cx, "tag");
|
let mut internal_tag = Attr::none(cx, "tag");
|
||||||
let mut content = Attr::none(cx, "content");
|
let mut content = Attr::none(cx, "content");
|
||||||
|
let mut from_type = Attr::none(cx, "from");
|
||||||
|
let mut into_type = Attr::none(cx, "into");
|
||||||
|
|
||||||
for meta_items in item.attrs.iter().filter_map(get_serde_meta_items) {
|
for meta_items in item.attrs.iter().filter_map(get_serde_meta_items) {
|
||||||
for meta_item in meta_items {
|
for meta_item in meta_items {
|
||||||
@ -270,6 +274,18 @@ impl Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse `#[serde(from = "type", into = "type")]
|
||||||
|
MetaItem(NameValue(ref name, ref lit)) if name == "from" => {
|
||||||
|
if let Ok(from_ty) = get_type(cx, name.as_ref(), lit) {
|
||||||
|
from_type.set_opt(Some(from_ty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MetaItem(NameValue(ref name, ref lit)) if name == "into" => {
|
||||||
|
if let Ok(into_ty) = get_type(cx, name.as_ref(), lit) {
|
||||||
|
into_type.set_opt(Some(into_ty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MetaItem(ref meta_item) => {
|
MetaItem(ref meta_item) => {
|
||||||
cx.error(format!("unknown serde container attribute `{}`",
|
cx.error(format!("unknown serde container attribute `{}`",
|
||||||
meta_item.name()));
|
meta_item.name()));
|
||||||
@ -339,6 +355,8 @@ impl Item {
|
|||||||
ser_bound: ser_bound.get(),
|
ser_bound: ser_bound.get(),
|
||||||
de_bound: de_bound.get(),
|
de_bound: de_bound.get(),
|
||||||
tag: tag,
|
tag: tag,
|
||||||
|
from_type: from_type.get(),
|
||||||
|
into_type: into_type.get(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,6 +387,14 @@ impl Item {
|
|||||||
pub fn tag(&self) -> &EnumTag {
|
pub fn tag(&self) -> &EnumTag {
|
||||||
&self.tag
|
&self.tag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_type(&self) -> Option<&syn::Ty> {
|
||||||
|
self.from_type.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_type(&self) -> Option<&syn::Ty> {
|
||||||
|
self.into_type.as_ref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents variant attribute information
|
/// Represents variant attribute information
|
||||||
@ -746,6 +772,18 @@ fn get_ser_and_de<T, F>(cx: &Ctxt,
|
|||||||
Ok((ser_item.get(), de_item.get()))
|
Ok((ser_item.get(), de_item.get()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_type(cx: &Ctxt,
|
||||||
|
attr_name: &str,
|
||||||
|
lit: &syn::Lit)
|
||||||
|
-> Result<syn::Ty, ()> {
|
||||||
|
if let Ok(ty) = parse_lit_into_ty(cx, attr_name, &lit) {
|
||||||
|
Ok(ty)
|
||||||
|
} else {
|
||||||
|
cx.error(format!("error parsing type for attribute {}", attr_name));
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_renames(cx: &Ctxt, items: &[syn::NestedMetaItem]) -> Result<SerAndDe<String>, ()> {
|
fn get_renames(cx: &Ctxt, items: &[syn::NestedMetaItem]) -> Result<SerAndDe<String>, ()> {
|
||||||
get_ser_and_de(cx, "rename", items, get_string_from_lit)
|
get_ser_and_de(cx, "rename", items, get_string_from_lit)
|
||||||
}
|
}
|
||||||
@ -797,3 +835,16 @@ fn parse_lit_into_where(cx: &Ctxt,
|
|||||||
|
|
||||||
syn::parse_where_clause(&where_string).map(|wh| wh.predicates).map_err(|err| cx.error(err))
|
syn::parse_where_clause(&where_string).map(|wh| wh.predicates).map_err(|err| cx.error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_lit_into_ty(cx: &Ctxt,
|
||||||
|
attr_name: &str,
|
||||||
|
lit: &syn::Lit)
|
||||||
|
-> Result<syn::Ty, ()> {
|
||||||
|
let string = try!(get_string_from_lit(cx, attr_name, attr_name, lit));
|
||||||
|
|
||||||
|
if let Ok(ty) = syn::parse_type(&string) {
|
||||||
|
Ok(ty)
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -85,36 +85,48 @@ fn requires_default(attrs: &attr::Field) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_body(item: &Item, generics: &syn::Generics) -> Fragment {
|
fn deserialize_body(item: &Item, generics: &syn::Generics) -> Fragment {
|
||||||
match item.body {
|
if let Some(fr_ty) = item.attrs.from_type() {
|
||||||
Body::Enum(ref variants) => {
|
deserialize_from(fr_ty)
|
||||||
deserialize_item_enum(&item.ident, generics, variants, &item.attrs)
|
} else {
|
||||||
}
|
match item.body {
|
||||||
Body::Struct(Style::Struct, ref fields) => {
|
Body::Enum(ref variants) => {
|
||||||
if fields.iter().any(|field| field.ident.is_none()) {
|
deserialize_item_enum(&item.ident, generics, variants, &item.attrs)
|
||||||
panic!("struct has unnamed fields");
|
|
||||||
}
|
}
|
||||||
|
Body::Struct(Style::Struct, ref fields) => {
|
||||||
deserialize_struct(&item.ident,
|
if fields.iter().any(|field| field.ident.is_none()) {
|
||||||
None,
|
panic!("struct has unnamed fields");
|
||||||
generics,
|
}
|
||||||
fields,
|
deserialize_struct(&item.ident,
|
||||||
&item.attrs,
|
None,
|
||||||
None)
|
generics,
|
||||||
}
|
fields,
|
||||||
Body::Struct(Style::Tuple, ref fields) |
|
&item.attrs,
|
||||||
Body::Struct(Style::Newtype, ref fields) => {
|
None)
|
||||||
if fields.iter().any(|field| field.ident.is_some()) {
|
|
||||||
panic!("tuple struct has named fields");
|
|
||||||
}
|
}
|
||||||
|
Body::Struct(Style::Tuple, ref fields) |
|
||||||
deserialize_tuple(&item.ident,
|
Body::Struct(Style::Newtype, ref fields) => {
|
||||||
None,
|
if fields.iter().any(|field| field.ident.is_some()) {
|
||||||
generics,
|
panic!("tuple struct has named fields");
|
||||||
fields,
|
}
|
||||||
&item.attrs,
|
deserialize_tuple(&item.ident,
|
||||||
None)
|
None,
|
||||||
|
generics,
|
||||||
|
fields,
|
||||||
|
&item.attrs,
|
||||||
|
None)
|
||||||
|
}
|
||||||
|
Body::Struct(Style::Unit, _) => deserialize_unit_struct(&item.ident, &item.attrs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_from(from_type: &syn::Ty) -> Fragment {
|
||||||
|
quote_block! {
|
||||||
|
let de_val = <#from_type as _serde::Deserialize>::deserialize(deserializer);
|
||||||
|
match de_val {
|
||||||
|
_serde::export::Result::Ok(from_in) => _serde::export::Result::Ok(_serde::export::convert::From::from(from_in)),
|
||||||
|
_serde::export::Result::Err(e) => _serde::export::Result::Err(e)
|
||||||
}
|
}
|
||||||
Body::Struct(Style::Unit, _) => deserialize_unit_struct(&item.ident, &item.attrs),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,28 +61,37 @@ fn needs_serialize_bound(attrs: &attr::Field) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_body(item: &Item, generics: &syn::Generics) -> Fragment {
|
fn serialize_body(item: &Item, generics: &syn::Generics) -> Fragment {
|
||||||
match item.body {
|
if let Some(in_ty) = item.attrs.into_type() {
|
||||||
Body::Enum(ref variants) => {
|
serialize_into(in_ty)
|
||||||
serialize_item_enum(&item.ident, generics, variants, &item.attrs)
|
} else {
|
||||||
}
|
match item.body {
|
||||||
Body::Struct(Style::Struct, ref fields) => {
|
Body::Enum(ref variants) => {
|
||||||
if fields.iter().any(|field| field.ident.is_none()) {
|
serialize_item_enum(&item.ident, generics, variants, &item.attrs)
|
||||||
panic!("struct has unnamed fields");
|
|
||||||
}
|
}
|
||||||
|
Body::Struct(Style::Struct, ref fields) => {
|
||||||
serialize_struct(&item.ident, generics, fields, &item.attrs)
|
if fields.iter().any(|field| field.ident.is_none()) {
|
||||||
}
|
panic!("struct has unnamed fields");
|
||||||
Body::Struct(Style::Tuple, ref fields) => {
|
}
|
||||||
if fields.iter().any(|field| field.ident.is_some()) {
|
serialize_struct(&item.ident, generics, fields, &item.attrs)
|
||||||
panic!("tuple struct has named fields");
|
|
||||||
}
|
}
|
||||||
|
Body::Struct(Style::Tuple, ref fields) => {
|
||||||
|
if fields.iter().any(|field| field.ident.is_some()) {
|
||||||
|
panic!("tuple struct has named fields");
|
||||||
|
}
|
||||||
|
serialize_tuple_struct(&item.ident, generics, fields, &item.attrs)
|
||||||
|
}
|
||||||
|
Body::Struct(Style::Newtype, ref fields) => {
|
||||||
|
serialize_newtype_struct(&item.ident, generics, &fields[0], &item.attrs)
|
||||||
|
}
|
||||||
|
Body::Struct(Style::Unit, _) => serialize_unit_struct(&item.attrs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serialize_tuple_struct(&item.ident, generics, fields, &item.attrs)
|
fn serialize_into(into_type: &syn::Ty) -> Fragment {
|
||||||
}
|
quote_block! {
|
||||||
Body::Struct(Style::Newtype, ref fields) => {
|
let cloned_val: #into_type = _serde::export::convert::Into::into(_serde::export::clone::Clone::clone(self));
|
||||||
serialize_newtype_struct(&item.ident, generics, &fields[0], &item.attrs)
|
_serde::Serialize::serialize(&cloned_val, _serializer)
|
||||||
}
|
|
||||||
Body::Struct(Style::Unit, _) => serialize_unit_struct(&item.attrs),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked
|
||||||
|
#[serde(from="")]
|
||||||
|
enum TestOne {
|
||||||
|
Testing,
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Three,
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
#[derive(Serialize)] //~ ERROR: proc-macro derive panicked
|
||||||
|
#[serde(into="")]
|
||||||
|
enum TestOne {
|
||||||
|
Testing,
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Three,
|
||||||
|
}
|
@ -977,3 +977,83 @@ fn test_invalid_length_enum() {
|
|||||||
Error::Message("invalid length 1, expected tuple of 2 elements".to_owned()),
|
Error::Message("invalid length 1, expected tuple of 2 elements".to_owned()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
#[serde(into="EnumToU32", from="EnumToU32")]
|
||||||
|
struct StructFromEnum(Option<u32>);
|
||||||
|
|
||||||
|
impl Into<EnumToU32> for StructFromEnum {
|
||||||
|
fn into(self) -> EnumToU32 {
|
||||||
|
match self {
|
||||||
|
StructFromEnum(v) => v.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EnumToU32> for StructFromEnum {
|
||||||
|
fn from(v: EnumToU32) -> Self {
|
||||||
|
StructFromEnum(v.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
#[serde(into="Option<u32>", from="Option<u32>")]
|
||||||
|
enum EnumToU32 {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Three,
|
||||||
|
Four,
|
||||||
|
Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Option<u32>> for EnumToU32 {
|
||||||
|
fn into(self) -> Option<u32> {
|
||||||
|
match self {
|
||||||
|
EnumToU32::One => Some(1),
|
||||||
|
EnumToU32::Two => Some(2),
|
||||||
|
EnumToU32::Three => Some(3),
|
||||||
|
EnumToU32::Four => Some(4),
|
||||||
|
EnumToU32::Nothing => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Option<u32>> for EnumToU32 {
|
||||||
|
fn from(v: Option<u32>) -> Self {
|
||||||
|
match v {
|
||||||
|
Some(1) => EnumToU32::One,
|
||||||
|
Some(2) => EnumToU32::Two,
|
||||||
|
Some(3) => EnumToU32::Three,
|
||||||
|
Some(4) => EnumToU32::Four,
|
||||||
|
_ => EnumToU32::Nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_into_traits() {
|
||||||
|
assert_ser_tokens::<EnumToU32>(&EnumToU32::One,
|
||||||
|
&[Token::Option(true),
|
||||||
|
Token::U32(1)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_ser_tokens::<EnumToU32>(&EnumToU32::Nothing,
|
||||||
|
&[Token::Option(false)]
|
||||||
|
);
|
||||||
|
assert_de_tokens::<EnumToU32>(&EnumToU32::Two,
|
||||||
|
&[Token::Option(true),
|
||||||
|
Token::U32(2)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_ser_tokens::<StructFromEnum>(&StructFromEnum(Some(5)),
|
||||||
|
&[Token::Option(false)]
|
||||||
|
);
|
||||||
|
assert_ser_tokens::<StructFromEnum>(&StructFromEnum(None),
|
||||||
|
&[Token::Option(false)]
|
||||||
|
);
|
||||||
|
assert_de_tokens::<StructFromEnum>(&StructFromEnum(Some(2)),
|
||||||
|
&[Token::Option(true),
|
||||||
|
Token::U32(2)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user