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