vantage_cli_util/output/
json.rs1use ciborium::Value as CborValue;
16use indexmap::IndexMap;
17use vantage_types::Record;
18
19pub fn write_list(records: &IndexMap<String, Record<CborValue>>) -> String {
20 let mut out = String::from("{");
21 let mut first = true;
22 for (id, record) in records {
23 if !first {
24 out.push(',');
25 }
26 first = false;
27 out.push_str(&write_string(id));
28 out.push(':');
29 out.push_str(&write_record_map(record));
30 }
31 out.push('}');
32 out.push('\n');
33 out
34}
35
36pub fn write_record(id: &str, record: &Record<CborValue>) -> String {
37 let mut out = String::from("{");
38 out.push_str(&write_string(id));
39 out.push(':');
40 out.push_str(&write_record_map(record));
41 out.push('}');
42 out.push('\n');
43 out
44}
45
46pub fn write_scalar(label: &str, value: &CborValue) -> String {
47 let mut out = String::from("{");
48 out.push_str(&write_string(label));
49 out.push(':');
50 out.push_str(&write_value(value));
51 out.push('}');
52 out.push('\n');
53 out
54}
55
56pub fn write_record_map(record: &Record<CborValue>) -> String {
57 let mut out = String::from("{");
58 let mut first = true;
59 for (k, v) in record.iter() {
60 if !first {
61 out.push(',');
62 }
63 first = false;
64 out.push_str(&write_string(k));
65 out.push(':');
66 out.push_str(&write_value(v));
67 }
68 out.push('}');
69 out
70}
71
72pub fn write_value(v: &CborValue) -> String {
73 match v {
74 CborValue::Integer(i) => {
75 let n = i128::from(*i);
76 if (i64::MIN as i128..=i64::MAX as i128).contains(&n) {
77 n.to_string()
78 } else {
79 format!("\"{n}\"")
82 }
83 }
84 CborValue::Float(f) => {
85 if f.is_nan() || f.is_infinite() {
86 "null".to_string()
87 } else {
88 f.to_string()
89 }
90 }
91 CborValue::Text(s) => write_string(s),
92 CborValue::Bytes(b) => {
93 let mut hex = String::with_capacity(b.len() * 2 + 4);
94 hex.push_str("\"0x");
95 for byte in b {
96 hex.push_str(&format!("{byte:02x}"));
97 }
98 hex.push('"');
99 hex
100 }
101 CborValue::Bool(b) => b.to_string(),
102 CborValue::Null => "null".to_string(),
103 CborValue::Array(items) => {
104 let mut out = String::from("[");
105 for (i, item) in items.iter().enumerate() {
106 if i > 0 {
107 out.push(',');
108 }
109 out.push_str(&write_value(item));
110 }
111 out.push(']');
112 out
113 }
114 CborValue::Map(pairs) => {
115 let mut out = String::from("{");
116 for (i, (k, v)) in pairs.iter().enumerate() {
117 if i > 0 {
118 out.push(',');
119 }
120 let key = match k {
122 CborValue::Text(s) => write_string(s),
123 other => write_string(&write_value(other)),
124 };
125 out.push_str(&key);
126 out.push(':');
127 out.push_str(&write_value(v));
128 }
129 out.push('}');
130 out
131 }
132 CborValue::Tag(_, inner) => write_value(inner),
133 other => write_string(&format!("{other:?}")),
134 }
135}
136
137fn write_string(s: &str) -> String {
138 serde_json::to_string(s).unwrap_or_else(|_| "\"\"".to_string())
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn scalar_kinds() {
149 assert_eq!(write_value(&CborValue::Integer(42.into())), "42");
150 assert_eq!(write_value(&CborValue::Integer((-7).into())), "-7");
151 assert_eq!(write_value(&CborValue::Bool(true)), "true");
152 assert_eq!(write_value(&CborValue::Null), "null");
153 assert_eq!(write_value(&CborValue::Text("hi".into())), "\"hi\"");
154 }
155
156 #[test]
157 fn float_specials_become_null() {
158 assert_eq!(write_value(&CborValue::Float(f64::NAN)), "null");
159 assert_eq!(write_value(&CborValue::Float(f64::INFINITY)), "null");
160 }
161
162 #[test]
163 fn bytes_are_hex_strings() {
164 assert_eq!(
165 write_value(&CborValue::Bytes(vec![0xab, 0xcd])),
166 "\"0xabcd\""
167 );
168 }
169
170 #[test]
171 fn record_framing() {
172 let mut r = Record::new();
173 r.insert("name".to_string(), CborValue::Text("alice".into()));
174 r.insert("age".to_string(), CborValue::Integer(30.into()));
175 let s = write_record("u1", &r);
176 assert_eq!(s, "{\"u1\":{\"name\":\"alice\",\"age\":30}}\n");
177 }
178
179 #[test]
180 fn list_framing() {
181 let mut records: IndexMap<String, Record<CborValue>> = IndexMap::new();
182 let mut r1 = Record::new();
183 r1.insert("x".to_string(), CborValue::Integer(1.into()));
184 records.insert("a".to_string(), r1);
185 assert_eq!(write_list(&records), "{\"a\":{\"x\":1}}\n");
186 }
187}