Skip to main content

cbor_core/value/
debug.rs

1//! --- CBOR::Core diagnostic notation (Section 2.3.6) ---
2//!
3//! `Debug` outputs diagnostic notation. The `#` (alternate/pretty) flag
4//! enables multi-line output for arrays and maps with indentation.
5//! `Display` forwards to `Debug`, so both produce the same text.
6
7use std::fmt;
8
9use crate::{SimpleValue, Value};
10
11impl<'a> fmt::Display for Value<'a> {
12    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13        fmt::Debug::fmt(self, f)
14    }
15}
16
17impl<'a> fmt::Debug for Value<'a> {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        match self {
20            Self::SimpleValue(sv) => match *sv {
21                SimpleValue::FALSE => f.write_str("false"),
22                SimpleValue::TRUE => f.write_str("true"),
23                SimpleValue::NULL => f.write_str("null"),
24                other => write!(f, "simple({})", other.0),
25            },
26
27            Self::Unsigned(n) => write!(f, "{n}"),
28
29            Self::Negative(n) => write!(f, "{actual}", actual = -i128::from(*n) - 1),
30
31            Self::Float(float) => {
32                let value = float.to_f64();
33                if value.is_nan() {
34                    use crate::float::Inner;
35                    match float.0 {
36                        Inner::F16(0x7e00) => f.write_str("NaN"), // Default NaN is the canonical f16 quiet NaN (f97e00)
37                        Inner::F16(bits) => write!(f, "float'{bits:04x}'"),
38                        Inner::F32(bits) => write!(f, "float'{bits:08x}'"),
39                        Inner::F64(bits) => write!(f, "float'{bits:016x}'"),
40                    }
41                } else if value.is_infinite() {
42                    if value.is_sign_positive() {
43                        f.write_str("Infinity")
44                    } else {
45                        f.write_str("-Infinity")
46                    }
47                } else {
48                    format_ecmascript_float(f, value)
49                }
50            }
51
52            Self::ByteString(bytes) => {
53                f.write_str("h'")?;
54                for b in bytes.as_ref() {
55                    write!(f, "{b:02x}")?;
56                }
57                f.write_str("'")
58            }
59
60            Self::TextString(s) => {
61                f.write_str("\"")?;
62                for c in s.chars() {
63                    match c {
64                        '"' => f.write_str("\\\"")?,
65                        '\\' => f.write_str("\\\\")?,
66                        '\u{08}' => f.write_str("\\b")?,
67                        '\u{0C}' => f.write_str("\\f")?,
68                        '\n' => f.write_str("\\n")?,
69                        '\r' => f.write_str("\\r")?,
70                        '\t' => f.write_str("\\t")?,
71                        c if c.is_control() => write!(f, "\\u{:04x}", c as u32)?,
72                        c => write!(f, "{c}")?,
73                    }
74                }
75                f.write_str("\"")
76            }
77
78            Self::Array(items) => {
79                let mut list = f.debug_list();
80                for item in items {
81                    list.entry(item);
82                }
83                list.finish()
84            }
85
86            Self::Map(map) => {
87                let mut m = f.debug_map();
88                for (key, value) in map {
89                    m.entry(key, value);
90                }
91                m.finish()
92            }
93
94            Self::Tag(tag, content) => {
95                // Big integers: show as decimal when they fit in i128/u128
96                if self.data_type().is_integer() {
97                    if let Ok(n) = self.to_u128() {
98                        return write!(f, "{n}");
99                    }
100                    if let Ok(n) = self.to_i128() {
101                        return write!(f, "{n}");
102                    }
103                }
104
105                if f.alternate() {
106                    write!(f, "{tag}({content:#?})")
107                } else {
108                    write!(f, "{tag}({content:?})")
109                }
110            }
111        }
112    }
113}
114
115/// Format a finite, non-NaN f64 in ECMAScript Number.toString style
116/// with the CBOR::Core enhancement that finite values always include
117/// a decimal point and at least one fractional digit.
118fn format_ecmascript_float(f: &mut fmt::Formatter<'_>, value: f64) -> fmt::Result {
119    if value == 0.0 {
120        return f.write_str(if value.is_sign_negative() { "-0.0" } else { "0.0" });
121    }
122
123    let sign = if value.is_sign_negative() { "-" } else { "" };
124    let scientific = format!("{:e}", value.abs());
125    let (mantissa, exponent) = scientific.split_once('e').unwrap();
126    let rust_exp: i32 = exponent.parse().unwrap();
127    let digits: String = mantissa.chars().filter(|c| *c != '.').collect();
128    let k = digits.len() as i32;
129    let e = rust_exp + 1;
130
131    f.write_str(sign)?;
132
133    if 0 < e && e <= 21 {
134        if e >= k {
135            f.write_str(&digits)?;
136            for _ in 0..(e - k) {
137                f.write_str("0")?;
138            }
139            f.write_str(".0")
140        } else {
141            let (int_part, frac_part) = digits.split_at(e as usize);
142            write!(f, "{int_part}.{frac_part}")
143        }
144    } else if -6 < e && e <= 0 {
145        f.write_str("0.")?;
146        for _ in 0..(-e) {
147            f.write_str("0")?;
148        }
149        f.write_str(&digits)
150    } else {
151        let exp_val = e - 1;
152        let (first, rest) = digits.split_at(1);
153        if rest.is_empty() {
154            write!(f, "{first}.0")?;
155        } else {
156            write!(f, "{first}.{rest}")?;
157        }
158        if exp_val >= 0 {
159            write!(f, "e+{exp_val}")
160        } else {
161            write!(f, "e{exp_val}")
162        }
163    }
164}