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());
}