use zerialize::{
Copied, Error, List, ListView, OwnedList, Zerializable, decode, encode, zerializable,
};
#[zerializable(derive(Debug, PartialEq))]
trait Contact {
#[n(0)]
fn name(&self) -> &str;
}
#[derive(Debug)]
struct OwnedContact(String);
impl Contact for OwnedContact {
fn name(&self) -> &str {
&self.0
}
}
#[zerializable(derive(PartialEq))]
#[derive(Debug)]
enum Reachable<C: Contact> {
#[variant(0)]
By(#[n(0)] C),
#[variant(1)]
Never,
}
#[zerializable(derive(Debug, PartialEq))]
trait Primitives {
#[n(0)]
fn flag(&self) -> bool;
#[n(1)]
fn letter(&self) -> char;
#[n(2)]
fn text(&self) -> &str;
#[n(3)]
fn blob(&self) -> &[u8];
#[n(4)]
fn byte(&self) -> u8;
#[n(5)]
fn short(&self) -> u16;
#[n(6)]
fn word(&self) -> u32;
#[n(7)]
fn long(&self) -> u64;
#[n(8)]
fn wide(&self) -> u128;
#[n(9)]
fn signed_byte(&self) -> i8;
#[n(10)]
fn signed_short(&self) -> i16;
#[n(11)]
fn signed_word(&self) -> i32;
#[n(12)]
fn signed_long(&self) -> i64;
#[n(13)]
fn signed_wide(&self) -> i128;
#[n(14)]
fn single(&self) -> f32;
#[n(15)]
fn double(&self) -> f64;
}
#[derive(Debug)]
struct OwnedPrimitives {
flag: bool,
letter: char,
text: String,
blob: Vec<u8>,
byte: u8,
short: u16,
word: u32,
long: u64,
wide: u128,
signed_byte: i8,
signed_short: i16,
signed_word: i32,
signed_long: i64,
signed_wide: i128,
single: f32,
double: f64,
}
impl Primitives for OwnedPrimitives {
fn flag(&self) -> bool {
self.flag
}
fn letter(&self) -> char {
self.letter
}
fn text(&self) -> &str {
&self.text
}
fn blob(&self) -> &[u8] {
&self.blob
}
fn byte(&self) -> u8 {
self.byte
}
fn short(&self) -> u16 {
self.short
}
fn word(&self) -> u32 {
self.word
}
fn long(&self) -> u64 {
self.long
}
fn wide(&self) -> u128 {
self.wide
}
fn signed_byte(&self) -> i8 {
self.signed_byte
}
fn signed_short(&self) -> i16 {
self.signed_short
}
fn signed_word(&self) -> i32 {
self.signed_word
}
fn signed_long(&self) -> i64 {
self.signed_long
}
fn signed_wide(&self) -> i128 {
self.signed_wide
}
fn single(&self) -> f32 {
self.single
}
fn double(&self) -> f64 {
self.double
}
}
fn lowest() -> OwnedPrimitives {
OwnedPrimitives {
flag: false,
letter: '\u{0}',
text: String::new(),
blob: Vec::new(),
byte: u8::MIN,
short: u16::MIN,
word: u32::MIN,
long: u64::MIN,
wide: u128::MIN,
signed_byte: i8::MIN,
signed_short: i16::MIN,
signed_word: i32::MIN,
signed_long: i64::MIN,
signed_wide: i128::MIN,
single: f32::NEG_INFINITY,
double: f64::MIN,
}
}
fn highest() -> OwnedPrimitives {
OwnedPrimitives {
flag: true,
letter: char::MAX,
text: "a\u{e9}\u{1f600}".to_string(),
blob: vec![0, 1, 254, 255],
byte: u8::MAX,
short: u16::MAX,
word: u32::MAX,
long: u64::MAX,
wide: u128::MAX,
signed_byte: i8::MAX,
signed_short: i16::MAX,
signed_word: i32::MAX,
signed_long: i64::MAX,
signed_wide: i128::MAX,
single: f32::MAX,
double: f64::INFINITY,
}
}
#[test]
fn every_primitive_round_trips() {
for source in [lowest(), highest()] {
let encoded = encode::<dyn Primitives>(&source);
let view = decode::<dyn Primitives>(&encoded).unwrap();
assert_eq!(view.flag(), source.flag);
assert_eq!(view.letter(), source.letter);
assert_eq!(view.text(), source.text);
assert_eq!(view.blob(), source.blob);
assert_eq!(view.byte(), source.byte);
assert_eq!(view.short(), source.short);
assert_eq!(view.word(), source.word);
assert_eq!(view.long(), source.long);
assert_eq!(view.wide(), source.wide);
assert_eq!(view.signed_byte(), source.signed_byte);
assert_eq!(view.signed_short(), source.signed_short);
assert_eq!(view.signed_word(), source.signed_word);
assert_eq!(view.signed_long(), source.signed_long);
assert_eq!(view.signed_wide(), source.signed_wide);
assert_eq!(view.single(), source.single);
assert_eq!(view.double(), source.double);
assert_eq!(view, source);
assert_eq!(encode::<dyn Primitives>(&view), encoded);
}
}
#[test]
fn a_str_borrows_from_the_buffer() {
let source = highest();
let encoded = encode::<dyn Primitives>(&source);
let view = decode::<dyn Primitives>(&encoded).unwrap();
let buffer = encoded.as_ptr() as usize..encoded.as_ptr() as usize + encoded.len();
assert!(buffer.contains(&(view.text().as_ptr() as usize)));
assert!(buffer.contains(&(view.blob().as_ptr() as usize)));
}
#[test]
fn corrupt_primitives_never_panic() {
let encoded = encode::<dyn Primitives>(&highest());
for index in 0..encoded.len() {
for bit in [0x01, 0x40, 0x80] {
let mut corrupted = encoded.clone();
corrupted[index] ^= bit;
if let Ok(view) = decode::<dyn Primitives>(&corrupted) {
let _ = view.letter();
let _ = view.text();
let _ = view.blob();
let _ = view.wide();
let _ = view.signed_wide();
}
}
}
}
#[zerializable(derive(Debug, PartialEq))]
trait Letter {
#[n(0)]
fn value(&self) -> char;
}
#[zerializable(derive(Debug, PartialEq))]
trait Number {
#[n(0)]
fn value(&self) -> u32;
}
struct Numbered(u32);
impl Number for Numbered {
fn value(&self) -> u32 {
self.0
}
}
#[test]
fn a_char_is_written_as_its_scalar_value() {
struct Lettered(char);
impl Letter for Lettered {
fn value(&self) -> char {
self.0
}
}
let encoded = encode::<dyn Letter>(&Lettered('A'));
assert_eq!(encoded, encode::<dyn Number>(&Numbered(0x41)));
assert_eq!(decode::<dyn Letter>(&encoded).unwrap().value(), 'A');
}
#[test]
fn a_char_that_is_not_a_scalar_value_is_rejected() {
for invalid in [0xd800, 0x110000] {
let encoded = encode::<dyn Number>(&Numbered(invalid));
assert_eq!(
decode::<dyn Letter>(&encoded).unwrap_err(),
Error::InvalidChar
);
}
let encoded = encode::<dyn Number>(&Numbered(0x10ffff));
assert_eq!(decode::<dyn Letter>(&encoded).unwrap().value(), char::MAX);
}
#[derive(Zerializable, Copy, Clone, Debug, PartialEq)]
struct Scalars {
#[n(0)]
flag: bool,
#[n(1)]
letter: char,
#[n(2)]
byte: u8,
#[n(3)]
short: u16,
#[n(4)]
word: u32,
#[n(5)]
long: u64,
#[n(6)]
wide: u128,
#[n(7)]
signed_byte: i8,
#[n(8)]
signed_short: i16,
#[n(9)]
signed_word: i32,
#[n(10)]
signed_long: i64,
#[n(11)]
signed_wide: i128,
#[n(12)]
single: f32,
#[n(13)]
double: f64,
}
const SCALARS: Scalars = Scalars {
flag: true,
letter: '\u{1f600}',
byte: u8::MAX,
short: u16::MAX,
word: u32::MAX,
long: u64::MAX,
wide: u128::MAX,
signed_byte: i8::MIN,
signed_short: i16::MIN,
signed_word: i32::MIN,
signed_long: i64::MIN,
signed_wide: i128::MIN,
single: -0.5,
double: 0.25,
};
#[zerializable(derive(Debug, PartialEq))]
trait Measured {
#[n(0)]
fn scalars(&self) -> Scalars;
}
struct OwnedMeasured(Scalars);
impl Measured for OwnedMeasured {
fn scalars(&self) -> Scalars {
self.0
}
}
#[test]
fn a_value_holds_every_scalar() {
let encoded = encode::<dyn Measured>(&OwnedMeasured(SCALARS));
let view = decode::<dyn Measured>(&encoded).unwrap();
assert_eq!(view.scalars(), SCALARS);
assert_eq!(view.scalars().letter, '\u{1f600}');
assert_eq!(view.scalars().wide, u128::MAX);
assert_eq!(encode::<dyn Measured>(&view), encoded);
}
#[zerializable(derive(PartialEq))]
#[derive(Debug)]
enum Signal {
#[variant(0)]
Reading {
#[n(0)]
flag: bool,
#[n(1)]
letter: char,
#[n(2)]
byte: u8,
#[n(3)]
short: u16,
#[n(4)]
word: u32,
#[n(5)]
long: u64,
#[n(6)]
wide: u128,
#[n(7)]
signed_byte: i8,
#[n(8)]
signed_short: i16,
#[n(9)]
signed_word: i32,
#[n(10)]
signed_long: i64,
#[n(11)]
signed_wide: i128,
#[n(12)]
single: f32,
#[n(13)]
double: f64,
},
#[variant(1)]
Silent,
}
fn reading() -> Signal {
Signal::Reading {
flag: SCALARS.flag,
letter: SCALARS.letter,
byte: SCALARS.byte,
short: SCALARS.short,
word: SCALARS.word,
long: SCALARS.long,
wide: SCALARS.wide,
signed_byte: SCALARS.signed_byte,
signed_short: SCALARS.signed_short,
signed_word: SCALARS.signed_word,
signed_long: SCALARS.signed_long,
signed_wide: SCALARS.signed_wide,
single: SCALARS.single,
double: SCALARS.double,
}
}
#[test]
fn a_variant_holds_every_scalar() {
let encoded = encode::<Signal>(&reading());
let decoded = decode::<Signal>(&encoded).unwrap();
match decoded {
Signal::Reading {
letter,
wide,
signed_wide,
double,
..
} => {
assert_eq!(letter, SCALARS.letter);
assert_eq!(wide, u128::MAX);
assert_eq!(signed_wide, i128::MIN);
assert_eq!(double, 0.25);
}
Signal::Silent => panic!("decoded the wrong variant"),
}
assert_eq!(decoded, reading());
assert_eq!(encode::<Signal>(&reading().as_ref()), encoded);
}
#[zerializable(derive(PartialEq))]
#[derive(Debug)]
enum Sample {
#[variant(0)]
Measured(#[n(0)] Scalars, #[n(1)] Option<Scalars>),
#[variant(1)]
Missing,
}
#[test]
fn a_variant_holds_a_value() {
for held in [Some(SCALARS), None] {
let sample = Sample::Measured(SCALARS, held);
let encoded = encode::<Sample>(&sample);
let decoded = decode::<Sample>(&encoded).unwrap();
match decoded {
Sample::Measured(measured, optional) => {
assert_eq!(measured, SCALARS);
assert_eq!(optional, held);
}
Sample::Missing => panic!("decoded the wrong variant"),
}
assert_eq!(decoded, sample);
assert_eq!(encode::<Sample>(&sample.as_ref()), encoded);
}
}
#[derive(Zerializable, Copy, Clone, Debug, PartialEq)]
struct Weight {
#[n(0)]
grams: u32,
}
#[zerializable(derive(Debug, PartialEq))]
trait Parcel {
#[n(0)]
fn count(&self) -> Option<u32>;
#[n(1)]
fn label(&self) -> Option<&str>;
#[n(2)]
fn seal(&self) -> Option<&[u8]>;
#[n(3)]
fn weight(&self) -> Option<Weight>;
#[n(4)]
fn sender(&self) -> Option<impl Contact + '_>
where
Self: Sized;
#[n(5)]
fn reachable(&self) -> Option<Reachable<impl Contact + '_>>
where
Self: Sized;
#[n(6)]
fn recipients(&self) -> Option<impl List<Item = impl Contact + '_> + '_>
where
Self: Sized;
}
#[derive(Debug, Default)]
struct OwnedParcel {
count: Option<u32>,
label: Option<String>,
seal: Option<Vec<u8>>,
weight: Option<Weight>,
sender: Option<OwnedContact>,
reachable: Option<Reachable<OwnedContact>>,
recipients: Option<OwnedList<OwnedContact>>,
}
impl Parcel for OwnedParcel {
fn count(&self) -> Option<u32> {
self.count
}
fn label(&self) -> Option<&str> {
self.label.as_deref()
}
fn seal(&self) -> Option<&[u8]> {
self.seal.as_deref()
}
fn weight(&self) -> Option<Weight> {
self.weight
}
fn sender(&self) -> Option<impl Contact + '_>
where
Self: Sized,
{
self.sender.as_ref()
}
fn reachable(&self) -> Option<Reachable<impl Contact + '_>>
where
Self: Sized,
{
self.reachable.as_ref().map(Reachable::as_ref)
}
fn recipients(&self) -> Option<impl List<Item = impl Contact + '_> + '_>
where
Self: Sized,
{
self.recipients.as_ref()
}
}
fn parcel() -> OwnedParcel {
OwnedParcel {
count: Some(3),
label: Some("fragile".to_string()),
seal: Some(vec![7, 8, 9]),
weight: Some(Weight { grams: 500 }),
sender: Some(OwnedContact("Ada".to_string())),
reachable: Some(Reachable::By(OwnedContact("Grace".to_string()))),
recipients: Some(
vec![
OwnedContact("Alan".to_string()),
OwnedContact("Edsger".to_string()),
]
.into(),
),
}
}
#[test]
fn every_optional_field_round_trips_present() {
let source = parcel();
let encoded = encode::<dyn Parcel>(&source);
let view = decode::<dyn Parcel>(&encoded).unwrap();
assert_eq!(view.count(), Some(3));
assert_eq!(view.label(), Some("fragile"));
assert_eq!(view.seal(), Some(&[7, 8, 9][..]));
assert_eq!(view.weight(), Some(Weight { grams: 500 }));
assert_eq!(view.sender().unwrap().name(), "Ada");
match view.reachable() {
Some(Reachable::By(contact)) => assert_eq!(contact.name(), "Grace"),
_ => panic!("decoded the wrong variant"),
}
let recipients = view.recipients().unwrap();
assert_eq!(recipients.len(), 2);
assert_eq!(recipients.get(1).unwrap().name(), "Edsger");
assert_eq!(view, source);
assert_eq!(encode::<dyn Parcel>(&view), encoded);
}
#[test]
fn every_optional_field_round_trips_absent() {
let source = OwnedParcel::default();
let encoded = encode::<dyn Parcel>(&source);
let view = decode::<dyn Parcel>(&encoded).unwrap();
assert_eq!(view.count(), None);
assert_eq!(view.label(), None);
assert_eq!(view.seal(), None);
assert_eq!(view.weight(), None);
assert!(view.sender().is_none());
assert!(view.reachable().is_none());
assert!(view.recipients().is_none());
assert_eq!(view, source);
assert_eq!(encode::<dyn Parcel>(&view), encoded);
assert!(encoded.len() < encode::<dyn Parcel>(&parcel()).len());
}
#[test]
fn an_absent_field_differs_from_a_present_one() {
let some = parcel();
let none = OwnedParcel::default();
let encoded = encode::<dyn Parcel>(&some);
let view = decode::<dyn Parcel>(&encoded).unwrap();
assert_ne!(view, none);
assert_ne!(
decode::<dyn Parcel>(&encode::<dyn Parcel>(&none)).unwrap(),
some
);
let empty = OwnedParcel {
label: Some(String::new()),
seal: Some(Vec::new()),
recipients: Some(OwnedList::new()),
..OwnedParcel::default()
};
let encoded = encode::<dyn Parcel>(&empty);
let view = decode::<dyn Parcel>(&encoded).unwrap();
assert_eq!(view.label(), Some(""));
assert_eq!(view.seal(), Some(&[][..]));
assert!(view.recipients().unwrap().is_empty());
assert_ne!(view, none);
assert_eq!(view, empty);
}
#[test]
fn an_optional_field_prints_as_what_it_holds() {
let encoded = encode::<dyn Parcel>(&parcel());
let view = decode::<dyn Parcel>(&encoded).unwrap();
let printed = format!("{view:?}");
assert!(printed.contains("Some(3)"));
assert!(printed.contains("Edsger"));
let encoded = encode::<dyn Parcel>(&OwnedParcel::default());
let printed = format!("{:?}", decode::<dyn Parcel>(&encoded).unwrap());
assert!(printed.contains("None"));
}
#[test]
fn corrupt_optional_input_never_panics() {
for source in [parcel(), OwnedParcel::default()] {
let encoded = encode::<dyn Parcel>(&source);
for index in 0..encoded.len() {
for bit in [0x01, 0x40, 0x80] {
let mut corrupted = encoded.clone();
corrupted[index] ^= bit;
let Ok(view) = decode::<dyn Parcel>(&corrupted) else {
continue;
};
let _ = view.count();
let _ = view.label();
let _ = view.seal();
let _ = view.weight();
let _ = view.sender().map(|sender| sender.name());
let _ = view.reachable();
if let Some(recipients) = view.recipients() {
for recipient in recipients.iter() {
let _ = recipient.name();
}
}
}
}
}
}
#[derive(Zerializable, Copy, Clone, Debug, PartialEq)]
struct Shipment {
#[n(0)]
weight: Option<Weight>,
#[n(1)]
priority: Option<u8>,
}
#[zerializable(derive(Debug, PartialEq))]
trait Manifest {
#[n(0)]
fn shipment(&self) -> Shipment;
}
struct OwnedManifest(Shipment);
impl Manifest for OwnedManifest {
fn shipment(&self) -> Shipment {
self.0
}
}
#[test]
fn a_value_holds_optional_fields() {
for shipment in [
Shipment {
weight: Some(Weight { grams: 250 }),
priority: Some(9),
},
Shipment {
weight: None,
priority: Some(9),
},
Shipment {
weight: None,
priority: None,
},
] {
let encoded = encode::<dyn Manifest>(&OwnedManifest(shipment));
let view = decode::<dyn Manifest>(&encoded).unwrap();
assert_eq!(view.shipment(), shipment);
assert_eq!(encode::<dyn Manifest>(&view), encoded);
}
}
#[zerializable(derive(PartialEq))]
#[derive(Debug)]
enum Delivery<C: Contact> {
#[variant(0)]
Sent {
#[n(0)]
to: Option<C>,
#[n(1)]
tracking: Option<u64>,
},
#[variant(1)]
Pending,
}
#[test]
fn a_variant_holds_optional_fields() {
let sent: Delivery<OwnedContact> = Delivery::Sent {
to: Some(OwnedContact("Ada".to_string())),
tracking: None,
};
let encoded = encode::<Delivery<dyn Contact>>(&sent);
match decode::<Delivery<dyn Contact>>(&encoded).unwrap() {
Delivery::Sent { to, tracking } => {
assert_eq!(to.unwrap().name(), "Ada");
assert_eq!(tracking, None);
}
Delivery::Pending => panic!("decoded the wrong variant"),
}
assert_eq!(decode::<Delivery<dyn Contact>>(&encoded).unwrap(), sent);
let borrowed: Delivery<&OwnedContact> = sent.as_ref();
assert_eq!(encode::<Delivery<dyn Contact>>(&borrowed), encoded);
let empty: Delivery<OwnedContact> = Delivery::Sent {
to: None,
tracking: Some(7),
};
let encoded = encode::<Delivery<dyn Contact>>(&empty);
let decoded = decode::<Delivery<dyn Contact>>(&encoded).unwrap();
assert!(matches!(
decoded,
Delivery::Sent {
to: None,
tracking: Some(7)
}
));
assert_eq!(decoded, empty);
assert_ne!(decode::<Delivery<dyn Contact>>(&encoded).unwrap(), sent);
}
mod earlier {
use zerialize::zerializable;
#[zerializable(derive(Debug, PartialEq))]
pub trait Record {
#[n(0)]
fn id(&self) -> u32;
}
pub struct OwnedRecord;
impl Record for OwnedRecord {
fn id(&self) -> u32 {
7
}
}
}
mod later {
use zerialize::zerializable;
#[zerializable(derive(Debug, PartialEq))]
pub trait Record {
#[n(0)]
fn id(&self) -> u32;
#[n(1)]
fn note(&self) -> Option<&str>;
}
pub struct OwnedRecord(pub Option<&'static str>);
impl Record for OwnedRecord {
fn id(&self) -> u32 {
7
}
fn note(&self) -> Option<&str> {
self.0
}
}
}
#[test]
fn a_field_added_as_optional_reads_both_ways() {
let encoded = encode::<dyn earlier::Record>(&earlier::OwnedRecord);
let view = decode::<dyn later::Record>(&encoded).unwrap();
assert_eq!(view.id(), 7);
assert_eq!(view.note(), None);
for note in [Some("added later"), None] {
let encoded = encode::<dyn later::Record>(&later::OwnedRecord(note));
assert_eq!(decode::<dyn earlier::Record>(&encoded).unwrap().id(), 7);
}
}
#[zerializable(derive(Debug, PartialEq))]
trait Directory {
#[n(0)]
fn contacts(&self) -> impl List<Item = impl Contact + '_> + '_
where
Self: Sized;
#[n(1)]
fn reachable(&self) -> impl List<Item = Reachable<impl Contact + '_>> + '_
where
Self: Sized;
}
#[derive(Debug)]
struct OwnedDirectory {
contacts: OwnedList<OwnedContact>,
reachable: OwnedList<Reachable<OwnedContact>>,
}
struct Reachables<'a>(&'a OwnedList<Reachable<OwnedContact>>);
impl<'a> List for Reachables<'a> {
type Item = Reachable<&'a OwnedContact>;
fn len(&self) -> usize {
self.0.len()
}
fn get(&self, index: usize) -> Option<Self::Item> {
Some(self.0.as_slice().get(index)?.as_ref())
}
}
impl Directory for OwnedDirectory {
fn contacts(&self) -> impl List<Item = impl Contact + '_> + '_
where
Self: Sized,
{
&self.contacts
}
fn reachable(&self) -> impl List<Item = Reachable<impl Contact + '_>> + '_
where
Self: Sized,
{
Reachables(&self.reachable)
}
}
fn directory() -> OwnedDirectory {
OwnedDirectory {
contacts: vec![
OwnedContact("Ada".to_string()),
OwnedContact("Grace".to_string()),
]
.into(),
reachable: vec![
Reachable::By(OwnedContact("Alan".to_string())),
Reachable::Never,
]
.into(),
}
}
#[test]
fn a_list_holds_messages_and_choices() {
let source = directory();
let encoded = encode::<dyn Directory>(&source);
let view = decode::<dyn Directory>(&encoded).unwrap();
let contacts = view.contacts();
assert_eq!(contacts.len(), 2);
assert_eq!(contacts.get(0).unwrap().name(), "Ada");
assert_eq!(contacts.get(1).unwrap().name(), "Grace");
assert!(contacts.get(2).is_none());
let reachable = view.reachable();
assert_eq!(reachable.len(), 2);
match reachable.get(0).unwrap() {
Reachable::By(contact) => assert_eq!(contact.name(), "Alan"),
Reachable::Never => panic!("decoded the wrong variant"),
}
assert!(matches!(reachable.get(1).unwrap(), Reachable::Never));
assert_eq!(view, source);
assert_eq!(encode::<dyn Directory>(&view), encoded);
}
#[test]
fn an_empty_list_round_trips() {
let source = OwnedDirectory {
contacts: OwnedList::new(),
reachable: OwnedList::new(),
};
let encoded = encode::<dyn Directory>(&source);
let view = decode::<dyn Directory>(&encoded).unwrap();
assert!(view.contacts().is_empty());
assert_eq!(view.reachable().iter().count(), 0);
assert_eq!(view, source);
}
#[zerializable(derive(Debug, PartialEq))]
trait Inventory {
#[n(0)]
fn counts(&self) -> impl List<Item = u32> + '_
where
Self: Sized;
#[n(1)]
fn marks(&self) -> impl List<Item = char> + '_
where
Self: Sized;
#[n(2)]
fn names(&self) -> impl List<Item = &str> + '_
where
Self: Sized;
#[n(3)]
fn blobs(&self) -> impl List<Item = &[u8]> + '_
where
Self: Sized;
#[n(4)]
fn weights(&self) -> impl List<Item = Weight> + '_
where
Self: Sized;
}
#[derive(Debug, Default)]
struct OwnedInventory {
counts: OwnedList<u32>,
marks: OwnedList<char>,
names: Vec<String>,
blobs: Vec<Vec<u8>>,
weights: OwnedList<Weight>,
}
#[derive(Debug)]
struct Borrowed<'a, T>(&'a [T]);
impl<'a> List for Borrowed<'a, String> {
type Item = &'a str;
fn len(&self) -> usize {
self.0.len()
}
fn get(&self, index: usize) -> Option<&'a str> {
Some(self.0.get(index)?.as_str())
}
}
impl<'a> List for Borrowed<'a, Vec<u8>> {
type Item = &'a [u8];
fn len(&self) -> usize {
self.0.len()
}
fn get(&self, index: usize) -> Option<&'a [u8]> {
Some(self.0.get(index)?.as_slice())
}
}
impl Inventory for OwnedInventory {
fn counts(&self) -> impl List<Item = u32> + '_
where
Self: Sized,
{
Copied(&self.counts)
}
fn marks(&self) -> impl List<Item = char> + '_
where
Self: Sized,
{
Copied(&self.marks)
}
fn names(&self) -> impl List<Item = &str> + '_
where
Self: Sized,
{
Borrowed(&self.names)
}
fn blobs(&self) -> impl List<Item = &[u8]> + '_
where
Self: Sized,
{
Borrowed(&self.blobs)
}
fn weights(&self) -> impl List<Item = Weight> + '_
where
Self: Sized,
{
Copied(&self.weights)
}
}
fn inventory() -> OwnedInventory {
OwnedInventory {
counts: vec![0, 1, u32::MAX].into(),
marks: vec!['a', '\u{1f600}'].into(),
names: vec!["one".to_string(), String::new()],
blobs: vec![vec![1, 2], Vec::new()],
weights: vec![Weight { grams: 5 }, Weight { grams: 6 }].into(),
}
}
#[test]
fn a_list_holds_primitives_and_values() {
let source = inventory();
let encoded = encode::<dyn Inventory>(&source);
let view = decode::<dyn Inventory>(&encoded).unwrap();
assert_eq!(view.counts().iter().collect::<Vec<_>>(), [0, 1, u32::MAX]);
assert_eq!(view.marks().get(1).unwrap(), '\u{1f600}');
assert_eq!(view.names().iter().collect::<Vec<_>>(), ["one", ""]);
assert_eq!(view.blobs().get(0).unwrap(), &[1, 2][..]);
assert!(view.blobs().get(1).unwrap().is_empty());
assert_eq!(view.weights().get(1).unwrap(), Weight { grams: 6 });
assert!(view.counts().get(3).is_none());
assert_eq!(size_of_val(&view.counts()), size_of_val(&view.names()));
assert_eq!(size_of_val(&view.counts()), size_of_val(&view.weights()));
assert_eq!(view, source);
assert_eq!(encode::<dyn Inventory>(&view), encoded);
}
#[test]
fn an_empty_list_of_primitives_round_trips() {
let source = OwnedInventory::default();
let encoded = encode::<dyn Inventory>(&source);
let view = decode::<dyn Inventory>(&encoded).unwrap();
assert!(view.counts().is_empty());
assert!(view.names().is_empty());
assert_eq!(view, source);
}
#[test]
fn a_borrowed_element_points_into_the_buffer() {
let encoded = encode::<dyn Inventory>(&inventory());
let view = decode::<dyn Inventory>(&encoded).unwrap();
let buffer = encoded.as_ptr() as usize..encoded.as_ptr() as usize + encoded.len();
let name = view.names().get(0).unwrap();
assert!(buffer.contains(&(name.as_ptr() as usize)));
}
#[test]
fn corrupt_lists_of_primitives_never_panic() {
let encoded = encode::<dyn Inventory>(&inventory());
for index in 0..encoded.len() {
for bit in [0x01, 0x40, 0x80] {
let mut corrupted = encoded.clone();
corrupted[index] ^= bit;
let Ok(view) = decode::<dyn Inventory>(&corrupted) else {
continue;
};
for count in view.counts().iter() {
let _ = count;
}
for mark in view.marks().iter() {
let _ = mark;
}
for name in view.names().iter() {
let _ = name;
}
for blob in view.blobs().iter() {
let _ = blob;
}
for weight in view.weights().iter() {
let _ = weight;
}
}
}
}
#[derive(Zerializable, Copy, Clone, Debug, PartialEq)]
enum Protocol {
#[variant(0)]
Tcp {
#[n(0)]
port: u16,
},
#[variant(1)]
Udp {
#[n(0)]
port: u16,
#[n(1)]
checked: Option<bool>,
},
#[variant(2)]
Raw(#[n(0)] u8, #[n(1)] Weight),
#[variant(3)]
Unknown,
}
#[zerializable(derive(Debug, PartialEq))]
trait Endpoint {
#[n(0)]
fn protocol(&self) -> Protocol;
#[n(1)]
fn fallback(&self) -> Option<Protocol>;
#[n(2)]
fn tried(&self) -> impl List<Item = Protocol> + '_
where
Self: Sized;
}
#[derive(Debug)]
struct OwnedEndpoint {
protocol: Protocol,
fallback: Option<Protocol>,
tried: OwnedList<Protocol>,
}
impl Endpoint for OwnedEndpoint {
fn protocol(&self) -> Protocol {
self.protocol
}
fn fallback(&self) -> Option<Protocol> {
self.fallback
}
fn tried(&self) -> impl List<Item = Protocol> + '_
where
Self: Sized,
{
Copied(&self.tried)
}
}
const PROTOCOLS: [Protocol; 5] = [
Protocol::Tcp { port: 443 },
Protocol::Udp {
port: 53,
checked: Some(true),
},
Protocol::Udp {
port: 53,
checked: None,
},
Protocol::Raw(7, Weight { grams: 9 }),
Protocol::Unknown,
];
#[test]
fn a_value_enum_carries_fields() {
for protocol in PROTOCOLS {
let source = OwnedEndpoint {
protocol,
fallback: Some(Protocol::Unknown),
tried: PROTOCOLS.to_vec().into(),
};
let encoded = encode::<dyn Endpoint>(&source);
let view = decode::<dyn Endpoint>(&encoded).unwrap();
assert_eq!(view.protocol(), protocol);
assert_eq!(view.fallback(), Some(Protocol::Unknown));
assert_eq!(view.tried().iter().collect::<Vec<_>>(), PROTOCOLS);
assert_eq!(view, source);
assert_eq!(encode::<dyn Endpoint>(&view), encoded);
}
}
#[test]
fn a_value_enum_outlives_the_buffer_it_was_read_from() {
let protocol = {
let source = OwnedEndpoint {
protocol: Protocol::Raw(1, Weight { grams: 2 }),
fallback: None,
tried: OwnedList::new(),
};
let encoded = encode::<dyn Endpoint>(&source);
decode::<dyn Endpoint>(&encoded).unwrap().protocol()
};
assert_eq!(protocol, Protocol::Raw(1, Weight { grams: 2 }));
}
#[test]
fn corrupt_value_enums_never_panic() {
let source = OwnedEndpoint {
protocol: Protocol::Udp {
port: 53,
checked: Some(false),
},
fallback: Some(Protocol::Tcp { port: 1 }),
tried: PROTOCOLS.to_vec().into(),
};
let encoded = encode::<dyn Endpoint>(&source);
for index in 0..encoded.len() {
for bit in [0x01, 0x40, 0x80] {
let mut corrupted = encoded.clone();
corrupted[index] ^= bit;
if let Ok(view) = decode::<dyn Endpoint>(&corrupted) {
let _ = view.protocol();
let _ = view.fallback();
for protocol in view.tried().iter() {
let _ = protocol;
}
}
}
}
}
mod flags_v1 {
use zerialize::{Zerializable, zerializable};
#[derive(Zerializable, Copy, Clone, Debug, PartialEq)]
pub enum Flag {
#[variant(0)]
Set {
#[n(0)]
bits: u8,
},
#[variant(1)]
Clear,
}
#[zerializable(derive(Debug, PartialEq))]
pub trait Flagged {
#[n(0)]
fn flag(&self) -> Flag;
}
pub struct OwnedFlagged(pub Flag);
impl Flagged for OwnedFlagged {
fn flag(&self) -> Flag {
self.0
}
}
}
mod flags_v2 {
use zerialize::{Zerializable, zerializable};
#[derive(Zerializable, Copy, Clone, Debug, PartialEq)]
pub enum Flag {
#[variant(0)]
Set {
#[n(0)]
bits: u8,
#[n(1)]
wide: u32,
},
#[variant(1)]
Clear {
#[n(0)]
reason: Option<u8>,
},
#[variant(2)]
Unknown,
}
#[zerializable(derive(Debug, PartialEq))]
pub trait Flagged {
#[n(0)]
fn flag(&self) -> Flag;
}
pub struct OwnedFlagged(pub Flag);
impl Flagged for OwnedFlagged {
fn flag(&self) -> Flag {
self.0
}
}
}
#[test]
fn a_value_enums_variants_evolve_like_a_choices() {
let encoded = encode::<dyn flags_v2::Flagged>(&flags_v2::OwnedFlagged(flags_v2::Flag::Set {
bits: 3,
wide: 9,
}));
assert_eq!(
decode::<dyn flags_v1::Flagged>(&encoded).unwrap().flag(),
flags_v1::Flag::Set { bits: 3 }
);
let encoded = encode::<dyn flags_v2::Flagged>(&flags_v2::OwnedFlagged(flags_v2::Flag::Unknown));
assert_eq!(
decode::<dyn flags_v1::Flagged>(&encoded).unwrap_err(),
Error::UnknownVariant
);
let encoded =
encode::<dyn flags_v1::Flagged>(&flags_v1::OwnedFlagged(flags_v1::Flag::Set { bits: 3 }));
assert_eq!(
decode::<dyn flags_v2::Flagged>(&encoded).unwrap_err(),
Error::MissingField
);
}
#[test]
fn a_variant_that_gains_its_first_field_carries_an_absent_one() {
let encoded = encode::<dyn flags_v1::Flagged>(&flags_v1::OwnedFlagged(flags_v1::Flag::Clear));
assert_eq!(
decode::<dyn flags_v2::Flagged>(&encoded).unwrap().flag(),
flags_v2::Flag::Clear { reason: None }
);
}
#[zerializable(derive(PartialEq))]
#[derive(Debug)]
enum Note<'a, C: Contact> {
#[variant(0)]
Written {
#[n(0)]
text: &'a str,
#[n(1)]
seal: &'a [u8],
#[n(2)]
signed: Option<&'a str>,
},
#[variant(1)]
From(#[n(0)] C),
#[variant(2)]
Blank,
}
#[zerializable(derive(PartialEq))]
#[derive(Debug)]
enum Label<'a> {
#[variant(0)]
Text(#[n(0)] &'a str),
#[variant(1)]
None,
}
fn notes<'a>() -> [Note<'a, OwnedContact>; 3] {
[
Note::Written {
text: "hello",
seal: &[1, 2, 3],
signed: Some("Ada"),
},
Note::Written {
text: "",
seal: &[],
signed: None,
},
Note::Blank,
]
}
#[test]
fn a_variant_holds_borrowed_fields() {
for note in notes() {
let encoded = encode::<Note<'_, dyn Contact>>(¬e);
let decoded = decode::<Note<'_, dyn Contact>>(&encoded).unwrap();
assert_eq!(decoded, note);
assert_eq!(encode::<Note<'_, dyn Contact>>(¬e.as_ref()), encoded);
}
let encoded = encode::<Note<'_, dyn Contact>>(¬es()[0]);
match decode::<Note<'_, dyn Contact>>(&encoded).unwrap() {
Note::Written { text, seal, signed } => {
assert_eq!(text, "hello");
assert_eq!(seal, &[1, 2, 3]);
assert_eq!(signed, Some("Ada"));
}
_ => panic!("decoded the wrong variant"),
}
let from: Note<'_, OwnedContact> = Note::From(OwnedContact("Grace".to_string()));
let encoded = encode::<Note<'_, dyn Contact>>(&from);
match decode::<Note<'_, dyn Contact>>(&encoded).unwrap() {
Note::From(contact) => assert_eq!(contact.name(), "Grace"),
_ => panic!("decoded the wrong variant"),
}
}
#[test]
fn a_borrowed_field_points_into_the_buffer() {
let encoded = encode::<Note<'_, dyn Contact>>(¬es()[0]);
let buffer = encoded.as_ptr() as usize..encoded.as_ptr() as usize + encoded.len();
match decode::<Note<'_, dyn Contact>>(&encoded).unwrap() {
Note::Written { text, seal, .. } => {
assert!(buffer.contains(&(text.as_ptr() as usize)));
assert!(buffer.contains(&(seal.as_ptr() as usize)));
}
_ => panic!("decoded the wrong variant"),
}
}
#[test]
fn an_enum_that_borrows_and_carries_nothing_round_trips() {
for label in [Label::Text("hi"), Label::Text(""), Label::None] {
let encoded = encode::<Label<'_>>(&label);
assert_eq!(decode::<Label<'_>>(&encoded).unwrap(), label);
assert_eq!(encode::<Label<'_>>(&label.as_ref()), encoded);
}
}
#[zerializable(derive(Debug, PartialEq))]
trait Book {
#[n(0)]
fn title(&self) -> Label<'_>;
#[n(1)]
fn notes(&self) -> impl List<Item = Note<'_, impl Contact + '_>> + '_
where
Self: Sized;
}
#[derive(Debug)]
struct OwnedBook {
title: String,
notes: Vec<String>,
}
struct Notes<'a>(&'a [String]);
impl<'a> List for Notes<'a> {
type Item = Note<'a, &'a OwnedContact>;
fn len(&self) -> usize {
self.0.len()
}
fn get(&self, index: usize) -> Option<Self::Item> {
Some(Note::Written {
text: self.0.get(index)?.as_str(),
seal: &[],
signed: None,
})
}
}
impl Book for OwnedBook {
fn title(&self) -> Label<'_> {
Label::Text(&self.title)
}
fn notes(&self) -> impl List<Item = Note<'_, impl Contact + '_>> + '_
where
Self: Sized,
{
Notes(&self.notes)
}
}
#[test]
fn a_message_carries_enums_that_borrow() {
let source = OwnedBook {
title: "Notes".to_string(),
notes: vec!["first".to_string(), "second".to_string()],
};
let encoded = encode::<dyn Book>(&source);
let view = decode::<dyn Book>(&encoded).unwrap();
assert_eq!(view.title(), Label::Text("Notes"));
assert_eq!(view.notes().len(), 2);
match view.notes().get(1).unwrap() {
Note::Written { text, .. } => assert_eq!(text, "second"),
_ => panic!("decoded the wrong variant"),
}
let buffer = encoded.as_ptr() as usize..encoded.as_ptr() as usize + encoded.len();
match view.title() {
Label::Text(title) => assert!(buffer.contains(&(title.as_ptr() as usize))),
Label::None => panic!("decoded the wrong variant"),
}
assert_eq!(view, source);
assert!(format!("{view:?}").contains("second"));
assert_eq!(encode::<dyn Book>(&view), encoded);
}
#[test]
fn corrupt_borrowed_enums_never_panic() {
let source = OwnedBook {
title: "Notes".to_string(),
notes: vec!["first".to_string()],
};
let encoded = encode::<dyn Book>(&source);
for index in 0..encoded.len() {
for bit in [0x01, 0x40, 0x80] {
let mut corrupted = encoded.clone();
corrupted[index] ^= bit;
let Ok(view) = decode::<dyn Book>(&corrupted) else {
continue;
};
let _ = view.title();
for note in view.notes().iter() {
let _ = note;
}
}
}
}
#[derive(Zerializable, Copy, Clone, Debug, PartialEq)]
enum Vacancy {
#[variant(0)]
Unit,
#[variant(1)]
Tuple(),
#[variant(2)]
Named {},
}
#[zerializable(derive(Debug, PartialEq))]
trait Vacancies {
#[n(0)]
fn vacancy(&self) -> Vacancy;
}
struct OwnedVacancies(Vacancy);
impl Vacancies for OwnedVacancies {
fn vacancy(&self) -> Vacancy {
self.0
}
}
#[test]
fn a_value_variant_carrying_nothing_keeps_the_shape_it_was_written_with() {
for vacancy in [Vacancy::Unit, Vacancy::Tuple(), Vacancy::Named {}] {
let encoded = encode::<dyn Vacancies>(&OwnedVacancies(vacancy));
let view = decode::<dyn Vacancies>(&encoded).unwrap();
assert_eq!(view.vacancy(), vacancy);
assert_eq!(encode::<dyn Vacancies>(&view), encoded);
}
assert!(matches!(
decode::<dyn Vacancies>(&encode::<dyn Vacancies>(&OwnedVacancies(Vacancy::Tuple())))
.unwrap()
.vacancy(),
Vacancy::Tuple()
));
}
mod signal_before {
use zerialize::zerializable;
#[zerializable(derive(PartialEq))]
#[derive(Debug)]
pub enum Signal {
#[variant(0)]
Raise(#[n(0)] u32),
#[variant(1)]
Lower,
}
}
mod signal_after {
use zerialize::zerializable;
#[zerializable(derive(PartialEq))]
#[derive(Debug)]
pub enum Signal {
#[variant(0)]
Raise(#[n(0)] u32),
#[variant(1)]
Lower {
#[n(0)]
reason: Option<u32>,
#[n(1)]
forced: Option<bool>,
},
}
}
#[test]
fn a_choices_variant_that_gains_its_first_field_carries_an_absent_one() {
let encoded = encode::<signal_before::Signal>(&signal_before::Signal::Lower);
assert_eq!(
decode::<signal_after::Signal>(&encoded).unwrap(),
signal_after::Signal::Lower {
reason: None,
forced: None,
}
);
let encoded = encode::<signal_before::Signal>(&signal_before::Signal::Lower);
assert!(decode::<signal_after::Signal>(&encoded).is_ok());
assert_eq!(
decode::<signal_before::Signal>(&encode::<signal_after::Signal>(
&signal_after::Signal::Lower {
reason: Some(1),
forced: None,
}
))
.unwrap(),
signal_before::Signal::Lower
);
}
#[zerializable(derive(PartialEq))]
#[derive(Debug)]
enum Roster<
'a,
C: Contact,
M: List<Item: Contact>,
N: List<Item = &'a str>,
S: List<Item = &'a [u8]>,
W: List<Item = Weight>,
Q: List<Item = u32>,
> {
#[variant(0)]
Team(#[n(0)] M),
#[variant(1)]
Named {
#[n(0)]
names: N,
#[n(1)]
seals: S,
},
#[variant(2)]
Weighed(#[n(0)] W, #[n(1)] Option<Q>),
#[variant(3)]
Counted(#[n(0)] Q),
#[variant(4)]
Lead(#[n(0)] C),
#[variant(5)]
Nobody,
}
type OwnedRoster<'a> = Roster<
'a,
&'a OwnedContact,
&'a OwnedList<OwnedContact>,
Borrowed<'a, String>,
Borrowed<'a, Vec<u8>>,
Copied<&'a OwnedList<Weight>>,
Copied<&'a OwnedList<u32>>,
>;
type RosterSchema<'a> = Roster<
'a,
dyn Contact,
ListView<'a, dyn Contact>,
ListView<'a, str>,
ListView<'a, [u8]>,
ListView<'a, Weight>,
ListView<'a, u32>,
>;
#[derive(Debug, Default)]
struct Held {
contacts: OwnedList<OwnedContact>,
names: Vec<String>,
seals: Vec<Vec<u8>>,
weights: OwnedList<Weight>,
counts: OwnedList<u32>,
}
impl Held {
fn rosters(&self) -> [OwnedRoster<'_>; 7] {
[
Roster::Team(&self.contacts),
Roster::Named {
names: Borrowed(&self.names),
seals: Borrowed(&self.seals),
},
Roster::Weighed(Copied(&self.weights), Some(Copied(&self.counts))),
Roster::Weighed(Copied(&self.weights), None),
Roster::Counted(Copied(&self.counts)),
Roster::Lead(&self.contacts.as_slice()[0]),
Roster::Nobody,
]
}
}
fn held() -> Held {
Held {
contacts: vec![
OwnedContact("Ada".to_string()),
OwnedContact("Grace".to_string()),
]
.into(),
names: vec!["one".to_string(), String::new()],
seals: vec![vec![1, 2], Vec::new()],
weights: vec![Weight { grams: 5 }, Weight { grams: 6 }].into(),
counts: vec![0, u32::MAX].into(),
}
}
#[test]
fn a_variant_holds_lists() {
let held = held();
for source in held.rosters() {
let encoded = encode::<RosterSchema<'_>>(&source);
let decoded = decode::<RosterSchema<'_>>(&encoded).unwrap();
assert_eq!(decoded, source);
assert_eq!(encode::<RosterSchema<'_>>(&decoded), encoded);
assert_eq!(encode::<RosterSchema<'_>>(&source.as_ref()), encoded);
}
let encoded = encode::<RosterSchema<'_>>(&held.rosters()[0]);
match decode::<RosterSchema<'_>>(&encoded).unwrap() {
Roster::Team(team) => {
assert_eq!(team.len(), 2);
assert_eq!(team.get(0).unwrap().name(), "Ada");
assert_eq!(team.get(1).unwrap().name(), "Grace");
assert!(team.get(2).is_none());
}
_ => panic!("decoded the wrong variant"),
}
let encoded = encode::<RosterSchema<'_>>(&held.rosters()[1]);
match decode::<RosterSchema<'_>>(&encoded).unwrap() {
Roster::Named { names, seals } => {
assert_eq!(names.iter().collect::<Vec<_>>(), ["one", ""]);
assert_eq!(seals.get(0).unwrap(), &[1, 2][..]);
assert!(seals.get(1).unwrap().is_empty());
}
_ => panic!("decoded the wrong variant"),
}
let encoded = encode::<RosterSchema<'_>>(&held.rosters()[2]);
match decode::<RosterSchema<'_>>(&encoded).unwrap() {
Roster::Weighed(weights, counts) => {
assert_eq!(weights.get(1).unwrap(), Weight { grams: 6 });
assert_eq!(counts.unwrap().iter().collect::<Vec<_>>(), [0, u32::MAX]);
}
_ => panic!("decoded the wrong variant"),
}
let encoded = encode::<RosterSchema<'_>>(&held.rosters()[3]);
match decode::<RosterSchema<'_>>(&encoded).unwrap() {
Roster::Weighed(_, counts) => assert!(counts.is_none()),
_ => panic!("decoded the wrong variant"),
}
}
#[test]
fn an_empty_list_a_variant_holds_round_trips() {
let held = Held::default();
let empty: [OwnedRoster<'_>; 2] = [
Roster::Team(&held.contacts),
Roster::Named {
names: Borrowed(&held.names),
seals: Borrowed(&held.seals),
},
];
for source in empty {
let encoded = encode::<RosterSchema<'_>>(&source);
let decoded = decode::<RosterSchema<'_>>(&encoded).unwrap();
assert_eq!(decoded, source);
match decoded {
Roster::Team(team) => assert!(team.is_empty()),
Roster::Named { names, seals } => {
assert!(names.is_empty());
assert!(seals.is_empty());
}
_ => panic!("decoded the wrong variant"),
}
}
}
#[test]
fn an_element_of_a_list_a_variant_holds_points_into_the_buffer() {
let held = held();
let encoded = encode::<RosterSchema<'_>>(&held.rosters()[1]);
let buffer = encoded.as_ptr() as usize..encoded.as_ptr() as usize + encoded.len();
match decode::<RosterSchema<'_>>(&encoded).unwrap() {
Roster::Named { names, seals } => {
let name = names.get(0).unwrap();
assert!(buffer.contains(&(name.as_ptr() as usize)));
let seal = seals.get(0).unwrap();
assert!(buffer.contains(&(seal.as_ptr() as usize)));
}
_ => panic!("decoded the wrong variant"),
}
}
#[zerializable(derive(Debug, PartialEq))]
trait Register {
#[n(0)]
fn roster(
&self,
) -> Roster<
'_,
impl Contact + '_,
impl List<Item: Contact> + '_,
impl List<Item = &str> + '_,
impl List<Item = &[u8]> + '_,
impl List<Item = Weight> + '_,
impl List<Item = u32> + '_,
>
where
Self: Sized;
}
#[derive(Debug)]
struct OwnedRegister(Held);
impl Register for OwnedRegister {
fn roster(
&self,
) -> Roster<
'_,
impl Contact + '_,
impl List<Item: Contact> + '_,
impl List<Item = &str> + '_,
impl List<Item = &[u8]> + '_,
impl List<Item = Weight> + '_,
impl List<Item = u32> + '_,
>
where
Self: Sized,
{
let roster: OwnedRoster<'_> = Roster::Team(&self.0.contacts);
roster
}
}
#[test]
fn a_message_carries_an_enum_that_carries_a_list() {
let source = OwnedRegister(held());
let encoded = encode::<dyn Register>(&source);
let view = decode::<dyn Register>(&encoded).unwrap();
match view.roster() {
Roster::Team(team) => assert_eq!(team.get(0).unwrap().name(), "Ada"),
_ => panic!("decoded the wrong variant"),
}
assert_eq!(view, source);
assert_eq!(encode::<dyn Register>(&view), encoded);
}
#[test]
fn corrupt_enums_carrying_lists_never_panic() {
let held = held();
for source in held.rosters() {
let encoded = encode::<RosterSchema<'_>>(&source);
for index in 0..encoded.len() {
for bit in [0x01, 0x40, 0x80] {
let mut corrupted = encoded.clone();
corrupted[index] ^= bit;
let Ok(decoded) = decode::<RosterSchema<'_>>(&corrupted) else {
continue;
};
match decoded {
Roster::Team(team) => {
for contact in team.iter() {
let _ = contact.name();
}
}
Roster::Named { names, seals } => {
for name in names.iter() {
let _ = name;
}
for seal in seals.iter() {
let _ = seal;
}
}
Roster::Weighed(weights, counts) => {
for weight in weights.iter() {
let _ = weight;
}
for count in counts.iter().flat_map(List::iter) {
let _ = count;
}
}
Roster::Counted(counts) => {
for count in counts.iter() {
let _ = count;
}
}
Roster::Lead(contact) => {
let _ = contact.name();
}
Roster::Nobody => (),
}
}
}
}
}
#[zerializable(derive(PartialEq))]
#[derive(Debug)]
enum Tagged<'a, C: Contact> {
#[variant(0)]
Labelled {
#[n(0)]
label: Label<'a>,
#[n(1)]
who: Reachable<C>,
},
#[variant(1)]
Maybe(#[n(0)] Option<Reachable<C>>),
#[variant(2)]
Untagged,
}
fn tagged(contact: &OwnedContact) -> [Tagged<'_, &OwnedContact>; 6] {
[
Tagged::Labelled {
label: Label::Text("hello"),
who: Reachable::By(contact),
},
Tagged::Labelled {
label: Label::None,
who: Reachable::Never,
},
Tagged::Maybe(Some(Reachable::By(contact))),
Tagged::Maybe(Some(Reachable::Never)),
Tagged::Maybe(None),
Tagged::Untagged,
]
}
#[test]
fn a_variant_holds_view_enums() {
let contact = OwnedContact("Ada".to_string());
for source in tagged(&contact) {
let encoded = encode::<Tagged<'_, dyn Contact>>(&source);
let decoded = decode::<Tagged<'_, dyn Contact>>(&encoded).unwrap();
assert_eq!(decoded, source);
assert_eq!(encode::<Tagged<'_, dyn Contact>>(&decoded), encoded);
assert_eq!(encode::<Tagged<'_, dyn Contact>>(&source.as_ref()), encoded);
}
let encoded = encode::<Tagged<'_, dyn Contact>>(&tagged(&contact)[0]);
match decode::<Tagged<'_, dyn Contact>>(&encoded).unwrap() {
Tagged::Labelled { label, who } => {
assert_eq!(label, Label::Text("hello"));
match who {
Reachable::By(contact) => assert_eq!(contact.name(), "Ada"),
Reachable::Never => panic!("decoded the wrong variant"),
}
}
_ => panic!("decoded the wrong variant"),
}
let encoded = encode::<Tagged<'_, dyn Contact>>(&tagged(&contact)[4]);
match decode::<Tagged<'_, dyn Contact>>(&encoded).unwrap() {
Tagged::Maybe(who) => assert!(who.is_none()),
_ => panic!("decoded the wrong variant"),
}
}
#[test]
fn corrupt_enums_carrying_enums_never_panic() {
let contact = OwnedContact("Ada".to_string());
for source in tagged(&contact) {
let encoded = encode::<Tagged<'_, dyn Contact>>(&source);
for index in 0..encoded.len() {
for bit in [0x01, 0x40, 0x80] {
let mut corrupted = encoded.clone();
corrupted[index] ^= bit;
let Ok(decoded) = decode::<Tagged<'_, dyn Contact>>(&corrupted) else {
continue;
};
match decoded {
Tagged::Labelled { label, who } => {
let _ = label;
if let Reachable::By(contact) = who {
let _ = contact.name();
}
}
Tagged::Maybe(Some(Reachable::By(contact))) => {
let _ = contact.name();
}
Tagged::Maybe(_) | Tagged::Untagged => (),
}
}
}
}
}
#[derive(Zerializable, Copy, Clone, Debug, PartialEq)]
struct Stamp<'a> {
#[n(0)]
code: &'a str,
#[n(1)]
seal: &'a [u8],
#[n(2)]
grams: u32,
#[n(3)]
note: Option<&'a str>,
}
#[derive(Zerializable, Copy, Clone, Debug, PartialEq)]
struct Franked<'a> {
#[n(0)]
stamp: Stamp<'a>,
#[n(1)]
second: Option<Stamp<'a>>,
}
#[derive(Zerializable, Copy, Clone, Debug, PartialEq)]
enum Mark<'a> {
#[variant(0)]
Printed(#[n(0)] &'a str),
#[variant(1)]
Stamped {
#[n(0)]
stamp: Stamp<'a>,
},
#[variant(2)]
Blank,
}
#[zerializable(derive(Debug, PartialEq))]
trait Franking {
#[n(0)]
fn stamp(&self) -> Stamp<'_>;
#[n(1)]
fn franked(&self) -> Franked<'_>;
#[n(2)]
fn mark(&self) -> Mark<'_>;
#[n(3)]
fn maybe(&self) -> Option<Stamp<'_>>;
#[n(4)]
fn stamps(&self) -> impl List<Item = Stamp<'_>> + '_
where
Self: Sized;
#[n(5)]
fn title(&self) -> Label<'_>;
#[n(6)]
fn weight(&self) -> Weight;
}
#[derive(Debug)]
struct OwnedFranking {
stamps: OwnedList<Stamp<'static>>,
}
const STAMP: Stamp<'static> = Stamp {
code: "AB12",
seal: &[1, 2, 3],
grams: 5,
note: Some("fragile"),
};
const BLANK: Stamp<'static> = Stamp {
code: "",
seal: &[],
grams: 0,
note: None,
};
impl Franking for OwnedFranking {
fn stamp(&self) -> Stamp<'_> {
STAMP
}
fn franked(&self) -> Franked<'_> {
Franked {
stamp: STAMP,
second: Some(BLANK),
}
}
fn mark(&self) -> Mark<'_> {
Mark::Stamped { stamp: STAMP }
}
fn maybe(&self) -> Option<Stamp<'_>> {
Some(BLANK)
}
fn stamps(&self) -> impl List<Item = Stamp<'_>> + '_
where
Self: Sized,
{
Copied(&self.stamps)
}
fn title(&self) -> Label<'_> {
Label::Text("parcel")
}
fn weight(&self) -> Weight {
Weight { grams: 500 }
}
}
fn franking() -> OwnedFranking {
OwnedFranking {
stamps: vec![STAMP, BLANK].into(),
}
}
#[test]
fn a_message_holds_values_that_borrow() {
let source = franking();
let encoded = encode::<dyn Franking>(&source);
let view = decode::<dyn Franking>(&encoded).unwrap();
assert_eq!(view.stamp(), STAMP);
assert_eq!(view.stamp().note, Some("fragile"));
assert_eq!(view.franked().second, Some(BLANK));
assert_eq!(view.mark(), Mark::Stamped { stamp: STAMP });
assert_eq!(view.maybe(), Some(BLANK));
assert_eq!(view.stamps().iter().collect::<Vec<_>>(), [STAMP, BLANK]);
assert_eq!(view.title(), Label::Text("parcel"));
assert_eq!(view.weight(), Weight { grams: 500 });
assert_eq!(view, source);
assert_eq!(encode::<dyn Franking>(&view), encoded);
}
#[test]
fn a_borrowed_field_of_a_value_points_into_the_buffer() {
let encoded = encode::<dyn Franking>(&franking());
let view = decode::<dyn Franking>(&encoded).unwrap();
let buffer = encoded.as_ptr() as usize..encoded.as_ptr() as usize + encoded.len();
assert!(buffer.contains(&(view.stamp().code.as_ptr() as usize)));
assert!(buffer.contains(&(view.stamp().seal.as_ptr() as usize)));
assert!(buffer.contains(&(view.franked().stamp.code.as_ptr() as usize)));
assert!(buffer.contains(&(view.stamps().get(0).unwrap().code.as_ptr() as usize)));
match view.mark() {
Mark::Stamped { stamp } => {
assert!(buffer.contains(&(stamp.code.as_ptr() as usize)))
}
_ => panic!("decoded the wrong variant"),
}
}
#[test]
fn a_value_that_borrows_nothing_still_outlives_the_buffer() {
let weight = {
let encoded = encode::<dyn Franking>(&franking());
decode::<dyn Franking>(&encoded).unwrap().weight()
};
assert_eq!(weight, Weight { grams: 500 });
}
#[zerializable(derive(PartialEq))]
#[derive(Debug)]
enum Posted<'a, C: Contact, L: List<Item = Stamp<'a>>> {
#[variant(0)]
By(#[n(0)] C),
#[variant(1)]
With {
#[n(0)]
stamp: Stamp<'a>,
#[n(1)]
label: Label<'a>,
},
#[variant(2)]
Many(#[n(0)] L),
#[variant(3)]
Maybe(#[n(0)] Option<Stamp<'a>>),
}
type PostedSchema<'a> = Posted<'a, dyn Contact, ListView<'a, Stamp<'static>>>;
type OwnedPosted<'a> = Posted<'a, &'a OwnedContact, Copied<&'a OwnedList<Stamp<'a>>>>;
fn posted<'a>(contact: &'a OwnedContact, stamps: &'a OwnedList<Stamp<'a>>) -> [OwnedPosted<'a>; 5] {
[
Posted::By(contact),
Posted::With {
stamp: STAMP,
label: Label::Text("hello"),
},
Posted::Many(Copied(stamps)),
Posted::Maybe(Some(BLANK)),
Posted::Maybe(None),
]
}
#[test]
fn a_variant_holds_values_that_borrow() {
let contact = OwnedContact("Ada".to_string());
let stamps: OwnedList<Stamp<'_>> = vec![STAMP, BLANK].into();
for source in posted(&contact, &stamps) {
let encoded = encode::<PostedSchema<'_>>(&source);
let decoded = decode::<PostedSchema<'_>>(&encoded).unwrap();
assert_eq!(decoded, source);
assert_eq!(encode::<PostedSchema<'_>>(&decoded), encoded);
assert_eq!(encode::<PostedSchema<'_>>(&source.as_ref()), encoded);
}
let encoded = encode::<PostedSchema<'_>>(&posted(&contact, &stamps)[1]);
match decode::<PostedSchema<'_>>(&encoded).unwrap() {
Posted::With { stamp, label } => {
assert_eq!(stamp, STAMP);
assert_eq!(label, Label::Text("hello"));
}
_ => panic!("decoded the wrong variant"),
}
let encoded = encode::<PostedSchema<'_>>(&posted(&contact, &stamps)[2]);
match decode::<PostedSchema<'_>>(&encoded).unwrap() {
Posted::Many(stamps) => assert_eq!(stamps.get(1).unwrap(), BLANK),
_ => panic!("decoded the wrong variant"),
}
}
#[test]
fn corrupt_values_that_borrow_never_panic() {
let encoded = encode::<dyn Franking>(&franking());
for index in 0..encoded.len() {
for bit in [0x01, 0x40, 0x80] {
let mut corrupted = encoded.clone();
corrupted[index] ^= bit;
let Ok(view) = decode::<dyn Franking>(&corrupted) else {
continue;
};
let _ = view.stamp();
let _ = view.franked();
let _ = view.mark();
let _ = view.maybe();
let _ = view.title();
let _ = view.weight();
for stamp in view.stamps().iter() {
let _ = stamp;
}
}
}
}
mod stamp_before {
use zerialize::Zerializable;
#[derive(Zerializable, Copy, Clone, Debug, PartialEq)]
pub struct Stamp<'a> {
#[n(0)]
pub code: &'a str,
}
}
mod stamp_after {
use zerialize::Zerializable;
#[derive(Zerializable, Copy, Clone, Debug, PartialEq)]
pub struct Stamp<'a> {
#[n(0)]
pub code: &'a str,
#[n(1)]
pub note: Option<&'a str>,
}
}
#[zerializable(derive(Debug, PartialEq))]
trait Franked1 {
#[n(0)]
fn stamp(&self) -> stamp_before::Stamp<'_>;
}
#[zerializable(derive(Debug, PartialEq))]
trait Franked2 {
#[n(0)]
fn stamp(&self) -> stamp_after::Stamp<'_>;
}
struct OwnedFranked1;
impl Franked1 for OwnedFranked1 {
fn stamp(&self) -> stamp_before::Stamp<'_> {
stamp_before::Stamp { code: "AB12" }
}
}
#[test]
fn a_borrowed_field_added_to_a_value_reads_as_absent() {
let encoded = encode::<dyn Franked1>(&OwnedFranked1);
assert_eq!(
decode::<dyn Franked2>(&encoded).unwrap().stamp(),
stamp_after::Stamp {
code: "AB12",
note: None,
}
);
}
#[zerializable(derive(PartialEq))]
#[derive(Debug)]
enum Bag<'a, L: List<Item = Stamp<'a>>> {
#[variant(0)]
Many(#[n(0)] L),
#[variant(1)]
Empty,
}
#[test]
fn a_list_of_values_that_borrow_is_what_a_lifetime_stands_for() {
type Schema<'a> = Bag<'a, ListView<'a, Stamp<'static>>>;
let stamps: OwnedList<Stamp<'_>> = vec![STAMP, BLANK].into();
let many: Bag<'_, Copied<&OwnedList<Stamp<'_>>>> = Bag::Many(Copied(&stamps));
let encoded = encode::<Schema<'_>>(&many);
let decoded = decode::<Schema<'_>>(&encoded).unwrap();
match &decoded {
Bag::Many(stamps) => {
assert_eq!(stamps.get(0).unwrap(), STAMP);
let buffer = encoded.as_ptr() as usize..encoded.as_ptr() as usize + encoded.len();
assert!(buffer.contains(&(stamps.get(0).unwrap().code.as_ptr() as usize)));
}
Bag::Empty => panic!("decoded the wrong variant"),
}
assert_eq!(decoded, many);
assert_eq!(encode::<Schema<'_>>(&many.as_ref()), encoded);
let empty: Bag<'_, Copied<&OwnedList<Stamp<'_>>>> = Bag::Empty;
let encoded = encode::<Schema<'_>>(&empty);
assert_eq!(decode::<Schema<'_>>(&encoded).unwrap(), empty);
}