diff --git a/src/storable/tests.rs b/src/storable/tests.rs index a735defa..9ffd6ba0 100644 --- a/src/storable/tests.rs +++ b/src/storable/tests.rs @@ -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::(), 0..4), v2 in pvec(any::(), 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::(), 4..5), y in any::()) { + 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::(), 0..4), v2 in pvec(any::(), 0..8), v3 in pvec(any::(), 0..12)) { let tuple = (v1, v2, v3); @@ -38,7 +54,6 @@ proptest! { prop_assert_eq!(tuple, Storable::from_bytes(bytes)); } - #[test] fn tuple_variable_width_u8_roundtrip(x in any::(), v in pvec(any::(), 0..40)) { let bytes = Blob::<48>::try_from(&v[..]).unwrap(); @@ -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::(), 0..4), pvec(any::(), 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::(), 0..4), pvec(any::(), 0..8), pvec(any::(), 0..12)))) { prop_assert_eq!(v.clone(), Storable::from_bytes(v.to_bytes())); @@ -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::(), pvec(any::(), 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::(), pvec(any::(), 0..40), pvec(any::(), 0..80)))) { prop_assert_eq!(v.clone(), Storable::from_bytes(v.to_bytes())); @@ -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> diff --git a/src/storable/tuples.rs b/src/storable/tuples.rs index e4d8fc8b..97ea8245 100644 --- a/src/storable/tuples.rs +++ b/src/storable/tuples.rs @@ -8,6 +8,11 @@ where A: Storable, B: Storable, { + // Tuple (A, B) serialization: + // If A has fixed size: + // + // Otherwise: + // fn to_bytes(&self) -> Cow<[u8]> { match Self::BOUND { Bound::Bounded { max_size, .. } => { @@ -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::(&mut bytes[offset..], a_bytes.as_ref(), false); + offset += encode_tuple_element::(&mut bytes[offset..], b_bytes.as_ref(), true); + + debug_assert_eq!(offset, output_size); + Cow::Owned(bytes) + } } } @@ -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::(&bytes[offset..], size_length_a, false); + offset += read; + let (b, read) = decode_tuple_element::(&bytes[offset..], None, true); + offset += read; + + debug_assert_eq!(offset, bytes.len()); + (a, b) + } } }