Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion serde_derive/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,9 @@ fn borrowed_lifetimes(cont: &Container) -> BorrowedLifetimes {
}

fn deserialize_body(cont: &Container, params: &Parameters) -> Fragment {
if cont.attrs.transparent() {
if let Some(path) = cont.attrs.deserialize_with() {
deserialize_with(path)
} else if cont.attrs.transparent() {
deserialize_transparent(cont, params)
} else if let Some(type_from) = cont.attrs.type_from() {
deserialize_from(type_from)
Expand Down Expand Up @@ -336,6 +338,7 @@ fn deserialize_in_place_body(cont: &Container, params: &Parameters) -> Option<St
assert!(!params.has_getter);

if cont.attrs.transparent()
|| cont.attrs.deserialize_with().is_some()
|| cont.attrs.type_from().is_some()
|| cont.attrs.type_try_from().is_some()
|| cont.attrs.identifier().is_some()
Expand Down Expand Up @@ -440,6 +443,17 @@ fn deserialize_try_from(type_try_from: &syn::Type) -> Fragment {
}
}

fn deserialize_with(deserialize_with: &syn::ExprPath) -> Fragment {
// Attach type errors to the path in #[serialize(deserialize_with = "path")]
let deserializer_arg = quote!(__deserializer);
let wrapper_deserialize = quote_spanned! {deserialize_with.span()=>
#deserialize_with(#deserializer_arg)
};
quote_block! {
#wrapper_deserialize
}
}

enum TupleForm<'a> {
Tuple,
/// Contains a variant name
Expand Down
40 changes: 40 additions & 0 deletions serde_derive/src/internals/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ pub struct Container {
/// Error message generated when type can't be deserialized
expecting: Option<String>,
non_exhaustive: bool,
serialize_with: Option<syn::ExprPath>,
deserialize_with: Option<syn::ExprPath>,
}

/// Styles of representing an enum.
Expand Down Expand Up @@ -258,6 +260,8 @@ impl Container {
let mut serde_path = Attr::none(cx, CRATE);
let mut expecting = Attr::none(cx, EXPECTING);
let mut non_exhaustive = false;
let mut serialize_with = Attr::none(cx, SERIALIZE_WITH);
let mut deserialize_with = Attr::none(cx, DESERIALIZE_WITH);

for attr in &item.attrs {
if attr.path() != SERDE {
Expand Down Expand Up @@ -490,6 +494,32 @@ impl Container {
if let Some(s) = get_lit_str(cx, EXPECTING, &meta)? {
expecting.set(&meta.path, s.value());
}
} else if meta.path == WITH {
// #[serde(with = "...")]
if let Some(path) = parse_lit_into_expr_path(cx, WITH, &meta)? {
let mut ser_path = path.clone();
ser_path
.path
.segments
.push(Ident::new("serialize", ser_path.span()).into());
serialize_with.set(&meta.path, ser_path);
let mut de_path = path;
de_path
.path
.segments
.push(Ident::new("deserialize", de_path.span()).into());
deserialize_with.set(&meta.path, de_path);
}
} else if meta.path == SERIALIZE_WITH {
// #[serde(serialize_with = "...")]
if let Some(path) = parse_lit_into_expr_path(cx, SERIALIZE_WITH, &meta)? {
serialize_with.set(&meta.path, path);
}
} else if meta.path == DESERIALIZE_WITH {
// #[serde(deserialize_with = "...")]
if let Some(path) = parse_lit_into_expr_path(cx, DESERIALIZE_WITH, &meta)? {
deserialize_with.set(&meta.path, path);
}
} else {
let path = meta.path.to_token_stream().to_string().replace(' ', "");
return Err(
Expand Down Expand Up @@ -541,6 +571,8 @@ impl Container {
is_packed,
expecting: expecting.get(),
non_exhaustive,
serialize_with: serialize_with.get(),
deserialize_with: deserialize_with.get(),
}
}

Expand Down Expand Up @@ -617,6 +649,14 @@ impl Container {
pub fn non_exhaustive(&self) -> bool {
self.non_exhaustive
}

pub fn serialize_with(&self) -> Option<&syn::ExprPath> {
self.serialize_with.as_ref()
}

pub fn deserialize_with(&self) -> Option<&syn::ExprPath> {
self.deserialize_with.as_ref()
}
}

fn decide_tag(
Expand Down
16 changes: 15 additions & 1 deletion serde_derive/src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@ fn needs_serialize_bound(field: &attr::Field, variant: Option<&attr::Variant>) -
}

fn serialize_body(cont: &Container, params: &Parameters) -> Fragment {
if cont.attrs.transparent() {
if let Some(path) = cont.attrs.serialize_with() {
serialize_with(params, path)
} else if cont.attrs.transparent() {
serialize_transparent(cont, params)
} else if let Some(type_into) = cont.attrs.type_into() {
serialize_into(params, type_into)
Expand Down Expand Up @@ -219,6 +221,18 @@ fn serialize_into(params: &Parameters, type_into: &syn::Type) -> Fragment {
}
}

fn serialize_with(params: &Parameters, path: &syn::ExprPath) -> Fragment {
let self_var = &params.self_var;
// Attach type errors to the path in #[serialize(serialize_with = "path")]
let serializer_var = quote!(__serializer);
let wrapper_serialize = quote_spanned! {path.span()=>
#path(#self_var, #serializer_var)
};
quote_expr! {
#wrapper_serialize
}
}

fn serialize_unit_struct(cattrs: &attr::Container) -> Fragment {
let type_name = cattrs.name().serialize_name();

Expand Down
134 changes: 134 additions & 0 deletions test_suite/tests/test_annotations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,27 @@ trait DeserializeWith: Sized {
D: Deserializer<'de>;
}

mod with {
use super::{DeserializeWith, SerializeWith};
use serde::{Deserializer, Serializer};

pub fn serialize<T, S>(value: &T, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: SerializeWith,
{
T::serialize_with(value, ser)
}

pub fn deserialize<'de, D, T>(de: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: DeserializeWith,
{
T::deserialize_with(de)
}
}

impl MyDefault for i32 {
fn my_default() -> Self {
123
Expand Down Expand Up @@ -1158,6 +1179,119 @@ fn test_serialize_with_enum() {
);
}

macro_rules! bool_container {
($name:ident, $container:meta) => {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[$container]
enum $name {
True,
False,
}

impl SerializeWith for $name {
fn serialize_with<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let boolean = match self {
$name::True => true,
$name::False => false,
};
boolean.serialize(ser)
}
}

impl DeserializeWith for $name {
fn deserialize_with<'de, D>(de: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let b = bool::deserialize(de)?;
match b {
true => Ok($name::True),
false => Ok($name::False),
}
}
}
};
}

macro_rules! int_container {
($name:ident, $container:meta) => {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[$container]
struct $name {
field: i32,
}

impl SerializeWith for $name {
fn serialize_with<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.field.to_string().serialize(ser)
}
}

impl DeserializeWith for $name {
fn deserialize_with<'de, D>(de: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(de)?;
let field = s.parse().unwrap();
Ok(Self { field })
}
}
};
}

#[test]
fn test_container_with_enum() {
bool_container!(
BoolSerializeWith,
serde(serialize_with = "SerializeWith::serialize_with")
);
assert_ser_tokens(&BoolSerializeWith::True, &[Token::Bool(true)]);
assert_ser_tokens(&BoolSerializeWith::False, &[Token::Bool(false)]);

bool_container!(
BoolDeserializeWith,
serde(deserialize_with = "DeserializeWith::deserialize_with")
);
assert_de_tokens(&BoolDeserializeWith::True, &[Token::Bool(true)]);
assert_de_tokens(&BoolDeserializeWith::False, &[Token::Bool(false)]);

bool_container!(BoolWith, serde(with = "with"));
assert_ser_tokens(&BoolWith::True, &[Token::Bool(true)]);
assert_ser_tokens(&BoolWith::False, &[Token::Bool(false)]);
assert_de_tokens(&BoolWith::True, &[Token::Bool(true)]);
assert_de_tokens(&BoolWith::False, &[Token::Bool(false)]);
}

#[test]
fn test_container_with_struct() {
int_container!(
IntS,
serde(serialize_with = "SerializeWith::serialize_with")
);
assert_ser_tokens(&IntS { field: 42 }, &[Token::Str("42")]);
assert_ser_tokens(&IntS { field: 123 }, &[Token::Str("123")]);

int_container!(
IntD,
serde(deserialize_with = "DeserializeWith::deserialize_with")
);
assert_de_tokens(&IntD { field: 42 }, &[Token::Str("42")]);
assert_de_tokens(&IntD { field: 123 }, &[Token::Str("123")]);

int_container!(IntW, serde(with = "with"));
assert_ser_tokens(&IntW { field: 42 }, &[Token::Str("42")]);
assert_ser_tokens(&IntW { field: 123 }, &[Token::Str("123")]);
assert_de_tokens(&IntW { field: 42 }, &[Token::Str("42")]);
assert_de_tokens(&IntW { field: 123 }, &[Token::Str("123")]);
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
enum WithVariant {
#[serde(serialize_with = "serialize_unit_variant_as_i8")]
Expand Down
26 changes: 26 additions & 0 deletions test_suite/tests/ui/with-container/incorrect_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use serde_derive::{Deserialize, Serialize};

mod w {
use serde::{Deserializer, Serializer};

pub fn deserialize<'de, D: Deserializer<'de>>(_: D) -> Result<(), D::Error> {
unimplemented!()
}
pub fn serialize<T, S: Serializer>(_: S) -> Result<S::Ok, S::Error> {
unimplemented!()
}
}

#[derive(Serialize, Deserialize)]
#[serde(with = "w")]
struct W(u8);

#[derive(Serialize, Deserialize)]
#[serde(serialize_with = "w::serialize")]
struct S(u8);

#[derive(Serialize, Deserialize)]
#[serde(deserialize_with = "w::deserialize")]
struct D(u8);

fn main() {}
Loading