diff --git a/serde_codegen/src/attr.rs b/serde_codegen/src/attr.rs index b440af7e..1ef63530 100644 --- a/serde_codegen/src/attr.rs +++ b/serde_codegen/src/attr.rs @@ -170,6 +170,7 @@ pub struct FieldAttrs { skip_serializing_field_if_empty: bool, skip_serializing_field_if_none: bool, default_expr_if_missing: Option>, + serialize_with: Option>, } impl FieldAttrs { @@ -194,6 +195,7 @@ impl FieldAttrs { skip_serializing_field_if_empty: false, skip_serializing_field_if_none: false, default_expr_if_missing: None, + serialize_with: None, }; for meta_items in field.node.attrs.iter().filter_map(get_serde_meta_items) { @@ -257,6 +259,18 @@ impl FieldAttrs { field_attrs.skip_serializing_field_if_empty = true; } + // Parse `#[serde(serialize_with="...")]` + ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"serialize_with" => { + let expr = wrap_serialize_with( + cx, + container_ty, + generics, + try!(parse_lit_into_expr(cx, name, lit)), + ); + + field_attrs.serialize_with = Some(expr); + } + _ => { cx.span_err( meta_item.span, @@ -324,6 +338,10 @@ impl FieldAttrs { pub fn skip_serializing_field_if_none(&self) -> bool { self.skip_serializing_field_if_none } + + pub fn serialize_with(&self) -> Option<&P> { + self.serialize_with.as_ref() + } } @@ -445,3 +463,55 @@ fn wrap_skip_serializing(cx: &ExtCtxt, self.value.__serde_should_skip_serializing() }) } + +/// This function wraps the expression in `#[serde(serialize_with="...")]` in a trait to +/// prevent it from accessing the internal `Serialize` state. +fn wrap_serialize_with(cx: &ExtCtxt, + container_ty: &P, + generics: &ast::Generics, + expr: P) -> P { + let where_clause = &generics.where_clause; + + quote_expr!(cx, { + trait __SerdeSerializeWith { + fn __serde_serialize_with(&self, serializer: &mut S) -> Result<(), S::Error> + where S: ::serde::ser::Serializer; + } + + impl<'a, T> __SerdeSerializeWith for &'a T + where T: 'a + __SerdeSerializeWith, + { + fn __serde_serialize_with(&self, serializer: &mut S) -> Result<(), S::Error> + where S: ::serde::ser::Serializer + { + (**self).__serde_serialize_with(serializer) + } + } + + impl $generics __SerdeSerializeWith for $container_ty $where_clause { + fn __serde_serialize_with(&self, serializer: &mut S) -> Result<(), S::Error> + where S: ::serde::ser::Serializer + { + $expr + } + } + + struct __SerdeSerializeWithStruct<'a, T: 'a> { + value: &'a T, + } + + impl<'a, T> ::serde::ser::Serialize for __SerdeSerializeWithStruct<'a, T> + where T: 'a + __SerdeSerializeWith + { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: ::serde::ser::Serializer + { + self.value.__serde_serialize_with(serializer) + } + } + + __SerdeSerializeWithStruct { + value: &self.value, + } + }) +} diff --git a/serde_codegen/src/ser.rs b/serde_codegen/src/ser.rs index a604d298..fdf42883 100644 --- a/serde_codegen/src/ser.rs +++ b/serde_codegen/src/ser.rs @@ -672,8 +672,13 @@ fn serialize_struct_visitor( } }; + let field_expr = match field_attr.serialize_with() { + Some(expr) => expr.clone(), + None => quote_expr!(cx, &self.value.$name), + }; + let expr = quote_expr!(cx, - serializer.$serializer_method($key_expr, &self.value.$name) + serializer.$serializer_method($key_expr, $field_expr) ); quote_arm!(cx, diff --git a/serde_tests/tests/test_annotations.rs b/serde_tests/tests/test_annotations.rs index 831c32cd..bd640a62 100644 --- a/serde_tests/tests/test_annotations.rs +++ b/serde_tests/tests/test_annotations.rs @@ -1,3 +1,6 @@ +use std::default::Default; +use serde::{Serialize, Serializer}; + use token::{ Error, Token, @@ -11,12 +14,25 @@ trait Trait { fn my_default() -> Self; fn should_skip(&self) -> bool; + + fn serialize_with(&self, ser: &mut S) -> Result<(), S::Error> + where S: Serializer; } impl Trait for i32 { fn my_default() -> Self { 123 } fn should_skip(&self) -> bool { *self == 123 } + + fn serialize_with(&self, ser: &mut S) -> Result<(), S::Error> + where S: Serializer + { + if *self == 123 { + true.serialize(ser) + } else { + false.serialize(ser) + } + } } #[derive(Debug, PartialEq, Serialize, Deserialize)] @@ -502,3 +518,107 @@ fn test_skip_serializing_enum() { ] ); } + +#[derive(Debug, PartialEq, Serialize)] +struct SerializeWithStruct<'a, B> where B: Trait { + a: &'a i8, + #[serde(serialize_with="self.b.serialize_with(serializer)")] + b: B, +} + +#[test] +fn test_serialize_with_struct() { + let a = 1; + assert_ser_tokens( + &SerializeWithStruct { + a: &a, + b: 2, + }, + &[ + Token::StructStart("SerializeWithStruct", Some(2)), + + Token::StructSep, + Token::Str("a"), + Token::I8(1), + + Token::StructSep, + Token::Str("b"), + Token::Bool(false), + + Token::StructEnd, + ] + ); + + assert_ser_tokens( + &SerializeWithStruct { + a: &a, + b: 123, + }, + &[ + Token::StructStart("SerializeWithStruct", Some(2)), + + Token::StructSep, + Token::Str("a"), + Token::I8(1), + + Token::StructSep, + Token::Str("b"), + Token::Bool(true), + + Token::StructEnd, + ] + ); +} + +#[derive(Debug, PartialEq, Serialize)] +enum SerializeWithEnum<'a, B> where B: Trait { + Struct { + a: &'a i8, + #[serde(serialize_with="self.b.serialize_with(serializer)")] + b: B, + } +} + +#[test] +fn test_serialize_with_enum() { + let a = 1; + assert_ser_tokens( + &SerializeWithEnum::Struct { + a: &a, + b: 2, + }, + &[ + Token::EnumMapStart("SerializeWithEnum", "Struct", Some(2)), + + Token::EnumMapSep, + Token::Str("a"), + Token::I8(1), + + Token::EnumMapSep, + Token::Str("b"), + Token::Bool(false), + + Token::EnumMapEnd, + ] + ); + + assert_ser_tokens( + &SerializeWithEnum::Struct { + a: &a, + b: 123, + }, + &[ + Token::EnumMapStart("SerializeWithEnum", "Struct", Some(2)), + + Token::EnumMapSep, + Token::Str("a"), + Token::I8(1), + + Token::EnumMapSep, + Token::Str("b"), + Token::Bool(true), + + Token::EnumMapEnd, + ] + ); +}