Macro fourleaf::fourleaf_retrofit [] [src]

macro_rules! fourleaf_retrofit {
    (@_STRUCT_BODY_SER ($dst:expr) {
        $([$tag:expr] $accessor:expr,)*
        $((*) $del_accessor:expr,)*
        $((?) $unk_accessor:expr,)*
    }) => { ... };
    (@_STRUCT_ELEMENT_SER ($dst:expr, $tag:expr) {
        $((*) $del_accessor:expr,)*
    }) => { ... };
    (@_STRUCT_ELEMENT_SER ($dst:expr, $tag:expr) {
        $((?) $unk_accessor:expr,)*
    }) => { ... };
    (@_STRUCT_BODY_DESER ($stream:expr, $context:expr) {
        $([$tag:expr] $field_name:ident: $field_type:ty,)*
        $((*) $del_field_name:ident: $del_field_type:ty,)*
        $((?) $unk_field_name:ident: $unk_field_type:ty,)*
        { $constructor:expr }
    }) => { ... };
    (@_STRUCT_ELEMENT_DESER ($context:expr, $field:expr) {
        $((*) $del_field_name:ident: $del_field_type:ty,)*
        { $constructor:expr }
    }) => { ... };
    (@_STRUCT_ELEMENT_DESER ($context:expr, $field:expr) {
        $((?) $unk_field_name:ident: $unk_field_type:ty,)*
        { $constructor:expr }
    }) => { ... };
    (@_DESER_BOILERPLATE) => { ... };
    ($form:tt $self_ty:ty : {} $impl_deser:tt $body:tt) => { ... };
    ($form:tt $self_ty:ty : $impl_ser:tt {} $body:tt) => { ... };
    (struct $self_ty:ty : {$($impl_ser:tt)*} {$($impl_deser:tt)*} {
        |$context:ident, $this:ident|
        $(
            [$tag:expr] $field_name:ident: $field_type:ty = $accessor:expr,
        )*
        $(
            ($special:tt) $s_field_name:ident: $s_field_type:ty =
                $s_accessor:expr,
        )*
        { $constructor:expr } }) => { ... };
    (enum $self_ty:ty : {$($impl_ser:tt)*} {$($impl_deser:tt)*} {
        |$context:ident|
    $(
        [$discriminant:expr] $disc_pat:pat => {
            $([$tag:expr] $field_name:ident: $field_type:ty = $accessor:expr,)*
            $(($special:tt) $s_field_name:ident: $s_field_type:ty =
                  $s_accessor:expr,)*
            { $constructor:expr }
        }
    ),* $(,
        (?) $ca_disc_pat:pat => {
            (=) $ca_disc_field_name:ident: u64 = $ca_disc_field_accessor:expr,
            $([$ca_tag:expr] $ca_field_name:ident: $ca_field_type:ty =
                  $ca_accessor:expr,)*
            $(($ca_special:tt) $ca_s_field_name:ident: $ca_s_field_type:ty =
                  $ca_s_accessor:expr,)*
            { $ca_constructor:expr }
        }
    ),* $(,)* }) => { ... };
}

Retrofits fourleaf support onto arbitrary types.

That is, it generates matching Serialize and Deserialize implementations for them. Note that this documentation skims over fourleaf concepts in general; you should read the crate documentation for an introduction to these and more detail.

This is currently a bit verbose. At some point in the future there may be something nicer.

The basic structure of the syntax for this macro is

fourleaf_retrofit!(struct /* or `enum` */ SomeType :
                   { /* Serialize impl */ } { /* Deserialize impl */ } {
    /* body... */
 });

In many cases, the "Serialize impl" and "Deserialize impl" sections can be left blank (i.e., empty braces) and will be filled in automatically.

Struct bodies

The body of a structure begins with the name of the Context variable and the variable holding a reference to the struct being serialised written in pipes (i.e., like a closure), followed by any number of field declarations, followed by a braced expression to construct the type. Each field is a tag number in brackets, the logical field name, a colon, the deserialised type of the field, an =, and an expression to read the value of the field at serialisation time. This is clearer with an example:

#[macro_use] extern crate fourleaf;

struct SimpleStruct {
  foo: u32,
  bar: u64,
}

fourleaf_retrofit!(struct SimpleStruct : {} {} {
  |_context, this|
  [1] foo: u32 = this.foo,
  [2] bar: u64 = this.bar,
  { Ok(SimpleStruct { foo: foo, bar: bar }) }
});

During serialisation, this is available to the accessors (the right-hand side of the =), and each accessor is evaluated to determine the serialised value for each field.

During deserialisation, _context is available to the constructor expression, as well as every logical field name, which are bound to exactly the types specified. The constructor expression can perform arbitrary validation on the inputs (and, e.g., return Err if validation fails, using the context to indicate the location of the problem).

