insta/content/
json.rs

1use std::fmt::{Display, Write};
2
3use crate::content::Content;
4
5/// The maximum number of characters to print in a single line
6/// when [`to_string_pretty`] is used.
7const COMPACT_MAX_CHARS: usize = 120;
8
9pub fn format_float<T: Display>(value: T) -> String {
10    let mut rv = format!("{}", value);
11    if !rv.contains('.') {
12        rv.push_str(".0");
13    }
14    rv
15}
16
17#[derive(PartialEq, Eq, Copy, Clone, Debug)]
18pub enum Format {
19    Condensed,
20    SingleLine,
21    Pretty,
22}
23
24/// Serializes a serializable to JSON.
25pub struct Serializer {
26    out: String,
27    format: Format,
28    indentation: usize,
29}
30
31impl Serializer {
32    /// Creates a new [`Serializer`] that writes into the given writer.
33    pub fn new() -> Serializer {
34        Serializer {
35            out: String::new(),
36            format: Format::Condensed,
37            indentation: 0,
38        }
39    }
40
41    pub fn into_result(self) -> String {
42        self.out
43    }
44
45    fn write_indentation(&mut self) {
46        if self.format == Format::Pretty {
47            write!(self.out, "{: ^1$}", "", self.indentation * 2).unwrap();
48        }
49    }
50
51    fn start_container(&mut self, c: char) {
52        self.write_char(c);
53        self.indentation += 1;
54    }
55
56    fn end_container(&mut self, c: char, empty: bool) {
57        self.indentation -= 1;
58        if self.format == Format::Pretty && !empty {
59            self.write_char('\n');
60            self.write_indentation();
61        }
62        self.write_char(c);
63    }
64
65    fn write_comma(&mut self, first: bool) {
66        match self.format {
67            Format::Pretty => {
68                if first {
69                    self.write_char('\n');
70                } else {
71                    self.write_str(",\n");
72                }
73                self.write_indentation();
74            }
75            Format::Condensed => {
76                if !first {
77                    self.write_char(',');
78                }
79            }
80            Format::SingleLine => {
81                if !first {
82                    self.write_str(", ");
83                }
84            }
85        }
86    }
87
88    fn write_colon(&mut self) {
89        match self.format {
90            Format::Pretty | Format::SingleLine => self.write_str(": "),
91            Format::Condensed => self.write_char(':'),
92        }
93    }
94
95    fn serialize_array(&mut self, items: &[Content]) {
96        self.start_container('[');
97        for (idx, item) in items.iter().enumerate() {
98            self.write_comma(idx == 0);
99            self.serialize(item);
100        }
101        self.end_container(']', items.is_empty());
102    }
103
104    fn serialize_object(&mut self, fields: &[(&str, Content)]) {
105        self.start_container('{');
106        for (idx, (key, value)) in fields.iter().enumerate() {
107            self.write_comma(idx == 0);
108            self.write_escaped_str(key);
109            self.write_colon();
110            self.serialize(value);
111        }
112        self.end_container('}', fields.is_empty());
113    }
114
115    pub fn serialize(&mut self, value: &Content) {
116        match value {
117            Content::Bool(true) => self.write_str("true"),
118            Content::Bool(false) => self.write_str("false"),
119            Content::U8(n) => write!(self.out, "{}", n).unwrap(),
120            Content::U16(n) => write!(self.out, "{}", n).unwrap(),
121            Content::U32(n) => write!(self.out, "{}", n).unwrap(),
122            Content::U64(n) => write!(self.out, "{}", n).unwrap(),
123            Content::U128(n) => write!(self.out, "{}", n).unwrap(),
124            Content::I8(n) => write!(self.out, "{}", n).unwrap(),
125            Content::I16(n) => write!(self.out, "{}", n).unwrap(),
126            Content::I32(n) => write!(self.out, "{}", n).unwrap(),
127            Content::I64(n) => write!(self.out, "{}", n).unwrap(),
128            Content::I128(n) => write!(self.out, "{}", n).unwrap(),
129            Content::F32(f) => {
130                if f.is_finite() {
131                    self.write_str(&format_float(f));
132                } else {
133                    self.write_str("null")
134                }
135            }
136            Content::F64(f) => {
137                if f.is_finite() {
138                    self.write_str(&format_float(f));
139                } else {
140                    self.write_str("null")
141                }
142            }
143            Content::Char(c) => self.write_escaped_str(&(*c).to_string()),
144            Content::String(s) => self.write_escaped_str(s),
145            Content::Bytes(bytes) => {
146                self.start_container('[');
147                for (idx, byte) in bytes.iter().enumerate() {
148                    self.write_comma(idx == 0);
149                    self.write_str(&byte.to_string());
150                }
151                self.end_container(']', bytes.is_empty());
152            }
153            Content::None | Content::Unit | Content::UnitStruct(_) => self.write_str("null"),
154            Content::Some(content) => self.serialize(content),
155            Content::UnitVariant(_, _, variant) => self.write_escaped_str(variant),
156            Content::NewtypeStruct(_, content) => self.serialize(content),
157            Content::NewtypeVariant(_, _, variant, content) => {
158                self.start_container('{');
159                self.write_comma(true);
160                self.write_escaped_str(variant);
161                self.write_colon();
162                self.serialize(content);
163                self.end_container('}', false);
164            }
165            Content::Seq(seq) | Content::Tuple(seq) | Content::TupleStruct(_, seq) => {
166                self.serialize_array(seq);
167            }
168            Content::TupleVariant(_, _, variant, seq) => {
169                self.start_container('{');
170                self.write_comma(true);
171                self.write_escaped_str(variant);
172                self.write_colon();
173                self.serialize_array(seq);
174                self.end_container('}', false);
175            }
176            Content::Map(map) => {
177                self.start_container('{');
178                for (idx, (key, value)) in map.iter().enumerate() {
179                    self.write_comma(idx == 0);
180                    let real_key = key.resolve_inner();
181                    if let Content::String(ref s) = real_key {
182                        self.write_escaped_str(s);
183                    } else if let Some(num) = real_key.as_i64() {
184                        self.write_escaped_str(&num.to_string());
185                    } else if let Some(num) = real_key.as_i128() {
186                        self.write_escaped_str(&num.to_string());
187                    } else {
188                        panic!("cannot serialize maps without string keys to JSON");
189                    }
190                    self.write_colon();
191                    self.serialize(value);
192                }
193                self.end_container('}', map.is_empty());
194            }
195            Content::Struct(_, fields) => {
196                self.serialize_object(fields);
197            }
198            Content::StructVariant(_, _, variant, fields) => {
199                self.start_container('{');
200                self.write_comma(true);
201                self.write_escaped_str(variant);
202                self.write_colon();
203                self.serialize_object(fields);
204                self.end_container('}', false);
205            }
206        }
207    }
208
209    fn write_str(&mut self, s: &str) {
210        self.out.push_str(s);
211    }
212
213    fn write_char(&mut self, c: char) {
214        self.out.push(c);
215    }
216
217    fn write_escaped_str(&mut self, value: &str) {
218        self.write_char('"');
219
220        let bytes = value.as_bytes();
221        let mut start = 0;
222
223        for (i, &byte) in bytes.iter().enumerate() {
224            let escape = ESCAPE[byte as usize];
225            if escape == 0 {
226                continue;
227            }
228
229            if start < i {
230                self.write_str(&value[start..i]);
231            }
232
233            match escape {
234                self::BB => self.write_str("\\b"),
235                self::TT => self.write_str("\\t"),
236                self::NN => self.write_str("\\n"),
237                self::FF => self.write_str("\\f"),
238                self::RR => self.write_str("\\r"),
239                self::QU => self.write_str("\\\""),
240                self::BS => self.write_str("\\\\"),
241                self::U => {
242                    static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";
243                    self.write_str("\\u00");
244                    self.write_char(HEX_DIGITS[(byte >> 4) as usize] as char);
245                    self.write_char(HEX_DIGITS[(byte & 0xF) as usize] as char);
246                }
247                _ => unreachable!(),
248            }
249
250            start = i + 1;
251        }
252
253        if start != bytes.len() {
254            self.write_str(&value[start..]);
255        }
256
257        self.write_char('"');
258    }
259}
260
261const BB: u8 = b'b'; // \x08
262const TT: u8 = b't'; // \x09
263const NN: u8 = b'n'; // \x0A
264const FF: u8 = b'f'; // \x0C
265const RR: u8 = b'r'; // \x0D
266const QU: u8 = b'"'; // \x22
267const BS: u8 = b'\\'; // \x5C
268const U: u8 = b'u'; // \x00...\x1F except the ones above
269
270// Lookup table of escape sequences. A value of b'x' at index i means that byte
271// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.
272#[rustfmt::skip]
273static ESCAPE: [u8; 256] = [
274    //  1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
275    U,  U,  U,  U,  U,  U,  U,  U, BB, TT, NN,  U, FF, RR,  U,  U, // 0
276    U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U, // 1
277    0,  0, QU,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 2
278    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 3
279    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 4
280    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, BS,  0,  0,  0, // 5
281    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 6
282    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 7
283    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 8
284    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 9
285    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // A
286    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // B
287    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // C
288    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // D
289    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // E
290    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // F
291];
292
293/// Serializes a value to JSON.
294pub fn to_string(value: &Content) -> String {
295    let mut ser = Serializer::new();
296    ser.serialize(value);
297    ser.into_result()
298}
299
300/// Serializes a value to JSON in single-line format.
301#[allow(unused)]
302pub fn to_string_compact(value: &Content) -> String {
303    let mut ser = Serializer::new();
304    ser.format = Format::SingleLine;
305    ser.serialize(value);
306    let rv = ser.into_result();
307    // this is pretty wasteful as we just format twice
308    // but it's acceptable for the way this is used in
309    // insta.
310    if rv.chars().count() > COMPACT_MAX_CHARS {
311        to_string_pretty(value)
312    } else {
313        rv
314    }
315}
316
317/// Serializes a value to JSON pretty
318#[allow(unused)]
319pub fn to_string_pretty(value: &Content) -> String {
320    let mut ser = Serializer::new();
321    ser.format = Format::Pretty;
322    ser.serialize(value);
323    ser.into_result()
324}
325
326#[test]
327fn test_to_string() {
328    let json = to_string(&Content::Map(vec![
329        (
330            Content::from("environments"),
331            Content::Seq(vec![
332                Content::from("development"),
333                Content::from("production"),
334            ]),
335        ),
336        (Content::from("cmdline"), Content::Seq(vec![])),
337        (Content::from("extra"), Content::Map(vec![])),
338    ]));
339    crate::assert_snapshot!(&json, @r###"{"environments":["development","production"],"cmdline":[],"extra":{}}"###);
340}
341
342#[test]
343fn test_to_string_pretty() {
344    let json = to_string_pretty(&Content::Map(vec![
345        (
346            Content::from("environments"),
347            Content::Seq(vec![
348                Content::from("development"),
349                Content::from("production"),
350            ]),
351        ),
352        (Content::from("cmdline"), Content::Seq(vec![])),
353        (Content::from("extra"), Content::Map(vec![])),
354    ]));
355    crate::assert_snapshot!(&json, @r###"
356    {
357      "environments": [
358        "development",
359        "production"
360      ],
361      "cmdline": [],
362      "extra": {}
363    }
364    "###);
365}
366
367#[test]
368fn test_to_string_num_keys() {
369    let content = Content::Map(vec![
370        (Content::from(42u32), Content::from(true)),
371        (Content::from(-23i32), Content::from(false)),
372    ]);
373    let json = to_string_pretty(&content);
374    crate::assert_snapshot!(&json, @r###"
375    {
376      "42": true,
377      "-23": false
378    }
379    "###);
380}
381
382#[test]
383fn test_to_string_pretty_complex() {
384    let content = Content::Map(vec![
385        (
386            Content::from("is_alive"),
387            Content::NewtypeStruct("Some", Content::from(true).into()),
388        ),
389        (
390            Content::from("newtype_variant"),
391            Content::NewtypeVariant(
392                "Foo",
393                0,
394                "variant_a",
395                Box::new(Content::Struct(
396                    "VariantA",
397                    vec![
398                        ("field_a", Content::String("value_a".into())),
399                        ("field_b", 42u32.into()),
400                    ],
401                )),
402            ),
403        ),
404        (
405            Content::from("struct_variant"),
406            Content::StructVariant(
407                "Foo",
408                0,
409                "variant_b",
410                vec![
411                    ("field_a", Content::String("value_a".into())),
412                    ("field_b", 42u32.into()),
413                ],
414            ),
415        ),
416        (
417            Content::from("tuple_variant"),
418            Content::TupleVariant(
419                "Foo",
420                0,
421                "variant_c",
422                vec![(Content::String("value_a".into())), (42u32.into())],
423            ),
424        ),
425        (Content::from("empty_array"), Content::Seq(vec![])),
426        (Content::from("empty_object"), Content::Map(vec![])),
427        (Content::from("array"), Content::Seq(vec![true.into()])),
428        (
429            Content::from("object"),
430            Content::Map(vec![("foo".into(), true.into())]),
431        ),
432        (
433            Content::from("array_of_objects"),
434            Content::Seq(vec![Content::Struct(
435                "MyType",
436                vec![
437                    ("foo", Content::from("bar".to_string())),
438                    ("bar", Content::from("xxx".to_string())),
439                ],
440            )]),
441        ),
442        (
443            Content::from("unit_variant"),
444            Content::UnitVariant("Stuff", 0, "value"),
445        ),
446        (Content::from("u8"), Content::U8(8)),
447        (Content::from("u16"), Content::U16(16)),
448        (Content::from("u32"), Content::U32(32)),
449        (Content::from("u64"), Content::U64(64)),
450        (Content::from("u128"), Content::U128(128)),
451        (Content::from("i8"), Content::I8(8)),
452        (Content::from("i16"), Content::I16(16)),
453        (Content::from("i32"), Content::I32(32)),
454        (Content::from("i64"), Content::I64(64)),
455        (Content::from("i128"), Content::I128(128)),
456        (Content::from("f32"), Content::F32(32.0)),
457        (Content::from("f64"), Content::F64(64.0)),
458        (Content::from("char"), Content::Char('A')),
459        (Content::from("bytes"), Content::Bytes(b"hehe".to_vec())),
460        (Content::from("null"), Content::None),
461        (Content::from("unit"), Content::Unit),
462        (
463            Content::from("crazy_string"),
464            Content::String((0u8..=126).map(|x| x as char).collect()),
465        ),
466    ]);
467    let json = to_string_pretty(&content);
468
469    crate::assert_snapshot!(&json, @r###"
470    {
471      "is_alive": true,
472      "newtype_variant": {
473        "variant_a": {
474          "field_a": "value_a",
475          "field_b": 42
476        }
477      },
478      "struct_variant": {
479        "variant_b": {
480          "field_a": "value_a",
481          "field_b": 42
482        }
483      },
484      "tuple_variant": {
485        "variant_c": [
486          "value_a",
487          42
488        ]
489      },
490      "empty_array": [],
491      "empty_object": {},
492      "array": [
493        true
494      ],
495      "object": {
496        "foo": true
497      },
498      "array_of_objects": [
499        {
500          "foo": "bar",
501          "bar": "xxx"
502        }
503      ],
504      "unit_variant": "value",
505      "u8": 8,
506      "u16": 16,
507      "u32": 32,
508      "u64": 64,
509      "u128": 128,
510      "i8": 8,
511      "i16": 16,
512      "i32": 32,
513      "i64": 64,
514      "i128": 128,
515      "f32": 32.0,
516      "f64": 64.0,
517      "char": "A",
518      "bytes": [
519        104,
520        101,
521        104,
522        101
523      ],
524      "null": null,
525      "unit": null,
526      "crazy_string": "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
527    }
528    "###);
529}