use revision::prelude::*;
#[revisioned(revision(1))]
#[derive(Debug, Clone, PartialEq)]
struct OnlyRev1 {
a: u32,
b: u32,
}
#[revisioned(revision(1), revision(2, optimised))]
#[derive(Debug, Clone, PartialEq)]
struct LegacyAndOptimised {
a: u32,
b: u32,
}
#[revisioned(revision(1, optimised))]
#[derive(Debug, Clone, PartialEq)]
struct OptimisedFromDayOne {
a: u32,
b: u32,
}
#[revisioned(revision(1), revision(2), revision(3, optimised))]
#[derive(Debug, Clone, PartialEq)]
struct ThreeRevisions {
a: u32,
b: u32,
}
#[revisioned(revision(1), revision(2, optimised))]
#[derive(Debug, Clone, PartialEq)]
struct GrowsAcrossBoundary {
a: u32,
#[revision(start = 2, default_fn = "default_b")]
b: u32,
}
impl GrowsAcrossBoundary {
fn default_b(_revision: u16) -> Result<u32, revision::Error> {
Ok(42)
}
}
#[test]
fn legacy_to_optimised_decoder_produces_same_value() {
let rev1 = OnlyRev1 {
a: 100,
b: 200,
};
let rev1_bytes = revision::to_vec(&rev1).unwrap();
let decoded: LegacyAndOptimised = revision::from_slice(&rev1_bytes).unwrap();
assert_eq!(decoded.a, 100);
assert_eq!(decoded.b, 200);
}
#[test]
fn optimised_encode_then_decode_matches_legacy_value() {
let v = LegacyAndOptimised {
a: 100,
b: 200,
};
let optimised_bytes = revision::to_vec(&v).unwrap();
let decoded: LegacyAndOptimised = revision::from_slice(&optimised_bytes).unwrap();
assert_eq!(decoded, v);
let legacy_bytes = revision::to_vec(&OnlyRev1 {
a: 100,
b: 200,
})
.unwrap();
let decoded_legacy: LegacyAndOptimised = revision::from_slice(&legacy_bytes).unwrap();
assert_eq!(decoded_legacy, v);
}
#[test]
fn current_encode_always_uses_latest_revision() {
let v = LegacyAndOptimised {
a: 1,
b: 2,
};
let bytes = revision::to_vec(&v).unwrap();
assert_eq!(bytes[0], 2u8, "encoded bytes should declare rev 2");
}
#[test]
fn optimised_from_day_one_round_trips() {
let v = OptimisedFromDayOne {
a: 999,
b: 1000,
};
let bytes = revision::to_vec(&v).unwrap();
let decoded: OptimisedFromDayOne = revision::from_slice(&bytes).unwrap();
assert_eq!(decoded, v);
assert_eq!(bytes[0], 1u8);
}
#[test]
fn three_revision_history_decodes_each_arm() {
#[revisioned(revision(1))]
#[derive(Debug)]
struct Shadow1 {
a: u32,
b: u32,
}
#[revisioned(revision(1), revision(2))]
#[derive(Debug)]
struct Shadow2 {
a: u32,
b: u32,
}
let s1 = Shadow1 {
a: 7,
b: 8,
};
let s2 = Shadow2 {
a: 9,
b: 10,
};
let bytes1 = revision::to_vec(&s1).unwrap();
let bytes2 = revision::to_vec(&s2).unwrap();
let bytes3 = revision::to_vec(&ThreeRevisions {
a: 11,
b: 12,
})
.unwrap();
let d1: ThreeRevisions = revision::from_slice(&bytes1).unwrap();
let d2: ThreeRevisions = revision::from_slice(&bytes2).unwrap();
let d3: ThreeRevisions = revision::from_slice(&bytes3).unwrap();
assert_eq!(
d1,
ThreeRevisions {
a: 7,
b: 8,
}
);
assert_eq!(
d2,
ThreeRevisions {
a: 9,
b: 10,
}
);
assert_eq!(
d3,
ThreeRevisions {
a: 11,
b: 12,
}
);
assert_eq!(bytes3[0], 3u8);
}
#[test]
fn field_lifecycle_crosses_encoding_boundary() {
#[revisioned(revision(1))]
#[derive(Debug)]
struct OnlyA {
a: u32,
}
let bytes_rev1 = revision::to_vec(&OnlyA {
a: 50,
})
.unwrap();
let decoded: GrowsAcrossBoundary = revision::from_slice(&bytes_rev1).unwrap();
assert_eq!(decoded.a, 50);
assert_eq!(decoded.b, 42, "default_b should fill in the field absent at rev 1");
let v = GrowsAcrossBoundary {
a: 60,
b: 70,
};
let bytes_rev2 = revision::to_vec(&v).unwrap();
let decoded: GrowsAcrossBoundary = revision::from_slice(&bytes_rev2).unwrap();
assert_eq!(decoded, v);
}
#[cfg(not(feature = "fixed-width-encoding"))]
#[test]
fn pin_legacy_rev1_decodes_into_legacy_and_optimised_type() {
let pinned: &[u8] = &[
1, 1, 2, ];
let decoded: LegacyAndOptimised = revision::from_slice(pinned).unwrap();
assert_eq!(decoded.a, 1);
assert_eq!(decoded.b, 2);
}
#[cfg(not(feature = "fixed-width-encoding"))]
#[test]
fn pin_optimised_rev1_struct_layout() {
let pinned: &[u8] = &[
1, 2, 0, 0, 0, 7, 11, ];
let decoded: OptimisedFromDayOne = revision::from_slice(pinned).unwrap();
assert_eq!(decoded.a, 7);
assert_eq!(decoded.b, 11);
}
#[cfg(not(feature = "fixed-width-encoding"))]
#[revisioned(revision(1, optimised, indexed_struct))]
#[derive(Debug, PartialEq)]
struct PinIndexedStruct {
a: u32,
b: u32,
c: u32,
}
#[cfg(not(feature = "fixed-width-encoding"))]
#[test]
fn pin_optimised_indexed_struct_layout() {
let pinned: &[u8] = &[
1, 15, 0, 0, 0, 12, 0, 0, 0, 13, 0, 0, 0, 14, 0, 0, 0, 7, 11, 19, ];
let decoded: PinIndexedStruct = revision::from_slice(pinned).unwrap();
assert_eq!(decoded.a, 7);
assert_eq!(decoded.b, 11);
assert_eq!(decoded.c, 19);
}
#[cfg(not(feature = "fixed-width-encoding"))]
#[revisioned(revision(1, optimised))]
#[derive(Debug, PartialEq)]
enum PinOptimisedEnum {
#[revision(size = "varlen")]
Greeting(String),
}
#[cfg(not(feature = "fixed-width-encoding"))]
#[test]
fn pin_optimised_varlen_enum_variant_layout() {
let pinned: &[u8] = &[
1, 64, 3, 0, 0, 0, 2, b'h', b'i', ];
let decoded: PinOptimisedEnum = revision::from_slice(pinned).unwrap();
assert_eq!(decoded, PinOptimisedEnum::Greeting("hi".into()));
}
#[revisioned(revision(1))]
#[derive(Debug, Clone, PartialEq)]
struct InnerLegacy {
x: u32,
y: u32,
}
#[revisioned(revision(1), revision(2, optimised))]
#[derive(Debug, Clone, PartialEq)]
struct InnerOptimised {
x: u32,
y: u32,
}
#[revisioned(revision(1))]
#[derive(Debug, Clone, PartialEq)]
struct OuterLegacy {
tag: u32,
inner: InnerOptimised,
}
#[revisioned(revision(1), revision(2, optimised))]
#[derive(Debug, Clone, PartialEq)]
struct OuterOptimised {
tag: u32,
inner: InnerLegacy,
}
#[test]
fn legacy_outer_with_optimised_inner_round_trips() {
let v = OuterLegacy {
tag: 0xAA,
inner: InnerOptimised {
x: 1,
y: 2,
},
};
let bytes = revision::to_vec(&v).unwrap();
let decoded: OuterLegacy = revision::from_slice(&bytes).unwrap();
assert_eq!(decoded, v);
assert_eq!(bytes[0], 1u8);
}
#[test]
fn optimised_outer_with_legacy_inner_round_trips() {
let v = OuterOptimised {
tag: 0xBB,
inner: InnerLegacy {
x: 3,
y: 4,
},
};
let bytes = revision::to_vec(&v).unwrap();
let decoded: OuterOptimised = revision::from_slice(&bytes).unwrap();
assert_eq!(decoded, v);
assert_eq!(bytes[0], 2u8);
}
#[revisioned(revision(1), revision(2, optimised))]
#[derive(Debug, Clone, PartialEq)]
enum VariantLifecycle {
#[revision(end = 2, convert_fn = "migrate_old", size = "fixed(8)")]
Old([u8; 8]),
#[revision(start = 1, size = "fixed(8)")]
New([u8; 8]),
}
impl VariantLifecycle {
fn migrate_old(
fields: VariantLifecycleOldFields,
_revision: u16,
) -> Result<Self, revision::Error> {
Ok(VariantLifecycle::New(fields.0))
}
}
#[test]
fn variant_removed_across_optimised_boundary_routes_through_convert_fn() {
#[revisioned(revision(1))]
#[derive(Debug)]
enum Shadow {
Old([u8; 8]),
#[allow(dead_code)]
New([u8; 8]),
}
let bytes = revision::to_vec(&Shadow::Old([1, 2, 3, 4, 5, 6, 7, 8])).unwrap();
let decoded: VariantLifecycle = revision::from_slice(&bytes).unwrap();
assert_eq!(decoded, VariantLifecycle::New([1, 2, 3, 4, 5, 6, 7, 8]));
}
#[test]
fn nested_optimised_both_levels_round_trips() {
#[revisioned(revision(1, optimised))]
#[derive(Debug, Clone, PartialEq)]
struct Inner2 {
a: u32,
}
#[revisioned(revision(1, optimised))]
#[derive(Debug, Clone, PartialEq)]
struct Outer2 {
tag: u32,
inner: Inner2,
}
let v = Outer2 {
tag: 7,
inner: Inner2 {
a: 42,
},
};
let bytes = revision::to_vec(&v).unwrap();
let decoded: Outer2 = revision::from_slice(&bytes).unwrap();
assert_eq!(decoded, v);
}