diff --git a/src/storable/tests.rs b/src/storable/tests.rs index 84ce1f39..720815d4 100644 --- a/src/storable/tests.rs +++ b/src/storable/tests.rs @@ -122,6 +122,29 @@ proptest! { prop_assert_eq!(v.clone(), Storable::from_bytes(v.to_bytes())); } + // 2-tuple unbounded roundtrips + + #[test] + fn tuple_two_unbounded_elements_roundtrip(v1 in pvec(any::(), 0..16), v2 in pvec(any::(), 0..32)) { + // (Vec, Vec): both unbounded + let tuple = (v1, v2); + prop_assert_eq!(tuple.clone(), Storable::from_bytes(tuple.to_bytes())); + } + + #[test] + fn tuple_fixed_and_unbounded_roundtrip(x in any::(), v in pvec(any::(), 0..32)) { + // (u64, Vec): fixed A, unbounded B — no size_lengths overhead + let tuple = (x, v); + prop_assert_eq!(tuple.clone(), Storable::from_bytes(tuple.to_bytes())); + } + + #[test] + fn tuple_unbounded_and_fixed_roundtrip(v in pvec(any::(), 0..32), x in any::()) { + // (Vec, u64): unbounded A, fixed B — size_lengths overhead needed + let tuple = (v, x); + prop_assert_eq!(tuple.clone(), Storable::from_bytes(tuple.to_bytes())); + } + #[test] fn principal_roundtrip(mut bytes in pvec(any::(), 0..=28), tag in proptest::prop_oneof![Just(1),Just(2),Just(3),Just(4),Just(7)]) { bytes.push(tag); diff --git a/src/storable/tuples.rs b/src/storable/tuples.rs index 26cebe84..01a949d5 100644 --- a/src/storable/tuples.rs +++ b/src/storable/tuples.rs @@ -42,7 +42,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 size_length_a = if A::BOUND.is_fixed_size() { + None + } else { + let lengths = decode_size_lengths(bytes[0], 1); + offset += 1; + 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) + } } } @@ -100,7 +117,35 @@ where bytes } - _ => todo!("Serializing tuples with unbounded types is not yet supported."), + Bound::Unbounded => { + let a_bytes = a.to_bytes(); + let b_bytes = b.to_bytes(); + let a_size = a_bytes.len(); + let b_size = b_bytes.len(); + + // If A is variable-size we need a 1B size_lengths header and A's size prefix. + // B is the last element, so its size is always inferred from remaining bytes. + 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); + bytes + } } }