macro_rules! define_versioned {
(
$(#[$attributes:meta])*
$vis:vis $versioned_name:ident($versions_name:ident)
// Now match the optional type parameters
// See https://stackoverflow.com/questions/41603424/rust-macro-accepting-type-with-generic-parameters
$(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? $( = $deflt:tt)? ),+ >)?
{
$(
previous_versions: [
$($version_num:expr => $version_type:ty: { updates_to: $update_to_version_num:expr }),*
$(,)? // Optional trailing comma
],
)?
latest_version: {
$latest_version:expr => $latest_version_alias:ty = $latest_version_type:ty
$(,)? // Optional trailing comma
}
$(,)? // Optional trailing comma
}
$(,)?
$(outer_attributes: [
$(#[$outer_attributes:meta])*
])?
$(, inner_attributes: [
$(#[$inner_attributes:meta])*
])?
$(,)?
) => { ... };
}Expand description
This macro is intended for creating a data model which supports versioning. This is useful for creating an SBOR data model which can be updated in future. In future, enum variants can be added, and automatically mapped to.
NOTE: A circular version update chain will be an infinite loop at runtime. Be careful.
In the future, this may become a programmatic macro to support better error handling / edge case detection, and opting into more explicit SBOR handling.
§Example usage
use sbor::prelude::*;
#[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)]
pub struct FooV1 {
bar: u8,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)]
pub struct FooV2 {
bar: u8,
baz: Option<u8>,
}
impl From<FooV1> for FooV2 {
fn from(value: FooV1) -> FooV2 {
FooV2 {
bar: value.bar,
// Could also use `value.bar` as sensible default during inline update
baz: None,
}
}
}
define_versioned!(
#[derive(Debug, Clone, PartialEq, Eq, Sbor)]
VersionedFoo(FooVersions) {
previous_versions: [
1 => FooV1: { updates_to: 2 },
],
latest_version: {
2 => Foo = FooV2,
},
}
);
let mut a = FooV1 { bar: 42 }.into_versioned();
let equivalent_a = VersionedFoo::from(FooVersions::V1(FooV1 { bar: 42 }));
assert_eq!(a, equivalent_a);
// `Foo` is created as an alias for the latest content, `FooV2`
let b = VersionedFoo::from(FooVersions::V2(Foo { bar: 42, baz: None }));
assert_ne!(a, b);
assert_eq!(&*a.in_place_fully_update_and_as_latest_version_mut(), b.as_latest_version().unwrap());
// After a call to `a.in_place_fully_update_and_as_latest_version_mut()`, `a` has now been updated:
assert_eq!(a, b);§Advanced attribute handling
The provided attributes get applied to both the outer “Versioned” type,
and the inner “Versions” type. To only apply to one type, you can include the
outer_attributes optional argument and/or the inner_attributes optional argument:
define_versioned! {
#[derive(Debug, Clone, PartialEq, Eq, Sbor)]
VersionedFoo(FooVersions) {
previous_versions: [
1 => FooV1: { updates_to: 2 },
],
latest_version: {
2 => Foo = FooV2,
},
}
outer_attributes: [
#[sbor(type_name = "MyVersionedFoo")]
],
inner_attributes: [
#[sbor(type_name = "MyFooVersions")]
],
}