1use alloc::collections::BTreeMap;
7use alloc::string::String;
8use alloc::vec::Vec;
9use core::fmt::Write;
10
11use crate::{Value, ValueType};
12
13#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub enum PathSegment {
16 Key(String),
18 Index(usize),
20}
21
22pub type Path = Vec<PathSegment>;
24
25pub type Span = (usize, usize);
27
28#[derive(Debug)]
30pub struct FormattedValue {
31 pub text: String,
33 pub spans: BTreeMap<Path, Span>,
35}
36
37pub fn format_value_with_spans(value: &Value) -> FormattedValue {
39 let mut ctx = FormatContext::new();
40 format_value_into(&mut ctx, value, &[]);
41 FormattedValue {
42 text: ctx.output,
43 spans: ctx.spans,
44 }
45}
46
47pub fn format_value(value: &Value) -> String {
49 let mut ctx = FormatContext::new();
50 format_value_into(&mut ctx, value, &[]);
51 ctx.output
52}
53
54struct FormatContext {
55 output: String,
56 spans: BTreeMap<Path, Span>,
57 indent: usize,
58}
59
60impl FormatContext {
61 fn new() -> Self {
62 Self {
63 output: String::new(),
64 spans: BTreeMap::new(),
65 indent: 0,
66 }
67 }
68
69 fn len(&self) -> usize {
70 self.output.len()
71 }
72
73 fn write_indent(&mut self) {
74 for _ in 0..self.indent {
75 self.output.push_str(" ");
76 }
77 }
78
79 fn record_span(&mut self, path: &[PathSegment], start: usize, end: usize) {
80 self.spans.insert(path.to_vec(), (start, end));
81 }
82}
83
84fn format_value_into(ctx: &mut FormatContext, value: &Value, current_path: &[PathSegment]) {
85 let start = ctx.len();
86
87 match value.value_type() {
88 ValueType::Null => {
89 ctx.output.push_str("null");
90 }
91 ValueType::Bool => {
92 if value.is_true() {
93 ctx.output.push_str("true");
94 } else {
95 ctx.output.push_str("false");
96 }
97 }
98 ValueType::Number => {
99 let num = value.as_number().unwrap();
100 if let Some(i) = num.to_i64() {
102 let _ = write!(ctx.output, "{i}");
103 } else if let Some(u) = num.to_u64() {
104 let _ = write!(ctx.output, "{u}");
105 } else if let Some(f) = num.to_f64() {
106 let _ = write!(ctx.output, "{f}");
107 }
108 }
109 ValueType::String => {
110 let s = value.as_string().unwrap();
111 ctx.output.push('"');
113 for c in s.as_str().chars() {
114 match c {
115 '"' => ctx.output.push_str("\\\""),
116 '\\' => ctx.output.push_str("\\\\"),
117 '\n' => ctx.output.push_str("\\n"),
118 '\r' => ctx.output.push_str("\\r"),
119 '\t' => ctx.output.push_str("\\t"),
120 c if c.is_control() => {
121 let _ = write!(ctx.output, "\\u{:04x}", c as u32);
122 }
123 c => ctx.output.push(c),
124 }
125 }
126 ctx.output.push('"');
127 }
128 ValueType::Bytes => {
129 let bytes = value.as_bytes().unwrap();
130 ctx.output.push_str("<bytes:");
131 let _ = write!(ctx.output, "{}", bytes.len());
132 ctx.output.push('>');
133 }
134 ValueType::Array => {
135 let arr = value.as_array().unwrap();
136 if arr.is_empty() {
137 ctx.output.push_str("[]");
138 } else {
139 ctx.output.push_str("[\n");
140 ctx.indent += 1;
141 for (i, item) in arr.iter().enumerate() {
142 ctx.write_indent();
143 let mut item_path = current_path.to_vec();
144 item_path.push(PathSegment::Index(i));
145 format_value_into(ctx, item, &item_path);
146 if i < arr.len() - 1 {
147 ctx.output.push(',');
148 }
149 ctx.output.push('\n');
150 }
151 ctx.indent -= 1;
152 ctx.write_indent();
153 ctx.output.push(']');
154 }
155 }
156 ValueType::Object => {
157 let obj = value.as_object().unwrap();
158 if obj.is_empty() {
159 ctx.output.push_str("{}");
160 } else {
161 ctx.output.push_str("{\n");
162 ctx.indent += 1;
163 let entries: Vec<_> = obj.iter().collect();
164 for (i, (key, val)) in entries.iter().enumerate() {
165 ctx.write_indent();
166 ctx.output.push('"');
168 ctx.output.push_str(key.as_str());
169 ctx.output.push_str("\": ");
170 let mut item_path = current_path.to_vec();
172 item_path.push(PathSegment::Key(key.as_str().into()));
173 format_value_into(ctx, val, &item_path);
174 if i < entries.len() - 1 {
175 ctx.output.push(',');
176 }
177 ctx.output.push('\n');
178 }
179 ctx.indent -= 1;
180 ctx.write_indent();
181 ctx.output.push('}');
182 }
183 }
184 ValueType::DateTime => {
185 let dt = value.as_datetime().unwrap();
186 let _ = write!(ctx.output, "{dt:?}");
188 }
189 ValueType::QName => {
190 let qname = value.as_qname().unwrap();
191 let _ = write!(ctx.output, "{qname:?}");
193 }
194 ValueType::Uuid => {
195 let uuid = value.as_uuid().unwrap();
196 let _ = write!(ctx.output, "{uuid:?}");
198 }
199 }
200
201 let end = ctx.len();
202 ctx.record_span(current_path, start, end);
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::{VArray, VObject, VString};
209
210 #[test]
211 fn test_format_primitives() {
212 assert_eq!(format_value(&Value::NULL), "null");
213 assert_eq!(format_value(&Value::TRUE), "true");
214 assert_eq!(format_value(&Value::FALSE), "false");
215 assert_eq!(format_value(&Value::from(42i64)), "42");
216 assert_eq!(
217 format_value(&Value::from(VString::new("hello"))),
218 "\"hello\""
219 );
220 }
221
222 #[test]
223 fn test_format_array() {
224 let mut arr = VArray::new();
225 arr.push(Value::from(1i64));
226 arr.push(Value::from(2i64));
227 arr.push(Value::from(3i64));
228 let value: Value = arr.into();
229
230 let result = format_value_with_spans(&value);
231 assert!(result.text.contains("1"));
232 assert!(result.text.contains("2"));
233 assert!(result.text.contains("3"));
234
235 let path_0 = vec![PathSegment::Index(0)];
237 assert!(result.spans.contains_key(&path_0));
238 }
239
240 #[test]
241 fn test_format_object() {
242 let mut obj = VObject::new();
243 obj.insert("name", Value::from(VString::new("Alice")));
244 obj.insert("age", Value::from(30i64));
245 let value: Value = obj.into();
246
247 let result = format_value_with_spans(&value);
248 assert!(result.text.contains("\"name\""));
249 assert!(result.text.contains("\"Alice\""));
250 assert!(result.text.contains("\"age\""));
251 assert!(result.text.contains("30"));
252
253 let name_path = vec![PathSegment::Key("name".into())];
255 let age_path = vec![PathSegment::Key("age".into())];
256 assert!(
257 result.spans.contains_key(&name_path),
258 "Missing span for 'name'"
259 );
260 assert!(
261 result.spans.contains_key(&age_path),
262 "Missing span for 'age'"
263 );
264
265 let age_span = result.spans.get(&age_path).unwrap();
267 let age_text = &result.text[age_span.0..age_span.1];
268 assert_eq!(age_text, "30");
269 }
270
271 #[test]
272 fn test_format_nested() {
273 let mut inner = VObject::new();
274 inner.insert("x", Value::from(10i64));
275
276 let mut outer = VObject::new();
277 outer.insert("point", Value::from(inner));
278 let value: Value = outer.into();
279
280 let result = format_value_with_spans(&value);
281
282 let nested_path = vec![
284 PathSegment::Key("point".into()),
285 PathSegment::Key("x".into()),
286 ];
287 assert!(
288 result.spans.contains_key(&nested_path),
289 "Missing span for nested path. Spans: {:?}",
290 result.spans
291 );
292
293 let span = result.spans.get(&nested_path).unwrap();
294 let text = &result.text[span.0..span.1];
295 assert_eq!(text, "10");
296 }
297}