use crate::internal_prelude::*;
pub trait Versioned: AsRef<Self::Versions> + AsMut<Self::Versions> + From<Self::Versions> {
type Versions: From<Self>;
type LatestVersion;
fn is_fully_updated(&self) -> bool;
fn in_place_fully_update_and_as_latest_version_mut(&mut self) -> &mut Self::LatestVersion {
self.in_place_fully_update();
self.as_latest_version_mut().unwrap()
}
fn in_place_fully_update(&mut self) -> &mut Self;
fn fully_update(mut self) -> Self {
self.in_place_fully_update();
self
}
fn fully_update_and_into_latest_version(self) -> Self::LatestVersion;
fn from_latest_version(latest: Self::LatestVersion) -> Self;
fn as_latest_version(&self) -> Option<&Self::LatestVersion>;
fn as_latest_version_mut(&mut self) -> Option<&mut Self::LatestVersion>;
fn as_versions(&self) -> &Self::Versions;
fn as_versions_mut(&mut self) -> &mut Self::Versions;
fn into_versions(self) -> Self::Versions;
fn from_versions(version: Self::Versions) -> Self;
}
pub trait UniqueVersioned: Versioned {
fn as_unique_version(&self) -> &Self::LatestVersion;
fn as_unique_version_mut(&mut self) -> &mut Self::LatestVersion;
fn into_unique_version(self) -> Self::LatestVersion;
fn from_unique_version(unique_version: Self::LatestVersion) -> Self;
}
#[macro_export]
macro_rules! define_single_versioned {
(
$(#[$attributes:meta])*
$vis:vis $versioned_name:ident(
$versions_name:ident
)
$(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? $( = $deflt:tt)? ),+ >)?
=>
$latest_version_alias:ty = $latest_version_type:ty
$(, outer_attributes: [
$(#[$outer_attributes:meta])*
])?
$(, inner_attributes: [
$(#[$inner_attributes:meta])*
])?
$(,)?
) => {
$crate::define_versioned!(
$(#[$attributes])*
$vis $versioned_name($versions_name)
$(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)?
{
previous_versions: [],
latest_version: {
1 => $latest_version_alias = $latest_version_type
},
}
$(, outer_attributes: [
$(#[$outer_attributes])*
])?
$(, inner_attributes: [
$(#[$inner_attributes])*
])?
);
$crate::paste::paste! {
#[allow(dead_code)]
impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?
UniqueVersioned
for $versioned_name $(< $( $lt ),+ >)?
{
fn as_unique_version(&self) -> &Self::LatestVersion {
match self.as_ref() {
$versions_name $(::< $( $lt ),+ >)? ::V1(content) => content,
}
}
fn as_unique_version_mut(&mut self) -> &mut Self::LatestVersion {
match self.as_mut() {
$versions_name $(::< $( $lt ),+ >)? ::V1(content) => content,
}
}
fn into_unique_version(self) -> Self::LatestVersion {
match $versions_name $(::< $( $lt ),+ >)? ::from(self) {
$versions_name $(::< $( $lt ),+ >)? ::V1(content) => content,
}
}
fn from_unique_version(content: Self::LatestVersion) -> Self {
$versions_name $(::< $( $lt ),+ >)? ::V1(content).into()
}
}
}
};
}
#[macro_export]
macro_rules! define_versioned {
(
$(#[$attributes:meta])*
$vis:vis $versioned_name:ident($versions_name:ident)
$(< $( $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
$(,)? }
$(,)? }
$(,)?
$(outer_attributes: [
$(#[$outer_attributes:meta])*
])?
$(, inner_attributes: [
$(#[$inner_attributes:meta])*
])?
$(,)?
) => {
$crate::prelude::preinterpret! {
[!set! #full_generics = $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)?]
[!set! #impl_generics = $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?]
[!set! #type_generics = $(< $( $lt ),+ >)?]
[!set! #versioned_type = $versioned_name $(< $( $lt ),+ >)?]
[!set! #versioned_type_path = $versioned_name $(::< $( $lt ),+ >)?]
[!set! #versions_type = $versions_name $(< $( $lt ),+ >)?]
[!set! #versions_type_path = $versions_name $(::< $( $lt ),+ >)?]
[!set! #permit_sbor_attribute_alias = [!ident! $versioned_name _PermitSborAttributes]]
#[allow(dead_code)]
$vis type $latest_version_alias = $latest_version_type;
use $crate::PermitSborAttributes as #permit_sbor_attribute_alias;
#[derive(#permit_sbor_attribute_alias)]
$(#[$attributes])*
$($(#[$outer_attributes])*)?
#[sbor(as_type = [!string! #versions_type])]
$vis struct $versioned_name #full_generics
{
inner: Option<#versions_type>,
}
impl #impl_generics #versioned_type
{
pub fn new(inner: #versions_type) -> Self {
Self {
inner: Some(inner),
}
}
}
impl #impl_generics AsRef<#versions_type> for #versioned_type
{
fn as_ref(&self) -> &#versions_type {
self.inner.as_ref().unwrap()
}
}
impl #impl_generics AsMut<#versions_type> for #versioned_type
{
fn as_mut(&mut self) -> &mut #versions_type {
self.inner.as_mut().unwrap()
}
}
impl #impl_generics From<#versions_type> for #versioned_type
{
fn from(value: #versions_type) -> Self {
Self::new(value)
}
}
impl #impl_generics From<#versioned_type> for #versions_type
{
fn from(value: #versioned_type) -> Self {
value.inner.unwrap()
}
}
impl #impl_generics Versioned for #versioned_type
{
type Versions = #versions_type;
type LatestVersion = $latest_version_type;
fn is_fully_updated(&self) -> bool {
self.as_ref().is_fully_updated()
}
fn in_place_fully_update(&mut self) -> &mut Self {
if !self.is_fully_updated() {
let current = self.inner.take().unwrap();
self.inner = Some(current.fully_update());
}
self
}
fn fully_update_and_into_latest_version(self) -> Self::LatestVersion {
self.inner.unwrap().fully_update_and_into_latest_version()
}
fn from_latest_version(latest: Self::LatestVersion) -> Self {
Self::new(latest.into())
}
fn as_latest_version(&self) -> Option<&Self::LatestVersion> {
self.as_ref().as_latest_version()
}
fn as_latest_version_mut(&mut self) -> Option<&mut Self::LatestVersion> {
self.as_mut().as_latest_version_mut()
}
fn as_versions(&self) -> &Self::Versions {
self.as_ref()
}
fn as_versions_mut(&mut self) -> &mut Self::Versions {
self.as_mut()
}
fn into_versions(self) -> Self::Versions {
self.inner.unwrap()
}
fn from_versions(version: Self::Versions) -> Self {
Self::new(version)
}
}
[!set! #discriminators = [!ident! $versioned_name _discriminators]]
#[allow(non_snake_case)]
mod #discriminators {
$($(
pub const [!ident! VERSION_ $version_num]: u8 = $version_num - 1;
)*)?
pub const LATEST_VERSION: u8 = $latest_version - 1;
}
#[derive(#permit_sbor_attribute_alias)]
$(#[$attributes])*
$($(#[$inner_attributes])*)?
$vis enum $versions_name #full_generics
{
$($(
#[sbor(discriminator(#discriminators::[!ident! VERSION_ $version_num]))]
[!ident! V $version_num]($version_type),
)*)?
#[sbor(discriminator(#discriminators::LATEST_VERSION))]
[!ident! V $latest_version]($latest_version_type),
}
#[allow(dead_code)]
impl #impl_generics #versions_type
{
fn attempt_single_update(self) -> (bool, Self) {
match self {
$($(
Self::[!ident! V $version_num](value) => (true, Self::[!ident! V $update_to_version_num](value.into())),
)*)?
this @ Self::[!ident! V $latest_version](_) => (false, this),
}
}
fn fully_update(mut self) -> Self {
loop {
let (did_update, updated) = self.attempt_single_update();
if did_update {
self = updated;
} else {
return updated;
}
}
}
#[allow(unreachable_patterns)]
pub fn is_fully_updated(&self) -> bool {
match self {
Self::[!ident! V $latest_version](_) => true,
_ => false,
}
}
#[allow(irrefutable_let_patterns)]
fn fully_update_and_into_latest_version(self) -> $latest_version_type {
let Self::[!ident! V $latest_version](latest) = self.fully_update() else {
panic!("Invalid resolved latest version not equal to latest type")
};
return latest;
}
fn from_latest_version(latest: $latest_version_type) -> Self {
Self::[!ident! V $latest_version](latest)
}
#[allow(unreachable_patterns)]
fn as_latest_version(&self) -> Option<&$latest_version_type> {
match self {
Self::[!ident! V $latest_version](latest) => Some(latest),
_ => None,
}
}
#[allow(unreachable_patterns)]
fn as_latest_version_mut(&mut self) -> Option<&mut $latest_version_type> {
match self {
Self::[!ident! V $latest_version](latest) => Some(latest),
_ => None,
}
}
pub fn into_versioned(self) -> #versioned_type {
#versioned_type_path::new(self)
}
}
$($(
#[allow(dead_code)]
impl #impl_generics From<$version_type> for #versions_type {
fn from(value: $version_type) -> Self {
Self::[!ident! V $version_num](value)
}
}
#[allow(dead_code)]
impl #impl_generics From<$version_type> for #versioned_type {
fn from(value: $version_type) -> Self {
Self::new(#versions_type_path::[!ident! V $version_num](value))
}
}
)*)?
#[allow(dead_code)]
impl #impl_generics From<$latest_version_type> for #versions_type {
fn from(value: $latest_version_type) -> Self {
Self::[!ident! V $latest_version](value)
}
}
#[allow(dead_code)]
impl #impl_generics From<$latest_version_type> for #versioned_type {
fn from(value: $latest_version_type) -> Self {
Self::new($versions_name::[!ident! V $latest_version](value))
}
}
[!set! #version_trait = [!ident! $versioned_name Version]]
#[allow(dead_code)]
$vis trait #version_trait {
type Versioned: sbor::Versioned;
const DISCRIMINATOR: u8;
type OwnedSborVariant;
type BorrowedSborVariant<'a> where Self: 'a;
fn as_encodable_variant(&self) -> Self::BorrowedSborVariant<'_>;
fn from_decoded_variant(variant: Self::OwnedSborVariant) -> Self where Self: core::marker::Sized;
fn into_versioned(self) -> Self::Versioned;
}
$($(
impl #impl_generics #version_trait for $version_type
{
type Versioned = #versioned_type;
const DISCRIMINATOR: u8 = #discriminators::[!ident! VERSION_ $version_num];
type OwnedSborVariant = sbor::SborFixedEnumVariant::<{ #discriminators::[!ident! VERSION_ $version_num] }, (Self,)>;
type BorrowedSborVariant<'a> = sbor::SborFixedEnumVariant::<{ #discriminators::[!ident! VERSION_ $version_num] }, (&'a Self,)> where Self: 'a;
fn as_encodable_variant(&self) -> Self::BorrowedSborVariant<'_> {
sbor::SborFixedEnumVariant::new((self,))
}
fn from_decoded_variant(variant: Self::OwnedSborVariant) -> Self {
variant.into_fields().0
}
fn into_versioned(self) -> Self::Versioned {
#versioned_type_path::new(self.into())
}
}
)*)?
impl #impl_generics #version_trait for $latest_version_type
{
type Versioned = $versioned_name #type_generics;
const DISCRIMINATOR: u8 = #discriminators::LATEST_VERSION;
type OwnedSborVariant = sbor::SborFixedEnumVariant::<{ #discriminators::LATEST_VERSION }, (Self,)>;
type BorrowedSborVariant<'a> = sbor::SborFixedEnumVariant::<{ #discriminators::LATEST_VERSION }, (&'a Self,)> where Self: 'a;
fn as_encodable_variant(&self) -> Self::BorrowedSborVariant<'_> {
sbor::SborFixedEnumVariant::new((self,))
}
fn from_decoded_variant(variant: Self::OwnedSborVariant) -> Self {
variant.into_fields().0
}
fn into_versioned(self) -> Self::Versioned {
#versioned_type_path::new(self.into())
}
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
crate::define_versioned!(
#[derive(Debug, Clone, PartialEq, Eq, Sbor)]
VersionedExample(ExampleVersions) {
previous_versions: [
1 => ExampleV1: { updates_to: 2 },
2 => ExampleV2: { updates_to: 4 },
3 => ExampleV3: { updates_to: 4 },
],
latest_version: {
4 => Example = ExampleV4,
},
}
);
type ExampleV1 = u8;
type ExampleV2 = u16;
#[derive(Debug, Clone, PartialEq, Eq, Sbor)]
struct ExampleV3(u16);
#[derive(Debug, Clone, PartialEq, Eq, Sbor)]
struct ExampleV4 {
the_value: u16,
}
impl ExampleV4 {
pub fn of(value: u16) -> Self {
Self { the_value: value }
}
}
impl From<ExampleV2> for ExampleV4 {
fn from(value: ExampleV2) -> Self {
Self { the_value: value }
}
}
impl From<ExampleV3> for ExampleV4 {
fn from(value: ExampleV3) -> Self {
Self { the_value: value.0 }
}
}
#[test]
pub fn updates_to_latest_work() {
let expected_latest = ExampleV4::of(5);
let v1: ExampleV1 = 5;
validate_latest(v1, expected_latest.clone());
let v2: ExampleV2 = 5;
validate_latest(v2, expected_latest.clone());
let v3 = ExampleV3(5);
validate_latest(v3, expected_latest.clone());
let v4 = ExampleV4::of(5);
validate_latest(v4, expected_latest);
}
fn validate_latest(
actual: impl Into<VersionedExample>,
expected: <VersionedExample as Versioned>::LatestVersion,
) {
let versioned_actual = actual.into().fully_update();
let versioned_expected = VersionedExample::from(expected.clone());
assert_eq!(versioned_actual, versioned_expected,);
assert_eq!(
versioned_actual.fully_update_and_into_latest_version(),
expected,
);
}
#[derive(Debug, Clone, PartialEq, Eq, Sbor)]
struct GenericModelV1<T>(T);
define_single_versioned!(
#[derive(Debug, Clone, PartialEq, Eq, Sbor)]
VersionedGenericModel(GenericModelVersions)<T> => GenericModel<T> = GenericModelV1<T>
);
#[test]
pub fn generated_single_versioned_works() {
let v1_model: GenericModel<_> = GenericModelV1(51u64);
let versioned = VersionedGenericModel::from(v1_model.clone());
let versioned_2 = v1_model.clone().into_versioned();
assert_eq!(
versioned.clone().fully_update_and_into_latest_version(),
v1_model
);
assert_eq!(versioned, versioned_2);
}
#[test]
pub fn verify_sbor_equivalence() {
let v1_model: GenericModel<_> = GenericModelV1(51u64);
let versions = GenericModelVersions::V1(v1_model.clone());
let versioned = VersionedGenericModel::from(v1_model.clone());
let expected_sbor_value = BasicEnumVariantValue {
discriminator: 0, fields: vec![
Value::Tuple {
fields: vec![Value::U64 { value: 51 }],
},
],
};
let encoded_versioned = basic_encode(&versioned).unwrap();
let encoded_versions = basic_encode(&versions).unwrap();
let expected = basic_encode(&expected_sbor_value).unwrap();
assert_eq!(encoded_versioned, expected);
assert_eq!(encoded_versions, expected);
check_identical_types::<VersionedGenericModel<u64>, GenericModelVersions<u64>>(Some(
"VersionedGenericModel",
));
}
fn check_identical_types<T1: Describe<NoCustomTypeKind>, T2: Describe<NoCustomTypeKind>>(
name: Option<&'static str>,
) {
let (type_id1, schema1) = generate_full_schema_from_single_type::<T1, NoCustomSchema>();
let (type_id2, schema2) = generate_full_schema_from_single_type::<T2, NoCustomSchema>();
assert_eq!(
schema1.v1().resolve_type_kind(type_id1),
schema2.v1().resolve_type_kind(type_id2)
);
assert_eq!(
schema1
.v1()
.resolve_type_metadata(type_id1)
.unwrap()
.clone(),
schema2
.v1()
.resolve_type_metadata(type_id2)
.unwrap()
.clone()
.with_name(name.map(Cow::Borrowed))
);
assert_eq!(
schema1.v1().resolve_type_validation(type_id1),
schema2.v1().resolve_type_validation(type_id2)
);
}
}