This is all somewhat repetitive for simple structs like this, but is necessary for dealing with structs with non-public members. For example:

#[macro_use] extern crate fourleaf;

mod some_other_module {
  pub struct TheStruct {
    foo: u32,
    bar: u64,
  }

  impl TheStruct {
    pub fn new(foo: u32, bar: u64) -> Self {
      TheStruct { foo: foo, bar: bar }
    }
    pub fn foo(&self) -> u32 { self.foo }
    pub fn bar(&self) -> u64 { self.bar }
  }
}

use some_other_module::TheStruct;
fourleaf_retrofit!(struct TheStruct : {} {} {
  |_context, this|
  [1] foo: u32 = this.foo(),
  [2] bar: u64 = this.bar(),
  { Ok(TheStruct::new(foo, bar)) }
});

Beware that there is nothing constraining the serialised type of each field to correspond to the deserialised field, since in many cases they won't (e.g., String on construction, but &str via accessor). The macro will not prevent declaring a struct whose serialised format is incompatible with its deserialiser.

Field delegation

Instead of listing multiple tagged fields, you can instead have a single field with the pseudo-tag (*). This will cause the implementation to delegate to that field for serialisation and deserialisation.

#[macro_use] extern crate fourleaf;

struct Wrapper(Vec<u8>);

fourleaf_retrofit!(struct Wrapper : {} {} {
  |_context, this|
  (*) inner: Vec<u8> = &this.0,
  { Ok(Wrapper(inner)) }
});

Note that this is not perfectly transparent delegation. The inner value will always be serialised as a single element, which will cause collections to be wrapped in a struct element, for example.

Preserving unknown fields

A field with the pseudo-tag (?) will cause all unknown fields on deserialisation to be passed to that field, and for that field's body to be concatenated with the container's body on serialisation. In conjunction with unknown::UnknownFields, this allows for making structures which preserve unknown fields. (That is, structures such that if deserialised and deserialised will have all fields in the original, including those that the reader did not understand.)

#[macro_use] extern crate fourleaf;

use fourleaf::adapt::Copied;
use fourleaf::UnknownFields;

struct UnknownPreserving {
  foo: u32,
  bar: u64,
  unknown: UnknownFields<'static>,
}

fourleaf_retrofit!(struct UnknownPreserving : {} {} {
  |_context, this|
  [1] foo: u32 = this.foo,
  [2] bar: u64 = this.bar,
  (?) unknown: Copied<UnknownFields<'static>> = &this.unknown,
  { Ok(UnknownPreserving { foo: foo, bar: bar, unknown: unknown.0 }) }
});

Having an unknown field handler effectively suppresses the ignore_unknown_fields configuration; i.e., an unknown field will alway simply be added to the catch-all field instead of resulting in an error.

Enum bodies

An enum body begins with the name for the context variable in pipes. It is followed by a number of variants. Each variant is a numeric discriminant in brackets, a pattern matching that enum case, and a variant body. The variant body is essentially like a struct body without the context, this variable names at the front.

#[macro_use] extern crate fourleaf;

enum SimpleEnum {
  Foo(u32, u64),
  Bar(String),
}

fourleaf_retrofit!(enum SimpleEnum : {} {} {
  |_context|
  [1] SimpleEnum::Foo(a, b) => {
    [1] a: u32 = a,
    [2] b: u64 = b,
    { Ok(SimpleEnum::Foo(a, b)) }
  },
  [2] SimpleEnum::Bar(ref a) => {
    [1] a: String = a,
    { Ok(SimpleEnum::Bar(a)) }
  },
});

On serialisation , the value to be serialised is matched against each pattern. In the one that matches, the discriminant in the tag is written, and then the value for each field, using the accessor to obtain the value of the field. Note that unlike struct bodies, there is no this variable; instead, the accessors can use the variables that were bound in the pattern for that variant.

Serialisation works much the same way as for structs. The discriminant is matched to one of the numeric discriminants, then each field is deserialised, and then the constructor expression evaluated. Notice that the constructor expression is also responsible for providing the correct variant.

The (*) and (?) pseudo-tags are also available in variant bodies and behave the same way as for structs.

Preserving unknown variants

To crate enums that can represent unknown deserialised values and reserialise to the same thing, a (?) pseudo-discriminant similar to the (?) pseudo-tag is available. This must be the last variant, and will cause that variant to be deserialised from any input that does not match any declared discriminant.

The "catch-all" variant's body must begin with an extra field with the (=) pseudo-tag and with the u64 type; this field is used to store the discriminant that was read during deserialisation. This can be used in conjunction with the (?) pseudo-tag to make an enum which preserves all input.

#[macro_use] extern crate fourleaf;

use fourleaf::adapt::Copied;
use fourleaf::UnknownFields;

