sbor/versioned.rs
1use crate::internal_prelude::*;
2
3/// A trait implemented by versioned types created via [`define_versioned`] and [`define_single_versioned`].
4///
5/// A versioned type is a type wrapping an enum, this enum is the associated type [`Versioned::Versions`],
6/// and contains a variant for each supported version.
7///
8/// This [`Versioned`] type itself is a struct wrapper around this enum, which allows for fully updating
9/// the contained version to [`Versioned::LatestVersion`]. This wrapper is required so that the wrapper
10/// can take ownership of old versions as part of the upgrade process, in order to incrementally update
11/// them using the [`From`] trait.
12pub trait Versioned: AsRef<Self::Versions> + AsMut<Self::Versions> + From<Self::Versions> {
13 /// The type for the enum of versions.
14 type Versions: From<Self>;
15
16 /// The type for the latest content.
17 type LatestVersion;
18
19 /// Returns true if at the latest version.
20 fn is_fully_updated(&self) -> bool;
21
22 /// Updates the latest version in place, and returns a `&mut` to the latest content
23 fn in_place_fully_update_and_as_latest_version_mut(&mut self) -> &mut Self::LatestVersion {
24 self.in_place_fully_update();
25 self.as_latest_version_mut().unwrap()
26 }
27
28 /// Updates to the latest version in place.
29 fn in_place_fully_update(&mut self) -> &mut Self;
30
31 /// Consumes self, updates to the latest version and returns itself.
32 fn fully_update(mut self) -> Self {
33 self.in_place_fully_update();
34 self
35 }
36
37 /// Updates itself to the latest version, then returns the latest content
38 fn fully_update_and_into_latest_version(self) -> Self::LatestVersion;
39
40 /// Constructs a versioned wrapper around the latest content
41 fn from_latest_version(latest: Self::LatestVersion) -> Self;
42
43 /// If the versioned wrapper is at the latest version, it returns
44 /// an immutable reference to the latest content, otherwise it returns `None`.
45 ///
46 /// If you require the latest version unconditionally, consider using
47 /// [`in_place_fully_update_and_as_latest_version_mut`] to update to the latest version first - or, if
48 /// there is only a single version, use [`as_unique_version`].
49 fn as_latest_version(&self) -> Option<&Self::LatestVersion>;
50
51 /// If the versioned wrapper is at the latest version, it returns
52 /// a mutable reference to the latest content, otherwise it returns `None`.
53 ///
54 /// If you require the latest version unconditionally, consider using
55 /// [`in_place_fully_update_and_as_latest_version_mut`] to update to the latest version first - or, if
56 /// there is only a single version, use [`as_unique_version_mut`].
57 fn as_latest_version_mut(&mut self) -> Option<&mut Self::LatestVersion>;
58
59 /// Gets a reference the inner versions enum, for e.g. matching on the enum.
60 ///
61 /// This is essentially a clearer alias for `as_ref`.
62 fn as_versions(&self) -> &Self::Versions;
63
64 /// Gets a mutable reference the inner versions enum, for e.g. matching on the enum.
65 ///
66 /// This is essentially a clearer alias for `as_mut`.
67 fn as_versions_mut(&mut self) -> &mut Self::Versions;
68
69 /// Removes the upgradable wrapper to get at the inner versions enum, for e.g. matching on the enum.
70 fn into_versions(self) -> Self::Versions;
71
72 /// Creates a new Versioned wrapper from a given specific version.
73 fn from_versions(version: Self::Versions) -> Self;
74}
75
76/// A trait for Versioned types which only have a single version.
77///
78/// This enables a number of special-cased methods to be implemented which are only possible when there
79/// is only one version.
80pub trait UniqueVersioned: Versioned {
81 /// Returns an immutable reference to (currently) the only possible version of the inner content.
82 fn as_unique_version(&self) -> &Self::LatestVersion;
83
84 /// Returns a mutable reference to (currently) the only possible version of the inner content.
85 ///
86 /// This is somewhat equivalent to `in_place_fully_update_and_as_latest_version_mut`, but doesn't need to do
87 /// any updating, so can be used where logical correctness requires there to be a unique version,
88 /// requires no updating, or simply for slightly better performance.
89 fn as_unique_version_mut(&mut self) -> &mut Self::LatestVersion;
90
91 /// Returns the (currently) only possible version of the inner content.
92 ///
93 /// This is somewhat equivalent to `fully_update_and_into_latest_version`, but doesn't need to do
94 /// any updating, so can be used where logical correctness requires there to be a unique version,
95 /// requires no updating, or simply for slightly better performance.
96 fn into_unique_version(self) -> Self::LatestVersion;
97
98 /// Creates the versioned wrapper from the (currently) only possible version.
99 ///
100 /// This is equivalent to `from_latest_version`, but useful to use instead if your logic's correctness
101 /// is dependent on there only being a single version. If another version gets added, this
102 /// method will give a compile error.
103 fn from_unique_version(unique_version: Self::LatestVersion) -> Self;
104}
105
106/// This macro is intended for creating a data model which supports versioning.
107/// This is useful for creating an SBOR data model which can be updated in future.
108///
109/// In future, the type can be converted to `define_versioned`, enum variants can
110/// be added, and automatically mapped to the latest version.
111///
112/// This macro is just a simpler wrapper around the [`define_versioned`] macro,
113/// for use when there's just a single version.
114///
115/// ## Example usage
116///
117/// ```rust
118/// use sbor::prelude::*;
119///
120/// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)]
121/// pub struct FooV1 {
122/// bar: u8,
123/// }
124///
125/// define_single_versioned! {
126/// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)]
127/// pub VersionedFoo(FooVersions) => Foo = FooV1
128/// }
129///
130/// // `Foo` is created as an alias for `FooV1`
131/// let a = Foo { bar: 42 }.into_versioned();
132/// let a3 = VersionedFoo::from(FooVersions::V1(FooV1 { bar: 42 }));
133/// let a2 = VersionedFoo::from_unique_version(Foo { bar: 42 });
134///
135/// assert_eq!(a, a2);
136/// assert_eq!(a2, a3);
137/// assert_eq!(42, a.as_unique_version().bar);
138/// ```
139///
140/// ## Advanced attribute handling
141///
142/// Note that the provided attributes get applied to _both_ the outer "Versioned" type,
143/// and the inner "Versions" type. To only apply to one type, you can include the
144/// `outer_attributes` optional argument and/or the `inner_attributes` optional argument:
145/// ```
146/// # use sbor::prelude::*;
147/// # #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)]
148/// # pub struct FooV1;
149/// define_single_versioned! {
150/// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)]
151/// pub VersionedFoo(FooVersions) => Foo = FooV1,
152/// outer_attributes: [
153/// #[sbor(type_name = "MyVersionedFoo")]
154/// ],
155/// inner_attributes: [
156/// #[sbor(type_name = "MyFooVersions")]
157/// ],
158/// }
159/// ```
160#[macro_export]
161macro_rules! define_single_versioned {
162 (
163 $(#[$attributes:meta])*
164 $vis:vis $versioned_name:ident(
165 $versions_name:ident
166 )
167 $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? $( = $deflt:tt)? ),+ >)?
168 =>
169 $latest_version_alias:ty = $latest_version_type:ty
170 $(, outer_attributes: [
171 $(#[$outer_attributes:meta])*
172 ])?
173 $(, inner_attributes: [
174 $(#[$inner_attributes:meta])*
175 ])?
176 $(,)?
177 ) => {
178 $crate::define_versioned!(
179 $(#[$attributes])*
180 $vis $versioned_name($versions_name)
181 $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)?
182 {
183 previous_versions: [],
184 latest_version: {
185 1 => $latest_version_alias = $latest_version_type
186 },
187 }
188 $(, outer_attributes: [
189 $(#[$outer_attributes])*
190 ])?
191 $(, inner_attributes: [
192 $(#[$inner_attributes])*
193 ])?
194 );
195
196 $crate::paste::paste! {
197 #[allow(dead_code)]
198 impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?
199 UniqueVersioned
200 for $versioned_name $(< $( $lt ),+ >)?
201 {
202 fn as_unique_version(&self) -> &Self::LatestVersion {
203 match self.as_ref() {
204 $versions_name $(::< $( $lt ),+ >)? ::V1(content) => content,
205 }
206 }
207
208 fn as_unique_version_mut(&mut self) -> &mut Self::LatestVersion {
209 match self.as_mut() {
210 $versions_name $(::< $( $lt ),+ >)? ::V1(content) => content,
211 }
212 }
213
214 fn into_unique_version(self) -> Self::LatestVersion {
215 match $versions_name $(::< $( $lt ),+ >)? ::from(self) {
216 $versions_name $(::< $( $lt ),+ >)? ::V1(content) => content,
217 }
218 }
219
220 fn from_unique_version(content: Self::LatestVersion) -> Self {
221 $versions_name $(::< $( $lt ),+ >)? ::V1(content).into()
222 }
223 }
224 }
225 };
226}
227
228/// This macro is intended for creating a data model which supports versioning.
229/// This is useful for creating an SBOR data model which can be updated in future.
230/// In future, enum variants can be added, and automatically mapped to.
231///
232/// NOTE: A circular version update chain will be an infinite loop at runtime. Be careful.
233///
234/// In the future, this may become a programmatic macro to support better error handling /
235/// edge case detection, and opting into more explicit SBOR handling.
236///
237/// ## Example usage
238///
239/// ```rust
240/// use sbor::prelude::*;
241///
242/// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)]
243/// pub struct FooV1 {
244/// bar: u8,
245/// }
246///
247/// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)]
248/// pub struct FooV2 {
249/// bar: u8,
250/// baz: Option<u8>,
251/// }
252///
253/// impl From<FooV1> for FooV2 {
254/// fn from(value: FooV1) -> FooV2 {
255/// FooV2 {
256/// bar: value.bar,
257/// // Could also use `value.bar` as sensible default during inline update
258/// baz: None,
259/// }
260/// }
261/// }
262///
263/// define_versioned!(
264/// #[derive(Debug, Clone, PartialEq, Eq, Sbor)]
265/// VersionedFoo(FooVersions) {
266/// previous_versions: [
267/// 1 => FooV1: { updates_to: 2 },
268/// ],
269/// latest_version: {
270/// 2 => Foo = FooV2,
271/// },
272/// }
273/// );
274///
275/// let mut a = FooV1 { bar: 42 }.into_versioned();
276/// let equivalent_a = VersionedFoo::from(FooVersions::V1(FooV1 { bar: 42 }));
277/// assert_eq!(a, equivalent_a);
278///
279/// // `Foo` is created as an alias for the latest content, `FooV2`
280/// let b = VersionedFoo::from(FooVersions::V2(Foo { bar: 42, baz: None }));
281///
282/// assert_ne!(a, b);
283/// assert_eq!(&*a.in_place_fully_update_and_as_latest_version_mut(), b.as_latest_version().unwrap());
284///
285/// // After a call to `a.in_place_fully_update_and_as_latest_version_mut()`, `a` has now been updated:
286/// assert_eq!(a, b);
287/// ```
288///
289/// ## Advanced attribute handling
290///
291/// The provided attributes get applied to _both_ the outer "Versioned" type,
292/// and the inner "Versions" type. To only apply to one type, you can include the
293/// `outer_attributes` optional argument and/or the `inner_attributes` optional argument:
294/// ```
295/// # use sbor::prelude::*;
296/// # #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)]
297/// # pub struct FooV1;
298/// # #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)]
299/// # pub struct FooV2;
300/// # impl From<FooV1> for FooV2 {
301/// # fn from(value: FooV1) -> FooV2 {
302/// # FooV2
303/// # }
304/// # }
305///
306/// define_versioned! {
307/// #[derive(Debug, Clone, PartialEq, Eq, Sbor)]
308/// VersionedFoo(FooVersions) {
309/// previous_versions: [
310/// 1 => FooV1: { updates_to: 2 },
311/// ],
312/// latest_version: {
313/// 2 => Foo = FooV2,
314/// },
315/// }
316/// outer_attributes: [
317/// #[sbor(type_name = "MyVersionedFoo")]
318/// ],
319/// inner_attributes: [
320/// #[sbor(type_name = "MyFooVersions")]
321/// ],
322/// }
323#[macro_export]
324macro_rules! define_versioned {
325 (
326 $(#[$attributes:meta])*
327 $vis:vis $versioned_name:ident($versions_name:ident)
328 // Now match the optional type parameters
329 // See https://stackoverflow.com/questions/41603424/rust-macro-accepting-type-with-generic-parameters
330 $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? $( = $deflt:tt)? ),+ >)?
331 {
332 $(
333 previous_versions: [
334 $($version_num:expr => $version_type:ty: { updates_to: $update_to_version_num:expr }),*
335 $(,)? // Optional trailing comma
336 ],
337 )?
338 latest_version: {
339 $latest_version:expr => $latest_version_alias:ty = $latest_version_type:ty
340 $(,)? // Optional trailing comma
341 }
342 $(,)? // Optional trailing comma
343 }
344 $(,)?
345 $(outer_attributes: [
346 $(#[$outer_attributes:meta])*
347 ])?
348 $(, inner_attributes: [
349 $(#[$inner_attributes:meta])*
350 ])?
351 $(,)?
352 ) => {
353 $crate::prelude::preinterpret! {
354 [!set! #full_generics = $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)?]
355 [!set! #impl_generics = $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?]
356 [!set! #type_generics = $(< $( $lt ),+ >)?]
357 [!set! #versioned_type = $versioned_name $(< $( $lt ),+ >)?]
358 [!set! #versioned_type_path = $versioned_name $(::< $( $lt ),+ >)?]
359 [!set! #versions_type = $versions_name $(< $( $lt ),+ >)?]
360 [!set! #versions_type_path = $versions_name $(::< $( $lt ),+ >)?]
361 [!set! #permit_sbor_attribute_alias = [!ident! $versioned_name _PermitSborAttributes]]
362
363 #[allow(dead_code)]
364 $vis type $latest_version_alias = $latest_version_type;
365
366 use $crate::PermitSborAttributes as #permit_sbor_attribute_alias;
367
368 #[derive(#permit_sbor_attribute_alias)]
369 $(#[$attributes])*
370 $($(#[$outer_attributes])*)?
371 // Needs to go below $attributes so that a #[derive(Sbor)] in the attributes can see it.
372 #[sbor(as_type = [!string! #versions_type])]
373 /// If you wish to get access to match on the versions, use `.as_ref()` or `.as_mut()`.
374 $vis struct $versioned_name #full_generics
375 {
376 inner: Option<#versions_type>,
377 }
378
379 impl #impl_generics #versioned_type
380 {
381 pub fn new(inner: #versions_type) -> Self {
382 Self {
383 inner: Some(inner),
384 }
385 }
386 }
387
388 impl #impl_generics AsRef<#versions_type> for #versioned_type
389 {
390 fn as_ref(&self) -> &#versions_type {
391 self.inner.as_ref().unwrap()
392 }
393 }
394
395 impl #impl_generics AsMut<#versions_type> for #versioned_type
396 {
397 fn as_mut(&mut self) -> &mut #versions_type {
398 self.inner.as_mut().unwrap()
399 }
400 }
401
402 impl #impl_generics From<#versions_type> for #versioned_type
403 {
404 fn from(value: #versions_type) -> Self {
405 Self::new(value)
406 }
407 }
408
409 impl #impl_generics From<#versioned_type> for #versions_type
410 {
411 fn from(value: #versioned_type) -> Self {
412 value.inner.unwrap()
413 }
414 }
415
416 impl #impl_generics Versioned for #versioned_type
417 {
418 type Versions = #versions_type;
419 type LatestVersion = $latest_version_type;
420
421 fn is_fully_updated(&self) -> bool {
422 self.as_ref().is_fully_updated()
423 }
424
425 fn in_place_fully_update(&mut self) -> &mut Self {
426 if !self.is_fully_updated() {
427 let current = self.inner.take().unwrap();
428 self.inner = Some(current.fully_update());
429 }
430 self
431 }
432
433 fn fully_update_and_into_latest_version(self) -> Self::LatestVersion {
434 self.inner.unwrap().fully_update_and_into_latest_version()
435 }
436
437 /// Constructs the versioned enum from the latest content
438 fn from_latest_version(latest: Self::LatestVersion) -> Self {
439 Self::new(latest.into())
440 }
441
442 fn as_latest_version(&self) -> Option<&Self::LatestVersion> {
443 self.as_ref().as_latest_version()
444 }
445
446 fn as_latest_version_mut(&mut self) -> Option<&mut Self::LatestVersion> {
447 self.as_mut().as_latest_version_mut()
448 }
449
450 fn as_versions(&self) -> &Self::Versions {
451 self.as_ref()
452 }
453
454 fn as_versions_mut(&mut self) -> &mut Self::Versions {
455 self.as_mut()
456 }
457
458 fn into_versions(self) -> Self::Versions {
459 self.inner.unwrap()
460 }
461
462 fn from_versions(version: Self::Versions) -> Self {
463 Self::new(version)
464 }
465 }
466
467 [!set! #discriminators = [!ident! $versioned_name _discriminators]]
468 #[allow(non_snake_case)]
469 mod #discriminators {
470 // The initial version of this tool used 0-indexed/off-by-one discriminators accidentally.
471 // We're stuck with these now unfortunately...
472 // But we make them explicit in case versions are skipped.
473 $($(
474 pub const [!ident! VERSION_ $version_num]: u8 = $version_num - 1;
475 )*)?
476 pub const LATEST_VERSION: u8 = $latest_version - 1;
477 }
478
479 #[derive(#permit_sbor_attribute_alias)]
480 $(#[$attributes])*
481 $($(#[$inner_attributes])*)?
482 $vis enum $versions_name #full_generics
483 {
484 $($(
485 #[sbor(discriminator(#discriminators::[!ident! VERSION_ $version_num]))]
486 [!ident! V $version_num]($version_type),
487 )*)?
488 #[sbor(discriminator(#discriminators::LATEST_VERSION))]
489 [!ident! V $latest_version]($latest_version_type),
490 }
491
492 #[allow(dead_code)]
493 impl #impl_generics #versions_type
494 {
495 /// Returns if update happened, and the updated versioned enum.
496 fn attempt_single_update(self) -> (bool, Self) {
497 match self {
498 $($(
499 Self::[!ident! V $version_num](value) => (true, Self::[!ident! V $update_to_version_num](value.into())),
500 )*)?
501 this @ Self::[!ident! V $latest_version](_) => (false, this),
502 }
503 }
504
505 fn fully_update(mut self) -> Self {
506 loop {
507 let (did_update, updated) = self.attempt_single_update();
508 if did_update {
509 // We should try updating
510 self = updated;
511 } else {
512 // We're at latest - return
513 return updated;
514 }
515 }
516 }
517
518 #[allow(unreachable_patterns)]
519 pub fn is_fully_updated(&self) -> bool {
520 match self {
521 Self::[!ident! V $latest_version](_) => true,
522 _ => false,
523 }
524 }
525
526 #[allow(irrefutable_let_patterns)]
527 fn fully_update_and_into_latest_version(self) -> $latest_version_type {
528 let Self::[!ident! V $latest_version](latest) = self.fully_update() else {
529 panic!("Invalid resolved latest version not equal to latest type")
530 };
531 return latest;
532 }
533
534 fn from_latest_version(latest: $latest_version_type) -> Self {
535 Self::[!ident! V $latest_version](latest)
536 }
537
538 #[allow(unreachable_patterns)]
539 fn as_latest_version(&self) -> Option<&$latest_version_type> {
540 match self {
541 Self::[!ident! V $latest_version](latest) => Some(latest),
542 _ => None,
543 }
544 }
545
546 #[allow(unreachable_patterns)]
547 fn as_latest_version_mut(&mut self) -> Option<&mut $latest_version_type> {
548 match self {
549 Self::[!ident! V $latest_version](latest) => Some(latest),
550 _ => None,
551 }
552 }
553
554 pub fn into_versioned(self) -> #versioned_type {
555 #versioned_type_path::new(self)
556 }
557 }
558
559 $($(
560 #[allow(dead_code)]
561 impl #impl_generics From<$version_type> for #versions_type {
562 fn from(value: $version_type) -> Self {
563 Self::[!ident! V $version_num](value)
564 }
565 }
566
567 #[allow(dead_code)]
568 impl #impl_generics From<$version_type> for #versioned_type {
569 fn from(value: $version_type) -> Self {
570 Self::new(#versions_type_path::[!ident! V $version_num](value))
571 }
572 }
573 )*)?
574
575 #[allow(dead_code)]
576 impl #impl_generics From<$latest_version_type> for #versions_type {
577 fn from(value: $latest_version_type) -> Self {
578 Self::[!ident! V $latest_version](value)
579 }
580 }
581
582 #[allow(dead_code)]
583 impl #impl_generics From<$latest_version_type> for #versioned_type {
584 fn from(value: $latest_version_type) -> Self {
585 Self::new($versions_name::[!ident! V $latest_version](value))
586 }
587 }
588
589 // This trait is similar to `SborEnumVariantFor<X, Versioned>`, but it's nicer to use as
590 // it's got a better name and can be implemented without needing a specific CustomValueKind.
591 [!set! #version_trait = [!ident! $versioned_name Version]]
592 #[allow(dead_code)]
593 $vis trait #version_trait {
594 // Note: We need to use an explicit associated type to capture the generics.
595 type Versioned: sbor::Versioned;
596
597 const DISCRIMINATOR: u8;
598 type OwnedSborVariant;
599 type BorrowedSborVariant<'a> where Self: 'a;
600
601 /// Can be used to encode the type as a variant under the Versioned type, without
602 /// needing to clone, like this: `encoder.encode(x.as_encodable_variant())`.
603 fn as_encodable_variant(&self) -> Self::BorrowedSborVariant<'_>;
604
605 /// Can be used to decode the type from an encoded variant, like this:
606 /// `X::from_decoded_variant(decoder.decode()?)`.
607 fn from_decoded_variant(variant: Self::OwnedSborVariant) -> Self where Self: core::marker::Sized;
608
609 fn into_versioned(self) -> Self::Versioned;
610 }
611
612 $($(
613 impl #impl_generics #version_trait for $version_type
614 {
615 type Versioned = #versioned_type;
616
617 const DISCRIMINATOR: u8 = #discriminators::[!ident! VERSION_ $version_num];
618 type OwnedSborVariant = sbor::SborFixedEnumVariant::<{ #discriminators::[!ident! VERSION_ $version_num] }, (Self,)>;
619 type BorrowedSborVariant<'a> = sbor::SborFixedEnumVariant::<{ #discriminators::[!ident! VERSION_ $version_num] }, (&'a Self,)> where Self: 'a;
620
621 fn as_encodable_variant(&self) -> Self::BorrowedSborVariant<'_> {
622 sbor::SborFixedEnumVariant::new((self,))
623 }
624
625 fn from_decoded_variant(variant: Self::OwnedSborVariant) -> Self {
626 variant.into_fields().0
627 }
628
629 fn into_versioned(self) -> Self::Versioned {
630 #versioned_type_path::new(self.into())
631 }
632 }
633 )*)?
634
635 impl #impl_generics #version_trait for $latest_version_type
636 {
637 type Versioned = $versioned_name #type_generics;
638
639 const DISCRIMINATOR: u8 = #discriminators::LATEST_VERSION;
640 type OwnedSborVariant = sbor::SborFixedEnumVariant::<{ #discriminators::LATEST_VERSION }, (Self,)>;
641 type BorrowedSborVariant<'a> = sbor::SborFixedEnumVariant::<{ #discriminators::LATEST_VERSION }, (&'a Self,)> where Self: 'a;
642
643 fn as_encodable_variant(&self) -> Self::BorrowedSborVariant<'_> {
644 sbor::SborFixedEnumVariant::new((self,))
645 }
646
647 fn from_decoded_variant(variant: Self::OwnedSborVariant) -> Self {
648 variant.into_fields().0
649 }
650
651 fn into_versioned(self) -> Self::Versioned {
652 #versioned_type_path::new(self.into())
653 }
654 }
655 }
656 };
657}
658
659#[cfg(test)]
660mod tests {
661 use super::*;
662 use crate::*;
663
664 crate::define_versioned!(
665 #[derive(Debug, Clone, PartialEq, Eq, Sbor)]
666 VersionedExample(ExampleVersions) {
667 previous_versions: [
668 1 => ExampleV1: { updates_to: 2 },
669 2 => ExampleV2: { updates_to: 4 },
670 3 => ExampleV3: { updates_to: 4 },
671 ],
672 latest_version: {
673 4 => Example = ExampleV4,
674 },
675 }
676 );
677
678 // Define the concrete versions
679 type ExampleV1 = u8;
680 type ExampleV2 = u16;
681
682 #[derive(Debug, Clone, PartialEq, Eq, Sbor)]
683 struct ExampleV3(u16);
684
685 #[derive(Debug, Clone, PartialEq, Eq, Sbor)]
686 struct ExampleV4 {
687 the_value: u16,
688 }
689
690 impl ExampleV4 {
691 pub fn of(value: u16) -> Self {
692 Self { the_value: value }
693 }
694 }
695
696 // And explicit updates between them, which are needed
697 // for the versioned type
698 impl From<ExampleV2> for ExampleV4 {
699 fn from(value: ExampleV2) -> Self {
700 Self { the_value: value }
701 }
702 }
703
704 impl From<ExampleV3> for ExampleV4 {
705 fn from(value: ExampleV3) -> Self {
706 Self { the_value: value.0 }
707 }
708 }
709
710 #[test]
711 pub fn updates_to_latest_work() {
712 let expected_latest = ExampleV4::of(5);
713 let v1: ExampleV1 = 5;
714 validate_latest(v1, expected_latest.clone());
715 let v2: ExampleV2 = 5;
716 validate_latest(v2, expected_latest.clone());
717 let v3 = ExampleV3(5);
718 validate_latest(v3, expected_latest.clone());
719 let v4 = ExampleV4::of(5);
720 validate_latest(v4, expected_latest);
721 }
722
723 fn validate_latest(
724 actual: impl Into<VersionedExample>,
725 expected: <VersionedExample as Versioned>::LatestVersion,
726 ) {
727 let versioned_actual = actual.into().fully_update();
728 let versioned_expected = VersionedExample::from(expected.clone());
729 // Check fully_update (which returns a VersionedExample)
730 assert_eq!(versioned_actual, versioned_expected,);
731 // Check fully_update_and_into_latest_version (which returns an ExampleV4)
732 assert_eq!(
733 versioned_actual.fully_update_and_into_latest_version(),
734 expected,
735 );
736 }
737
738 #[derive(Debug, Clone, PartialEq, Eq, Sbor)]
739 struct GenericModelV1<T>(T);
740
741 define_single_versioned!(
742 /// This is some rust doc as an example annotation
743 #[derive(Debug, Clone, PartialEq, Eq, Sbor)]
744 VersionedGenericModel(GenericModelVersions)<T> => GenericModel<T> = GenericModelV1<T>
745 );
746
747 #[test]
748 pub fn generated_single_versioned_works() {
749 let v1_model: GenericModel<_> = GenericModelV1(51u64);
750 let versioned = VersionedGenericModel::from(v1_model.clone());
751 let versioned_2 = v1_model.clone().into_versioned();
752 assert_eq!(
753 versioned.clone().fully_update_and_into_latest_version(),
754 v1_model
755 );
756 assert_eq!(versioned, versioned_2);
757 }
758
759 #[test]
760 pub fn verify_sbor_equivalence() {
761 // Value model
762 let v1_model: GenericModel<_> = GenericModelV1(51u64);
763 let versions = GenericModelVersions::V1(v1_model.clone());
764 let versioned = VersionedGenericModel::from(v1_model.clone());
765 let expected_sbor_value = BasicEnumVariantValue {
766 // GenericModelVersions
767 discriminator: 0, // V1 maps to 0 for legacy compatibility
768 fields: vec![
769 // GenericModelV1
770 Value::Tuple {
771 fields: vec![Value::U64 { value: 51 }],
772 },
773 ],
774 };
775 let encoded_versioned = basic_encode(&versioned).unwrap();
776 let encoded_versions = basic_encode(&versions).unwrap();
777 let expected = basic_encode(&expected_sbor_value).unwrap();
778 assert_eq!(encoded_versioned, expected);
779 assert_eq!(encoded_versions, expected);
780
781 // Type model
782 check_identical_types::<VersionedGenericModel<u64>, GenericModelVersions<u64>>(Some(
783 "VersionedGenericModel",
784 ));
785 }
786
787 fn check_identical_types<T1: Describe<NoCustomTypeKind>, T2: Describe<NoCustomTypeKind>>(
788 name: Option<&'static str>,
789 ) {
790 let (type_id1, schema1) = generate_full_schema_from_single_type::<T1, NoCustomSchema>();
791 let (type_id2, schema2) = generate_full_schema_from_single_type::<T2, NoCustomSchema>();
792
793 assert_eq!(
794 schema1.v1().resolve_type_kind(type_id1),
795 schema2.v1().resolve_type_kind(type_id2)
796 );
797 assert_eq!(
798 schema1
799 .v1()
800 .resolve_type_metadata(type_id1)
801 .unwrap()
802 .clone(),
803 schema2
804 .v1()
805 .resolve_type_metadata(type_id2)
806 .unwrap()
807 .clone()
808 .with_name(name.map(Cow::Borrowed))
809 );
810 assert_eq!(
811 schema1.v1().resolve_type_validation(type_id1),
812 schema2.v1().resolve_type_validation(type_id2)
813 );
814 }
815}