Skip to main content

reddb_types/
value_codec.rs

1//! On-disk codec registry for [`Value`].
2//!
3//! This module is the **single source of truth** for the byte layout
4//! of every [`Value`] variant. Adding a new variant means:
5//!
6//! 1. Add the variant to [`Value`].
7//! 2. Add the matching [`DataType`] tag (the on-disk type byte).
8//! 3. Add an arm to [`encode`] and [`decode`] in this file.
9//!
10//! That's it — no other file needs to learn the layout. The inherent
11//! [`Value::to_bytes`] / [`Value::from_bytes`] methods stay as the
12//! public API, but they only delegate here.
13//!
14//! ## Why a registry
15//!
16//! Before this module the encode / decode arms lived inside
17//! `types.rs`, mixed with display / coercion / hashing logic. A
18//! parallel `value_type_tag` helper in `storage::query` carried a
19//! third numbering scheme. The result was that every new variant
20//! required edits in three or more places and the tag spaces were
21//! free to drift.
22//!
23//! With the registry there is exactly one mapping
24//! `Value <-> on-disk bytes`. The wire protocol keeps its own,
25//! independent `VAL_*` tag space (see `wire/protocol.rs`); the two
26//! were never identical and any future unification is out of scope.
27//!
28//! ## On-disk format
29//!
30//! Bytes are unchanged versus the previous in-place implementation.
31//! The pinned-byte regression test [`tests::pinned_bytes`] guards
32//! the layout for the canonical variants (Null, Integer, Text, Bool,
33//! Blob).
34
35use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
36
37use super::types::{read_varint, write_varint, DataType, Value, ValueError};
38
39/// Alias kept for callers that prefer the registry's own name. The
40/// on-disk tag space is owned by [`DataType`]; `ValueKind` reads
41/// better in registry contexts where the type name is the schema
42/// label rather than a parser concept.
43pub type ValueKind = DataType;
44
45/// On-disk tag byte for a value.
46///
47/// `Value::Null` uses tag `0` (the same byte the legacy code reserved
48/// as the explicit null marker before the [`DataType`] enum existed).
49/// Every other variant returns `data_type().to_byte()`.
50#[inline]
51pub fn type_tag(value: &Value) -> u8 {
52    match value {
53        Value::Null => 0,
54        other => other.data_type().to_byte(),
55    }
56}
57
58/// Reverse lookup for [`type_tag`]. Returns `None` for unknown bytes;
59/// `Some(DataType::Nullable)` for the dedicated null marker `0`.
60#[inline]
61pub fn type_for_tag(tag: u8) -> Option<ValueKind> {
62    if tag == 0 {
63        Some(DataType::Nullable)
64    } else {
65        DataType::from_byte(tag)
66    }
67}
68
69/// C3 TOAST: minimum byte length to attempt zstd compression. Values
70/// shorter than this are stored uncompressed — compression overhead
71/// (~50 ns + header bytes) outweighs savings for small values.
72pub(super) const TOAST_THRESHOLD: usize = 2048;
73
74/// zstd compression level for TOAST values. Level 3 is PG's default
75/// (balanced speed/ratio).
76pub(super) const TOAST_ZSTD_LEVEL: i32 = 3;
77
78/// Encode a value into `out`, appending its on-disk byte sequence.
79///
80/// The first byte is always [`type_tag`] of `value`; the remainder
81/// is the variant-specific payload.
82pub fn encode(value: &Value, out: &mut Vec<u8>) {
83    match value {
84        Value::Null => {
85            out.push(0); // Null marker
86        }
87        Value::Integer(v) => {
88            out.push(DataType::Integer.to_byte());
89            out.extend_from_slice(&v.to_le_bytes());
90        }
91        Value::UnsignedInteger(v) => {
92            out.push(DataType::UnsignedInteger.to_byte());
93            out.extend_from_slice(&v.to_le_bytes());
94        }
95        Value::Float(v) => {
96            out.push(DataType::Float.to_byte());
97            out.extend_from_slice(&v.to_le_bytes());
98        }
99        Value::Text(s) => {
100            let bytes = s.as_bytes();
101            // C3 TOAST: compress text values larger than the threshold.
102            // Stores with `TextZstd` type byte when compression wins;
103            // falls back to plain `Text` for small values or when zstd
104            // doesn't reduce the size (e.g. already-compressed content).
105            if bytes.len() > TOAST_THRESHOLD {
106                if let Ok(compressed) = zstd::bulk::compress(bytes, TOAST_ZSTD_LEVEL) {
107                    if compressed.len() < bytes.len() {
108                        out.push(DataType::TextZstd.to_byte());
109                        // original length first (needed to pre-allocate decompression buffer)
110                        write_varint(out, bytes.len() as u64);
111                        write_varint(out, compressed.len() as u64);
112                        out.extend_from_slice(&compressed);
113                        return;
114                    }
115                }
116            }
117            out.push(DataType::Text.to_byte());
118            write_varint(out, bytes.len() as u64);
119            out.extend_from_slice(bytes);
120        }
121        Value::Blob(data) => {
122            // C3 TOAST: same pattern as Text.
123            if data.len() > TOAST_THRESHOLD {
124                if let Ok(compressed) = zstd::bulk::compress(data, TOAST_ZSTD_LEVEL) {
125                    if compressed.len() < data.len() {
126                        out.push(DataType::BlobZstd.to_byte());
127                        write_varint(out, data.len() as u64);
128                        write_varint(out, compressed.len() as u64);
129                        out.extend_from_slice(&compressed);
130                        return;
131                    }
132                }
133            }
134            out.push(DataType::Blob.to_byte());
135            write_varint(out, data.len() as u64);
136            out.extend_from_slice(data);
137        }
138        Value::Boolean(v) => {
139            out.push(DataType::Boolean.to_byte());
140            out.push(if *v { 1 } else { 0 });
141        }
142        Value::Timestamp(v) => {
143            out.push(DataType::Timestamp.to_byte());
144            out.extend_from_slice(&v.to_le_bytes());
145        }
146        Value::Duration(v) => {
147            out.push(DataType::Duration.to_byte());
148            out.extend_from_slice(&v.to_le_bytes());
149        }
150        Value::IpAddr(addr) => {
151            out.push(DataType::IpAddr.to_byte());
152            match addr {
153                IpAddr::V4(v4) => {
154                    out.push(4); // IPv4 marker
155                    out.extend_from_slice(&v4.octets());
156                }
157                IpAddr::V6(v6) => {
158                    out.push(6); // IPv6 marker
159                    out.extend_from_slice(&v6.octets());
160                }
161            }
162        }
163        Value::MacAddr(mac) => {
164            out.push(DataType::MacAddr.to_byte());
165            out.extend_from_slice(mac);
166        }
167        Value::Vector(vec) => {
168            out.push(DataType::Vector.to_byte());
169            write_varint(out, vec.len() as u64);
170            for v in vec {
171                out.extend_from_slice(&v.to_le_bytes());
172            }
173        }
174        Value::Json(data) => {
175            out.push(DataType::Json.to_byte());
176            write_varint(out, data.len() as u64);
177            out.extend_from_slice(data);
178        }
179        Value::Uuid(uuid) => {
180            out.push(DataType::Uuid.to_byte());
181            out.extend_from_slice(uuid);
182        }
183        Value::NodeRef(node_id) => {
184            out.push(DataType::NodeRef.to_byte());
185            let bytes = node_id.as_bytes();
186            write_varint(out, bytes.len() as u64);
187            out.extend_from_slice(bytes);
188        }
189        Value::EdgeRef(edge_id) => {
190            out.push(DataType::EdgeRef.to_byte());
191            let bytes = edge_id.as_bytes();
192            write_varint(out, bytes.len() as u64);
193            out.extend_from_slice(bytes);
194        }
195        Value::VectorRef(collection, vector_id) => {
196            out.push(DataType::VectorRef.to_byte());
197            let coll_bytes = collection.as_bytes();
198            write_varint(out, coll_bytes.len() as u64);
199            out.extend_from_slice(coll_bytes);
200            out.extend_from_slice(&vector_id.to_le_bytes());
201        }
202        Value::RowRef(table, row_id) => {
203            out.push(DataType::RowRef.to_byte());
204            let table_bytes = table.as_bytes();
205            write_varint(out, table_bytes.len() as u64);
206            out.extend_from_slice(table_bytes);
207            out.extend_from_slice(&row_id.to_le_bytes());
208        }
209        Value::Color(rgb) => {
210            out.push(DataType::Color.to_byte());
211            out.extend_from_slice(rgb);
212        }
213        Value::Email(s) => {
214            out.push(DataType::Email.to_byte());
215            let bytes = s.as_bytes();
216            write_varint(out, bytes.len() as u64);
217            out.extend_from_slice(bytes);
218        }
219        Value::Url(s) => {
220            out.push(DataType::Url.to_byte());
221            let bytes = s.as_bytes();
222            write_varint(out, bytes.len() as u64);
223            out.extend_from_slice(bytes);
224        }
225        Value::Phone(n) => {
226            out.push(DataType::Phone.to_byte());
227            out.extend_from_slice(&n.to_le_bytes());
228        }
229        Value::Semver(packed) => {
230            out.push(DataType::Semver.to_byte());
231            out.extend_from_slice(&packed.to_le_bytes());
232        }
233        Value::Cidr(ip, prefix) => {
234            out.push(DataType::Cidr.to_byte());
235            out.extend_from_slice(&ip.to_le_bytes());
236            out.push(*prefix);
237        }
238        Value::Date(days) => {
239            out.push(DataType::Date.to_byte());
240            out.extend_from_slice(&days.to_le_bytes());
241        }
242        Value::Time(ms) => {
243            out.push(DataType::Time.to_byte());
244            out.extend_from_slice(&ms.to_le_bytes());
245        }
246        Value::Decimal(v) => {
247            out.push(DataType::Decimal.to_byte());
248            out.extend_from_slice(&v.to_le_bytes());
249        }
250        Value::DecimalText(s) => {
251            out.push(DataType::DecimalText.to_byte());
252            let bytes = s.as_bytes();
253            write_varint(out, bytes.len() as u64);
254            out.extend_from_slice(bytes);
255        }
256        Value::EnumValue(idx) => {
257            out.push(DataType::Enum.to_byte());
258            out.push(*idx);
259        }
260        Value::Array(elements) => {
261            out.push(DataType::Array.to_byte());
262            write_varint(out, elements.len() as u64);
263            for elem in elements {
264                encode(elem, out);
265            }
266        }
267        Value::TimestampMs(v) => {
268            out.push(DataType::TimestampMs.to_byte());
269            out.extend_from_slice(&v.to_le_bytes());
270        }
271        Value::Ipv4(v) => {
272            out.push(DataType::Ipv4.to_byte());
273            out.extend_from_slice(&v.to_le_bytes());
274        }
275        Value::Ipv6(bytes) => {
276            out.push(DataType::Ipv6.to_byte());
277            out.extend_from_slice(bytes);
278        }
279        Value::Subnet(ip, mask) => {
280            out.push(DataType::Subnet.to_byte());
281            out.extend_from_slice(&ip.to_le_bytes());
282            out.extend_from_slice(&mask.to_le_bytes());
283        }
284        Value::Port(v) => {
285            out.push(DataType::Port.to_byte());
286            out.extend_from_slice(&v.to_le_bytes());
287        }
288        Value::Latitude(v) => {
289            out.push(DataType::Latitude.to_byte());
290            out.extend_from_slice(&v.to_le_bytes());
291        }
292        Value::Longitude(v) => {
293            out.push(DataType::Longitude.to_byte());
294            out.extend_from_slice(&v.to_le_bytes());
295        }
296        Value::GeoPoint(lat, lon) => {
297            out.push(DataType::GeoPoint.to_byte());
298            out.extend_from_slice(&lat.to_le_bytes());
299            out.extend_from_slice(&lon.to_le_bytes());
300        }
301        Value::Country2(c) => {
302            out.push(DataType::Country2.to_byte());
303            out.extend_from_slice(c);
304        }
305        Value::Country3(c) => {
306            out.push(DataType::Country3.to_byte());
307            out.extend_from_slice(c);
308        }
309        Value::Lang2(c) => {
310            out.push(DataType::Lang2.to_byte());
311            out.extend_from_slice(c);
312        }
313        Value::Lang5(c) => {
314            out.push(DataType::Lang5.to_byte());
315            out.extend_from_slice(c);
316        }
317        Value::Currency(c) => {
318            out.push(DataType::Currency.to_byte());
319            out.extend_from_slice(c);
320        }
321        Value::AssetCode(code) => {
322            out.push(DataType::AssetCode.to_byte());
323            let bytes = code.as_bytes();
324            write_varint(out, bytes.len() as u64);
325            out.extend_from_slice(bytes);
326        }
327        Value::Money {
328            asset_code,
329            minor_units,
330            scale,
331        } => {
332            out.push(DataType::Money.to_byte());
333            let bytes = asset_code.as_bytes();
334            write_varint(out, bytes.len() as u64);
335            out.extend_from_slice(bytes);
336            out.push(*scale);
337            out.extend_from_slice(&minor_units.to_le_bytes());
338        }
339        Value::ColorAlpha(rgba) => {
340            out.push(DataType::ColorAlpha.to_byte());
341            out.extend_from_slice(rgba);
342        }
343        Value::BigInt(v) => {
344            out.push(DataType::BigInt.to_byte());
345            out.extend_from_slice(&v.to_le_bytes());
346        }
347        Value::KeyRef(col, key) => {
348            out.push(DataType::KeyRef.to_byte());
349            let col_bytes = col.as_bytes();
350            write_varint(out, col_bytes.len() as u64);
351            out.extend_from_slice(col_bytes);
352            let key_bytes = key.as_bytes();
353            write_varint(out, key_bytes.len() as u64);
354            out.extend_from_slice(key_bytes);
355        }
356        Value::DocRef(col, id) => {
357            out.push(DataType::DocRef.to_byte());
358            let col_bytes = col.as_bytes();
359            write_varint(out, col_bytes.len() as u64);
360            out.extend_from_slice(col_bytes);
361            out.extend_from_slice(&id.to_le_bytes());
362        }
363        Value::TableRef(name) => {
364            out.push(DataType::TableRef.to_byte());
365            let name_bytes = name.as_bytes();
366            write_varint(out, name_bytes.len() as u64);
367            out.extend_from_slice(name_bytes);
368        }
369        Value::PageRef(page_id) => {
370            out.push(DataType::PageRef.to_byte());
371            out.extend_from_slice(&page_id.to_le_bytes());
372        }
373        Value::Secret(bytes) => {
374            out.push(DataType::Secret.to_byte());
375            write_varint(out, bytes.len() as u64);
376            out.extend_from_slice(bytes);
377        }
378        Value::Password(hash) => {
379            out.push(DataType::Password.to_byte());
380            let bytes = hash.as_bytes();
381            write_varint(out, bytes.len() as u64);
382            out.extend_from_slice(bytes);
383        }
384    }
385}
386
387/// Decode a single value from `data`, returning the value and the
388/// number of bytes consumed.
389pub fn decode(data: &[u8]) -> Result<(Value, usize), ValueError> {
390    if data.is_empty() {
391        return Err(ValueError::EmptyData);
392    }
393
394    let type_byte = data[0];
395    let mut offset = 1;
396
397    // Null marker
398    if type_byte == 0 {
399        return Ok((Value::Null, 1));
400    }
401
402    let data_type = DataType::from_byte(type_byte).ok_or(ValueError::InvalidType(type_byte))?;
403
404    let value = match data_type {
405        DataType::Integer => {
406            if data.len() < offset + 8 {
407                return Err(ValueError::TruncatedData);
408            }
409            let v = i64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
410            offset += 8;
411            Value::Integer(v)
412        }
413        DataType::UnsignedInteger => {
414            if data.len() < offset + 8 {
415                return Err(ValueError::TruncatedData);
416            }
417            let v = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
418            offset += 8;
419            Value::UnsignedInteger(v)
420        }
421        DataType::Float => {
422            if data.len() < offset + 8 {
423                return Err(ValueError::TruncatedData);
424            }
425            let v = f64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
426            offset += 8;
427            Value::Float(v)
428        }
429        DataType::Text => {
430            let (len, varint_size) = read_varint(&data[offset..])?;
431            offset += varint_size;
432            if data.len() < offset + len as usize {
433                return Err(ValueError::TruncatedData);
434            }
435            let s = String::from_utf8(data[offset..offset + len as usize].to_vec())
436                .map_err(|_| ValueError::InvalidUtf8)?;
437            offset += len as usize;
438            Value::text(s)
439        }
440        DataType::Blob => {
441            let (len, varint_size) = read_varint(&data[offset..])?;
442            offset += varint_size;
443            if data.len() < offset + len as usize {
444                return Err(ValueError::TruncatedData);
445            }
446            let blob = data[offset..offset + len as usize].to_vec();
447            offset += len as usize;
448            Value::Blob(blob)
449        }
450        DataType::Boolean => {
451            if data.len() < offset + 1 {
452                return Err(ValueError::TruncatedData);
453            }
454            let v = data[offset] != 0;
455            offset += 1;
456            Value::Boolean(v)
457        }
458        DataType::Timestamp => {
459            if data.len() < offset + 8 {
460                return Err(ValueError::TruncatedData);
461            }
462            let v = i64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
463            offset += 8;
464            Value::Timestamp(v)
465        }
466        DataType::Duration => {
467            if data.len() < offset + 8 {
468                return Err(ValueError::TruncatedData);
469            }
470            let v = i64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
471            offset += 8;
472            Value::Duration(v)
473        }
474        DataType::IpAddr => {
475            if data.len() < offset + 1 {
476                return Err(ValueError::TruncatedData);
477            }
478            let version = data[offset];
479            offset += 1;
480            match version {
481                4 => {
482                    if data.len() < offset + 4 {
483                        return Err(ValueError::TruncatedData);
484                    }
485                    let octets: [u8; 4] = data[offset..offset + 4].try_into().unwrap();
486                    offset += 4;
487                    Value::IpAddr(IpAddr::V4(Ipv4Addr::from(octets)))
488                }
489                6 => {
490                    if data.len() < offset + 16 {
491                        return Err(ValueError::TruncatedData);
492                    }
493                    let octets: [u8; 16] = data[offset..offset + 16].try_into().unwrap();
494                    offset += 16;
495                    Value::IpAddr(IpAddr::V6(Ipv6Addr::from(octets)))
496                }
497                _ => return Err(ValueError::InvalidIpVersion(version)),
498            }
499        }
500        DataType::MacAddr => {
501            if data.len() < offset + 6 {
502                return Err(ValueError::TruncatedData);
503            }
504            let mac: [u8; 6] = data[offset..offset + 6].try_into().unwrap();
505            offset += 6;
506            Value::MacAddr(mac)
507        }
508        DataType::Vector => {
509            let (len, varint_size) = read_varint(&data[offset..])?;
510            offset += varint_size;
511            let float_count = len as usize;
512            if data.len() < offset + float_count * 4 {
513                return Err(ValueError::TruncatedData);
514            }
515            let mut vec = Vec::with_capacity(float_count);
516            for _ in 0..float_count {
517                let v = f32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
518                offset += 4;
519                vec.push(v);
520            }
521            Value::Vector(vec)
522        }
523        DataType::Json => {
524            let (len, varint_size) = read_varint(&data[offset..])?;
525            offset += varint_size;
526            if data.len() < offset + len as usize {
527                return Err(ValueError::TruncatedData);
528            }
529            let json = data[offset..offset + len as usize].to_vec();
530            offset += len as usize;
531            Value::Json(json)
532        }
533        DataType::Uuid => {
534            if data.len() < offset + 16 {
535                return Err(ValueError::TruncatedData);
536            }
537            let uuid: [u8; 16] = data[offset..offset + 16].try_into().unwrap();
538            offset += 16;
539            Value::Uuid(uuid)
540        }
541        DataType::NodeRef => {
542            let (len, len_bytes) = read_varint(&data[offset..])?;
543            offset += len_bytes;
544            if data.len() < offset + len as usize {
545                return Err(ValueError::TruncatedData);
546            }
547            let node_id = String::from_utf8_lossy(&data[offset..offset + len as usize]).to_string();
548            offset += len as usize;
549            Value::NodeRef(node_id)
550        }
551        DataType::EdgeRef => {
552            let (len, len_bytes) = read_varint(&data[offset..])?;
553            offset += len_bytes;
554            if data.len() < offset + len as usize {
555                return Err(ValueError::TruncatedData);
556            }
557            let edge_id = String::from_utf8_lossy(&data[offset..offset + len as usize]).to_string();
558            offset += len as usize;
559            Value::EdgeRef(edge_id)
560        }
561        DataType::VectorRef => {
562            let (len, len_bytes) = read_varint(&data[offset..])?;
563            offset += len_bytes;
564            if data.len() < offset + len as usize + 8 {
565                return Err(ValueError::TruncatedData);
566            }
567            let collection =
568                String::from_utf8_lossy(&data[offset..offset + len as usize]).to_string();
569            offset += len as usize;
570            let vector_id = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
571            offset += 8;
572            Value::VectorRef(collection, vector_id)
573        }
574        DataType::RowRef => {
575            let (len, len_bytes) = read_varint(&data[offset..])?;
576            offset += len_bytes;
577            if data.len() < offset + len as usize + 8 {
578                return Err(ValueError::TruncatedData);
579            }
580            let table = String::from_utf8_lossy(&data[offset..offset + len as usize]).to_string();
581            offset += len as usize;
582            let row_id = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
583            offset += 8;
584            Value::RowRef(table, row_id)
585        }
586        DataType::Color => {
587            if data.len() < offset + 3 {
588                return Err(ValueError::TruncatedData);
589            }
590            let rgb: [u8; 3] = data[offset..offset + 3].try_into().unwrap();
591            offset += 3;
592            Value::Color(rgb)
593        }
594        DataType::Email => {
595            let (len, varint_size) = read_varint(&data[offset..])?;
596            offset += varint_size;
597            if data.len() < offset + len as usize {
598                return Err(ValueError::TruncatedData);
599            }
600            let s = String::from_utf8(data[offset..offset + len as usize].to_vec())
601                .map_err(|_| ValueError::InvalidUtf8)?;
602            offset += len as usize;
603            Value::Email(s)
604        }
605        DataType::Url => {
606            let (len, varint_size) = read_varint(&data[offset..])?;
607            offset += varint_size;
608            if data.len() < offset + len as usize {
609                return Err(ValueError::TruncatedData);
610            }
611            let s = String::from_utf8(data[offset..offset + len as usize].to_vec())
612                .map_err(|_| ValueError::InvalidUtf8)?;
613            offset += len as usize;
614            Value::Url(s)
615        }
616        DataType::Phone => {
617            if data.len() < offset + 8 {
618                return Err(ValueError::TruncatedData);
619            }
620            let v = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
621            offset += 8;
622            Value::Phone(v)
623        }
624        DataType::Semver => {
625            if data.len() < offset + 4 {
626                return Err(ValueError::TruncatedData);
627            }
628            let v = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
629            offset += 4;
630            Value::Semver(v)
631        }
632        DataType::Cidr => {
633            if data.len() < offset + 5 {
634                return Err(ValueError::TruncatedData);
635            }
636            let ip = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
637            offset += 4;
638            let prefix = data[offset];
639            offset += 1;
640            Value::Cidr(ip, prefix)
641        }
642        DataType::Date => {
643            if data.len() < offset + 4 {
644                return Err(ValueError::TruncatedData);
645            }
646            let v = i32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
647            offset += 4;
648            Value::Date(v)
649        }
650        DataType::Time => {
651            if data.len() < offset + 4 {
652                return Err(ValueError::TruncatedData);
653            }
654            let v = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
655            offset += 4;
656            Value::Time(v)
657        }
658        DataType::Decimal => {
659            if data.len() < offset + 8 {
660                return Err(ValueError::TruncatedData);
661            }
662            let v = i64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
663            offset += 8;
664            Value::Decimal(v)
665        }
666        DataType::DecimalText => {
667            let (len, varint_size) = read_varint(&data[offset..])?;
668            offset += varint_size;
669            if data.len() < offset + len as usize {
670                return Err(ValueError::TruncatedData);
671            }
672            let value = String::from_utf8(data[offset..offset + len as usize].to_vec())
673                .map_err(|_| ValueError::InvalidUtf8)?;
674            offset += len as usize;
675            Value::DecimalText(value)
676        }
677        DataType::Enum => {
678            if data.len() < offset + 1 {
679                return Err(ValueError::TruncatedData);
680            }
681            let idx = data[offset];
682            offset += 1;
683            Value::EnumValue(idx)
684        }
685        DataType::Array => {
686            let (len, varint_size) = read_varint(&data[offset..])?;
687            offset += varint_size;
688            let count = len as usize;
689            let mut elements = Vec::with_capacity(count);
690            for _ in 0..count {
691                let (elem, elem_size) = decode(&data[offset..])?;
692                offset += elem_size;
693                elements.push(elem);
694            }
695            Value::Array(elements)
696        }
697        DataType::TimestampMs => {
698            if data.len() < offset + 8 {
699                return Err(ValueError::TruncatedData);
700            }
701            let v = i64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
702            offset += 8;
703            Value::TimestampMs(v)
704        }
705        DataType::Ipv4 => {
706            if data.len() < offset + 4 {
707                return Err(ValueError::TruncatedData);
708            }
709            let v = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
710            offset += 4;
711            Value::Ipv4(v)
712        }
713        DataType::Ipv6 => {
714            if data.len() < offset + 16 {
715                return Err(ValueError::TruncatedData);
716            }
717            let bytes: [u8; 16] = data[offset..offset + 16].try_into().unwrap();
718            offset += 16;
719            Value::Ipv6(bytes)
720        }
721        DataType::Subnet => {
722            if data.len() < offset + 8 {
723                return Err(ValueError::TruncatedData);
724            }
725            let ip = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
726            offset += 4;
727            let mask = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
728            offset += 4;
729            Value::Subnet(ip, mask)
730        }
731        DataType::Port => {
732            if data.len() < offset + 2 {
733                return Err(ValueError::TruncatedData);
734            }
735            let v = u16::from_le_bytes(data[offset..offset + 2].try_into().unwrap());
736            offset += 2;
737            Value::Port(v)
738        }
739        DataType::Latitude => {
740            if data.len() < offset + 4 {
741                return Err(ValueError::TruncatedData);
742            }
743            let v = i32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
744            offset += 4;
745            Value::Latitude(v)
746        }
747        DataType::Longitude => {
748            if data.len() < offset + 4 {
749                return Err(ValueError::TruncatedData);
750            }
751            let v = i32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
752            offset += 4;
753            Value::Longitude(v)
754        }
755        DataType::GeoPoint => {
756            if data.len() < offset + 8 {
757                return Err(ValueError::TruncatedData);
758            }
759            let lat = i32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
760            offset += 4;
761            let lon = i32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
762            offset += 4;
763            Value::GeoPoint(lat, lon)
764        }
765        DataType::Country2 => {
766            if data.len() < offset + 2 {
767                return Err(ValueError::TruncatedData);
768            }
769            let c: [u8; 2] = data[offset..offset + 2].try_into().unwrap();
770            offset += 2;
771            Value::Country2(c)
772        }
773        DataType::Country3 => {
774            if data.len() < offset + 3 {
775                return Err(ValueError::TruncatedData);
776            }
777            let c: [u8; 3] = data[offset..offset + 3].try_into().unwrap();
778            offset += 3;
779            Value::Country3(c)
780        }
781        DataType::Lang2 => {
782            if data.len() < offset + 2 {
783                return Err(ValueError::TruncatedData);
784            }
785            let c: [u8; 2] = data[offset..offset + 2].try_into().unwrap();
786            offset += 2;
787            Value::Lang2(c)
788        }
789        DataType::Lang5 => {
790            if data.len() < offset + 5 {
791                return Err(ValueError::TruncatedData);
792            }
793            let c: [u8; 5] = data[offset..offset + 5].try_into().unwrap();
794            offset += 5;
795            Value::Lang5(c)
796        }
797        DataType::Currency => {
798            if data.len() < offset + 3 {
799                return Err(ValueError::TruncatedData);
800            }
801            let c: [u8; 3] = data[offset..offset + 3].try_into().unwrap();
802            offset += 3;
803            Value::Currency(c)
804        }
805        DataType::AssetCode => {
806            let (len, len_bytes) = read_varint(&data[offset..])?;
807            offset += len_bytes;
808            if data.len() < offset + len as usize {
809                return Err(ValueError::TruncatedData);
810            }
811            let code = String::from_utf8(data[offset..offset + len as usize].to_vec())
812                .map_err(|_| ValueError::InvalidUtf8)?;
813            offset += len as usize;
814            Value::AssetCode(code)
815        }
816        DataType::Money => {
817            let (len, len_bytes) = read_varint(&data[offset..])?;
818            offset += len_bytes;
819            if data.len() < offset + len as usize + 1 + 8 {
820                return Err(ValueError::TruncatedData);
821            }
822            let asset_code = String::from_utf8(data[offset..offset + len as usize].to_vec())
823                .map_err(|_| ValueError::InvalidUtf8)?;
824            offset += len as usize;
825            let scale = data[offset];
826            offset += 1;
827            let minor_units = i64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
828            offset += 8;
829            Value::Money {
830                asset_code,
831                minor_units,
832                scale,
833            }
834        }
835        DataType::ColorAlpha => {
836            if data.len() < offset + 4 {
837                return Err(ValueError::TruncatedData);
838            }
839            let rgba: [u8; 4] = data[offset..offset + 4].try_into().unwrap();
840            offset += 4;
841            Value::ColorAlpha(rgba)
842        }
843        DataType::BigInt => {
844            if data.len() < offset + 8 {
845                return Err(ValueError::TruncatedData);
846            }
847            let v = i64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
848            offset += 8;
849            Value::BigInt(v)
850        }
851        DataType::KeyRef => {
852            let (col_len, col_varint) = read_varint(&data[offset..])?;
853            offset += col_varint;
854            if data.len() < offset + col_len as usize {
855                return Err(ValueError::TruncatedData);
856            }
857            let col = String::from_utf8(data[offset..offset + col_len as usize].to_vec())
858                .map_err(|_| ValueError::InvalidUtf8)?;
859            offset += col_len as usize;
860            let (key_len, key_varint) = read_varint(&data[offset..])?;
861            offset += key_varint;
862            if data.len() < offset + key_len as usize {
863                return Err(ValueError::TruncatedData);
864            }
865            let key = String::from_utf8(data[offset..offset + key_len as usize].to_vec())
866                .map_err(|_| ValueError::InvalidUtf8)?;
867            offset += key_len as usize;
868            Value::KeyRef(col, key)
869        }
870        DataType::DocRef => {
871            let (col_len, col_varint) = read_varint(&data[offset..])?;
872            offset += col_varint;
873            if data.len() < offset + col_len as usize + 8 {
874                return Err(ValueError::TruncatedData);
875            }
876            let col = String::from_utf8(data[offset..offset + col_len as usize].to_vec())
877                .map_err(|_| ValueError::InvalidUtf8)?;
878            offset += col_len as usize;
879            let id = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
880            offset += 8;
881            Value::DocRef(col, id)
882        }
883        DataType::TableRef => {
884            let (len, varint_size) = read_varint(&data[offset..])?;
885            offset += varint_size;
886            if data.len() < offset + len as usize {
887                return Err(ValueError::TruncatedData);
888            }
889            let name = String::from_utf8(data[offset..offset + len as usize].to_vec())
890                .map_err(|_| ValueError::InvalidUtf8)?;
891            offset += len as usize;
892            Value::TableRef(name)
893        }
894        DataType::PageRef => {
895            if data.len() < offset + 4 {
896                return Err(ValueError::TruncatedData);
897            }
898            let page_id = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
899            offset += 4;
900            Value::PageRef(page_id)
901        }
902        DataType::Secret => {
903            let (len, varint_size) = read_varint(&data[offset..])?;
904            offset += varint_size;
905            if data.len() < offset + len as usize {
906                return Err(ValueError::TruncatedData);
907            }
908            let bytes = data[offset..offset + len as usize].to_vec();
909            offset += len as usize;
910            Value::Secret(bytes)
911        }
912        DataType::Password => {
913            let (len, varint_size) = read_varint(&data[offset..])?;
914            offset += varint_size;
915            if data.len() < offset + len as usize {
916                return Err(ValueError::TruncatedData);
917            }
918            let hash = String::from_utf8(data[offset..offset + len as usize].to_vec())
919                .map_err(|_| ValueError::InvalidUtf8)?;
920            offset += len as usize;
921            Value::Password(hash)
922        }
923        DataType::Nullable => {
924            // Nullable without inner type means null
925            Value::Null
926        }
927        DataType::Unknown => {
928            // Polymorphic placeholder — never stored on disk.
929            // Reaching here means corrupted data or a bug; treat
930            // as null to stay forward-compatible.
931            Value::Null
932        }
933        // C3 TOAST: zstd-compressed Text — transparent decompression.
934        // Wire: encode writes TextZstd when text > TOAST_THRESHOLD and
935        // compression saves space; decode always materialises as Value::Text.
936        DataType::TextZstd => {
937            let (orig_len, vs1) = read_varint(&data[offset..])?;
938            offset += vs1;
939            let (comp_len, vs2) = read_varint(&data[offset..])?;
940            offset += vs2;
941            if data.len() < offset + comp_len as usize {
942                return Err(ValueError::TruncatedData);
943            }
944            let compressed = &data[offset..offset + comp_len as usize];
945            let mut decompressed = vec![0u8; orig_len as usize];
946            zstd::bulk::decompress_to_buffer(compressed, &mut decompressed)
947                .map_err(|_| ValueError::InvalidUtf8)?;
948            offset += comp_len as usize;
949            let s = String::from_utf8(decompressed).map_err(|_| ValueError::InvalidUtf8)?;
950            Value::text(s)
951        }
952        // C3 TOAST: zstd-compressed Blob — same pattern as TextZstd.
953        DataType::BlobZstd => {
954            let (orig_len, vs1) = read_varint(&data[offset..])?;
955            offset += vs1;
956            let (comp_len, vs2) = read_varint(&data[offset..])?;
957            offset += vs2;
958            if data.len() < offset + comp_len as usize {
959                return Err(ValueError::TruncatedData);
960            }
961            let compressed = &data[offset..offset + comp_len as usize];
962            let mut decompressed = vec![0u8; orig_len as usize];
963            zstd::bulk::decompress_to_buffer(compressed, &mut decompressed)
964                .map_err(|_| ValueError::InvalidUtf8)?;
965            offset += comp_len as usize;
966            Value::Blob(decompressed)
967        }
968    };
969
970    Ok((value, offset))
971}
972
973#[cfg(test)]
974mod tests {
975    use super::*;
976    use proptest::prelude::*;
977    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
978
979    /// Pinned on-disk byte layout for the canonical [`Value`]
980    /// variants. **If this test breaks, callers with persisted data
981    /// will fail to read older files** — only update the expected
982    /// bytes when you have intentionally migrated the format. A
983    /// silent rewrite is a corruption bug.
984    ///
985    /// Variants pinned: Null, Integer, Text, Boolean, Blob — the
986    /// minimum five required by the codec registry contract.
987    #[test]
988    fn pinned_bytes() {
989        // Null: just the null marker (0x00).
990        let mut buf = Vec::new();
991        encode(&Value::Null, &mut buf);
992        assert_eq!(buf, vec![0x00], "Value::Null layout drifted");
993
994        // Integer(-1): tag (Integer = 1) + i64 little-endian.
995        let mut buf = Vec::new();
996        encode(&Value::Integer(-1), &mut buf);
997        assert_eq!(
998            buf,
999            vec![0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
1000            "Value::Integer layout drifted"
1001        );
1002
1003        // Text("hi"): tag (Text = 4) + varint(2) + UTF-8 bytes.
1004        let mut buf = Vec::new();
1005        encode(&Value::text("hi"), &mut buf);
1006        assert_eq!(
1007            buf,
1008            vec![0x04, 0x02, b'h', b'i'],
1009            "Value::Text layout drifted"
1010        );
1011
1012        // Boolean(true): tag (Boolean = 6) + 0x01.
1013        let mut buf = Vec::new();
1014        encode(&Value::Boolean(true), &mut buf);
1015        assert_eq!(buf, vec![0x06, 0x01], "Value::Boolean layout drifted");
1016
1017        // Blob([0x01, 0x02, 0x03]): tag (Blob = 5) + varint(3) + raw.
1018        let mut buf = Vec::new();
1019        encode(&Value::Blob(vec![0x01, 0x02, 0x03]), &mut buf);
1020        assert_eq!(
1021            buf,
1022            vec![0x05, 0x03, 0x01, 0x02, 0x03],
1023            "Value::Blob layout drifted"
1024        );
1025    }
1026
1027    /// Sanity check that the registry's [`type_tag`] lines up with
1028    /// [`DataType::to_byte`] for every storable variant — this is
1029    /// what guarantees the on-disk tag space stays single-source.
1030    #[test]
1031    fn type_tag_matches_data_type_byte() {
1032        let samples: &[Value] = &[
1033            Value::Null,
1034            Value::Integer(0),
1035            Value::UnsignedInteger(0),
1036            Value::Float(0.0),
1037            Value::text(""),
1038            Value::Blob(Vec::new()),
1039            Value::Boolean(false),
1040            Value::Timestamp(0),
1041            Value::Duration(0),
1042            Value::Uuid([0; 16]),
1043        ];
1044        for v in samples {
1045            let tag = type_tag(v);
1046            if matches!(v, Value::Null) {
1047                assert_eq!(tag, 0);
1048            } else {
1049                assert_eq!(tag, v.data_type().to_byte());
1050                let kind = type_for_tag(tag).expect("registered tag");
1051                assert_eq!(kind, v.data_type());
1052            }
1053        }
1054    }
1055
1056    /// Decoder must reject a type byte it does not recognise rather
1057    /// than silently returning a default. Guards against on-disk
1058    /// corruption being interpreted as a valid value.
1059    #[test]
1060    fn rejects_unknown_type_tag() {
1061        // 0xFF is outside the registered DataType range.
1062        let buf = [0xFFu8];
1063        let err = decode(&buf).expect_err("unknown tag must error");
1064        assert!(matches!(err, ValueError::InvalidType(0xFF)));
1065    }
1066
1067    /// A buffer truncated mid-payload must surface as
1068    /// `TruncatedData`, not panic on a slice index. Covers the
1069    /// fixed-width and length-prefixed code paths.
1070    #[test]
1071    fn rejects_truncated_buffer() {
1072        // Empty buffer.
1073        assert!(matches!(decode(&[]), Err(ValueError::EmptyData)));
1074
1075        // Integer tag (0x01) needs 8 payload bytes; supply 3.
1076        let mut buf = vec![DataType::Integer.to_byte()];
1077        buf.extend_from_slice(&[0x01, 0x02, 0x03]);
1078        assert!(matches!(decode(&buf), Err(ValueError::TruncatedData)));
1079
1080        // Text tag (0x04) with varint len=5 but only 2 payload bytes.
1081        let mut buf = vec![DataType::Text.to_byte()];
1082        write_varint(&mut buf, 5);
1083        buf.extend_from_slice(b"ab");
1084        assert!(matches!(decode(&buf), Err(ValueError::TruncatedData)));
1085    }
1086
1087    #[test]
1088    fn type_for_tag_handles_null_registered_and_unknown_tags() {
1089        assert_eq!(type_for_tag(0), Some(DataType::Nullable));
1090        assert_eq!(
1091            type_for_tag(DataType::Money.to_byte()),
1092            Some(DataType::Money)
1093        );
1094        assert_eq!(type_for_tag(0xFF), None);
1095    }
1096
1097    #[test]
1098    fn round_trip_every_value_variant_directly_through_registry() {
1099        let values = vec![
1100            Value::Null,
1101            Value::Integer(-1),
1102            Value::UnsignedInteger(2),
1103            Value::Float(3.5),
1104            Value::text("hello"),
1105            Value::Blob(vec![1, 2, 3]),
1106            Value::Boolean(true),
1107            Value::Timestamp(4),
1108            Value::Duration(5),
1109            Value::IpAddr(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
1110            Value::IpAddr(IpAddr::V6(Ipv6Addr::LOCALHOST)),
1111            Value::MacAddr([1, 2, 3, 4, 5, 6]),
1112            Value::Vector(vec![1.0, 2.0]),
1113            Value::Json(br#"{"ok":true}"#.to_vec()),
1114            Value::Uuid([7; 16]),
1115            Value::NodeRef("node".to_string()),
1116            Value::EdgeRef("edge".to_string()),
1117            Value::VectorRef("vectors".to_string(), 8),
1118            Value::RowRef("rows".to_string(), 9),
1119            Value::Color([0xAA, 0xBB, 0xCC]),
1120            Value::Email("a@example.com".to_string()),
1121            Value::Url("https://example.com".to_string()),
1122            Value::Phone(5511999),
1123            Value::Semver(1_002_003),
1124            Value::Cidr(10 << 24, 8),
1125            Value::Date(20_000),
1126            Value::Time(43_200_000),
1127            Value::Decimal(123_456),
1128            Value::DecimalText("18446744073709551616.00000000000000000001".to_string()),
1129            Value::EnumValue(3),
1130            Value::Array(vec![Value::Integer(1), Value::text("two")]),
1131            Value::TimestampMs(123_456),
1132            Value::Ipv4(0x7f000001),
1133            Value::Ipv6([1; 16]),
1134            Value::Subnet(10 << 24, 0xff000000),
1135            Value::Port(5432),
1136            Value::Latitude(-23_550_520),
1137            Value::Longitude(-46_633_308),
1138            Value::GeoPoint(-23_550_520, -46_633_308),
1139            Value::Country2(*b"BR"),
1140            Value::Country3(*b"BRA"),
1141            Value::Lang2(*b"pt"),
1142            Value::Lang5(*b"pt-BR"),
1143            Value::Currency(*b"USD"),
1144            Value::AssetCode("BTC".to_string()),
1145            Value::Money {
1146                asset_code: "USD".to_string(),
1147                minor_units: 1234,
1148                scale: 2,
1149            },
1150            Value::ColorAlpha([1, 2, 3, 4]),
1151            Value::BigInt(-10),
1152            Value::KeyRef("kv".to_string(), "key".to_string()),
1153            Value::DocRef("docs".to_string(), 42),
1154            Value::TableRef("users".to_string()),
1155            Value::PageRef(99),
1156            Value::Secret(vec![9, 8, 7]),
1157            Value::Password("$argon2id$v=19$hash".to_string()),
1158        ];
1159
1160        for original in values {
1161            let mut bytes = Vec::new();
1162            encode(&original, &mut bytes);
1163            let (decoded, consumed) = decode(&bytes).expect("decode");
1164            assert_eq!(consumed, bytes.len());
1165            assert_eq!(decoded, original, "{bytes:?}");
1166        }
1167    }
1168
1169    #[test]
1170    fn compressed_text_and_blob_decode_to_plain_values() {
1171        let text = Value::text("reddb ".repeat(700));
1172        let mut bytes = Vec::new();
1173        encode(&text, &mut bytes);
1174        assert_eq!(bytes[0], DataType::TextZstd.to_byte());
1175        let (decoded, consumed) = decode(&bytes).unwrap();
1176        assert_eq!(consumed, bytes.len());
1177        assert_eq!(decoded, text);
1178
1179        let blob = Value::Blob(vec![0xAB; TOAST_THRESHOLD + 512]);
1180        let mut bytes = Vec::new();
1181        encode(&blob, &mut bytes);
1182        assert_eq!(bytes[0], DataType::BlobZstd.to_byte());
1183        let (decoded, consumed) = decode(&bytes).unwrap();
1184        assert_eq!(consumed, bytes.len());
1185        assert_eq!(decoded, blob);
1186    }
1187
1188    #[test]
1189    fn decode_rejects_short_payload_for_registered_tags() {
1190        let truncated_tags = [
1191            DataType::Integer,
1192            DataType::UnsignedInteger,
1193            DataType::Float,
1194            DataType::Text,
1195            DataType::Blob,
1196            DataType::Boolean,
1197            DataType::Timestamp,
1198            DataType::Duration,
1199            DataType::IpAddr,
1200            DataType::MacAddr,
1201            DataType::Vector,
1202            DataType::Json,
1203            DataType::Uuid,
1204            DataType::NodeRef,
1205            DataType::EdgeRef,
1206            DataType::VectorRef,
1207            DataType::RowRef,
1208            DataType::Color,
1209            DataType::Email,
1210            DataType::Url,
1211            DataType::Phone,
1212            DataType::Semver,
1213            DataType::Cidr,
1214            DataType::Date,
1215            DataType::Time,
1216            DataType::Decimal,
1217            DataType::DecimalText,
1218            DataType::Enum,
1219            DataType::Array,
1220            DataType::TimestampMs,
1221            DataType::Ipv4,
1222            DataType::Ipv6,
1223            DataType::Subnet,
1224            DataType::Port,
1225            DataType::Latitude,
1226            DataType::Longitude,
1227            DataType::GeoPoint,
1228            DataType::Country2,
1229            DataType::Country3,
1230            DataType::Lang2,
1231            DataType::Lang5,
1232            DataType::Currency,
1233            DataType::AssetCode,
1234            DataType::Money,
1235            DataType::ColorAlpha,
1236            DataType::BigInt,
1237            DataType::KeyRef,
1238            DataType::DocRef,
1239            DataType::TableRef,
1240            DataType::PageRef,
1241            DataType::Secret,
1242            DataType::Password,
1243            DataType::TextZstd,
1244            DataType::BlobZstd,
1245        ];
1246
1247        for data_type in truncated_tags {
1248            let err = decode(&[data_type.to_byte()]).expect_err("short payload must error");
1249            assert_eq!(err, ValueError::TruncatedData, "{data_type:?}");
1250        }
1251
1252        assert_eq!(
1253            decode(&[DataType::Nullable.to_byte()]).unwrap().0,
1254            Value::Null
1255        );
1256    }
1257
1258    #[test]
1259    fn decode_rejects_invalid_embedded_tags_and_utf8_payloads() {
1260        assert_eq!(
1261            decode(&[DataType::IpAddr.to_byte(), 5]).expect_err("bad ip version"),
1262            ValueError::InvalidIpVersion(5)
1263        );
1264
1265        let invalid_text = [DataType::Text.to_byte(), 1, 0xff];
1266        assert_eq!(
1267            decode(&invalid_text).expect_err("invalid utf8"),
1268            ValueError::InvalidUtf8
1269        );
1270
1271        let invalid_email = [DataType::Email.to_byte(), 1, 0xff];
1272        assert_eq!(
1273            decode(&invalid_email).expect_err("invalid utf8"),
1274            ValueError::InvalidUtf8
1275        );
1276
1277        let invalid_url = [DataType::Url.to_byte(), 1, 0xff];
1278        assert_eq!(
1279            decode(&invalid_url).expect_err("invalid utf8"),
1280            ValueError::InvalidUtf8
1281        );
1282
1283        let invalid_asset = [DataType::AssetCode.to_byte(), 1, 0xff];
1284        assert_eq!(
1285            decode(&invalid_asset).expect_err("invalid utf8"),
1286            ValueError::InvalidUtf8
1287        );
1288    }
1289
1290    /// Round-trip: encode then decode must recover the original
1291    /// value, byte for byte.
1292    #[test]
1293    fn round_trip_canonical_variants() {
1294        let cases = vec![
1295            Value::Null,
1296            Value::Integer(-12345),
1297            Value::text("hello"),
1298            Value::Boolean(true),
1299            Value::Blob(vec![1, 2, 3, 4, 5]),
1300        ];
1301        for original in cases {
1302            let mut bytes = Vec::new();
1303            encode(&original, &mut bytes);
1304            let (recovered, consumed) = decode(&bytes).expect("decode");
1305            assert_eq!(consumed, bytes.len());
1306            assert_eq!(original, recovered);
1307        }
1308    }
1309
1310    fn value_variant_strategy() -> impl Strategy<Value = Value> {
1311        prop_oneof![
1312            Just(Value::Null),
1313            any::<bool>().prop_map(Value::Boolean),
1314            any::<i64>().prop_map(Value::Integer),
1315            prop_oneof![
1316                any::<f64>(),
1317                Just(f64::NAN),
1318                Just(f64::INFINITY),
1319                Just(f64::NEG_INFINITY),
1320                Just(f64::MIN_POSITIVE),
1321                Just(f64::from_bits(1)),
1322            ]
1323            .prop_map(Value::Float),
1324            "[1-9][0-9]{18,36}\\.[0-9]{1,24}".prop_map(Value::DecimalText),
1325            proptest::collection::vec(any::<u8>(), 0..4096).prop_map(Value::Blob),
1326            prop_oneof![
1327                Just(br#"{"a":null,"b":[1,true]}"#.to_vec()),
1328                Just(br#"[]"#.to_vec()),
1329                Just(br#"{"deep":{"nest":{"leaf":[false]}}}"#.to_vec()),
1330            ]
1331            .prop_map(Value::Json),
1332            any::<i64>().prop_map(Value::Timestamp),
1333            any::<[u8; 16]>().prop_map(Value::Uuid),
1334        ]
1335    }
1336
1337    proptest! {
1338        #![proptest_config(ProptestConfig::with_cases(256))]
1339
1340        #[test]
1341        fn prop_value_codec_round_trips_remaining_variants(original in value_variant_strategy()) {
1342            let mut bytes = Vec::new();
1343            encode(&original, &mut bytes);
1344            let (recovered, consumed) = decode(&bytes).expect("decode");
1345            prop_assert_eq!(consumed, bytes.len());
1346            prop_assert!(
1347                values_equivalent(&recovered, &original),
1348                "recovered={recovered:?} original={original:?}"
1349            );
1350        }
1351    }
1352
1353    fn values_equivalent(left: &Value, right: &Value) -> bool {
1354        match (left, right) {
1355            (Value::Float(a), Value::Float(b)) => a.to_bits() == b.to_bits(),
1356            _ => left == right,
1357        }
1358    }
1359}