diff --git a/serde_macros/src/de.rs b/serde_macros/src/de.rs index 66fe052b..35853243 100644 --- a/serde_macros/src/de.rs +++ b/serde_macros/src/de.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use syntax::ast::{ Ident, MetaItem, @@ -353,7 +355,8 @@ fn deserialize_item_enum( cx, builder, enum_def.variants.iter() - .map(|variant| builder.expr().str(variant.node.name)) + .map(|variant| + field::FieldLit::Global(builder.expr().str(variant.node.name))) .collect() ); @@ -531,7 +534,7 @@ fn deserialize_struct_variant( fn deserialize_field_visitor( cx: &ExtCtxt, builder: &aster::AstBuilder, - field_exprs: Vec>, + field_exprs: Vec, ) -> Vec> { // Create the field names for the fields. let field_idents: Vec = (0 .. field_exprs.len()) @@ -548,14 +551,80 @@ fn deserialize_field_visitor( ) .build(); + + let fmts = field_exprs.iter() + .fold(HashSet::new(), |mut set, field_expr| + match field_expr { + &field::FieldLit::Format{ref formats, default: _} => { + for (fmt, _) in formats.iter() { + set.insert(fmt.clone()); + }; + set + }, + _ => set + }); + // Match arms to extract a field from a string - let field_arms: Vec<_> = field_idents.iter() - .zip(field_exprs.into_iter()) + let default_field_arms: Vec<_> = field_idents.iter() + .zip(field_exprs.iter()) .map(|(field_ident, field_expr)| { - quote_arm!(cx, $field_expr => { Ok(__Field::$field_ident) }) + match field_expr { + &field::FieldLit::Global(ref expr) => + quote_arm!(cx, $expr => { Ok(__Field::$field_ident) }), + &field::FieldLit::Format{formats: _, ref default} => + quote_arm!(cx, $default => { Ok(__Field::$field_ident)}) + } }) .collect(); + let body = if fmts.is_empty() { + quote_expr!(cx, + match value { + $default_field_arms, + _ => Err(::serde::de::Error::unknown_field_error(value)), + }) + } else { + let field_arms : Vec<_> = fmts.iter() + .map(|fmt| { + field_idents.iter() + .zip(field_exprs.iter()) + .map(|(field_ident, field_expr)| { + match field_expr { + &field::FieldLit::Global(ref expr) => + quote_arm!(cx, + $expr => { Ok(__Field::$field_ident) }), + &field::FieldLit::Format{ref formats, ref default} => { + let expr = formats.get(fmt).unwrap_or(default); + quote_arm!(cx, + $expr => { Ok(__Field::$field_ident) })}} + }) + .collect::>() + }) + .collect(); + + let fmt_matches : Vec<_> = fmts.iter() + .zip(field_arms.iter()) + .map(|(ref fmt, ref arms)| { + quote_arm!(cx, $fmt => { + match value { + $arms, + _ => { + Err(::serde::de::Error::unknown_field_error(value)) + } + }}) + }) + .collect(); + + quote_expr!(cx, + match D::fmt() { + $fmt_matches, + _ => match value { + $default_field_arms, + _ => Err(::serde::de::Error::unknown_field_error(value)), + } + }) + }; + vec![ field_enum, @@ -565,26 +634,30 @@ fn deserialize_field_visitor( fn deserialize(deserializer: &mut D) -> ::std::result::Result<__Field, D::Error> where D: ::serde::de::Deserializer, { - struct __FieldVisitor; + use std::marker::PhantomData; - impl ::serde::de::Visitor for __FieldVisitor { + struct __FieldVisitor { + phantom: PhantomData + } + + impl ::serde::de::Visitor for __FieldVisitor + where D: ::serde::de::Deserializer + { type Value = __Field; fn visit_str(&mut self, value: &str) -> ::std::result::Result<__Field, E> where E: ::serde::de::Error, { - match value { - $field_arms - _ => Err(::serde::de::Error::unknown_field_error(value)), - } + $body } } - deserializer.visit(__FieldVisitor) + deserializer.visit( + __FieldVisitor::{ phantom: PhantomData }) } } ).unwrap(), - ] + ] } fn deserialize_struct_visitor( @@ -596,7 +669,7 @@ fn deserialize_struct_visitor( let field_visitor = deserialize_field_visitor( cx, builder, - field::struct_field_strs(cx, builder, struct_def, field::Direction::Deserialize), + field::struct_field_strs(cx, builder, struct_def), ); let visit_map_expr = deserialize_map( @@ -639,11 +712,16 @@ fn deserialize_map( let extract_values: Vec> = field_names.iter() .zip(struct_def.fields.iter()) .map(|(field_name, field)| { - let rename = field::field_rename(field, &field::Direction::Deserialize); + let rename = field::field_rename(builder, field); let name_str = match (rename, field.node.kind) { - (Some(rename), _) => builder.expr().build_lit(P(rename.clone())), - (None, ast::NamedField(name, _)) => builder.expr().str(name), - (None, ast::UnnamedField(_)) => panic!("struct contains unnamed fields"), + (field::Rename::Global(rename), _) + => builder.expr().build_lit(P(rename.clone())), + (field::Rename::None, ast::NamedField(name, _)) + => builder.expr().str(name), + (field::Rename::None, ast::UnnamedField(_)) + => panic!("struct contains unnamed fields"), + (field::Rename::Format(renames), _) + => builder.expr().str("fixme"), }; let missing_expr = if field::default_value(field) { diff --git a/serde_macros/src/field.rs b/serde_macros/src/field.rs index c35e694b..95ae8d6b 100644 --- a/serde_macros/src/field.rs +++ b/serde_macros/src/field.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use syntax::ast; use syntax::attr; use syntax::ext::base::ExtCtxt; @@ -5,19 +7,17 @@ use syntax::ptr::P; use aster; -pub enum Direction { - Serialize, - Deserialize, +pub enum Rename<'a> { + None, + Global(&'a ast::Lit), + // Format(HashMap) + Format(HashMap, &'a ast::Lit>) } pub fn field_rename<'a>( + builder: &aster::AstBuilder, field: &'a ast::StructField, - direction: &Direction, -) -> Option<&'a ast::Lit> { - let dir_attr = match *direction { - Direction::Serialize => "rename_serialize", - Direction::Deserialize => "rename_deserialize", - }; +) -> Rename<'a> { field.node.attrs.iter() .find(|sa| { if let ast::MetaList(ref n, _) = sa.node.value.node { @@ -30,47 +30,99 @@ pub fn field_rename<'a>( if let ast::MetaList(_, ref vals) = sa.node.value.node { attr::mark_used(&sa); vals.iter().fold(None, |v, mi| { - if let ast::MetaNameValue(ref n, ref lit) = mi.node { - if n == &"rename" || n == &dir_attr { - Some(lit) - } else { - v - } - } else { - v + match mi.node { + ast::MetaNameValue(ref n, ref lit) => { + if n == &"rename" { + Some(Rename::Global(lit)) + } else { + v + } + }, + ast::MetaList(ref n, ref items) => { + if n == &"rename" { + let mut m = HashMap::new(); + m.extend( + items.iter() + .filter_map( + |item| + match item.node { + ast::MetaNameValue(ref n, ref lit) => + Some((// (n.to_owned(), lit) + builder.expr().str(n), + lit + )), + _ => None + })); + Some(Rename::Format(m)) + } else { + v + } + }, + _ => {v} } }) } else { None } }) + .unwrap_or(Rename::None) +} + +pub enum FieldLit { + Global(P), + Format{ + formats: HashMap, P>, + default: P, + } } pub fn struct_field_strs( cx: &ExtCtxt, builder: &aster::AstBuilder, struct_def: &ast::StructDef, - direction: Direction, -) -> Vec> { +) -> Vec { struct_def.fields.iter() .map(|field| { - match field_rename(field, &direction) { - Some(rename) => builder.expr().build_lit(P(rename.clone())), - None => { - match field.node.kind { - ast::NamedField(name, _) => { - builder.expr().str(name) - } - ast::UnnamedField(_) => { - cx.bug("struct has named and unnamed fields") - } + match field_rename(builder, field) { + Rename::Global(rename) => + FieldLit::Global( + builder.expr().build_lit(P(rename.clone()))), + Rename::Format(renames) => { + let mut res = HashMap::new(); + res.extend( + renames.into_iter() + .map(|(k,v)| + (k, builder.expr().build_lit(P(v.clone()))))); + FieldLit::Format{ + formats: res, + default: default_field(cx, builder, field.node.kind), } + }, + Rename::None => { + FieldLit::Global( + default_field(cx, builder, field.node.kind)) } } }) .collect() } +fn default_field( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + kind: ast::StructFieldKind, +) -> P { + match kind { + ast::NamedField(name, _) => { + builder.expr().str(name) + } + ast::UnnamedField(_) => { + cx.bug("struct has named and unnamed fields") + } + } +} + + pub fn default_value(field: &ast::StructField) -> bool { field.node.attrs.iter() .any(|sa| { diff --git a/serde_macros/src/ser.rs b/serde_macros/src/ser.rs index d9c58d02..be0896d7 100644 --- a/serde_macros/src/ser.rs +++ b/serde_macros/src/ser.rs @@ -13,7 +13,7 @@ use syntax::ptr::P; use aster; -use field::{Direction, struct_field_strs}; +use field::{FieldLit, struct_field_strs}; pub fn expand_derive_serialize( cx: &mut ExtCtxt, @@ -517,12 +517,29 @@ fn serialize_struct_visitor( { let len = struct_def.fields.len(); - let key_exprs = struct_field_strs(cx, builder, struct_def, Direction::Serialize); + let key_exprs = struct_field_strs(cx, builder, struct_def); - let arms: Vec = key_exprs.iter() + let arms: Vec = key_exprs.into_iter() .zip(value_exprs) .enumerate() - .map(|(i, (key_expr, value_expr))| { + .map(|(i, (field, value_expr))| { + let key_expr = match field { + FieldLit::Global(x) => x, + FieldLit::Format{formats, default} => { + let arms = formats.iter() + .map(|(fmt, lit)| { + quote_arm!(cx, $fmt => { $lit }) + }) + .collect::>(); + quote_expr!(cx, + { + match S::fmt() { + $arms, + _ => $default + } + }) + }, + }; quote_arm!(cx, $i => { self.state += 1; diff --git a/src/de/mod.rs b/src/de/mod.rs index b10f12c0..091adbc6 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -113,6 +113,10 @@ pub trait Deserializer { { self.visit(visitor) } + + fn fmt() -> &'static str { + "" + } } /////////////////////////////////////////////////////////////////////////////// diff --git a/src/json/de.rs b/src/json/de.rs index 532d46e3..dde68ed2 100644 --- a/src/json/de.rs +++ b/src/json/de.rs @@ -463,6 +463,11 @@ impl de::Deserializer for Deserializer Err(self.error(ErrorCode::ExpectedSomeValue)) } } + + #[inline] + fn fmt() -> &'static str { + "json" + } } struct SeqVisitor<'a, Iter: 'a + Iterator>> { diff --git a/src/json/ser.rs b/src/json/ser.rs index 824af96a..5537859e 100644 --- a/src/json/ser.rs +++ b/src/json/ser.rs @@ -256,6 +256,11 @@ impl ser::Serializer for Serializer try!(self.formatter.colon(&mut self.writer)); value.serialize(self) } + + #[inline] + fn fmt() -> &'static str { + "json" + } } pub trait Formatter { diff --git a/src/json/value.rs b/src/json/value.rs index 6d041343..c9b8afc0 100644 --- a/src/json/value.rs +++ b/src/json/value.rs @@ -571,6 +571,11 @@ impl ser::Serializer for Serializer { Ok(()) } + + #[inline] + fn fmt() -> &'static str { + "value" + } } pub struct Deserializer { @@ -677,6 +682,11 @@ impl de::Deserializer for Deserializer { None => Ok(value) } } + + #[inline] + fn fmt() -> &'static str { + "value" + } } struct SeqDeserializer<'a> { diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 5c317398..432d2535 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -179,6 +179,10 @@ pub trait Serializer { fn visit_map_elt(&mut self, key: K, value: V) -> Result<(), Self::Error> where K: Serialize, V: Serialize; + + fn fmt() -> &'static str { + "" + } } pub trait SeqVisitor { diff --git a/tests/test_annotations.rs b/tests/test_annotations.rs index 2bc9a336..5f3d37b6 100644 --- a/tests/test_annotations.rs +++ b/tests/test_annotations.rs @@ -21,9 +21,9 @@ struct Rename { } #[derive(Debug, PartialEq, Serialize, Deserialize)] -struct DirectionRename { +struct FormatRename { a1: i32, - #[serde(rename_serialize="a3", rename_deserialize="a4")] + #[serde(rename(xml= "a4", json="a5"))] a2: i32, } @@ -47,11 +47,11 @@ fn test_rename() { } #[test] -fn test_direction_rename() { - let value = DirectionRename { a1: 1, a2: 2 }; +fn test_format_rename() { + let value = FormatRename { a1: 1, a2: 2 }; let serialized_value = json::to_string(&value).unwrap(); - assert_eq!(serialized_value, "{\"a1\":1,\"a3\":2}"); + assert_eq!(serialized_value, "{\"a1\":1,\"a5\":2}"); - let deserialized_value = json::from_str("{\"a1\":1,\"a4\":2}").unwrap(); + let deserialized_value = json::from_str("{\"a1\":1,\"a5\":2}").unwrap(); assert_eq!(value, deserialized_value); } diff --git a/tests/test_json.rs b/tests/test_json.rs index ba45d974..bbead941 100644 --- a/tests/test_json.rs +++ b/tests/test_json.rs @@ -1024,7 +1024,7 @@ fn test_missing_field() { fn test_missing_renamed_field() { #[derive(Debug, PartialEq, Deserialize)] struct Foo { - #[serde(rename_deserialize="y")] + #[serde(rename="y")] x: Option, } @@ -1042,3 +1042,26 @@ fn test_missing_renamed_field() { ))).unwrap(); assert_eq!(value, Foo { x: Some(5) }); } + +#[test] +fn test_missing_fmt_renamed_field() { + #[derive(Debug, PartialEq, Deserialize)] + struct Foo { + #[serde(rename(json="y", value="z"))] + x: Option, + } + + let value: Foo = from_str("{}").unwrap(); + assert_eq!(value, Foo { x: None }); + + let value: Foo = from_str("{\"y\": 5}").unwrap(); + assert_eq!(value, Foo { x: Some(5) }); + + let value: Foo = from_value(Value::Object(treemap!())).unwrap(); + assert_eq!(value, Foo { x: None }); + + let value : Foo = from_value(Value::Object(treemap!( + "z".to_string() => Value::I64(5) + ))).unwrap(); + assert_eq!(value, Foo { x: Some(5) }); +}