Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion src/storable/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ proptest! {
prop_assert_eq!(tuple, Storable::from_bytes(bytes));
}

#[test]
fn tuple_with_two_unbounded_elements_roundtrip(v1 in pvec(any::<u8>(), 0..4), v2 in pvec(any::<u8>(), 0..8)) {
let tuple = (v1, v2);
assert_eq!(tuple, Storable::from_bytes(tuple.to_bytes()));
}

#[test]
fn tuple_with_two_elements_bounded_and_unbounded_roundtrip(x in pvec(any::<u8>(), 4..5), y in any::<u64>()) {
let tuple = (x, y);
let tuple_copy = tuple.clone();
let bytes = tuple_copy.to_bytes();
// 1B sizes len | 1B x size | 4B x bytes | 8B y bytes
prop_assert_eq!(bytes.len(), 14);
prop_assert_eq!(tuple, Storable::from_bytes(bytes));
}

#[test]
fn tuple_with_three_unbounded_elements_roundtrip(v1 in pvec(any::<u8>(), 0..4), v2 in pvec(any::<u8>(), 0..8), v3 in pvec(any::<u8>(), 0..12)) {
let tuple = (v1, v2, v3);
Expand All @@ -38,7 +54,6 @@ proptest! {
prop_assert_eq!(tuple, Storable::from_bytes(bytes));
}


#[test]
fn tuple_variable_width_u8_roundtrip(x in any::<u64>(), v in pvec(any::<u8>(), 0..40)) {
let bytes = Blob::<48>::try_from(&v[..]).unwrap();
Expand Down Expand Up @@ -100,6 +115,11 @@ proptest! {
prop_assert_eq!(v, Storable::from_bytes(v.to_bytes()));
}

#[test]
fn optional_tuple_with_two_unbounded_elements_roundtrip(v in proptest::option::of((pvec(any::<u8>(), 0..4), pvec(any::<u8>(), 0..8)))) {
prop_assert_eq!(v.clone(), Storable::from_bytes(v.to_bytes()));
}

#[test]
fn optional_tuple_with_three_unbounded_elements_roundtrip(v in proptest::option::of((pvec(any::<u8>(), 0..4), pvec(any::<u8>(), 0..8), pvec(any::<u8>(), 0..12)))) {
prop_assert_eq!(v.clone(), Storable::from_bytes(v.to_bytes()));
Expand All @@ -117,6 +137,11 @@ proptest! {
prop_assert_eq!(v, Storable::from_bytes(v.to_bytes()));
}

#[test]
fn optional_tuple_with_two_elements_bounded_and_unbounded_roundtrip(v in proptest::option::of((any::<u64>(), pvec(any::<u8>(), 0..40)))) {
prop_assert_eq!(v.clone(), Storable::from_bytes(v.to_bytes()));
}

#[test]
fn optional_tuple_with_three_elements_bounded_and_unbounded_roundtrip(v in proptest::option::of((any::<u64>(), pvec(any::<u8>(), 0..40), pvec(any::<u8>(), 0..80)))) {
prop_assert_eq!(v.clone(), Storable::from_bytes(v.to_bytes()));
Expand Down Expand Up @@ -240,6 +265,29 @@ fn storable_for_bool() {
assert!(bool::from_bytes(true.to_bytes()));
}

#[test]
fn tuple_with_two_elements_test_bound() {
// <8B a_bytes> <8B b_bytes>
assert_eq!(<(u64, u64)>::BOUND.max_size(), 16);
assert!(<(u64, u64)>::BOUND.is_fixed_size());

// <8B a_bytes> <8B b_bytes (zero-padded)> <1B size_b>
assert_eq!(<(u64, Blob<8>)>::BOUND.max_size(), 17);
assert!(!<(u64, Blob<8>)>::BOUND.is_fixed_size());

// <8B a_bytes (zero-padded)> <8B b_bytes> <1B size_a>
assert_eq!(<(Blob<8>, u64)>::BOUND.max_size(), 17);
assert!(!<(Blob<8>, u64)>::BOUND.is_fixed_size());

// <8B a_bytes (zero-padded)> <8B b_bytes (zero-padded)> <1B size_a> <1B size_b>
assert_eq!(<(Blob<8>, Blob<8>)>::BOUND.max_size(), 18);
assert!(!<(Blob<8>, Blob<8>)>::BOUND.is_fixed_size());

assert_eq!(<(Blob<8>, String)>::BOUND, Bound::Unbounded);
assert_eq!(<(String, Blob<8>)>::BOUND, Bound::Unbounded);
assert_eq!(<(String, String)>::BOUND, Bound::Unbounded);
}

#[test]
fn tuple_with_three_elements_test_bound() {
// <8B a_bytes> <8B b_bytes> <8B c_bytes>
Expand Down
52 changes: 50 additions & 2 deletions src/storable/tuples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ where
A: Storable,
B: Storable,
{
// Tuple (A, B) serialization:
// If A has fixed size:
// <a_bytes> <b_bytes>
// Otherwise:
// <size_lengths (1B)> <size_a (1-4B)> <a_bytes> <b_bytes>
fn to_bytes(&self) -> Cow<[u8]> {
match Self::BOUND {
Bound::Bounded { max_size, .. } => {
Expand Down Expand Up @@ -41,7 +46,33 @@ where

Cow::Owned(bytes)
}
_ => todo!("Serializing tuples with unbounded types is not yet supported."),
Bound::Unbounded => {
let a_bytes = self.0.to_bytes();
let b_bytes = self.1.to_bytes();
let a_size = a_bytes.len();
let b_size = b_bytes.len();

let sizes_overhead = if A::BOUND.is_fixed_size() {
0
} else {
1 + bytes_to_store_size(a_size)
};

let output_size = a_size + b_size + sizes_overhead;
let mut bytes = vec![0; output_size];
let mut offset = 0;

if sizes_overhead != 0 {
bytes[offset] = encode_size_lengths(&[a_size]);
offset += 1;
}

offset += encode_tuple_element::<A>(&mut bytes[offset..], a_bytes.as_ref(), false);
offset += encode_tuple_element::<B>(&mut bytes[offset..], b_bytes.as_ref(), true);

debug_assert_eq!(offset, output_size);
Cow::Owned(bytes)
}
}
}

Expand Down Expand Up @@ -71,7 +102,24 @@ where
let b = B::from_bytes(Cow::Borrowed(&bytes[a_max_size..a_max_size + b_len]));
(a, b)
}
_ => todo!("Deserializing tuples with unbounded types is not yet supported."),
Bound::Unbounded => {
let mut offset = 0;
let mut size_length_a = None;

if !A::BOUND.is_fixed_size() {
let lengths = decode_size_lengths(bytes[0], 1);
offset += 1;
size_length_a = Some(lengths[0]);
}

let (a, read) = decode_tuple_element::<A>(&bytes[offset..], size_length_a, false);
offset += read;
let (b, read) = decode_tuple_element::<B>(&bytes[offset..], None, true);
offset += read;

debug_assert_eq!(offset, bytes.len());
(a, b)
}
}
}

Expand Down
Loading