1use crate::types::Value;
12use chrono::DateTime;
13use std::net::{Ipv4Addr, Ipv6Addr};
14
15pub struct ValueFormatter;
17
18const HEX_LOWER: &[u8; 16] = b"0123456789abcdef";
20
21impl ValueFormatter {
22 #[inline]
39 pub fn is_null(value: &Value) -> bool {
40 match value {
41 Value::Null => true,
42 Value::Frozen(inner) => Self::is_null(inner),
43 _ => false,
44 }
45 }
46
47 pub fn format_into(value: &Value, out: &mut String) {
55 use std::fmt::Write as _;
56 match value {
57 Value::Null => out.push_str("null"),
58 Value::Boolean(b) => out.push_str(if *b { "true" } else { "false" }),
59 Value::TinyInt(i) => {
60 let _ = write!(out, "{}", i);
61 }
62 Value::SmallInt(i) => {
63 let _ = write!(out, "{}", i);
64 }
65 Value::Integer(i) => {
66 let _ = write!(out, "{}", i);
67 }
68 Value::BigInt(i) => {
69 let _ = write!(out, "{}", i);
70 }
71 Value::Counter(i) => {
72 let _ = write!(out, "{}", i);
73 }
74 Value::Text(s) => out.push_str(&String::from_utf8_lossy(s)),
77 Value::Uuid(bytes) => Self::format_uuid_into(bytes, out),
78 other => out.push_str(&Self::format_value(other)),
81 }
82 }
83
84 pub fn format_uuid_into(bytes: &[u8; 16], out: &mut String) {
88 out.reserve(36);
89 for (i, b) in bytes.iter().enumerate() {
90 if matches!(i, 4 | 6 | 8 | 10) {
92 out.push('-');
93 }
94 out.push(HEX_LOWER[(b >> 4) as usize] as char);
95 out.push(HEX_LOWER[(b & 0x0f) as usize] as char);
96 }
97 }
98
99 pub fn format_value(value: &Value) -> String {
109 match value {
110 Value::Null => "null".to_string(),
111
112 Value::Boolean(b) => b.to_string(),
114
115 Value::TinyInt(i) => i.to_string(),
117 Value::SmallInt(i) => i.to_string(),
118 Value::Integer(i) => i.to_string(),
119 Value::BigInt(i) => i.to_string(),
120 Value::Counter(i) => i.to_string(),
121
122 Value::Float32(f) => Self::format_float32(*f),
124 Value::Float(f) => Self::format_float64(*f),
125
126 Value::Text(s) => String::from_utf8_lossy(s).into_owned(),
129
130 Value::Blob(bytes) => format!("0x{}", hex::encode(bytes)),
132
133 Value::Timestamp(millis) => Self::format_timestamp(*millis),
135
136 Value::Date(days) => Self::format_date(*days),
138
139 Value::Time(nanos) => Self::format_time(*nanos),
141
142 Value::Uuid(bytes) => Self::format_uuid(bytes),
144
145 Value::Varint(bytes) => Self::format_varint(bytes),
147
148 Value::Decimal { scale, unscaled } => Self::format_decimal(*scale, unscaled),
150
151 Value::Duration {
153 months,
154 days,
155 nanos,
156 } => Self::format_duration(*months, *days, *nanos),
157
158 Value::Json(json_value) => json_value.to_string(),
160
161 Value::List(elements) => Self::format_list(elements),
163 Value::Set(elements) => Self::format_set(elements),
164 Value::Map(pairs) => Self::format_map(pairs),
165 Value::Tuple(fields) => Self::format_tuple(fields),
166
167 Value::Udt(udt) => Self::format_udt(udt),
169
170 Value::Frozen(inner) => Self::format_value(inner),
172
173 Value::Tombstone(info) => format!("<deleted@{}>", info.deletion_time),
175
176 Value::Inet(bytes) => Self::format_inet(bytes),
178 }
179 }
180
181 fn format_float32(f: f32) -> String {
185 if f.is_nan() {
186 "NaN".to_string()
187 } else if f.is_infinite() {
188 if f.is_sign_positive() {
189 "Infinity".to_string()
190 } else {
191 "-Infinity".to_string()
192 }
193 } else if f.abs() < 1e-6 || f.abs() > 1e10 {
194 format!("{:e}", f)
195 } else {
196 format!("{}", f)
197 }
198 }
199
200 fn format_float64(f: f64) -> String {
202 if f.is_nan() {
203 "NaN".to_string()
204 } else if f.is_infinite() {
205 if f.is_sign_positive() {
206 "Infinity".to_string()
207 } else {
208 "-Infinity".to_string()
209 }
210 } else if f.abs() < 1e-6 || f.abs() > 1e10 {
211 format!("{:e}", f)
212 } else {
213 format!("{}", f)
214 }
215 }
216
217 fn format_timestamp(millis: i64) -> String {
219 if let Some(datetime) = DateTime::from_timestamp_millis(millis) {
222 datetime.format("%Y-%m-%d %H:%M:%S%.3f+0000").to_string()
224 } else {
225 format!("<invalid-timestamp:{}>", millis)
226 }
227 }
228
229 fn format_date(days: i32) -> String {
231 let epoch = DateTime::from_timestamp(0, 0)
233 .map(|dt| dt.date_naive())
234 .unwrap_or_else(|| {
235 chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap_or(chrono::NaiveDate::MIN)
238 });
239
240 if let Some(date) = epoch.checked_add_signed(chrono::Duration::days(days as i64)) {
241 date.format("%Y-%m-%d").to_string()
242 } else {
243 format!("<invalid-date:{}>", days)
244 }
245 }
246
247 fn format_time(nanos: i64) -> String {
249 if nanos < 0 {
250 return format!("<invalid-time:{}>", nanos);
251 }
252
253 let total_secs = nanos / 1_000_000_000;
254 let hours = total_secs / 3600;
255 let minutes = (total_secs % 3600) / 60;
256 let seconds = total_secs % 60;
257 let remaining_nanos = nanos % 1_000_000_000;
258
259 if hours >= 24 {
260 return format!("<invalid-time:{}>", nanos);
261 }
262
263 format!(
264 "{:02}:{:02}:{:02}.{:09}",
265 hours, minutes, seconds, remaining_nanos
266 )
267 }
268
269 fn format_uuid(bytes: &[u8; 16]) -> String {
271 let mut s = String::with_capacity(36);
272 Self::format_uuid_into(bytes, &mut s);
273 s
274 }
275
276 fn format_varint(bytes: &[u8]) -> String {
278 if bytes.is_empty() {
279 return "0".to_string();
280 }
281
282 let result = num_bigint::BigInt::from_signed_bytes_be(bytes);
284 result.to_string()
285 }
286
287 fn format_decimal(scale: i32, unscaled: &[u8]) -> String {
289 if unscaled.is_empty() {
290 return "0".to_string();
291 }
292
293 const DECIMAL_MAX_UNSCALED_BYTES: usize = 32 * 1024;
303 if unscaled.len() > DECIMAL_MAX_UNSCALED_BYTES {
304 return format!(
305 "<corrupt-decimal:scale={scale},unscaled_len={}bytes>",
306 unscaled.len()
307 );
308 }
309
310 let bigint = num_bigint::BigInt::from_signed_bytes_be(unscaled);
314 let mut decimal_str = bigint.to_string();
318 let is_neg = decimal_str.starts_with('-');
319 if is_neg {
320 decimal_str = decimal_str[1..].to_string();
321 }
322
323 const DECIMAL_POSITIONAL_MAX_BYTES: usize = 1024;
333 const SCALE_RENDER_CAP: usize = 1_000_000;
334 if unscaled.len() > DECIMAL_POSITIONAL_MAX_BYTES
335 || scale.unsigned_abs() as usize > SCALE_RENDER_CAP
336 {
337 let body = if is_neg {
338 format!("-{decimal_str}")
339 } else {
340 decimal_str
341 };
342 return if scale == 0 {
344 body
345 } else {
346 format!("{body}e{}", -(scale as i64))
347 };
348 }
349
350 if scale <= 0 {
352 decimal_str.push_str(&"0".repeat(scale.unsigned_abs() as usize));
355 } else if scale as usize >= decimal_str.len() {
356 let leading_zeros = scale as usize - decimal_str.len() + 1;
358 decimal_str = format!("0.{}{}", "0".repeat(leading_zeros - 1), decimal_str);
359 } else {
360 let pos = decimal_str.len() - scale as usize;
362 decimal_str.insert(pos, '.');
363 }
364
365 if is_neg {
366 format!("-{}", decimal_str)
367 } else {
368 decimal_str
369 }
370 }
371
372 fn format_duration(months: i32, days: i32, nanos: i64) -> String {
374 let mut parts = Vec::new();
375
376 if months != 0 {
377 parts.push(format!("{}mo", months));
378 }
379 if days != 0 {
380 parts.push(format!("{}d", days));
381 }
382 if nanos != 0 {
383 parts.push(format!("{}ns", nanos));
384 }
385
386 if parts.is_empty() {
387 "0ns".to_string()
388 } else {
389 parts.join("")
390 }
391 }
392
393 fn format_list(elements: &[Value]) -> String {
395 let formatted_elements: Vec<String> = elements.iter().map(Self::format_value).collect();
396 format!("[{}]", formatted_elements.join(", "))
397 }
398
399 fn format_set(elements: &[Value]) -> String {
401 let formatted_elements: Vec<String> = elements.iter().map(Self::format_value).collect();
402 format!("{{{}}}", formatted_elements.join(", "))
403 }
404
405 fn format_map(pairs: &[(Value, Value)]) -> String {
407 let formatted_pairs: Vec<String> = pairs
408 .iter()
409 .map(|(k, v)| format!("{}: {}", Self::format_value(k), Self::format_value(v)))
410 .collect();
411 format!("{{{}}}", formatted_pairs.join(", "))
412 }
413
414 fn format_tuple(fields: &[Value]) -> String {
416 let formatted_fields: Vec<String> = fields.iter().map(Self::format_value).collect();
417 format!("({})", formatted_fields.join(", "))
418 }
419
420 fn format_udt(udt: &crate::types::UdtValue) -> String {
422 let formatted_fields: Vec<String> = udt
423 .fields
424 .iter()
425 .map(|field| {
426 let value_str = field
427 .value
428 .as_ref()
429 .map(Self::format_value)
430 .unwrap_or_else(|| "null".to_string());
431 format!("{}: {}", field.name, value_str)
432 })
433 .collect();
434 format!("{{{}}}", formatted_fields.join(", "))
435 }
436
437 fn format_inet(bytes: &[u8]) -> String {
439 if bytes.len() == 4 {
440 let addr = Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]);
442 addr.to_string()
443 } else if bytes.len() == 16 {
444 let mut octets = [0u8; 16];
446 octets.copy_from_slice(bytes);
447 let addr = Ipv6Addr::from(octets);
448 addr.to_string()
449 } else {
450 format!("<invalid-inet:{}-bytes>", bytes.len())
451 }
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use crate::types::{UdtField, UdtValue};
459
460 #[test]
461 fn test_null() {
462 assert_eq!(ValueFormatter::format_value(&Value::Null), "null");
463 }
464
465 #[test]
466 fn test_boolean() {
467 assert_eq!(ValueFormatter::format_value(&Value::Boolean(true)), "true");
468 assert_eq!(
469 ValueFormatter::format_value(&Value::Boolean(false)),
470 "false"
471 );
472 }
473
474 #[test]
475 fn test_integers() {
476 assert_eq!(ValueFormatter::format_value(&Value::TinyInt(127)), "127");
477 assert_eq!(ValueFormatter::format_value(&Value::TinyInt(-128)), "-128");
478 assert_eq!(
479 ValueFormatter::format_value(&Value::SmallInt(32767)),
480 "32767"
481 );
482 assert_eq!(
483 ValueFormatter::format_value(&Value::Integer(2147483647)),
484 "2147483647"
485 );
486 assert_eq!(
487 ValueFormatter::format_value(&Value::BigInt(9223372036854775807)),
488 "9223372036854775807"
489 );
490 assert_eq!(
491 ValueFormatter::format_value(&Value::Counter(1000000)),
492 "1000000"
493 );
494 }
495
496 #[test]
497 fn test_floats() {
498 assert_eq!(ValueFormatter::format_value(&Value::Float32(3.25)), "3.25");
499 assert_eq!(ValueFormatter::format_value(&Value::Float(2.75)), "2.75");
500
501 assert_eq!(
503 ValueFormatter::format_value(&Value::Float32(f32::NAN)),
504 "NaN"
505 );
506 assert_eq!(
507 ValueFormatter::format_value(&Value::Float32(f32::INFINITY)),
508 "Infinity"
509 );
510 assert_eq!(
511 ValueFormatter::format_value(&Value::Float32(f32::NEG_INFINITY)),
512 "-Infinity"
513 );
514
515 let small = Value::Float(1e-7);
517 let formatted = ValueFormatter::format_value(&small);
518 assert!(formatted.contains('e') || formatted.contains('E'));
519 }
520
521 #[test]
522 fn test_text() {
523 assert_eq!(
524 ValueFormatter::format_value(&Value::text("hello world".to_string())),
525 "hello world"
526 );
527 assert_eq!(
528 ValueFormatter::format_value(&Value::text("".to_string())),
529 ""
530 );
531 }
532
533 #[test]
534 fn test_blob() {
535 let blob = Value::blob(vec![0xDE, 0xAD, 0xBE, 0xEF]);
536 assert_eq!(ValueFormatter::format_value(&blob), "0xdeadbeef");
537
538 let empty_blob = Value::blob(vec![]);
539 assert_eq!(ValueFormatter::format_value(&empty_blob), "0x");
540 }
541
542 #[test]
543 fn test_uuid() {
544 let uuid = Value::Uuid([
546 0xa8, 0xf1, 0x67, 0xf0, 0xeb, 0xe7, 0x4f, 0x20, 0xa3, 0x86, 0x31, 0xff, 0x13, 0x8b,
547 0xec, 0x3b,
548 ]);
549 assert_eq!(
550 ValueFormatter::format_value(&uuid),
551 "a8f167f0-ebe7-4f20-a386-31ff138bec3b"
552 );
553 }
554
555 #[test]
556 fn test_timestamp() {
557 let timestamp = Value::Timestamp(1673778645123);
559 let formatted = ValueFormatter::format_value(×tamp);
560 assert!(formatted.starts_with("2023-01-15"));
561 assert!(formatted.contains("10:30:45"));
562 assert!(formatted.ends_with("+0000"));
563 }
564
565 #[test]
566 fn test_date() {
567 let date = Value::Date(19358);
569 assert_eq!(ValueFormatter::format_value(&date), "2023-01-01");
570
571 let epoch = Value::Date(0);
573 assert_eq!(ValueFormatter::format_value(&epoch), "1970-01-01");
574 }
575
576 #[test]
577 fn test_time() {
578 let nanos =
580 14 * 3600 * 1_000_000_000 + 30 * 60 * 1_000_000_000 + 45 * 1_000_000_000 + 123_456_789;
581 let time = Value::Time(nanos);
582 assert_eq!(ValueFormatter::format_value(&time), "14:30:45.123456789");
583
584 let midnight = Value::Time(0);
586 assert_eq!(
587 ValueFormatter::format_value(&midnight),
588 "00:00:00.000000000"
589 );
590 }
591
592 #[test]
593 fn test_duration() {
594 let duration = Value::Duration {
595 months: 2,
596 days: 15,
597 nanos: 123456789,
598 };
599 assert_eq!(ValueFormatter::format_value(&duration), "2mo15d123456789ns");
600
601 let zero_duration = Value::Duration {
602 months: 0,
603 days: 0,
604 nanos: 0,
605 };
606 assert_eq!(ValueFormatter::format_value(&zero_duration), "0ns");
607
608 let partial_duration = Value::Duration {
609 months: 0,
610 days: 5,
611 nanos: 0,
612 };
613 assert_eq!(ValueFormatter::format_value(&partial_duration), "5d");
614 }
615
616 #[test]
617 fn test_list() {
618 let list = Value::List(vec![
619 Value::Integer(1),
620 Value::Integer(2),
621 Value::Integer(3),
622 ]);
623 assert_eq!(ValueFormatter::format_value(&list), "[1, 2, 3]");
624
625 let empty_list = Value::List(vec![]);
626 assert_eq!(ValueFormatter::format_value(&empty_list), "[]");
627 }
628
629 #[test]
630 fn test_set() {
631 let set = Value::Set(vec![
632 Value::text("apple".to_string()),
633 Value::text("banana".to_string()),
634 ]);
635 assert_eq!(ValueFormatter::format_value(&set), "{apple, banana}");
636
637 let empty_set = Value::Set(vec![]);
638 assert_eq!(ValueFormatter::format_value(&empty_set), "{}");
639 }
640
641 #[test]
642 fn test_map() {
643 let map = Value::Map(vec![
644 (Value::text("key1".to_string()), Value::Integer(100)),
645 (Value::text("key2".to_string()), Value::Integer(200)),
646 ]);
647 assert_eq!(ValueFormatter::format_value(&map), "{key1: 100, key2: 200}");
648
649 let empty_map = Value::Map(vec![]);
650 assert_eq!(ValueFormatter::format_value(&empty_map), "{}");
651 }
652
653 #[test]
654 fn test_tuple() {
655 let tuple = Value::Tuple(vec![
656 Value::Integer(42),
657 Value::text("hello".to_string()),
658 Value::Boolean(true),
659 ]);
660 assert_eq!(ValueFormatter::format_value(&tuple), "(42, hello, true)");
661 }
662
663 #[test]
664 fn test_udt() {
665 let udt = Value::Udt(Box::new(UdtValue {
666 type_name: "person".to_string(),
667 keyspace: "test_ks".to_string(),
668 fields: vec![
669 UdtField {
670 name: "name".to_string(),
671 value: Some(Value::text("Alice".to_string())),
672 },
673 UdtField {
674 name: "age".to_string(),
675 value: Some(Value::Integer(30)),
676 },
677 UdtField {
678 name: "email".to_string(),
679 value: None,
680 },
681 ],
682 }));
683 assert_eq!(
684 ValueFormatter::format_value(&udt),
685 "{name: Alice, age: 30, email: null}"
686 );
687 }
688
689 #[test]
690 fn test_frozen() {
691 let frozen = Value::Frozen(Box::new(Value::List(vec![
692 Value::Integer(1),
693 Value::Integer(2),
694 ])));
695 assert_eq!(ValueFormatter::format_value(&frozen), "[1, 2]");
696 }
697
698 #[test]
699 fn test_inet() {
700 let ipv4 = Value::inet(vec![192, 168, 1, 1]);
702 assert_eq!(ValueFormatter::format_value(&ipv4), "192.168.1.1");
703
704 let ipv6 = Value::inet(vec![
706 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
707 0x00, 0x01,
708 ]);
709 let formatted = ValueFormatter::format_value(&ipv6);
710 assert!(formatted.contains("2001:db8"));
711 }
712
713 #[test]
714 fn test_nested_collections() {
715 let nested = Value::List(vec![
717 Value::List(vec![Value::Integer(1), Value::Integer(2)]),
718 Value::List(vec![Value::Integer(3), Value::Integer(4)]),
719 ]);
720 assert_eq!(ValueFormatter::format_value(&nested), "[[1, 2], [3, 4]]");
721
722 let complex_map = Value::Map(vec![(
724 Value::text("data".to_string()),
725 Value::Set(vec![Value::Integer(1), Value::Integer(2)]),
726 )]);
727 assert_eq!(ValueFormatter::format_value(&complex_map), "{data: {1, 2}}");
728 }
729
730 #[test]
731 fn test_json() {
732 let json = Value::Json(Box::new(serde_json::json!({
733 "name": "Alice",
734 "age": 30
735 })));
736 let formatted = ValueFormatter::format_value(&json);
737 assert!(formatted.contains("Alice"));
738 assert!(formatted.contains("30"));
739 }
740
741 #[test]
742 fn test_varint() {
743 let varint = Value::varint(vec![0x01, 0x00]);
745 let formatted = ValueFormatter::format_value(&varint);
746 assert_eq!(formatted, "256");
747
748 let zero = Value::varint(vec![]);
750 assert_eq!(ValueFormatter::format_value(&zero), "0");
751 }
752
753 #[test]
754 fn test_decimal() {
755 let decimal = Value::Decimal {
757 scale: 2,
758 unscaled: vec![0x30, 0x39], };
760 let formatted = ValueFormatter::format_value(&decimal);
761 assert!(formatted.contains('.'));
763 }
764
765 #[test]
771 fn test_decimal_pathological_scale_is_bounded_not_panic() {
772 let d_min = Value::Decimal {
774 scale: i32::MIN,
775 unscaled: vec![0x01],
776 };
777 let s = ValueFormatter::format_value(&d_min);
778 assert!(
779 s.contains('e'),
780 "extreme scale renders in exponent form: {s}"
781 );
782 assert!(
783 s.len() < 64,
784 "must not materialize an unbounded string: {s}"
785 );
786
787 let d_max = Value::Decimal {
789 scale: i32::MAX,
790 unscaled: vec![0x01],
791 };
792 let s2 = ValueFormatter::format_value(&d_max);
793 assert!(
794 s2.len() < 64,
795 "must not materialize an unbounded string: {s2}"
796 );
797 }
798
799 #[test]
806 fn test_decimal_large_valid_renders_faithfully_fast() {
807 let unscaled = vec![0x7fu8; 2048];
811 let start = std::time::Instant::now();
812 let s = ValueFormatter::format_value(&Value::Decimal { scale: 4, unscaled });
813 let elapsed = start.elapsed();
814 assert!(
815 !s.starts_with("<corrupt-decimal:"),
816 "a well-formed large decimal must not be called corrupt: {s}"
817 );
818 assert!(
820 s.ends_with("e-4"),
821 "expected exponent form, got: {}",
822 &s[..40]
823 );
824 let digits = s.trim_end_matches("e-4");
825 assert!(
827 digits.len() >= 4900 && digits.chars().all(|c| c.is_ascii_digit()),
828 "expected full digit string, got {} chars",
829 digits.len()
830 );
831 assert!(
832 elapsed < std::time::Duration::from_millis(500),
833 "expected fast single-conversion render, took {elapsed:?}"
834 );
835 }
836
837 #[test]
841 fn test_decimal_beyond_ceiling_bounded_fast() {
842 let unscaled = vec![0xffu8; 415_000];
843 let start = std::time::Instant::now();
844 let s = ValueFormatter::format_value(&Value::Decimal { scale: 0, unscaled });
845 let elapsed = start.elapsed();
846 assert!(
847 s.starts_with("<corrupt-decimal:"),
848 "beyond-ceiling magnitude renders the bounded fallback: {s}"
849 );
850 assert!(
851 elapsed < std::time::Duration::from_millis(500),
852 "expected fast O(1) rejection, took {elapsed:?} — the base conversion ran"
853 );
854 }
855
856 #[test]
859 fn test_decimal_in_range_scale_unchanged() {
860 let d = Value::Decimal {
861 scale: 5,
862 unscaled: vec![0x7B], };
864 assert_eq!(ValueFormatter::format_value(&d), "0.00123");
865 let dn = Value::Decimal {
866 scale: -2,
867 unscaled: vec![0x05], };
869 assert_eq!(ValueFormatter::format_value(&dn), "500");
870 }
871
872 #[test]
873 fn test_is_null_only_matches_null_variant() {
874 assert!(ValueFormatter::is_null(&Value::Null));
876 assert!(!ValueFormatter::is_null(&Value::text("null".to_string())));
877 assert!(!ValueFormatter::is_null(&Value::Integer(0)));
878 assert!(!ValueFormatter::is_null(&Value::text(String::new())));
879 }
880
881 #[test]
882 fn test_is_null_unwraps_frozen_null() {
883 assert!(ValueFormatter::is_null(&Value::Frozen(Box::new(
888 Value::Null
889 ))));
890 assert!(ValueFormatter::is_null(&Value::Frozen(Box::new(
891 Value::Frozen(Box::new(Value::Null))
892 ))));
893 assert!(!ValueFormatter::is_null(&Value::Frozen(Box::new(
894 Value::Integer(7)
895 ))));
896 assert!(!ValueFormatter::is_null(&Value::Frozen(Box::new(
898 Value::text("null".to_string())
899 ))));
900 }
901
902 #[test]
903 fn test_format_into_matches_format_value() {
904 let samples = vec![
907 Value::Null,
908 Value::Boolean(true),
909 Value::Boolean(false),
910 Value::TinyInt(-7),
911 Value::SmallInt(1234),
912 Value::Integer(-2147483648),
913 Value::BigInt(9223372036854775807),
914 Value::Counter(42),
915 Value::text("hello".to_string()),
916 Value::text("null".to_string()),
917 Value::text(String::new()),
918 Value::Uuid([
919 0xa8, 0xf1, 0x67, 0xf0, 0xeb, 0xe7, 0x4f, 0x20, 0xa3, 0x86, 0x31, 0xff, 0x13, 0x8b,
920 0xec, 0x3b,
921 ]),
922 Value::Float32(3.25),
923 Value::Float(2.75),
924 Value::blob(vec![0xDE, 0xAD, 0xBE, 0xEF]),
925 Value::List(vec![Value::Integer(1), Value::Integer(2)]),
926 Value::Set(vec![Value::text("a".to_string())]),
927 Value::Map(vec![(Value::text("k".to_string()), Value::Integer(1))]),
928 Value::varint(vec![0x01, 0x00]),
929 ];
930 for v in &samples {
931 let mut buf = String::new();
932 ValueFormatter::format_into(v, &mut buf);
933 assert_eq!(
934 buf,
935 ValueFormatter::format_value(v),
936 "format_into mismatch for {v:?}"
937 );
938 }
939 }
940
941 #[test]
942 fn test_format_into_reuses_buffer() {
943 let mut buf = String::from("stale-contents");
945 buf.clear();
946 ValueFormatter::format_into(&Value::Integer(99), &mut buf);
947 assert_eq!(buf, "99");
948 }
949
950 #[test]
951 fn test_format_uuid_into_matches_reference() {
952 let bytes = [
953 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
954 0x77, 0x88,
955 ];
956 let mut s = String::new();
957 ValueFormatter::format_uuid_into(&bytes, &mut s);
958 assert_eq!(s, "12345678-9abc-def0-1122-334455667788");
959 assert_eq!(ValueFormatter::format_value(&Value::Uuid(bytes)), s);
961 }
962
963 #[test]
964 fn test_format_varint_negative() {
965 let negative_bytes = vec![0xFF];
967 let formatted = ValueFormatter::format_value(&Value::Varint(negative_bytes.into()));
968 assert_eq!(
969 formatted, "-1",
970 "Negative varint -1 should format correctly"
971 );
972
973 let negative_256 = vec![0xFF, 0x00];
975 let formatted_256 = ValueFormatter::format_value(&Value::Varint(negative_256.into()));
976 assert_eq!(
977 formatted_256, "-256",
978 "Negative varint -256 should format correctly"
979 );
980
981 assert!(!formatted.contains('<'), "Should not contain debug markers");
983 assert!(!formatted.contains('>'), "Should not contain debug markers");
984 }
985}