cbor_data/
visit.rs

1use crate::{ItemKind, TaggedItem};
2
3/// Visitor for the structure of a CBOR item.
4///
5/// The visit is guided by the visitor in the sense that uninteresting arrays, dicts,
6/// or some of their values can be skipped by returning `false` from the respective
7/// methods.
8///
9/// **Note the different lifetimes needed on the `impl Visitor`!** The first lifetime
10/// `'a` describes how long the underlying `Cbor` value lives, i.e. how long the items
11/// passed into the visitor’s methods stay available. The second lifetime `'b` denotes
12/// the mutable borrow of the `String` we’re writing into, which needs to be more
13/// short-lived since we want to move the `String` before the `Cbor` expires.
14///
15/// Example:
16///
17/// ```
18/// use std::fmt::{Error, Formatter, Write};
19/// use cbor_data::{Cbor, CborOwned, TaggedItem, Visitor};
20///
21/// fn pretty_print(value: &Cbor) -> Result<String, Error> {
22///     struct X<'a>(&'a mut String);
23///     impl<'a, 'b> Visitor<'a, Error> for X<'b> {
24///         fn visit_simple(&mut self, item: TaggedItem<'a>) -> Result<(), Error> {
25///             write!(self.0, "{}", item)
26///         }
27///         fn visit_array_begin(&mut self, item: TaggedItem<'a>, size: Option<u64>) -> Result<bool, Error> {
28///             write!(self.0, "[")?;
29///             Ok(true)
30///         }
31///         fn visit_array_index(&mut self, item: TaggedItem<'a>, idx: u64) -> Result<bool, Error> {
32///             if idx > 0 {
33///                 write!(self.0, ", ")?;
34///             }
35///             Ok(true)
36///         }
37///         fn visit_array_end(&mut self, item: TaggedItem<'a>) -> Result<(), Error> {
38///             write!(self.0, "]")
39///         }
40///         fn visit_dict_begin(&mut self, item: TaggedItem<'a>, size: Option<u64>) -> Result<bool, Error> {
41///             write!(self.0, "{{")?;
42///             Ok(true)
43///         }
44///         fn visit_dict_key(&mut self, item: TaggedItem<'a>, key: TaggedItem<'a>, is_first: bool) -> Result<bool, Error> {
45///             if !is_first {
46///                 write!(self.0, ", ")?;
47///             }
48///             write!(self.0, "{}: ", key)?;
49///             Ok(true)
50///         }
51///         fn visit_dict_end(&mut self, item: TaggedItem<'a>) -> Result<(), Error> {
52///             write!(self.0, "}}")
53///         }
54///     }
55///     let mut s = String::new();
56///     value.visit(&mut X(&mut s))?;
57///     Ok(s)
58/// }
59///
60/// let bytes = vec![
61///     0xc4u8, 0x84, 5, 0xa2, 0x61, b'a', 0x39, 2, 154, 0x61, b'b', 0x46, b'd', b'e', b'f', b'd',
62///     b'e', b'f', 0x82, 0xf4, 0x65, b'h', b'e', b'l', b'l', b'o', 0xd9, 48, 57, 0xf6,
63/// ];
64/// let cbor = CborOwned::canonical(bytes).expect("invalid CBOR");
65///
66/// let pretty = pretty_print(cbor.as_ref()).expect("should always be able to write to a String …");
67///
68/// assert_eq!(pretty, r#"[5, {"a": -667, "b": h'646566646566'}, [false, "hello"], 12345(null)]"#);
69/// ```
70#[allow(unused_variables)]
71pub trait Visitor<'a, Err> {
72    /// Visit a simple item, i.e. `item.kind` will neither be `Array` nor `Dict`.
73    fn visit_simple(&mut self, item: TaggedItem<'a>) -> Result<(), Err> {
74        Ok(())
75    }
76    /// Visit the beginning of an array. `size` is None for indefinite size encoding.
77    /// Return `false` to skip this array, meaning that `visit_array_index`
78    /// will NOT be called for it.
79    fn visit_array_begin(&mut self, array: TaggedItem<'a>, size: Option<u64>) -> Result<bool, Err> {
80        Ok(true)
81    }
82    /// Visit an array element at the given index. Return `false` to skip over the element’s
83    /// contents, otherwise nested items (simple or otherwise) will be visited before visiting
84    /// the next element or the array’s end.
85    fn visit_array_index(&mut self, array: TaggedItem<'a>, index: u64) -> Result<bool, Err> {
86        Ok(true)
87    }
88    /// Visit the end of the current array.
89    fn visit_array_end(&mut self, array: TaggedItem<'a>) -> Result<(), Err> {
90        Ok(())
91    }
92    /// Visit the beginning of an dict. `size` is None for indefinite size encoding.
93    /// Return `false` to skip this dict, meaning that `visit_dict_key`
94    /// will NOT be called for it.
95    fn visit_dict_begin(&mut self, dict: TaggedItem<'a>, size: Option<u64>) -> Result<bool, Err> {
96        Ok(true)
97    }
98    /// Visit a dict value at the given key. Return `false` to skip over the value’s
99    /// contents, otherwise nested items (simple or otherwise) will be visited before visiting
100    /// the next key or the dict’s end.
101    ///
102    /// In most cases the key will be a string or an integer. In the rare case where a key is a
103    /// complex struct, you can visit it manually.
104    fn visit_dict_key(
105        &mut self,
106        dict: TaggedItem<'a>,
107        key: TaggedItem<'a>,
108        is_first: bool,
109    ) -> Result<bool, Err> {
110        Ok(true)
111    }
112    /// Visit the end of the current dict.
113    fn visit_dict_end(&mut self, dict: TaggedItem<'a>) -> Result<(), Err> {
114        Ok(())
115    }
116}
117
118pub fn visit<'a, 'b, Err, V: Visitor<'b, Err>>(v: &'a mut V, c: TaggedItem<'b>) -> Result<(), Err> {
119    match c.kind() {
120        ItemKind::Array(iter) => {
121            if v.visit_array_begin(c, iter.size())? {
122                for (idx, item) in iter.enumerate() {
123                    if v.visit_array_index(c, idx as u64)? {
124                        visit(v, item.tagged_item())?;
125                    }
126                }
127            }
128
129            v.visit_array_end(c)
130        }
131        ItemKind::Dict(iter) => {
132            if v.visit_dict_begin(c, iter.size())? {
133                let mut is_first = true;
134                for (key, item) in iter {
135                    if v.visit_dict_key(c, key.tagged_item(), is_first)? {
136                        visit(v, item.tagged_item())?;
137                    }
138                    is_first = false;
139                }
140            }
141
142            v.visit_dict_end(c)
143        }
144        _ => v.visit_simple(c),
145    }
146}
147
148// check https://datatracker.ietf.org/doc/html/rfc8949#section-8 for the format
149impl<'a> Visitor<'a, std::fmt::Error> for &mut std::fmt::Formatter<'_> {
150    fn visit_simple(&mut self, item: TaggedItem<'a>) -> Result<(), std::fmt::Error> {
151        write!(self, "{}", item)?;
152        Ok(())
153    }
154
155    fn visit_array_begin(
156        &mut self,
157        array: TaggedItem<'a>,
158        size: Option<u64>,
159    ) -> Result<bool, std::fmt::Error> {
160        for tag in array.tags() {
161            write!(self, "{}(", tag)?;
162        }
163        write!(self, "[")?;
164        if size.is_none() {
165            write!(self, "_ ")?;
166        }
167        Ok(true)
168    }
169
170    fn visit_array_index(
171        &mut self,
172        _array: TaggedItem<'a>,
173        index: u64,
174    ) -> Result<bool, std::fmt::Error> {
175        if index > 0 {
176            write!(self, ", ")?;
177        }
178        Ok(true)
179    }
180
181    fn visit_array_end(&mut self, array: TaggedItem<'a>) -> Result<(), std::fmt::Error> {
182        write!(self, "]")?;
183        for _ in array.tags() {
184            write!(self, ")")?;
185        }
186        Ok(())
187    }
188
189    fn visit_dict_begin(
190        &mut self,
191        dict: TaggedItem<'a>,
192        size: Option<u64>,
193    ) -> Result<bool, std::fmt::Error> {
194        for tag in dict.tags() {
195            write!(self, "{}(", tag)?;
196        }
197        write!(self, "{{")?;
198        if size.is_none() {
199            write!(self, "_ ")?;
200        }
201        Ok(true)
202    }
203
204    fn visit_dict_key(
205        &mut self,
206        _dict: TaggedItem<'a>,
207        key: TaggedItem<'a>,
208        is_first: bool,
209    ) -> Result<bool, std::fmt::Error> {
210        if !is_first {
211            write!(self, ", ")?;
212        }
213        visit(self, key)?;
214        write!(self, ": ")?;
215        Ok(true)
216    }
217
218    fn visit_dict_end(&mut self, dict: TaggedItem<'a>) -> Result<(), std::fmt::Error> {
219        write!(self, "}}")?;
220        for _ in dict.tags() {
221            write!(self, ")")?;
222        }
223        Ok(())
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use crate::{constants::TAG_CBOR_ITEM, CborBuilder, ItemKind, PathElement, TaggedItem, Writer};
230    use pretty_assertions::assert_eq;
231
232    #[test]
233    fn smoke() {
234        let x = CborBuilder::new().write_array([1, 2], |b| {
235            b.write_bool(true, [3, 4]);
236            b.write_dict([5, 6], |b| {
237                b.with_key("k", |b| b.write_pos(5, [7, 8]));
238                b.with_cbor_key(|b| b.write_neg(42, [9, 10]), |b| b.write_null([11, 12]));
239            });
240            b.write_bytes(
241                CborBuilder::new()
242                    .write_array([], |b| {
243                        b.write_bool(false, [0]);
244                        b.write_undefined([42]);
245                    })
246                    .as_slice(),
247                [TAG_CBOR_ITEM],
248            );
249        });
250
251        struct Visitor<'b>(&'b mut Vec<String>, bool);
252        impl<'a, 'b> super::Visitor<'a, &'static str> for Visitor<'b> {
253            fn visit_simple(&mut self, item: TaggedItem<'a>) -> Result<(), &'static str> {
254                self.0.push(format!("simple {:?}", item));
255                if self.1 {
256                    Err("buh")
257                } else {
258                    Ok(())
259                }
260            }
261
262            fn visit_array_begin(
263                &mut self,
264                array: TaggedItem<'a>,
265                size: Option<u64>,
266            ) -> Result<bool, &'static str> {
267                self.0.push(format!("array_begin {:?} {:?}", array, size));
268                Ok(true)
269            }
270
271            fn visit_array_index(
272                &mut self,
273                array: TaggedItem<'a>,
274                index: u64,
275            ) -> Result<bool, &'static str> {
276                self.0.push(format!("array_index {:?} {}", array, index));
277                Ok(true)
278            }
279
280            fn visit_array_end(&mut self, array: TaggedItem<'a>) -> Result<(), &'static str> {
281                self.0.push(format!("array_end {:?}", array));
282                Ok(())
283            }
284
285            fn visit_dict_begin(
286                &mut self,
287                dict: TaggedItem<'a>,
288                size: Option<u64>,
289            ) -> Result<bool, &'static str> {
290                self.0.push(format!("dict_begin {:?} {:?}", dict, size));
291                Ok(true)
292            }
293
294            fn visit_dict_key(
295                &mut self,
296                dict: TaggedItem<'a>,
297                key: TaggedItem<'a>,
298                is_first: bool,
299            ) -> Result<bool, &'static str> {
300                self.0
301                    .push(format!("dict_key {:?} {:?} {}", dict, key, is_first));
302                Ok(true)
303            }
304
305            fn visit_dict_end(&mut self, dict: TaggedItem<'a>) -> Result<(), &'static str> {
306                self.0.push(format!("dict_end {:?}", dict));
307                Ok(())
308            }
309        }
310
311        let mut trace = Vec::new();
312        x.visit(&mut Visitor(trace.as_mut(), false)).unwrap();
313        assert_eq!(
314            trace,
315            vec![
316                "array_begin TaggedItem(Tags(1,2), Array(Some(3))) Some(3)",
317                "array_index TaggedItem(Tags(1,2), Array(Some(3))) 0",
318                "simple TaggedItem(Tags(3,4), Bool(true))",
319                "array_index TaggedItem(Tags(1,2), Array(Some(3))) 1",
320                "dict_begin TaggedItem(Tags(5,6), Dict(Some(2))) Some(2)",
321                "dict_key TaggedItem(Tags(5,6), Dict(Some(2))) TaggedItem(Tags(), Str(k)) true",
322                "simple TaggedItem(Tags(7,8), Pos(5))",
323                "dict_key TaggedItem(Tags(5,6), Dict(Some(2))) TaggedItem(Tags(9,10), Neg(42)) false",
324                "simple TaggedItem(Tags(11,12), Null)",
325                "dict_end TaggedItem(Tags(5,6), Dict(Some(2)))",
326                "array_index TaggedItem(Tags(1,2), Array(Some(3))) 2",
327                "simple TaggedItem(Tags(24), Bytes(82c0f4d82af7))",
328                "array_end TaggedItem(Tags(1,2), Array(Some(3)))",
329            ]
330        );
331
332        trace.clear();
333        assert_eq!(
334            x.visit(&mut Visitor(trace.as_mut(), true)).unwrap_err(),
335            "buh"
336        );
337        assert_eq!(
338            trace,
339            vec![
340                "array_begin TaggedItem(Tags(1,2), Array(Some(3))) Some(3)",
341                "array_index TaggedItem(Tags(1,2), Array(Some(3))) 0",
342                "simple TaggedItem(Tags(3,4), Bool(true))",
343            ]
344        );
345
346        trace.clear();
347        x.index([PathElement::Number(2)])
348            .unwrap()
349            .visit(&mut Visitor(trace.as_mut(), false))
350            .unwrap();
351        assert_eq!(
352            trace,
353            vec![
354                "array_begin TaggedItem(Tags(), Array(Some(2))) Some(2)",
355                "array_index TaggedItem(Tags(), Array(Some(2))) 0",
356                "simple TaggedItem(Tags(0), Bool(false))",
357                "array_index TaggedItem(Tags(), Array(Some(2))) 1",
358                "simple TaggedItem(Tags(42), Undefined)",
359                "array_end TaggedItem(Tags(), Array(Some(2)))",
360            ]
361        );
362
363        assert_eq!(
364            x.index([PathElement::Number(2), PathElement::Number(1)])
365                .unwrap()
366                .kind(),
367            ItemKind::Undefined
368        );
369    }
370}