ocpi_tariffs/json/
write.rs

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