deser_debug/
lib.rs

1//! This library takes a [`Serialize`](deser::ser::Serialize) and
2//! formats it with [`std::fmt`] to debug representation.
3use std::fmt;
4use std::sync::atomic::{self, AtomicUsize};
5
6use deser::ser::{Serialize, SerializeDriver};
7use deser::{Atom, Event};
8
9/// Serializes a serializable value to `Debug` format.
10pub struct ToDebug {
11    events: Vec<(Event<'static>, Option<String>)>,
12}
13
14impl fmt::Display for ToDebug {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        fmt::Debug::fmt(&Helper(&self.events, AtomicUsize::default()), f)
17    }
18}
19
20impl fmt::Debug for ToDebug {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        fmt::Debug::fmt(&Helper(&self.events, AtomicUsize::default()), f)
23    }
24}
25
26impl ToDebug {
27    /// Creates a new [`ToDebug`] object from a serializable value.
28    pub fn new(value: &dyn Serialize) -> ToDebug {
29        let mut events = Vec::new();
30        let mut driver = SerializeDriver::new(value);
31        while let Some((event, descriptor, _)) = driver.next().unwrap() {
32            events.push((event.to_static(), descriptor.name().map(|x| x.to_string())));
33        }
34        ToDebug { events }
35    }
36}
37
38fn dump<'a, 'f>(
39    tokens: &'a [(Event<'a>, Option<String>)],
40    f: &'f mut fmt::Formatter<'_>,
41) -> Result<&'a [(Event<'a>, Option<String>)], fmt::Error> {
42    if let Some((first, mut rest)) = tokens.split_first() {
43        match first.0 {
44            Event::Atom(Atom::Null) => fmt::Debug::fmt(&(), f)?,
45            Event::Atom(Atom::Bool(v)) => fmt::Debug::fmt(&v, f)?,
46            Event::Atom(Atom::Str(ref v)) => fmt::Debug::fmt(v, f)?,
47            Event::Atom(Atom::Bytes(ref v)) => {
48                write!(f, "b\"")?;
49                for &b in &v[..] {
50                    if b == b'\n' {
51                        write!(f, "\\n")?;
52                    } else if b == b'\r' {
53                        write!(f, "\\r")?;
54                    } else if b == b'\t' {
55                        write!(f, "\\t")?;
56                    } else if b == b'\\' || b == b'"' {
57                        write!(f, "\\{}", b as char)?;
58                    } else if b == b'\0' {
59                        write!(f, "\\0")?;
60                    } else if (0x20..0x7f).contains(&b) {
61                        write!(f, "{}", b as char)?;
62                    } else {
63                        write!(f, "\\x{:02x}", b)?;
64                    }
65                }
66                write!(f, "\"")?;
67            }
68            Event::Atom(Atom::Char(v)) => fmt::Debug::fmt(&v, f)?,
69            Event::Atom(Atom::U64(v)) => fmt::Debug::fmt(&v, f)?,
70            Event::Atom(Atom::I64(v)) => fmt::Debug::fmt(&v, f)?,
71            Event::Atom(Atom::F64(v)) => fmt::Debug::fmt(&v, f)?,
72            Event::Atom(..) => f.debug_struct("?").finish()?,
73            Event::MapStart => {
74                if let Some(ref name) = first.1 {
75                    write!(f, "{} ", name)?;
76                }
77                let mut map = f.debug_map();
78                let mut is_key = true;
79                loop {
80                    if rest.get(0).map_or(false, |x| matches!(x.0, Event::MapEnd)) {
81                        rest = &rest[1..];
82                        break;
83                    }
84                    let inner = Helper(rest, AtomicUsize::default());
85                    if is_key {
86                        map.key(&inner);
87                    } else {
88                        map.value(&inner);
89                    }
90                    is_key = !is_key;
91                    rest = &rest[inner.1.load(atomic::Ordering::Relaxed)..];
92                }
93                map.finish()?;
94            }
95            Event::MapEnd => unreachable!(),
96            Event::SeqStart => {
97                if let Some(ref name) = first.1 {
98                    if name != "Vec" && name != "slice" {
99                        write!(f, "{} ", name)?;
100                    }
101                }
102                let mut list = f.debug_list();
103                loop {
104                    if rest.get(0).map_or(false, |x| matches!(x.0, Event::SeqEnd)) {
105                        rest = &rest[1..];
106                        break;
107                    }
108                    let inner = Helper(rest, AtomicUsize::default());
109                    list.entry(&inner);
110                    rest = &rest[inner.1.load(atomic::Ordering::Relaxed)..];
111                }
112                list.finish()?;
113            }
114            Event::SeqEnd => unreachable!(),
115        }
116        Ok(rest)
117    } else {
118        Ok(tokens)
119    }
120}
121
122struct Helper<'a>(&'a [(Event<'a>, Option<String>)], AtomicUsize);
123
124impl<'a> fmt::Debug for Helper<'a> {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        let new = dump(self.0, f)?;
127        self.1
128            .store(self.0.len() - new.len(), atomic::Ordering::Relaxed);
129        Ok(())
130    }
131}
132
133#[test]
134fn test_debug_format() {
135    let mut m = std::collections::BTreeMap::new();
136    m.insert(true, vec![vec![&b"x"[..], b"yyy"], vec![b"zzzz\x00\x01"]]);
137    m.insert(false, vec![]);
138
139    assert_eq!(
140        ToDebug::new(&m).to_string(),
141        "BTreeMap {false: [], true: [[b\"x\", b\"yyy\"], [b\"zzzz\\0\\x01\"]]}"
142    );
143}