enum UnknownPreservingEnum {
  Foo(u32, UnknownFields<'static>),
  Bar(String, UnknownFields<'static>),
  Unknown(u64, UnknownFields<'static>),
}

fourleaf_retrofit!(enum UnknownPreservingEnum : {} {} {
  |_context|
  [1] UnknownPreservingEnum::Foo(a, ref unknown) => {
    [1] a: u32 = a,
    (?) unknown: Copied<UnknownFields<'static>> = unknown,
    { Ok(UnknownPreservingEnum::Foo(a, unknown.0)) }
  },
  [2] UnknownPreservingEnum::Bar(ref a, ref unknown) => {
    [1] a: String = a,
    (?) unknown: Copied<UnknownFields<'static>> = unknown,
    { Ok(UnknownPreservingEnum::Bar(a, unknown.0)) }
  },
  (?) UnknownPreservingEnum::Unknown(discriminant, ref fields) => {
    (=) discriminant: u64 = discriminant,
    [1] fields: Copied<UnknownFields<'static>> = fields,
    { Ok(UnknownPreservingEnum::Unknown(discriminant, fields.0)) }
  },
});

Use with types not in your crate

If you want to make a Serialize and Deserialize implementation for a type not in your crate, you will need to wrap it in a newtype due to the trait orphan rules. Fortunately, this is easy with this macro, though note that the struct/enum distinction applies to the item being wrapped. For example, if we wanted to serialise a custom Result type a way different from the default implementation:

#[macro_use] extern crate fourleaf;

struct MyResult(Result<u32,u64>);

// Need to use `enum` because we're serialising it as an enum, even though
// `MyResult` itself is a struct.
fourleaf_retrofit!(enum MyResult : {} {} {
  |_context|
  [1] MyResult(Ok(a)) => {
    [1] a: u32 = a,
    { Ok(MyResult(Ok(a))) }
  },
  [2] MyResult(Err(e)) => {
    [1] e: u64 = e,
    { Ok(MyResult(Err(e))) }
  },
});

Notice how the patterns effectively unwrap the newtype away, and the constructors add it back in. The pattern for structs is similar, in that you can simply add .0 in the accessors to go from the newtype to its contents.

The impl sections

The two empty brace pairs in the above examples are used to provide alternate impl declarations for Serialize and Deserialize. By default, they expand to approximately:

impl Serialize for TYPE
impl<R : Read, STYLE> Deserialize<R, STYLE> for TYPE

For many types, this is sufficient. However, if your type has special requirements, you may need to provide these manually. Note that whatever impl declaration is provided, it must have type parameters R : Read and STYLE somewhere.

Types with special requirements

If a type cannot implement Deserialize for every <R,STYLE> pair, a custom impl line will be needed for the second block. For example:

#[macro_use] extern crate fourleaf;

use std::io::Read;

use fourleaf::{Deserialize, UnknownFields};

struct MyStruct {
  foo: u32,
  unknown: UnknownFields<'static>,
}

fourleaf_retrofit!(struct MyStruct : {} {
  impl<R : Read, STYLE> Deserialize<R, STYLE>
  // `UnknownFields<'static>` cannot be deserialised from, eg,
  // `<&'a [u8], style::ZeroCopy>` unless `'a` is `'static`, so we
  // need this extra constraint on the implementation.
  //
  // The prior examples side-stepped this issue by deserialising
  // the field wrapped in `Copied`.
  for MyStruct where UnknownFields<'static>: Deserialize<R, STYLE>
} {
  |_context, this|
  [1] foo: u32 = this.foo,
  (?) unknown: UnknownFields<'static> = &this.unknown,
  { Ok(MyStruct { foo: foo, unknown: unknown }) }
});

Generic types

If the type in question has generic parameters, defining both blocks will be necessary so that those parameters are filled.

#[macro_use] extern crate fourleaf;

use std::borrow::Cow;
use std::io::Read;

use fourleaf::{Deserialize, Serialize};

enum MyEnum<'a, A, B> {
  A(A),
  B(B),
  C(Cow<'a, [u8]>),
}

fourleaf_retrofit!(enum MyEnum<'a, A, B> : {
  impl<'a, A : Serialize, B : Serialize> Serialize
  for MyEnum<'a, A, B>
} {
  impl<'a, R : Read, STYLE, A : Deserialize<R, STYLE>,
       B : Deserialize<R, STYLE>> Deserialize<R, STYLE>
  for MyEnum<'a, A, B> where Cow<'a, [u8]>: Deserialize<R, STYLE>
} {
  |_context|
  [1] MyEnum::A(ref a) => {
    [1] a: A = a,
    { Ok(MyEnum::A(a)) }
  },
  [2] MyEnum::B(ref b) => {
    [1] b: B = b,
    { Ok(MyEnum::B(b)) }
  },
  [3] MyEnum::C(ref c) => {
    [1] c: Cow<'a, [u8]> = c,
    { Ok(MyEnum::C(c)) }
  },
});