Attribute Macro revision::revisioned
source · #[revisioned]Expand description
Generates serialization and deserialization code as an implementation of
the Revisioned trait for structs and enums.
This procedural macro attribute currently analyses the struct field and enum variant revisions, and generates custom serializer and deserializer implementations for each version. In the future, this procedural macro will also automatically remove old struct fields entirely, reducing the memory size of the struct, and ensuring that field types can be changed.
This macro works by generating a single serializer implementation for the latest revision of a struct, and multiple deserializer implementations for each historical revision of a struct. There is no limit to the maximum number of revisions that are possible to be defined for a struct or enum.
§Revisioned requirements
Currently, all struct field values, and all enum variant fields need to
implement the Revisioned trait. This is already implemented for a number
of primitive and custom types. In addition, the Revisioned derive macro
can not be used with generics.
§Attribute annotations
To facilitate version tolerant serialization “history metadata” is attached
to the structure or enum. This is done by using the revision attribute for
each field. In the below example a new field is added to the structure
starting with version 2: #[revision(start = 2)]. The struct revision must
match the maximum computed revision of every struct field or enum variant.
use revision::revisioned;
#[derive(Debug)]
#[revisioned(revision = 2)]
struct Test {
a: u32,
#[revision(start = 2)]
b: u8,
}Multiple version annotations can be defined for a field, like for example:
#[revision(start = 2, end = 3)]. Field was added in structure version 2
and removed in version 3. The generated code will ensure that this field
will only be deserialized for version 2 of the structure.
§Supported field attributes and usage
The struct field and enum variant revision attribute accepts several key-
value pairs to be specified in order to support struct revisions, default
values for newly added fields, and value conversion for old fields which
have been removed. The macro will automatically detect whether a conversion
function is required for a removed field or variant.
§start/end
Defines the field revision lifetime. Fields can be added by specifing the
start revision number of the structure when first defining them and can
be removed from serialization logic by adding an end revision number.
For example: #[revision(start = 2, end = 4)]. The field would be present
in the structure at revisions 2 and 3, but starting with revision 4 it would
no longer be serialized or deserialized.
§default_fn
Provides an initialization value for a field when deserializing from an
older structure version which does not contain this field. If not specified
the Default trait is used to initialize the field.
The function name needs to be specified as a string. The first function argument is the source revision that is being deserialized, and the return value is the same type as the field.
use revision::revisioned;
#[derive(Debug)]
#[revisioned(revision = 2)]
struct TestStruct {
a: u32,
#[version(start = 2, default_fn = "default_b")]
b: u8,
}
impl TestStruct {
fn default_b(_revision: u16) -> u8 {
12u8
}
}§convert_fn
If defined, the method is called when the field existed at some previous revision, but no longer exists in the latest revision. The implementation and behaviour is slightly different depending on whether it is applied to a removed struct field or a removed enum variant. If defined, the function name needs to be specified as a string, and will be called when the field existed at a previous revision, but no longer exists in the latest revision.
When defined on a removed struct field, the first function argument is the
&mut self of the struct to update, the second argument is the source
revision that was deserialized, and the third argument is the deserialized
value from the field which has been removed.
When defined on a removed enum variant field, the first function argument is the source revision that was deserialized, and the second argument is a tuple with the enum variant field values for the variant which has been removed. If the enum variant is unit-like, then an empty tuple will be used for the second argument.
use revision::Error;
use revision::revisioned;
#[derive(Debug)]
#[revisioned(revision = 2)]
struct SomeStruct {
some_u32: u32,
#[version(end = 2, convert_fn = "convert_some_u16")]
some_u16: u16,
#[revision(start = 2)]
some_u64: u64,
}
impl SomeStruct {
fn convert_some_u16(&mut self, _revision: u16, value: u16) -> Result<(), Error> {
self.some_u64 = self.some_u16 as u64;
Ok(())
}
}
#[derive(Debug)]
#[revisioned(revision = 2)]
enum SomeTuple {
One,
#[revision(end = 2, convert_fn = "convert_variant_two")]
Two(i64, u32),
#[revision(start = 2)]
Three(i64, u64, bool),
}
impl SomeTuple {
fn convert_variant_two(_revision: u16, (a, b): (i64, u32)) -> Result<Self, Error> {
Ok(Self::Three(a, b as u64, true))
}
}