ocpi_tariffs/json/
write.rs

1use std::fmt::{self, Write};
2
3use super::{Element, Field, Value};
4
5const TAB: &str = "    ";
6const ARRAY_OPEN: char = '[';
7const ARRAY_CLOSE: char = ']';
8const OBJECT_OPEN: char = '{';
9const OBJECT_CLOSE: char = '}';
10const COMMA: char = ',';
11const NEWLINE: char = '\n';
12
13/// Write a parsed and potentially modified `json::Element` tree to a buffer formatted
14/// for human readability.
15///
16/// The JSON is formatted so that each element is indented and put on it's own line.
17pub struct Pretty<'a, 'bin> {
18    elem: &'a Element<'bin>,
19}
20
21impl<'a, 'bin> Pretty<'a, 'bin> {
22    pub fn new(elem: &'a Element<'bin>) -> Self {
23        Pretty { elem }
24    }
25}
26
27impl fmt::Display for Pretty<'_, '_> {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        let mut stack = vec![State::Root(self.elem)];
30        let mut write_comma = WriteComma::Skip;
31
32        // This outer loop drives the pushing of elements on to the stack.
33        loop {
34            // This inner loop drives the popping of states off the stack.
35            let elem = loop {
36                let depth = stack.len();
37
38                let Some(mut state) = stack.pop() else {
39                    // If the stack is empty, we're done writing.
40                    return Ok(());
41                };
42
43                let elem = match &mut state {
44                    State::Root(elem) => {
45                        // The root `Element` never needs a comma written after it.
46                        // It's either an opening compound object such as an array or object; or it's
47                        // a single Value.
48                        write_elem(elem, f)?;
49                        elem
50                    }
51                    State::Array(iter) => {
52                        let Some(elem) = iter.next() else {
53                            // If there is no next `Element` then we need to move up the stack
54                            // and check there for a next `Element`.
55                            write_nl_and_indent(depth - 1, f)?;
56                            f.write_char(ARRAY_CLOSE)?;
57                            continue;
58                        };
59
60                        if let WriteComma::Write = write_comma {
61                            f.write_char(COMMA)?;
62                        }
63                        write_nl_and_indent(depth, f)?;
64                        write_comma = write_elem(elem, f)?;
65                        elem
66                    }
67                    State::Object(iter) => {
68                        let Some(field) = iter.next() else {
69                            // If there is no next `Element` then we need to move up the stack
70                            // and check there for a next `Element`.
71                            write_nl_and_indent(depth - 1, f)?;
72                            f.write_char(OBJECT_CLOSE)?;
73                            continue;
74                        };
75
76                        if let WriteComma::Write = write_comma {
77                            f.write_char(COMMA)?;
78                        }
79                        write_nl_and_indent(depth, f)?;
80                        write_comma = write_field(field, f)?;
81                        field.element()
82                    }
83                };
84
85                match &state {
86                    State::Array(_) | State::Object(_) => stack.push(state),
87                    State::Root(_) => (),
88                }
89
90                break elem;
91            };
92
93            match elem.value() {
94                Value::Array(elements) => stack.push(State::Array(elements.iter())),
95                Value::Object(fields) => stack.push(State::Object(fields.iter())),
96                _ => (),
97            }
98        }
99    }
100}
101
102/// The single stack level.
103#[derive(Debug)]
104enum State<'a, 'bin> {
105    /// The root element.
106    Root(&'a Element<'bin>),
107
108    /// A collection of Array `Element`s.
109    Array(std::slice::Iter<'a, Element<'bin>>),
110
111    /// A collection of Object `Field`s.
112    Object(std::slice::Iter<'a, Field<'bin>>),
113}
114
115/// Write a newline and indent ready for the next `Element`'s `Value` to be written.
116fn write_nl_and_indent(depth: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117    f.write_char(NEWLINE)?;
118
119    for _ in 0..depth {
120        f.write_str(TAB)?;
121    }
122
123    Ok(())
124}
125
126/// Whether or not a comma should be interspersed between values.
127#[derive(Copy, Clone)]
128enum WriteComma {
129    /// Write a comma on the next iteration.
130    Write,
131
132    /// Skip writing a comma on the next iteration.
133    Skip,
134}
135
136/// Shallow write an `Element` and return whether a comma should be written on the next iteration.
137///
138/// If the `Element` is a compound object like an `Array` or `Object`, only the opening brace is written.
139/// This is done to avoid implementing a recursive write fn.
140fn write_elem(elem: &Element<'_>, f: &mut fmt::Formatter<'_>) -> Result<WriteComma, fmt::Error> {
141    match elem.value() {
142        Value::Null => {
143            f.write_str("null")?;
144            Ok(WriteComma::Write)
145        }
146        Value::True => {
147            f.write_str("true")?;
148            Ok(WriteComma::Write)
149        }
150        Value::False => {
151            f.write_str("false")?;
152            Ok(WriteComma::Write)
153        }
154        Value::String(s) => {
155            write!(f, "\"{s}\"")?;
156            Ok(WriteComma::Write)
157        }
158        Value::Number(n) => {
159            write!(f, "{n}")?;
160            Ok(WriteComma::Write)
161        }
162        Value::Array(_) => {
163            f.write_char(ARRAY_OPEN)?;
164            Ok(WriteComma::Skip)
165        }
166        Value::Object(_) => {
167            f.write_char(OBJECT_OPEN)?;
168            Ok(WriteComma::Skip)
169        }
170    }
171}
172
173/// Write a `Field`'s key and value and return whether a comma should be written on the next iteration.
174fn write_field(field: &Field<'_>, f: &mut fmt::Formatter<'_>) -> Result<WriteComma, fmt::Error> {
175    write!(f, "\"{}\": ", field.key())?;
176    write_elem(field.element(), f)
177}
178
179#[cfg(test)]
180mod test_tree_writer {
181    use crate::{json, test};
182
183    use super::Pretty;
184
185    #[test]
186    fn should_pretty_print_root_null() {
187        const JSON_IN: &str = "null";
188
189        test::setup();
190        let elem = json::parse(JSON_IN).unwrap();
191        let pretty = Pretty::new(&elem);
192
193        let s = format!("{pretty}");
194        assert_eq!(s, JSON_IN);
195        let _ignored = serde_json::from_str::<serde_json::Value>(&s).unwrap();
196    }
197
198    #[test]
199    fn should_pretty_print_root_bool() {
200        const JSON_IN: &str = "true";
201
202        test::setup();
203        let elem = json::parse(JSON_IN).unwrap();
204        let pretty = Pretty::new(&elem);
205
206        let s = format!("{pretty}");
207        assert_eq!(s, JSON_IN);
208        let _ignored = serde_json::from_str::<serde_json::Value>(&s).unwrap();
209    }
210
211    #[test]
212    fn should_pretty_print_root_string() {
213        const JSON_IN: &str = r#""one""#;
214
215        test::setup();
216        let elem = json::parse(JSON_IN).unwrap();
217        let pretty = Pretty::new(&elem);
218
219        let s = format!("{pretty}");
220        assert_eq!(s, JSON_IN);
221        let _ignored = serde_json::from_str::<serde_json::Value>(&s).unwrap();
222    }
223
224    #[test]
225    fn should_pretty_print_escaped_str() {
226        const JSON_IN: &str = r#""one\ntwo\u0021three\"four\"five""#;
227
228        test::setup();
229        let elem = json::parse(JSON_IN).unwrap();
230        let pretty = Pretty::new(&elem);
231
232        let s = format!("{pretty}");
233        assert_eq!(s, JSON_IN);
234        let _ignored = serde_json::from_str::<serde_json::Value>(&s).unwrap();
235    }
236
237    #[test]
238    fn should_pretty_print_empty_array() {
239        const JSON_IN: &str = "[]";
240        const JSON_OUT: &str = "[
241]";
242
243        test::setup();
244        let elem = json::parse(JSON_IN).unwrap();
245        let pretty = Pretty::new(&elem);
246
247        let s = format!("{pretty}");
248        assert_eq!(s, JSON_OUT);
249        let _ignored = serde_json::from_str::<serde_json::Value>(&s).unwrap();
250    }
251
252    #[test]
253    fn should_pretty_print_string_array() {
254        const JSON_IN: &str = r#"["a", "b", "c"]"#;
255        const JSON_OUT: &str = r#"[
256    "a",
257    "b",
258    "c"
259]"#;
260
261        test::setup();
262        let elem = json::parse(JSON_IN).unwrap();
263        let pretty = Pretty::new(&elem);
264
265        let s = format!("{pretty}");
266        assert_eq!(s, JSON_OUT);
267        let _ignored = serde_json::from_str::<serde_json::Value>(&s).unwrap();
268    }
269
270    #[test]
271    fn should_pretty_print_float_array() {
272        const JSON_IN: &str = "[0.000564, 1.0, 3.14159, 2.71828182845904523536028747135266249775724709369995957496696]";
273        const JSON_OUT: &str = "[
274    0.000564,
275    1.0,
276    3.14159,
277    2.71828182845904523536028747135266249775724709369995957496696
278]";
279
280        test::setup();
281        let elem = json::parse(JSON_IN).unwrap();
282        let pretty = Pretty::new(&elem);
283
284        let s = format!("{pretty}");
285        assert_eq!(s, JSON_OUT);
286        let _ignored = serde_json::from_str::<serde_json::Value>(&s).unwrap();
287    }
288
289    #[test]
290    fn should_pretty_print_empty_object() {
291        const JSON_IN: &str = "{}";
292        const JSON_OUT: &str = "{
293}";
294
295        test::setup();
296        let elem = json::parse(JSON_IN).unwrap();
297        let pretty = Pretty::new(&elem);
298
299        let s = format!("{pretty}");
300        assert_eq!(s, JSON_OUT);
301        let _ignored = serde_json::from_str::<serde_json::Value>(&s).unwrap();
302    }
303
304    #[test]
305    fn should_pretty_print_object_with_bools() {
306        const JSON_IN: &str = r#"{"one":true,"two":false}"#;
307
308        const JSON_OUT: &str = r#"{
309    "one": true,
310    "two": false
311}"#;
312
313        test::setup();
314        let elem = json::parse(JSON_IN).unwrap();
315        let pretty = Pretty::new(&elem);
316
317        let s = format!("{pretty}");
318        assert_eq!(s, JSON_OUT);
319        let _ignored = serde_json::from_str::<serde_json::Value>(&s).unwrap();
320    }
321
322    #[test]
323    fn should_pretty_print_object_with_nulls() {
324        const JSON_IN: &str = r#"{"one":null,"two":null}"#;
325
326        const JSON_OUT: &str = r#"{
327    "one": null,
328    "two": null
329}"#;
330
331        test::setup();
332        let elem = json::parse(JSON_IN).unwrap();
333        let pretty = Pretty::new(&elem);
334
335        let s = format!("{pretty}");
336        assert_eq!(s, JSON_OUT);
337        let _ignored = serde_json::from_str::<serde_json::Value>(&s).unwrap();
338    }
339
340    #[test]
341    fn should_pretty_print_root_object() {
342        const JSON_IN: &str =
343            r#"{"one": 1,"two": ["a", "b", "c"],"three": {"d": 4,"e": 5,"f": 6},"four":null}"#;
344
345        const JSON_OUT: &str = r#"{
346    "one": 1,
347    "two": [
348        "a",
349        "b",
350        "c"
351    ],
352    "three": {
353        "d": 4,
354        "e": 5,
355        "f": 6
356    },
357    "four": null
358}"#;
359
360        test::setup();
361        let elem = json::parse(JSON_IN).unwrap();
362        let pretty = Pretty::new(&elem);
363
364        let s = format!("{pretty}");
365        assert_eq!(s, JSON_OUT);
366        let _ignored = serde_json::from_str::<serde_json::Value>(&s).unwrap();
367    }
368}