1use crate::api::error::UniError;
47use crate::core::id::{Eid, Vid};
48use crate::value::{Edge, Node, Path, Value};
49use serde::{Deserialize, Serialize};
50use std::collections::{BTreeMap, HashMap};
51
52pub const TAG_NULL: u8 = 0;
54pub const TAG_BOOL: u8 = 1;
55pub const TAG_INT: u8 = 2;
56pub const TAG_FLOAT: u8 = 3;
57pub const TAG_STRING: u8 = 4;
58pub const TAG_LIST: u8 = 5;
59pub const TAG_MAP: u8 = 6;
60pub const TAG_BYTES: u8 = 7;
61pub const TAG_NODE: u8 = 8;
62pub const TAG_EDGE: u8 = 9;
63pub const TAG_PATH: u8 = 10;
64pub const TAG_DATE: u8 = 11;
65pub const TAG_TIME: u8 = 12;
66pub const TAG_DATETIME: u8 = 13;
67pub const TAG_DURATION: u8 = 14;
68pub const TAG_VECTOR: u8 = 16;
70pub const TAG_LOCALTIME: u8 = 17;
71pub const TAG_LOCALDATETIME: u8 = 18;
72pub const TAG_BTIC: u8 = 19;
73
74fn decode_msgpack<'de, T: Deserialize<'de>>(
82 payload: &'de [u8],
83 type_name: &'static str,
84) -> Result<T, UniError> {
85 rmp_serde::from_slice(payload).map_err(|e| UniError::Storage {
86 message: format!("failed to decode {type_name}: {e}"),
87 source: None,
88 })
89}
90
91fn encode_msgpack<T: Serialize>(buf: &mut Vec<u8>, tag: u8, value: &T, type_name: &'static str) {
95 buf.push(tag);
96 rmp_serde::encode::write(buf, value).unwrap_or_else(|_| panic!("{type_name} encode failed"));
97}
98
99pub fn encode(value: &Value) -> Vec<u8> {
105 let mut buf = Vec::new();
106 encode_to_buf(value, &mut buf);
107 buf
108}
109
110pub fn decode(bytes: &[u8]) -> Result<Value, UniError> {
112 if bytes.is_empty() {
113 return Err(UniError::Storage {
114 message: "empty CypherValue bytes".to_string(),
115 source: None,
116 });
117 }
118 let tag = bytes[0];
119 let payload = &bytes[1..];
120
121 match tag {
122 TAG_NULL => Ok(Value::Null),
123 TAG_BOOL => Ok(Value::Bool(decode_msgpack(payload, "bool")?)),
124 TAG_INT => Ok(Value::Int(decode_msgpack(payload, "int")?)),
125 TAG_FLOAT => Ok(Value::Float(decode_msgpack(payload, "float")?)),
126 TAG_STRING => Ok(Value::String(decode_msgpack(payload, "string")?)),
127 TAG_BYTES => Ok(Value::Bytes(decode_msgpack(payload, "bytes")?)),
128 TAG_LIST => {
129 let blobs: Vec<Vec<u8>> = decode_msgpack(payload, "list")?;
130 let items: Result<Vec<Value>, UniError> = blobs.iter().map(|b| decode(b)).collect();
131 Ok(Value::List(items?))
132 }
133 TAG_MAP => {
134 let blob_map: HashMap<String, Vec<u8>> = decode_msgpack(payload, "map")?;
135 let mut map = HashMap::new();
136 for (k, v_blob) in blob_map {
137 map.insert(k, decode(&v_blob)?);
138 }
139 Ok(Value::Map(map))
140 }
141 TAG_NODE => {
142 let np: NodePayload = decode_msgpack(payload, "node")?;
143 let mut props = HashMap::new();
144 for (k, v_blob) in np.properties {
145 props.insert(k, decode(&v_blob)?);
146 }
147 Ok(Value::Node(Node {
148 vid: np.vid,
149 labels: np.labels,
150 properties: props,
151 }))
152 }
153 TAG_EDGE => {
154 let ep: EdgePayload = decode_msgpack(payload, "edge")?;
155 let mut props = HashMap::new();
156 for (k, v_blob) in ep.properties {
157 props.insert(k, decode(&v_blob)?);
158 }
159 Ok(Value::Edge(Edge {
160 eid: ep.eid,
161 edge_type: ep.edge_type,
162 src: ep.src,
163 dst: ep.dst,
164 properties: props,
165 }))
166 }
167 TAG_PATH => {
168 let pp: PathPayload = decode_msgpack(payload, "path")?;
169 let nodes: Result<Vec<Node>, UniError> = pp
170 .nodes
171 .iter()
172 .map(|b| match decode(b)? {
173 Value::Node(n) => Ok(n),
174 _ => Err(UniError::Storage {
175 message: "path node blob is not a Node".to_string(),
176 source: None,
177 }),
178 })
179 .collect();
180 let edges: Result<Vec<Edge>, UniError> = pp
181 .edges
182 .iter()
183 .map(|b| match decode(b)? {
184 Value::Edge(e) => Ok(e),
185 _ => Err(UniError::Storage {
186 message: "path edge blob is not an Edge".to_string(),
187 source: None,
188 }),
189 })
190 .collect();
191 Ok(Value::Path(Path {
192 nodes: nodes?,
193 edges: edges?,
194 }))
195 }
196 TAG_VECTOR => Ok(Value::Vector(decode_msgpack(payload, "vector")?)),
197 TAG_DATE => Ok(Value::Temporal(crate::value::TemporalValue::Date {
198 days_since_epoch: decode_msgpack(payload, "date")?,
199 })),
200 TAG_LOCALTIME => Ok(Value::Temporal(crate::value::TemporalValue::LocalTime {
201 nanos_since_midnight: decode_msgpack(payload, "localtime")?,
202 })),
203 TAG_TIME => {
204 let tp: TimePayload = decode_msgpack(payload, "time")?;
205 Ok(Value::Temporal(crate::value::TemporalValue::Time {
206 nanos_since_midnight: tp.nanos,
207 offset_seconds: tp.offset,
208 }))
209 }
210 TAG_LOCALDATETIME => Ok(Value::Temporal(
211 crate::value::TemporalValue::LocalDateTime {
212 nanos_since_epoch: decode_msgpack(payload, "localdatetime")?,
213 },
214 )),
215 TAG_DATETIME => {
216 let dp: DateTimePayload = decode_msgpack(payload, "datetime")?;
217 Ok(Value::Temporal(crate::value::TemporalValue::DateTime {
218 nanos_since_epoch: dp.nanos,
219 offset_seconds: dp.offset,
220 timezone_name: dp.tz_name,
221 }))
222 }
223 TAG_DURATION => {
224 let dp: DurationPayload = decode_msgpack(payload, "duration")?;
225 Ok(Value::Temporal(crate::value::TemporalValue::Duration {
226 months: dp.months,
227 days: dp.days,
228 nanos: dp.nanos,
229 }))
230 }
231 TAG_BTIC => {
232 let btic = uni_btic::encode::decode_slice(payload).map_err(|e| UniError::Storage {
233 message: format!("failed to decode BTIC: {e}"),
234 source: None,
235 })?;
236 Ok(Value::Temporal(crate::value::TemporalValue::Btic {
237 lo: btic.lo(),
238 hi: btic.hi(),
239 meta: btic.meta(),
240 }))
241 }
242 _ => Err(UniError::Storage {
243 message: format!("unknown CypherValue tag: {tag}"),
244 source: None,
245 }),
246 }
247}
248
249pub fn peek_tag(bytes: &[u8]) -> Option<u8> {
255 bytes.first().copied()
256}
257
258pub fn is_null(bytes: &[u8]) -> bool {
260 peek_tag(bytes) == Some(TAG_NULL)
261}
262
263pub fn decode_int(bytes: &[u8]) -> Option<i64> {
269 if bytes.first().copied() != Some(TAG_INT) {
270 return None;
271 }
272 rmp_serde::from_slice(&bytes[1..]).ok()
273}
274
275pub fn decode_float(bytes: &[u8]) -> Option<f64> {
277 if bytes.first().copied() != Some(TAG_FLOAT) {
278 return None;
279 }
280 rmp_serde::from_slice(&bytes[1..]).ok()
281}
282
283pub fn decode_bool(bytes: &[u8]) -> Option<bool> {
285 if bytes.first().copied() != Some(TAG_BOOL) {
286 return None;
287 }
288 rmp_serde::from_slice(&bytes[1..]).ok()
289}
290
291pub fn decode_string(bytes: &[u8]) -> Option<String> {
293 if bytes.first().copied() != Some(TAG_STRING) {
294 return None;
295 }
296 rmp_serde::from_slice(&bytes[1..]).ok()
297}
298
299pub fn encode_int(value: i64) -> Vec<u8> {
305 let mut buf = Vec::new();
306 buf.push(TAG_INT);
307 rmp_serde::encode::write(&mut buf, &value).expect("int encode failed");
308 buf
309}
310
311pub fn encode_float(value: f64) -> Vec<u8> {
313 let mut buf = Vec::new();
314 buf.push(TAG_FLOAT);
315 rmp_serde::encode::write(&mut buf, &value).expect("float encode failed");
316 buf
317}
318
319pub fn encode_bool(value: bool) -> Vec<u8> {
321 let mut buf = Vec::new();
322 buf.push(TAG_BOOL);
323 rmp_serde::encode::write(&mut buf, &value).expect("bool encode failed");
324 buf
325}
326
327pub fn encode_string(value: &str) -> Vec<u8> {
329 let mut buf = Vec::new();
330 buf.push(TAG_STRING);
331 rmp_serde::encode::write(&mut buf, value).expect("string encode failed");
332 buf
333}
334
335pub fn encode_null() -> Vec<u8> {
337 vec![TAG_NULL]
338}
339
340pub fn extract_map_entry_raw(blob: &[u8], key: &str) -> Option<Vec<u8>> {
350 if blob.first().copied() != Some(TAG_MAP) {
351 return None;
352 }
353 let payload = &blob[1..];
354 let blob_map: HashMap<String, Vec<u8>> = rmp_serde::from_slice(payload).ok()?;
355 blob_map.get(key).cloned()
356}
357
358fn encode_to_buf(value: &Value, buf: &mut Vec<u8>) {
363 match value {
364 Value::Null => buf.push(TAG_NULL),
365 Value::Bool(b) => encode_msgpack(buf, TAG_BOOL, b, "bool"),
366 Value::Int(i) => encode_msgpack(buf, TAG_INT, i, "int"),
367 Value::Float(f) => encode_msgpack(buf, TAG_FLOAT, f, "float"),
368 Value::String(s) => encode_msgpack(buf, TAG_STRING, s, "string"),
369 Value::Bytes(b) => encode_msgpack(buf, TAG_BYTES, b, "bytes"),
370 Value::List(items) => {
371 let blobs: Vec<Vec<u8>> = items.iter().map(encode).collect();
372 encode_msgpack(buf, TAG_LIST, &blobs, "list");
373 }
374 Value::Map(map) => {
375 let blob_map: BTreeMap<String, Vec<u8>> =
376 map.iter().map(|(k, v)| (k.clone(), encode(v))).collect();
377 encode_msgpack(buf, TAG_MAP, &blob_map, "map");
378 }
379 Value::Node(node) => {
380 let mut props_blobs: Vec<(String, Vec<u8>)> = node
381 .properties
382 .iter()
383 .map(|(k, v)| (k.clone(), encode(v)))
384 .collect();
385 props_blobs.sort_by(|a, b| a.0.cmp(&b.0));
386 let payload = NodePayload {
387 vid: node.vid,
388 labels: node.labels.clone(),
389 properties: props_blobs,
390 };
391 encode_msgpack(buf, TAG_NODE, &payload, "node");
392 }
393 Value::Edge(edge) => {
394 let mut props_blobs: Vec<(String, Vec<u8>)> = edge
395 .properties
396 .iter()
397 .map(|(k, v)| (k.clone(), encode(v)))
398 .collect();
399 props_blobs.sort_by(|a, b| a.0.cmp(&b.0));
400 let payload = EdgePayload {
401 eid: edge.eid,
402 edge_type: edge.edge_type.clone(),
403 src: edge.src,
404 dst: edge.dst,
405 properties: props_blobs,
406 };
407 encode_msgpack(buf, TAG_EDGE, &payload, "edge");
408 }
409 Value::Path(path) => {
410 let payload = PathPayload {
411 nodes: path
412 .nodes
413 .iter()
414 .map(|n| encode(&Value::Node(n.clone())))
415 .collect(),
416 edges: path
417 .edges
418 .iter()
419 .map(|e| encode(&Value::Edge(e.clone())))
420 .collect(),
421 };
422 encode_msgpack(buf, TAG_PATH, &payload, "path");
423 }
424 Value::Vector(v) => encode_msgpack(buf, TAG_VECTOR, v, "vector"),
425 Value::Temporal(t) => match t {
426 crate::value::TemporalValue::Date { days_since_epoch } => {
427 encode_msgpack(buf, TAG_DATE, days_since_epoch, "date");
428 }
429 crate::value::TemporalValue::LocalTime {
430 nanos_since_midnight,
431 } => encode_msgpack(buf, TAG_LOCALTIME, nanos_since_midnight, "localtime"),
432 crate::value::TemporalValue::Time {
433 nanos_since_midnight,
434 offset_seconds,
435 } => {
436 let payload = TimePayload {
437 nanos: *nanos_since_midnight,
438 offset: *offset_seconds,
439 };
440 encode_msgpack(buf, TAG_TIME, &payload, "time");
441 }
442 crate::value::TemporalValue::LocalDateTime { nanos_since_epoch } => {
443 encode_msgpack(buf, TAG_LOCALDATETIME, nanos_since_epoch, "localdatetime");
444 }
445 crate::value::TemporalValue::DateTime {
446 nanos_since_epoch,
447 offset_seconds,
448 timezone_name,
449 } => {
450 let payload = DateTimePayload {
451 nanos: *nanos_since_epoch,
452 offset: *offset_seconds,
453 tz_name: timezone_name.clone(),
454 };
455 encode_msgpack(buf, TAG_DATETIME, &payload, "datetime");
456 }
457 crate::value::TemporalValue::Duration {
458 months,
459 days,
460 nanos,
461 } => {
462 let payload = DurationPayload {
463 months: *months,
464 days: *days,
465 nanos: *nanos,
466 };
467 encode_msgpack(buf, TAG_DURATION, &payload, "duration");
468 }
469 crate::value::TemporalValue::Btic { lo, hi, meta } => {
470 buf.push(TAG_BTIC);
471 let btic = uni_btic::Btic::new(*lo, *hi, *meta).expect("invalid BTIC value");
472 buf.extend_from_slice(&uni_btic::encode::encode(&btic));
473 }
474 },
475 }
476}
477
478#[derive(Serialize, Deserialize)]
483struct NodePayload {
484 vid: Vid,
485 labels: Vec<String>,
486 properties: Vec<(String, Vec<u8>)>,
487}
488
489#[derive(Serialize, Deserialize)]
490struct EdgePayload {
491 eid: Eid,
492 edge_type: String,
493 src: Vid,
494 dst: Vid,
495 properties: Vec<(String, Vec<u8>)>,
496}
497
498#[derive(Serialize, Deserialize)]
499struct PathPayload {
500 nodes: Vec<Vec<u8>>,
501 edges: Vec<Vec<u8>>,
502}
503
504#[derive(Serialize, Deserialize)]
505struct TimePayload {
506 nanos: i64,
507 offset: i32,
508}
509
510#[derive(Serialize, Deserialize)]
511struct DateTimePayload {
512 nanos: i64,
513 offset: i32,
514 tz_name: Option<String>,
515}
516
517#[derive(Serialize, Deserialize)]
518struct DurationPayload {
519 months: i64,
520 days: i64,
521 nanos: i64,
522}
523
524#[cfg(test)]
529mod tests {
530 use super::*;
531
532 #[test]
533 fn test_round_trip_null() {
534 let v = Value::Null;
535 let bytes = encode(&v);
536 assert_eq!(bytes[0], TAG_NULL);
537 assert_eq!(bytes.len(), 1);
538 let decoded = decode(&bytes).unwrap();
539 assert_eq!(decoded, v);
540 }
541
542 #[test]
543 fn test_round_trip_bool() {
544 for b in [true, false] {
545 let v = Value::Bool(b);
546 let bytes = encode(&v);
547 assert_eq!(bytes[0], TAG_BOOL);
548 let decoded = decode(&bytes).unwrap();
549 assert_eq!(decoded, v);
550 }
551 }
552
553 #[test]
554 fn test_round_trip_int() {
555 for i in [-100, 0, 42, i64::MAX, i64::MIN] {
556 let v = Value::Int(i);
557 let bytes = encode(&v);
558 assert_eq!(bytes[0], TAG_INT);
559 let decoded = decode(&bytes).unwrap();
560 assert_eq!(decoded, v);
561 }
562 }
563
564 #[test]
565 fn test_round_trip_float() {
566 for f in [-3.15, 0.0, 42.5, f64::MAX, f64::MIN] {
567 let v = Value::Float(f);
568 let bytes = encode(&v);
569 assert_eq!(bytes[0], TAG_FLOAT);
570 let decoded = decode(&bytes).unwrap();
571 assert_eq!(decoded, v);
572 }
573 }
574
575 #[test]
576 fn test_round_trip_string() {
577 for s in ["", "hello", "unicode: 🦀"] {
578 let v = Value::String(s.to_string());
579 let bytes = encode(&v);
580 assert_eq!(bytes[0], TAG_STRING);
581 let decoded = decode(&bytes).unwrap();
582 assert_eq!(decoded, v);
583 }
584 }
585
586 #[test]
587 fn test_round_trip_bytes() {
588 let v = Value::Bytes(vec![1, 2, 3, 255]);
589 let bytes = encode(&v);
590 assert_eq!(bytes[0], TAG_BYTES);
591 let decoded = decode(&bytes).unwrap();
592 assert_eq!(decoded, v);
593 }
594
595 #[test]
596 fn test_round_trip_list() {
597 let v = Value::List(vec![
598 Value::Int(1),
599 Value::String("two".to_string()),
600 Value::Float(3.0),
601 Value::Null,
602 ]);
603 let bytes = encode(&v);
604 assert_eq!(bytes[0], TAG_LIST);
605 let decoded = decode(&bytes).unwrap();
606 assert_eq!(decoded, v);
607 }
608
609 #[test]
610 fn test_round_trip_nested_list() {
611 let v = Value::List(vec![
612 Value::Int(1),
613 Value::List(vec![
614 Value::String("nested".to_string()),
615 Value::List(vec![Value::Bool(true)]),
616 ]),
617 ]);
618 let bytes = encode(&v);
619 let decoded = decode(&bytes).unwrap();
620 assert_eq!(decoded, v);
621 }
622
623 #[test]
624 fn test_round_trip_map() {
625 let mut map = HashMap::new();
626 map.insert("a".to_string(), Value::Int(1));
627 map.insert("b".to_string(), Value::String("two".to_string()));
628 map.insert("c".to_string(), Value::Null);
629 let v = Value::Map(map);
630 let bytes = encode(&v);
631 assert_eq!(bytes[0], TAG_MAP);
632 let decoded = decode(&bytes).unwrap();
633 assert_eq!(decoded, v);
634 }
635
636 #[test]
637 fn test_round_trip_node() {
638 let mut props = HashMap::new();
639 props.insert("name".to_string(), Value::String("Alice".to_string()));
640 props.insert("age".to_string(), Value::Int(30));
641 let v = Value::Node(Node {
642 vid: Vid::from(123),
643 labels: vec!["Person".to_string()],
644 properties: props,
645 });
646 let bytes = encode(&v);
647 assert_eq!(bytes[0], TAG_NODE);
648 let decoded = decode(&bytes).unwrap();
649 assert_eq!(decoded, v);
650 }
651
652 #[test]
653 fn test_round_trip_edge() {
654 let mut props = HashMap::new();
655 props.insert("since".to_string(), Value::Int(2020));
656 let v = Value::Edge(Edge {
657 eid: Eid::from(456),
658 edge_type: "KNOWS".to_string(),
659 src: Vid::from(1),
660 dst: Vid::from(2),
661 properties: props,
662 });
663 let bytes = encode(&v);
664 assert_eq!(bytes[0], TAG_EDGE);
665 let decoded = decode(&bytes).unwrap();
666 assert_eq!(decoded, v);
667 }
668
669 #[test]
670 fn test_round_trip_path() {
671 let v = Value::Path(Path {
672 nodes: vec![Node {
673 vid: Vid::from(1),
674 labels: vec!["A".to_string()],
675 properties: HashMap::new(),
676 }],
677 edges: vec![Edge {
678 eid: Eid::from(1),
679 edge_type: "REL".to_string(),
680 src: Vid::from(1),
681 dst: Vid::from(2),
682 properties: HashMap::new(),
683 }],
684 });
685 let bytes = encode(&v);
686 assert_eq!(bytes[0], TAG_PATH);
687 let decoded = decode(&bytes).unwrap();
688 assert_eq!(decoded, v);
689 }
690
691 #[test]
692 fn test_round_trip_vector() {
693 let v = Value::Vector(vec![0.1, 0.2, 0.3]);
694 let bytes = encode(&v);
695 assert_eq!(bytes[0], TAG_VECTOR);
696 let decoded = decode(&bytes).unwrap();
697 assert_eq!(decoded, v);
698 }
699
700 #[test]
701 fn test_peek_tag() {
702 assert_eq!(peek_tag(&encode(&Value::Null)), Some(TAG_NULL));
703 assert_eq!(peek_tag(&encode(&Value::Bool(true))), Some(TAG_BOOL));
704 assert_eq!(peek_tag(&encode(&Value::Int(42))), Some(TAG_INT));
705 assert_eq!(peek_tag(&encode(&Value::Float(3.15))), Some(TAG_FLOAT));
706 assert_eq!(
707 peek_tag(&encode(&Value::String("x".to_string()))),
708 Some(TAG_STRING)
709 );
710 assert_eq!(peek_tag(&[]), None);
711 }
712
713 #[test]
714 fn test_is_null() {
715 assert!(is_null(&encode(&Value::Null)));
716 assert!(!is_null(&encode(&Value::Int(0))));
717 assert!(!is_null(&[]));
718 }
719
720 #[test]
721 fn test_fast_decode_int() {
722 let bytes = encode(&Value::Int(42));
723 assert_eq!(decode_int(&bytes), Some(42));
724 assert_eq!(decode_int(&encode(&Value::Float(42.0))), None);
725 assert_eq!(decode_int(&encode(&Value::String("42".to_string()))), None);
726 }
727
728 #[test]
729 fn test_fast_decode_float() {
730 let bytes = encode(&Value::Float(3.15));
731 assert_eq!(decode_float(&bytes), Some(3.15));
732 assert_eq!(decode_float(&encode(&Value::Int(3))), None);
733 }
734
735 #[test]
736 fn test_fast_decode_bool() {
737 let bytes = encode(&Value::Bool(true));
738 assert_eq!(decode_bool(&bytes), Some(true));
739 assert_eq!(decode_bool(&encode(&Value::Int(1))), None);
740 }
741
742 #[test]
743 fn test_fast_decode_string() {
744 let bytes = encode(&Value::String("hello".to_string()));
745 assert_eq!(decode_string(&bytes), Some("hello".to_string()));
746 assert_eq!(decode_string(&encode(&Value::Int(42))), None);
747 }
748
749 #[test]
750 fn test_int_float_distinction() {
751 let int_val = Value::Int(42);
753 let float_val = Value::Float(42.0);
754
755 let int_bytes = encode(&int_val);
756 let float_bytes = encode(&float_val);
757
758 assert_eq!(int_bytes[0], TAG_INT);
760 assert_eq!(float_bytes[0], TAG_FLOAT);
761
762 assert_ne!(int_bytes, float_bytes);
764
765 assert_eq!(decode(&int_bytes).unwrap(), Value::Int(42));
767 assert_eq!(decode(&float_bytes).unwrap(), Value::Float(42.0));
768 }
769
770 #[test]
771 fn test_round_trip_btic_epoch_instant() {
772 let v = Value::Temporal(crate::value::TemporalValue::Btic {
773 lo: 0,
774 hi: 1,
775 meta: 0x0000_0000_0000_0000,
776 });
777 let bytes = encode(&v);
778 assert_eq!(bytes[0], TAG_BTIC);
779 assert_eq!(bytes.len(), 25); let decoded = decode(&bytes).unwrap();
781 assert_eq!(decoded, v);
782 }
783
784 #[test]
785 fn test_round_trip_btic_year_1985() {
786 let meta = 0x7700_0000_0000_0000u64; let v = Value::Temporal(crate::value::TemporalValue::Btic {
788 lo: 473_385_600_000,
789 hi: 504_921_600_000,
790 meta,
791 });
792 let bytes = encode(&v);
793 assert_eq!(bytes[0], TAG_BTIC);
794 let decoded = decode(&bytes).unwrap();
795 assert_eq!(decoded, v);
796 }
797
798 #[test]
799 fn test_round_trip_btic_unbounded() {
800 let v = Value::Temporal(crate::value::TemporalValue::Btic {
801 lo: i64::MIN,
802 hi: i64::MAX,
803 meta: 0,
804 });
805 let bytes = encode(&v);
806 assert_eq!(bytes[0], TAG_BTIC);
807 let decoded = decode(&bytes).unwrap();
808 assert_eq!(decoded, v);
809 }
810
811 #[test]
812 fn test_round_trip_btic_with_certainty() {
813 let meta = 0x7750_0000_0000_0000u64; let v = Value::Temporal(crate::value::TemporalValue::Btic {
816 lo: -77_914_137_600_000, hi: -77_882_601_600_000,
818 meta,
819 });
820 let bytes = encode(&v);
821 let decoded = decode(&bytes).unwrap();
822 assert_eq!(decoded, v);
823 }
824}