1use crate::key_dictionary::KeyDictionary;
28use crate::types::Value;
29use crate::value_codec;
30
31pub const MAGIC: &[u8; 4] = b"RDOC";
33
34pub const VERSION: u8 = 0x03;
36
37pub const VERSION_DICT: u8 = 0x04;
43
44const SUPERSEDED_VERSION: u8 = 0x01;
45const SUPERSEDED_VERSION_DICT: u8 = 0x02;
46
47const ENTRY_SIZE: usize = 6;
49
50const DICT_ENTRY_SIZE: usize = 9;
56
57const KEY_KIND_DICT: u8 = 0x00;
59
60const KEY_KIND_INLINE: u8 = 0x01;
62
63pub type DictEntry = (u8, u32, u32);
68
69#[derive(Debug, PartialEq)]
71pub enum DocBodyError {
72 TruncatedData,
74 BadMagic,
76 UnsupportedVersion(u8),
78 SupersededVersion(u8),
80 OffsetOutOfBounds,
82 InvalidFieldName,
84 FieldLimitExceeded,
86 UnknownKeyId(u32),
88 BadKeyKind(u8),
90 ValueCodecError(crate::types::ValueError),
92}
93
94impl From<crate::types::ValueError> for DocBodyError {
95 fn from(e: crate::types::ValueError) -> Self {
96 DocBodyError::ValueCodecError(e)
97 }
98}
99
100impl std::fmt::Display for DocBodyError {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 match self {
103 Self::TruncatedData => write!(f, "document body: truncated data"),
104 Self::BadMagic => write!(f, "document body: bad magic bytes (expected RDOC)"),
105 Self::UnsupportedVersion(v) => write!(f, "document body: unsupported version {v}"),
106 Self::SupersededVersion(v) => write!(
107 f,
108 "document body: format version {v} is superseded by version {VERSION} for the \
109 lossless-integer clean break (#1768). Pre-bump bodies stored numbers as f64, \
110 so they are refused rather than silently reinterpreted; re-insert the document \
111 to rewrite it in the current format."
112 ),
113 Self::OffsetOutOfBounds => write!(f, "document body: offset points outside buffer"),
114 Self::InvalidFieldName => write!(f, "document body: field name is not valid UTF-8"),
115 Self::FieldLimitExceeded => {
116 write!(f, "document body: field or key-length limit exceeded")
117 }
118 Self::UnknownKeyId(id) => {
119 write!(f, "document body: key-id {id} not found in dictionary")
120 }
121 Self::BadKeyKind(k) => write!(f, "document body: unrecognised key kind {k:#04x}"),
122 Self::ValueCodecError(e) => write!(f, "document body: value codec error: {e}"),
123 }
124 }
125}
126
127impl std::error::Error for DocBodyError {}
128
129pub fn encode(fields: &[(&str, &Value)], out: &mut Vec<u8>) -> Result<(), DocBodyError> {
137 let n = fields.len();
138 if n > u16::MAX as usize {
139 return Err(DocBodyError::FieldLimitExceeded);
140 }
141
142 let mut key_buf: Vec<u8> = Vec::new();
145 let mut val_buf: Vec<u8> = Vec::new();
146 let mut key_lens: Vec<u16> = Vec::with_capacity(n);
147 let mut val_starts: Vec<u32> = Vec::with_capacity(n);
148
149 for (key, value) in fields {
150 let kb = key.as_bytes();
151 if kb.len() > u16::MAX as usize {
152 return Err(DocBodyError::FieldLimitExceeded);
153 }
154 key_lens.push(kb.len() as u16);
155 key_buf.extend_from_slice(kb);
156
157 val_starts.push(val_buf.len() as u32); value_codec::encode(value, &mut val_buf);
159 }
160
161 let vals_abs_start = 4 + 1 + 2 + n * ENTRY_SIZE + key_buf.len();
164
165 out.extend_from_slice(MAGIC);
167 out.push(VERSION);
168 out.extend_from_slice(&(n as u16).to_le_bytes());
169
170 for i in 0..n {
172 let abs_val_offset = vals_abs_start + val_starts[i] as usize;
173 out.extend_from_slice(&key_lens[i].to_le_bytes());
174 out.extend_from_slice(&(abs_val_offset as u32).to_le_bytes());
175 }
176
177 out.extend_from_slice(&key_buf);
179 out.extend_from_slice(&val_buf);
180
181 Ok(())
182}
183
184pub fn decode(data: &[u8]) -> Result<Vec<(String, Value)>, DocBodyError> {
188 let (n, table) = parse_header(data)?;
189 let header_end = 7 + n * ENTRY_SIZE; let mut key_cursor = header_end;
191 let mut result = Vec::with_capacity(n);
192
193 for &(key_len, val_offset) in &table {
194 let key_end = key_cursor + key_len as usize;
196 if key_end > data.len() {
197 return Err(DocBodyError::OffsetOutOfBounds);
198 }
199 let key = std::str::from_utf8(&data[key_cursor..key_end])
200 .map_err(|_| DocBodyError::InvalidFieldName)?
201 .to_string();
202 key_cursor = key_end;
203
204 let value = decode_value_at_offset(data, val_offset)?;
206 result.push((key, value));
207 }
208
209 Ok(result)
210}
211
212pub fn field_names(data: &[u8]) -> Result<Vec<String>, DocBodyError> {
219 let (n, table) = parse_header(data)?;
220 let header_end = 7 + n * ENTRY_SIZE; let mut key_cursor = header_end;
222 let mut names = Vec::with_capacity(n);
223
224 for &(key_len, _val_offset) in &table {
225 let key_end = key_cursor + key_len as usize;
226 if key_end > data.len() {
227 return Err(DocBodyError::OffsetOutOfBounds);
228 }
229 let key = std::str::from_utf8(&data[key_cursor..key_end])
230 .map_err(|_| DocBodyError::InvalidFieldName)?
231 .to_string();
232 key_cursor = key_end;
233 names.push(key);
234 }
235
236 Ok(names)
237}
238
239pub fn read_field_by_name(data: &[u8], name: &str) -> Result<Option<Value>, DocBodyError> {
245 let (n, table) = parse_header(data)?;
246 let header_end = 7 + n * ENTRY_SIZE;
247 let mut key_cursor = header_end;
248
249 for &(key_len, val_offset) in &table {
250 let key_end = key_cursor + key_len as usize;
251 if key_end > data.len() {
252 return Err(DocBodyError::OffsetOutOfBounds);
253 }
254 let key_bytes = &data[key_cursor..key_end];
255 key_cursor = key_end;
256
257 if key_bytes == name.as_bytes() {
258 let value = decode_value_at_offset(data, val_offset)?;
259 return Ok(Some(value));
260 }
261 }
262
263 Ok(None)
264}
265
266pub fn decode_value_at_offset(data: &[u8], val_offset: u32) -> Result<Value, DocBodyError> {
272 let off = val_offset as usize;
273 if off >= data.len() {
274 return Err(DocBodyError::OffsetOutOfBounds);
275 }
276 let (value, _) = value_codec::decode(&data[off..])?;
277 Ok(value)
278}
279
280fn version_error(version: u8) -> DocBodyError {
281 if version == SUPERSEDED_VERSION || version == SUPERSEDED_VERSION_DICT {
282 DocBodyError::SupersededVersion(version)
283 } else {
284 DocBodyError::UnsupportedVersion(version)
285 }
286}
287
288pub fn parse_header(data: &[u8]) -> Result<(usize, Vec<(u16, u32)>), DocBodyError> {
296 if data.len() < 7 {
297 return Err(DocBodyError::TruncatedData);
298 }
299 if &data[0..4] != MAGIC.as_slice() {
300 return Err(DocBodyError::BadMagic);
301 }
302 if data[4] != VERSION {
303 return Err(version_error(data[4]));
304 }
305
306 let n = u16::from_le_bytes([data[5], data[6]]) as usize;
307 let table_end = 7 + n * ENTRY_SIZE;
308 if data.len() < table_end {
309 return Err(DocBodyError::TruncatedData);
310 }
311
312 let mut table = Vec::with_capacity(n);
313 for i in 0..n {
314 let base = 7 + i * ENTRY_SIZE;
315 let key_len = u16::from_le_bytes([data[base], data[base + 1]]);
316 let val_offset = u32::from_le_bytes([
317 data[base + 2],
318 data[base + 3],
319 data[base + 4],
320 data[base + 5],
321 ]);
322 table.push((key_len, val_offset));
323 }
324
325 Ok((n, table))
326}
327
328pub fn encode_with_dictionary(
341 fields: &[(&str, &Value)],
342 dict: &mut KeyDictionary,
343 classify: impl Fn(&str) -> bool,
344 out: &mut Vec<u8>,
345) -> Result<(), DocBodyError> {
346 let n = fields.len();
347 if n > u16::MAX as usize {
348 return Err(DocBodyError::FieldLimitExceeded);
349 }
350
351 let mut key_buf: Vec<u8> = Vec::new();
354 let mut val_buf: Vec<u8> = Vec::new();
355 let mut kinds: Vec<u8> = Vec::with_capacity(n);
356 let mut refs: Vec<u32> = Vec::with_capacity(n);
357 let mut val_starts: Vec<u32> = Vec::with_capacity(n);
358
359 for (key, value) in fields {
360 if classify(key) {
361 let id = dict.intern(key);
362 kinds.push(KEY_KIND_DICT);
363 refs.push(id);
364 } else {
365 let kb = key.as_bytes();
366 if kb.len() > u16::MAX as usize {
367 return Err(DocBodyError::FieldLimitExceeded);
368 }
369 kinds.push(KEY_KIND_INLINE);
370 refs.push(kb.len() as u32);
371 key_buf.extend_from_slice(kb);
372 }
373 val_starts.push(val_buf.len() as u32); value_codec::encode(value, &mut val_buf);
375 }
376
377 let vals_abs_start = 4 + 1 + 2 + n * DICT_ENTRY_SIZE + key_buf.len();
380
381 out.extend_from_slice(MAGIC);
382 out.push(VERSION_DICT);
383 out.extend_from_slice(&(n as u16).to_le_bytes());
384
385 for i in 0..n {
386 let abs_val_offset = vals_abs_start + val_starts[i] as usize;
387 out.push(kinds[i]);
388 out.extend_from_slice(&refs[i].to_le_bytes());
389 out.extend_from_slice(&(abs_val_offset as u32).to_le_bytes());
390 }
391
392 out.extend_from_slice(&key_buf);
393 out.extend_from_slice(&val_buf);
394
395 Ok(())
396}
397
398pub fn decode_with_dictionary(
405 data: &[u8],
406 dict: &KeyDictionary,
407) -> Result<Vec<(String, Value)>, DocBodyError> {
408 let (n, table) = parse_dict_header(data)?;
409 let header_end = 7 + n * DICT_ENTRY_SIZE; let mut key_cursor = header_end;
411 let mut result = Vec::with_capacity(n);
412
413 for &(kind, key_ref, val_offset) in &table {
414 let key = match kind {
415 KEY_KIND_DICT => dict
416 .name_of(key_ref)
417 .ok_or(DocBodyError::UnknownKeyId(key_ref))?
418 .to_string(),
419 KEY_KIND_INLINE => {
420 let key_end = key_cursor + key_ref as usize;
421 if key_end > data.len() {
422 return Err(DocBodyError::OffsetOutOfBounds);
423 }
424 let name = std::str::from_utf8(&data[key_cursor..key_end])
425 .map_err(|_| DocBodyError::InvalidFieldName)?
426 .to_string();
427 key_cursor = key_end;
428 name
429 }
430 other => return Err(DocBodyError::BadKeyKind(other)),
431 };
432
433 let value = decode_value_at_offset(data, val_offset)?;
434 result.push((key, value));
435 }
436
437 Ok(result)
438}
439
440pub fn parse_dict_header(data: &[u8]) -> Result<(usize, Vec<DictEntry>), DocBodyError> {
446 if data.len() < 7 {
447 return Err(DocBodyError::TruncatedData);
448 }
449 if &data[0..4] != MAGIC.as_slice() {
450 return Err(DocBodyError::BadMagic);
451 }
452 if data[4] != VERSION_DICT {
453 return Err(version_error(data[4]));
454 }
455
456 let n = u16::from_le_bytes([data[5], data[6]]) as usize;
457 let table_end = 7 + n * DICT_ENTRY_SIZE;
458 if data.len() < table_end {
459 return Err(DocBodyError::TruncatedData);
460 }
461
462 let mut table = Vec::with_capacity(n);
463 for i in 0..n {
464 let base = 7 + i * DICT_ENTRY_SIZE;
465 let kind = data[base];
466 let key_ref = u32::from_le_bytes([
467 data[base + 1],
468 data[base + 2],
469 data[base + 3],
470 data[base + 4],
471 ]);
472 let val_offset = u32::from_le_bytes([
473 data[base + 5],
474 data[base + 6],
475 data[base + 7],
476 data[base + 8],
477 ]);
478 table.push((kind, key_ref, val_offset));
479 }
480
481 Ok((n, table))
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487 use crate::types::Value;
488 use std::net::{IpAddr, Ipv4Addr};
489
490 fn round_trip(fields: &[(&str, Value)]) -> Vec<(String, Value)> {
491 let refs: Vec<(&str, &Value)> = fields.iter().map(|(k, v)| (*k, v)).collect();
492 let mut buf = Vec::new();
493 encode(&refs, &mut buf).expect("encode");
494 decode(&buf).expect("decode")
495 }
496
497 #[test]
498 fn empty_document_round_trips() {
499 let got = round_trip(&[]);
500 assert!(got.is_empty());
501 }
502
503 #[test]
504 fn single_field_round_trip() {
505 let fields = [("name", Value::text("Alice"))];
506 let got = round_trip(&fields);
507 assert_eq!(got.len(), 1);
508 assert_eq!(got[0].0, "name");
509 assert_eq!(got[0].1, Value::text("Alice"));
510 }
511
512 #[test]
513 fn multi_field_round_trip() {
514 let fields = [
515 ("id", Value::Integer(42)),
516 ("email", Value::Email("a@example.com".to_string())),
517 ("active", Value::Boolean(true)),
518 ("score", Value::Float(9.5)),
519 ];
520 let got = round_trip(&fields);
521 assert_eq!(got.len(), 4);
522 for (i, (k, v)) in fields.iter().enumerate() {
523 assert_eq!(got[i].0, *k);
524 assert_eq!(got[i].1, *v);
525 }
526 }
527
528 #[test]
530 fn rich_semantic_types_round_trip() {
531 let fields = [
532 ("email", Value::Email("user@example.com".to_string())),
533 ("ipv4", Value::Ipv4(0x7f000001)),
534 ("subnet", Value::Subnet(0x0a000000, 0xff000000)),
535 ("color", Value::Color([0xDE, 0xAD, 0xBE])),
536 ("phone", Value::Phone(5511999000000)),
537 ("semver", Value::Semver(1_002_003)),
538 ("uuid", Value::Uuid([0xAB; 16])),
539 (
540 "money",
541 Value::Money {
542 asset_code: "USD".to_string(),
543 minor_units: 9999,
544 scale: 2,
545 },
546 ),
547 ("geo", Value::GeoPoint(-23_550_520, -46_633_308)),
548 (
549 "ip_mixed",
550 Value::IpAddr(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))),
551 ),
552 ("url", Value::Url("https://reddb.io".to_string())),
553 ("color_alpha", Value::ColorAlpha([1, 2, 3, 255])),
554 ("lang", Value::Lang2(*b"en")),
555 ("country", Value::Country3(*b"USA")),
556 ];
557 let got = round_trip(&fields);
558 assert_eq!(got.len(), fields.len());
559 for (i, (k, v)) in fields.iter().enumerate() {
560 assert_eq!(got[i].0, *k, "key mismatch at {i}");
561 assert_eq!(got[i].1, *v, "value mismatch for field {k}");
562 }
563 }
564
565 #[test]
567 fn read_field_by_name_skips_other_fields() {
568 let fields = [
569 ("a", Value::Integer(1)),
570 ("b", Value::text("hello")),
571 ("c", Value::Boolean(false)),
572 ];
573 let refs: Vec<(&str, &Value)> = fields.iter().map(|(k, v)| (*k, v)).collect();
574 let mut buf = Vec::new();
575 encode(&refs, &mut buf).expect("encode");
576
577 let v = read_field_by_name(&buf, "b")
578 .expect("lookup")
579 .expect("found");
580 assert_eq!(v, Value::text("hello"));
581
582 let missing = read_field_by_name(&buf, "z").expect("lookup");
583 assert!(missing.is_none());
584 }
585
586 #[test]
588 fn field_names_lists_keys_in_encode_order() {
589 let fields = [
590 ("name", Value::text("Alice")),
591 ("score", Value::Integer(30)),
592 ("active", Value::Boolean(true)),
593 ];
594 let refs: Vec<(&str, &Value)> = fields.iter().map(|(k, v)| (*k, v)).collect();
595 let mut buf = Vec::new();
596 encode(&refs, &mut buf).expect("encode");
597
598 let names = field_names(&buf).expect("field_names");
599 assert_eq!(names, vec!["name", "score", "active"]);
600
601 assert!(field_names(&[]).is_err());
602 }
603
604 #[test]
606 fn decode_value_at_offset_matches_full_decode() {
607 let fields = [("x", Value::Integer(100)), ("y", Value::Float(1.5))];
608 let refs: Vec<(&str, &Value)> = fields.iter().map(|(k, v)| (*k, v)).collect();
609 let mut buf = Vec::new();
610 encode(&refs, &mut buf).expect("encode");
611
612 let (n, table) = parse_header(&buf).expect("parse_header");
613 assert_eq!(n, 2);
614
615 for (i, (k, v)) in fields.iter().enumerate() {
616 let (_key_len, val_offset) = table[i];
617 let got = decode_value_at_offset(&buf, val_offset).expect("decode_at_offset");
618 assert_eq!(got, *v, "field {k} mismatch");
619 }
620 }
621
622 #[test]
623 fn rejects_bad_magic() {
624 let mut buf = vec![b'X', b'X', b'X', b'X', VERSION, 0, 0];
625 assert_eq!(decode(&buf), Err(DocBodyError::BadMagic));
626 buf[0..4].copy_from_slice(MAGIC);
627 buf[4] = 0x99;
628 assert_eq!(decode(&buf), Err(DocBodyError::UnsupportedVersion(0x99)));
629 }
630
631 #[test]
632 fn superseded_plain_version_is_rejected_didactically() {
633 let fields = [("id", Value::Integer(1))];
634 let refs: Vec<(&str, &Value)> = fields.iter().map(|(k, v)| (*k, v)).collect();
635 let mut buf = Vec::new();
636 encode(&refs, &mut buf).expect("encode");
637 assert_eq!(buf[4], VERSION);
638
639 buf[4] = SUPERSEDED_VERSION;
640 let err = decode(&buf).expect_err("pre-bump body must be refused");
641 assert_eq!(err, DocBodyError::SupersededVersion(SUPERSEDED_VERSION));
642
643 let msg = err.to_string();
644 assert!(msg.contains("superseded"), "{msg}");
645 assert!(msg.contains("#1768"), "{msg}");
646 assert!(msg.contains("re-insert"), "{msg}");
647 }
648
649 #[test]
650 fn superseded_dict_version_is_rejected_didactically() {
651 let fields = [("id", Value::Integer(1))];
652 let refs: Vec<(&str, &Value)> = fields.iter().map(|(k, v)| (*k, v)).collect();
653 let mut dict = KeyDictionary::new();
654 let mut buf = Vec::new();
655 encode_with_dictionary(&refs, &mut dict, |_| true, &mut buf).expect("encode");
656 assert_eq!(buf[4], VERSION_DICT);
657
658 buf[4] = SUPERSEDED_VERSION_DICT;
659 let err = decode_with_dictionary(&buf, &dict).expect_err("pre-bump body must be refused");
660 assert_eq!(
661 err,
662 DocBodyError::SupersededVersion(SUPERSEDED_VERSION_DICT)
663 );
664 }
665
666 #[test]
667 fn rejects_truncated_buffer() {
668 assert_eq!(decode(&[]), Err(DocBodyError::TruncatedData));
669 assert_eq!(
670 decode(&[b'R', b'D', b'O', b'C', 1, 0]),
671 Err(DocBodyError::TruncatedData)
672 );
673 }
674
675 #[test]
676 fn null_values_round_trip() {
677 let fields = [("nothing", Value::Null), ("something", Value::Integer(7))];
678 let got = round_trip(&fields);
679 assert_eq!(got[0].1, Value::Null);
680 assert_eq!(got[1].1, Value::Integer(7));
681 }
682
683 #[test]
684 fn array_value_round_trip() {
685 let fields = [(
686 "tags",
687 Value::Array(vec![Value::text("a"), Value::text("b"), Value::Integer(3)]),
688 )];
689 let got = round_trip(&fields);
690 assert_eq!(
691 got[0].1,
692 Value::Array(vec![Value::text("a"), Value::text("b"), Value::Integer(3)])
693 );
694 }
695
696 #[test]
698 fn offset_table_offsets_are_valid() {
699 let fields = [("k", Value::Boolean(true))];
700 let refs: Vec<(&str, &Value)> = fields.iter().map(|(k, v)| (*k, v)).collect();
701 let mut buf = Vec::new();
702 encode(&refs, &mut buf).expect("encode");
703
704 let (n, table) = parse_header(&buf).expect("header");
705 assert_eq!(n, 1);
706 let (_klen, val_offset) = table[0];
707 let (v, _) = value_codec::decode(&buf[val_offset as usize..]).expect("decode at offset");
709 assert_eq!(v, Value::Boolean(true));
710 }
711
712 fn all_common(_: &str) -> bool {
716 true
717 }
718
719 #[test]
722 fn common_keys_are_interned_as_key_ids() {
723 let fields = [
724 ("id", Value::Integer(1)),
725 ("name", Value::text("Alice")),
726 ("active", Value::Boolean(true)),
727 ];
728 let refs: Vec<(&str, &Value)> = fields.iter().map(|(k, v)| (*k, v)).collect();
729 let mut dict = KeyDictionary::new();
730 let mut buf = Vec::new();
731 encode_with_dictionary(&refs, &mut dict, all_common, &mut buf).expect("encode");
732
733 assert_eq!(dict.len(), 3);
735 assert_eq!(dict.id_of("id"), Some(0));
736 assert_eq!(dict.id_of("name"), Some(1));
737 assert_eq!(dict.id_of("active"), Some(2));
738
739 let (n, table) = parse_dict_header(&buf).expect("header");
741 assert_eq!(n, 3);
742 for (kind, _key_ref, _off) in &table {
743 assert_eq!(*kind, KEY_KIND_DICT);
744 }
745 assert!(!buf.windows(4).any(|w| w == b"name"));
747 }
748
749 #[test]
752 fn new_common_key_appends_to_dictionary() {
753 let mut dict = KeyDictionary::new();
754
755 let doc1 = [("id", Value::Integer(1)), ("name", Value::text("Alice"))];
756 let r1: Vec<(&str, &Value)> = doc1.iter().map(|(k, v)| (*k, v)).collect();
757 let mut b1 = Vec::new();
758 encode_with_dictionary(&r1, &mut dict, all_common, &mut b1).expect("encode 1");
759 assert_eq!(dict.len(), 2);
760
761 let doc2 = [
763 ("id", Value::Integer(2)),
764 ("name", Value::text("Bob")),
765 ("email", Value::Email("bob@example.com".to_string())),
766 ];
767 let r2: Vec<(&str, &Value)> = doc2.iter().map(|(k, v)| (*k, v)).collect();
768 let mut b2 = Vec::new();
769 encode_with_dictionary(&r2, &mut dict, all_common, &mut b2).expect("encode 2");
770
771 assert_eq!(dict.len(), 3);
773 assert_eq!(dict.id_of("id"), Some(0));
774 assert_eq!(dict.id_of("name"), Some(1));
775 assert_eq!(dict.id_of("email"), Some(2));
776
777 let g1 = decode_with_dictionary(&b1, &dict).expect("decode 1");
779 assert_eq!(g1[1], ("name".to_string(), Value::text("Alice")));
780 let g2 = decode_with_dictionary(&b2, &dict).expect("decode 2");
781 assert_eq!(
782 g2[2],
783 (
784 "email".to_string(),
785 Value::Email("bob@example.com".to_string())
786 )
787 );
788 }
789
790 #[test]
792 fn rare_key_stays_inline_and_out_of_dictionary() {
793 let common = |k: &str| k == "id" || k == "name";
795 let fields = [
796 ("id", Value::Integer(7)),
797 ("name", Value::text("Carol")),
798 ("x9f3_one_off_attr", Value::text("rare-value")),
799 ];
800 let refs: Vec<(&str, &Value)> = fields.iter().map(|(k, v)| (*k, v)).collect();
801 let mut dict = KeyDictionary::new();
802 let mut buf = Vec::new();
803 encode_with_dictionary(&refs, &mut dict, common, &mut buf).expect("encode");
804
805 assert_eq!(dict.len(), 2);
807 assert_eq!(dict.id_of("x9f3_one_off_attr"), None);
808
809 let (_n, table) = parse_dict_header(&buf).expect("header");
811 assert_eq!(table[0].0, KEY_KIND_DICT);
812 assert_eq!(table[1].0, KEY_KIND_DICT);
813 assert_eq!(table[2].0, KEY_KIND_INLINE);
814 assert!(buf
815 .windows("x9f3_one_off_attr".len())
816 .any(|w| w == b"x9f3_one_off_attr"));
817
818 let got = decode_with_dictionary(&buf, &dict).expect("decode");
820 assert_eq!(
821 got[2],
822 ("x9f3_one_off_attr".to_string(), Value::text("rare-value"))
823 );
824 }
825
826 #[test]
829 fn mixed_dictionary_and_inline_round_trip() {
830 let common = |k: &str| matches!(k, "id" | "email" | "active");
831 let fields = [
832 ("id", Value::Integer(42)),
833 ("email", Value::Email("a@example.com".to_string())),
834 ("active", Value::Boolean(true)),
835 ("one_off_color", Value::Color([0xDE, 0xAD, 0xBE])),
836 ("rare_geo", Value::GeoPoint(-23_550_520, -46_633_308)),
837 ];
838 let refs: Vec<(&str, &Value)> = fields.iter().map(|(k, v)| (*k, v)).collect();
839 let mut dict = KeyDictionary::new();
840 let mut buf = Vec::new();
841 encode_with_dictionary(&refs, &mut dict, common, &mut buf).expect("encode");
842
843 let mut dict_bytes = Vec::new();
845 dict.encode(&mut dict_bytes);
846 let reloaded = KeyDictionary::decode(&dict_bytes).expect("reload dict");
847
848 let got = decode_with_dictionary(&buf, &reloaded).expect("decode");
849 assert_eq!(got.len(), fields.len());
850 for (i, (k, v)) in fields.iter().enumerate() {
851 assert_eq!(got[i].0, *k, "key mismatch at {i}");
852 assert_eq!(got[i].1, *v, "value mismatch for field {k}");
853 }
854 }
855
856 #[test]
857 fn empty_dictionary_document_round_trips() {
858 let mut dict = KeyDictionary::new();
859 let mut buf = Vec::new();
860 encode_with_dictionary(&[], &mut dict, all_common, &mut buf).expect("encode");
861 let got = decode_with_dictionary(&buf, &dict).expect("decode");
862 assert!(got.is_empty());
863 assert!(dict.is_empty());
864 }
865
866 #[test]
869 fn unknown_key_id_is_rejected() {
870 let fields = [("only_key", Value::Integer(1))];
871 let refs: Vec<(&str, &Value)> = fields.iter().map(|(k, v)| (*k, v)).collect();
872 let mut dict = KeyDictionary::new();
873 let mut buf = Vec::new();
874 encode_with_dictionary(&refs, &mut dict, all_common, &mut buf).expect("encode");
875
876 let empty = KeyDictionary::new();
877 assert_eq!(
878 decode_with_dictionary(&buf, &empty),
879 Err(DocBodyError::UnknownKeyId(0))
880 );
881 }
882
883 #[test]
886 fn v1_and_v2_versions_do_not_alias() {
887 let fields = [("k", Value::Integer(1))];
888 let refs: Vec<(&str, &Value)> = fields.iter().map(|(k, v)| (*k, v)).collect();
889
890 let mut v1 = Vec::new();
891 encode(&refs, &mut v1).expect("v1");
892 assert_eq!(v1[4], VERSION);
893
894 let mut dict = KeyDictionary::new();
895 let mut v2 = Vec::new();
896 encode_with_dictionary(&refs, &mut dict, all_common, &mut v2).expect("v2");
897 assert_eq!(v2[4], VERSION_DICT);
898
899 assert_eq!(
901 decode(&v2),
902 Err(DocBodyError::UnsupportedVersion(VERSION_DICT))
903 );
904 assert_eq!(
905 parse_dict_header(&v1),
906 Err(DocBodyError::UnsupportedVersion(VERSION))
907 );
908 }
909}