Crate derive_serialize_into [−] [src]
Derive Serialize
and Deserialize
for validating wrapper types
This crate provides several custom derives that provide implementations of
serde's Serialize
and Deserialize
traits for wrapper types, as well as
Deserialize
implementations that perform some validation.
Sometimes you have a single-field type
struct Contact { email: String, }
which you want to serialize and deserialize as a string instead of a struct, e.g. you want its
JSON representation to just be ""user@domain.com"
" instead of
"{ "email": "user@domain.com" }
". You can derive Serialize
and Deserialize
implementations
for that purpose, as well as Into
and From
implementations to convert between String
and
Contact
.
Another example is a validated wrapper type like
struct Email(String);
that should never be instantianted with a string that doesn't represent a valid email address.
If you implement Email: TryFrom<String>
using the
try_from crate, such that conversion fails for invalid
addresses, the derived Deserialize
will also fail if the string is in the wrong format.
Custom derives for From
and TryFrom
are also provided for some common use cases.
Supported derive attributes
#[derive(SerializeInto)]
On structures T
, given an impl Into<S> for &T
, this creates an impl Serialize for T
which
first converts to S
and then serializes it.
You can optionally specify S
with #[serialize_into(S)]
. If you don't, T
must be a
structure with a single field, and a reference to that field's type will be used as S
.
Together with #[derive(IntoInner)]
, this makes it easy to serialize structs like
struct Foo(S)
or struct Foo { bar: S }
as if they were type S
.
#[derive(DeserializeFrom)]
On structures T
, given an impl Into<T> for S
, this creates an
impl<'de> Deserialize<'de> for T
which first deserializes to S
and then converts to T
.
You can optionally specify S
with #[deserialize_from(S)]
. If you don't, T
must be a
structure with a single field, and that field's type will be used as S
.
Together with #[derive(FromInner)]
, this makes it easy to deserialize structs like
struct Foo(S)
or struct Foo { bar: S }
as if they were just S
.
#[derive(DeserializeTryFrom)]
On structures T
, given an impl TryInto<T> for S
, this creates an
impl<'de> Deserialize<'de> for T
which first deserializes to S
and then tries to convert to
T
. A failure to convert will cause deserialization to fail. The TryInto
implementation's
error type must implement Display
and will be converted to a custom deserialization error.
You can optionally specify S
with #[deserialize_from(S)]
. If you don't, T
must be a
structure with a single field, and that field's type will be used as S
.
Together with #[derive(TryFromInner)]
, this allows deserializing structs like
struct Foo(S)
or struct Foo { bar: S }
as if they were just S
, but with additional
validation. For deserialized values of S
that don't validate, deserialization into T
will
fail.
#[derive(IntoInner)]
On structures T
with a single field of type S
, this creates an impl From<&T> for &S
that
returns a reference to the field.
#[derive(FromInner)]
On structures T
with a single field of type S
, this creates an impl From<S> for T
.
#[derive(TryFromInner)]
On structures T
with a single field of type S
, this creates an impl TryFrom<S> for T
that
applies some validation to S
. The error type will be &'static str
.
You can specify the validation criterion as a function fn check(&S) -> bool
with
#[try_from_inner = "check"]
to make try_from
fail if check
returns false
.
Or, if S
is String
, add an attribute #[try_from_inner_regex = "..."]
to allow
only values that contain a match for the given regular expression "..."
. You need to use the
crates lazy_static and
regex for this to work. To enforce that the regex matches
the whole string, remember to start it with ^
and end it with $
!
Example: simple wrapper types
#[macro_use] extern crate derive_serialize_into; extern crate serde; extern crate serde_json; #[derive(DeserializeFrom, FromInner, IntoInner, SerializeInto, Debug, Eq, PartialEq)] struct Seconds(i64); #[derive(DeserializeFrom, FromInner, IntoInner, SerializeInto, Debug, Eq, PartialEq)] struct Days { number: i64, } fn main() { assert_eq!(Seconds(5), serde_json::from_str("5").unwrap()); assert_eq!("5", &serde_json::to_string(&Seconds(5)).unwrap()); assert!(serde_json::from_str::<Seconds>("nan").is_err()); assert_eq!(Days { number: 5 }, serde_json::from_str("5").unwrap()); assert_eq!("5", &serde_json::to_string(&Days { number: 5 }).unwrap()); assert!(serde_json::from_str::<Days>("nan").is_err()); }
Example: validated email addresses
#[macro_use] extern crate derive_serialize_into; extern crate serde; extern crate serde_json; extern crate try_from; extern crate validator; #[derive(DeserializeTryFrom, IntoInner, SerializeInto, TryFromInner, Debug, Eq, PartialEq)] #[try_from_inner = "validator::validate_email"] struct Email(String); fn main() { let email_json = r#""user@domain.com""#; let email = Email("user@domain.com".to_string()); assert_eq!(email, serde_json::from_str(email_json).unwrap()); assert_eq!(email_json, &serde_json::to_string(&email).unwrap()); assert!(serde_json::from_str::<Email>(r#""missing_at_sign""#).is_err()); }
Example: validated phone numbers
#[macro_use] extern crate derive_serialize_into; #[macro_use] extern crate lazy_static; extern crate regex; extern crate try_from; use try_from::TryFrom; #[derive(TryFromInner)] #[try_from_inner_regex = "^\\+?[[:digit:]]+$"] struct Phone(String); fn main() { assert!(Phone::try_from("+12345".to_string()).is_ok()); assert!(Phone::try_from("12345".to_string()).is_ok()); assert!(Phone::try_from("12345XY".to_string()).is_err()); }
Example: custom TryFrom
and Into
#[macro_use] extern crate derive_serialize_into; extern crate serde; extern crate serde_json; extern crate try_from; #[derive(DeserializeTryFrom, SerializeInto, Debug, Eq, PartialEq)] #[serialize_into(String)] #[deserialize_from(String)] enum Money { Eur(u64), Usd(u64), } impl try_from::TryFrom<String> for Money { type Err = &'static str; fn try_from(s: String) -> Result<Money, &'static str> { let amt_str: String = s.chars().skip(1).collect::<String>(); let amt = amt_str.parse().map_err(|_| "invalid amount")?; match s.chars().next() { Some('€') => Ok(Money::Eur(amt)), Some('$') => Ok(Money::Usd(amt)), _ => Err("invalid currency"), } } } impl<'a> From<&'a Money> for String { fn from(money: &Money) -> String { match *money { Money::Eur(amt) => format!("€{}", amt), Money::Usd(amt) => format!("${}", amt), } } } fn main() { assert_eq!(Money::Eur(5), serde_json::from_str(r#""€5""#).unwrap()); assert_eq!(r#""$5""#, &serde_json::to_string(&Money::Usd(5)).unwrap()); assert!(serde_json::from_str::<Money>(r#""8""#).is_err()); }