diff --git a/serde/src/de/impls.rs b/serde/src/de/impls.rs index 70196573..9b68ebf7 100644 --- a/serde/src/de/impls.rs +++ b/serde/src/de/impls.rs @@ -887,6 +887,98 @@ macro_rules! parse_ip_impl { } } +macro_rules! deserialize_enum { + ($name: ident $name_kind: ident ( $($variant: ident; $bytes: expr; $index: expr),* ) $deserializer: expr) => { + enum $name_kind { + $( $variant ),* + } + + static VARIANTS: &'static [&'static str] = &[ $( stringify!($variant) ),*]; + + impl<'de> Deserialize<'de> for $name_kind { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct KindVisitor; + + impl<'de> Visitor<'de> for KindVisitor { + type Value = $name_kind; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("`V4` or `V6`") + } + + fn visit_u32(self, value: u32) -> Result + where + E: Error, + { + match value { + $( + $index => Ok($name_kind :: $variant), + )* + _ => Err(Error::invalid_value(Unexpected::Unsigned(value as u64), &self),), + } + } + + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + match value { + $( + stringify!($variant) => Ok($name_kind :: $variant), + )* + _ => Err(Error::unknown_variant(value, VARIANTS)), + } + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + E: Error, + { + match value { + $( + $bytes => Ok($name_kind :: $variant), + )* + _ => { + match str::from_utf8(value) { + Ok(value) => Err(Error::unknown_variant(value, VARIANTS)), + Err(_) => Err(Error::invalid_value(Unexpected::Bytes(value), &self)), + } + } + } + } + } + + deserializer.deserialize_identifier(KindVisitor) + } + } + + struct EnumVisitor; + impl<'de> Visitor<'de> for EnumVisitor { + type Value = $name; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(concat!("a ", stringify!($name))) + } + + + fn visit_enum(self, data: A) -> Result + where + A: EnumAccess<'de>, + { + match try!(data.variant()) { + $( + ($name_kind :: $variant, v) => v.newtype_variant().map($name :: $variant), + )* + } + } + } + $deserializer.deserialize_enum(stringify!($name), VARIANTS, EnumVisitor) + } +} + #[cfg(feature = "std")] impl<'de> Deserialize<'de> for net::IpAddr { fn deserialize(deserializer: D) -> Result @@ -897,28 +989,10 @@ impl<'de> Deserialize<'de> for net::IpAddr { let s = try!(String::deserialize(deserializer)); s.parse().map_err(Error::custom) } else { - struct EnumVisitor; - impl<'de> Visitor<'de> for EnumVisitor { - type Value = net::IpAddr; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a IpAddr") - } - - - fn visit_enum(self, data: A) -> Result - where - A: EnumAccess<'de>, - { - match try!(data.variant()) { - (0_u32, v) => v.newtype_variant().map(net::IpAddr::V4), - (1_u32, v) => v.newtype_variant().map(net::IpAddr::V6), - (_, _) => Err(Error::custom("Invalid IpAddr variant")), - } - } + use self::net::IpAddr; + deserialize_enum!{ + IpAddr IpAddrKind (V4; b"V4"; 0, V6; b"V6"; 1) deserializer } - const VARIANTS: &'static [&'static str] = &["V4", "V6"]; - deserializer.deserialize_enum("IpAddr", VARIANTS, EnumVisitor) } } } @@ -949,7 +1023,22 @@ macro_rules! parse_socket_impl { } #[cfg(feature = "std")] -parse_socket_impl!(net::SocketAddr, net::SocketAddr::new); +impl<'de> Deserialize<'de> for net::SocketAddr { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + let s = try!(String::deserialize(deserializer)); + s.parse().map_err(Error::custom) + } else { + use self::net::SocketAddr; + deserialize_enum!{ + SocketAddr SocketAddrKind (V4; b"V4"; 0, V6; b"V6"; 1) deserializer + } + } + } +} #[cfg(feature = "std")] parse_socket_impl!(net::SocketAddrV4, net::SocketAddrV4::new); diff --git a/serde_test/src/assert.rs b/serde_test/src/assert.rs index 31eb622f..b95cb739 100644 --- a/serde_test/src/assert.rs +++ b/serde_test/src/assert.rs @@ -47,8 +47,18 @@ pub fn assert_tokens<'de, T>(value: &T, tokens: &'de [Token]) where T: Serialize + Deserialize<'de> + PartialEq + Debug, { - assert_ser_tokens(value, tokens); - assert_de_tokens(value, tokens); + assert_tokens_readable(value, tokens, true); +} + +/// Runs both `assert_ser_tokens` and `assert_de_tokens`. +/// +/// See: `assert_tokens` +pub fn assert_tokens_readable<'de, T>(value: &T, tokens: &'de [Token], human_readable: bool) +where + T: Serialize + Deserialize<'de> + PartialEq + Debug, +{ + assert_ser_tokens_readable(value, tokens, human_readable); + assert_de_tokens_readable(value, tokens, human_readable); } /// Asserts that `value` serializes to the given `tokens`. diff --git a/serde_test/src/lib.rs b/serde_test/src/lib.rs index 0ccf578a..b7c4ae92 100644 --- a/serde_test/src/lib.rs +++ b/serde_test/src/lib.rs @@ -168,7 +168,7 @@ mod token; mod assert; pub use token::Token; -pub use assert::{assert_tokens, assert_ser_tokens, assert_ser_tokens_error, assert_ser_tokens_readable, +pub use assert::{assert_tokens, assert_tokens_readable, assert_ser_tokens, assert_ser_tokens_error, assert_ser_tokens_readable, assert_de_tokens, assert_de_tokens_error, assert_de_tokens_readable}; // Not public API. diff --git a/test_suite/tests/test_de.rs b/test_suite/tests/test_de.rs index ec0100bd..5318fd88 100644 --- a/test_suite/tests/test_de.rs +++ b/test_suite/tests/test_de.rs @@ -781,9 +781,9 @@ declare_non_human_readable_tests!{ } test_non_human_readable_net_socketaddr { net::SocketAddr::from((*b"1234567890123456", 1234)) => &seq![ + Token::NewtypeVariant { name: "SocketAddr", variant: "V6" }, + Token::Tuple { len: 2 }, - Token::Enum { name: "IpAddr" }, - Token::U32(1), Token::Tuple { len: 16 }, seq b"1234567890123456".iter().map(|&b| Token::U8(b)), @@ -793,9 +793,9 @@ declare_non_human_readable_tests!{ Token::TupleEnd ], net::SocketAddr::from((*b"1234", 1234)) => &seq![ + Token::NewtypeVariant { name: "SocketAddr", variant: "V4" }, + Token::Tuple { len: 2 }, - Token::Enum { name: "IpAddr" }, - Token::U32(0), Token::Tuple { len: 4 }, seq b"1234".iter().map(|&b| Token::U8(b)), diff --git a/test_suite/tests/test_roundtrip.rs b/test_suite/tests/test_roundtrip.rs new file mode 100644 index 00000000..eee7e83c --- /dev/null +++ b/test_suite/tests/test_roundtrip.rs @@ -0,0 +1,45 @@ +extern crate serde_test; +use self::serde_test::{Token, assert_tokens_readable}; + +use std::net; + +#[macro_use] +#[allow(unused_macros)] +mod macros; + +#[test] +fn ip_addr_roundtrip() { + + assert_tokens_readable( + &net::IpAddr::from(*b"1234"), + &seq![ + Token::NewtypeVariant { name: "IpAddr", variant: "V4" }, + + Token::Tuple { len: 4 }, + seq b"1234".iter().map(|&b| Token::U8(b)), + Token::TupleEnd, + ], + false, + ); +} + +#[test] +fn socked_addr_roundtrip() { + + assert_tokens_readable( + &net::SocketAddr::from((*b"1234567890123456", 1234)), + &seq![ + Token::NewtypeVariant { name: "SocketAddr", variant: "V6" }, + + Token::Tuple { len: 2 }, + + Token::Tuple { len: 16 }, + seq b"1234567890123456".iter().map(|&b| Token::U8(b)), + Token::TupleEnd, + + Token::U16(1234), + Token::TupleEnd, + ], + false, + ); +}