Skip to main content

grovedb_visualize/
lib.rs

1// MIT LICENSE
2//
3// Copyright (c) 2021 Dash Core Group
4//
5// Permission is hereby granted, free of charge, to any
6// person obtaining a copy of this software and associated
7// documentation files (the "Software"), to deal in the
8// Software without restriction, including without
9// limitation the rights to use, copy, modify, merge,
10// publish, distribute, sublicense, and/or sell copies of
11// the Software, and to permit persons to whom the Software
12// is furnished to do so, subject to the following
13// conditions:
14//
15// The above copyright notice and this permission notice
16// shall be included in all copies or substantial portions
17// of the Software.
18//
19// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
20// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
21// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
22// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
23// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
26// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27// DEALINGS IN THE SOFTWARE.
28
29//! Visualize
30
31#![deny(missing_docs)]
32
33use core::fmt;
34use std::io::{Result, Write};
35
36use itertools::Itertools;
37
38static HEX_LEN: usize = 8;
39static STR_LEN: usize = 32;
40static INDENT_SPACES: usize = 4;
41
42/// Pretty visualization of GroveDB components.
43pub trait Visualize {
44    /// Visualize
45    fn visualize<W: Write>(&self, drawer: Drawer<W>) -> Result<Drawer<W>>;
46}
47
48/// Wrapper struct with a `Debug` implementation to represent bytes vector in
49/// human-friendly way.
50#[derive(PartialOrd, Ord, PartialEq, Eq, Hash)]
51pub struct DebugBytes(pub Vec<u8>);
52
53impl fmt::Debug for DebugBytes {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        let mut v = Vec::new();
56        visualize_to_vec(&mut v, self.0.as_slice());
57
58        f.write_str(&String::from_utf8_lossy(&v))
59    }
60}
61
62/// Wrapper struct with a `Debug` implementation to represent vector of bytes
63/// vectors in human-friendly way.
64#[derive(PartialOrd, Ord, PartialEq, Eq, Hash)]
65pub struct DebugByteVectors(pub Vec<Vec<u8>>);
66
67impl fmt::Debug for DebugByteVectors {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        let mut v = Vec::new();
70        let mut drawer = Drawer::new(&mut v);
71
72        drawer.write(b"[ ").expect("write to a vector");
73
74        for v in self.0.iter() {
75            drawer = v.visualize(drawer).expect("write to a vector");
76            drawer.write(b", ").expect("write to a vector");
77        }
78
79        drawer.write(b" ]").expect("write to a vector");
80
81        f.write_str(&String::from_utf8_lossy(&v))
82    }
83}
84
85/// A `io::Write` proxy to prepend padding and symbols to draw trees
86pub struct Drawer<W: Write> {
87    level: usize,
88    write: W,
89}
90
91impl<W: Write> Drawer<W> {
92    /// New
93    pub fn new(write: W) -> Self {
94        Drawer { level: 0, write }
95    }
96
97    /// Down
98    pub fn down(&mut self) {
99        self.level += 1;
100    }
101
102    /// Up
103    pub fn up(&mut self) {
104        self.level -= 1;
105    }
106
107    /// Write
108    pub fn write(&mut self, buf: &[u8]) -> Result<()> {
109        let lines_iter = buf.split(|c| *c == b'\n');
110        let sep = if self.level > 0 {
111            let mut result = " ".repeat(INDENT_SPACES * self.level - 1);
112            result.insert(0, '\n');
113            result
114        } else {
115            String::new()
116        };
117        let interspersed_lines_iter = Itertools::intersperse(lines_iter, sep.as_bytes());
118        for line in interspersed_lines_iter {
119            self.write.write_all(line)?;
120        }
121        Ok(())
122    }
123
124    /// Flush
125    pub fn flush(&mut self) -> Result<()> {
126        self.write.write_all(b"\n")?;
127        self.write.flush()?;
128        Ok(())
129    }
130}
131
132/// To hex
133pub fn to_hex(bytes: &[u8]) -> String {
134    let encoded = hex::encode(bytes);
135    let remaining = encoded.len().saturating_sub(HEX_LEN);
136    if remaining >= 8 {
137        format!("{}..{}", &encoded[0..HEX_LEN], &encoded[remaining..])
138    } else {
139        encoded
140    }
141}
142
143impl Visualize for [u8] {
144    fn visualize<'a, W: Write>(&self, mut drawer: Drawer<W>) -> Result<Drawer<W>> {
145        let hex_repr = to_hex(self);
146        let str_repr = String::from_utf8(self.to_vec());
147        drawer.write(format!("[hex: {hex_repr}").as_bytes())?;
148        if let Ok(str_repr) = str_repr {
149            let str_part = if str_repr.len() > STR_LEN {
150                &str_repr[..=STR_LEN]
151            } else {
152                &str_repr
153            };
154            drawer.write(format!(", str: {str_part}").as_bytes())?;
155        }
156        drawer.write(b"]")?;
157        Ok(drawer)
158    }
159}
160
161impl Visualize for Vec<u8> {
162    fn visualize<W: Write>(&self, drawer: Drawer<W>) -> Result<Drawer<W>> {
163        self.as_slice().visualize(drawer)
164    }
165}
166
167impl<T: Visualize + ?Sized> Visualize for &T {
168    fn visualize<'a, W: Write>(&self, drawer: Drawer<W>) -> Result<Drawer<W>> {
169        (*self).visualize(drawer)
170    }
171}
172
173impl<T: Visualize> Visualize for Option<T> {
174    fn visualize<'a, W: Write>(&self, mut drawer: Drawer<W>) -> Result<Drawer<W>> {
175        Ok(if let Some(v) = self {
176            v.visualize(drawer)?
177        } else {
178            drawer.write(b"None")?;
179            drawer
180        })
181    }
182}
183
184/// `visualize` shortcut to write straight into stderr offhand
185pub fn visualize_stderr<T: Visualize + ?Sized>(value: &T) {
186    let mut out = std::io::stderr();
187    let drawer = Drawer::new(&mut out);
188    value
189        .visualize(drawer)
190        .expect("IO error when trying to `visualize`");
191}
192
193/// `visualize` shortcut to write straight into stdout offhand
194pub fn visualize_stdout<T: Visualize + ?Sized>(value: &T) {
195    let mut out = std::io::stdout();
196    let drawer = Drawer::new(&mut out);
197    value
198        .visualize(drawer)
199        .expect("IO error when trying to `visualize`");
200}
201
202/// `visualize` shortcut to write into provided buffer, should be a `Vec` not a
203/// slice because slices won't grow if needed.
204pub fn visualize_to_vec<T: Visualize + ?Sized>(v: &mut Vec<u8>, value: &T) {
205    let drawer = Drawer::new(v);
206    value
207        .visualize(drawer)
208        .expect("error while writing into slice");
209}
210
211#[cfg(test)]
212mod tests {
213    use std::io::{Error, ErrorKind, Write};
214
215    use super::{
216        to_hex, visualize_stderr, visualize_stdout, visualize_to_vec, DebugByteVectors, DebugBytes,
217        Drawer, Visualize,
218    };
219
220    fn visualized<T: Visualize + ?Sized>(value: &T) -> String {
221        let mut out = Vec::new();
222        visualize_to_vec(&mut out, value);
223        String::from_utf8(out).expect("visualization is utf8")
224    }
225
226    #[derive(Default)]
227    struct RecordingWriter {
228        buf: Vec<u8>,
229        flushes: usize,
230    }
231
232    impl Write for RecordingWriter {
233        fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
234            self.buf.extend_from_slice(data);
235            Ok(data.len())
236        }
237
238        fn flush(&mut self) -> std::io::Result<()> {
239            self.flushes += 1;
240            Ok(())
241        }
242    }
243
244    struct FailWriteWriter;
245
246    impl Write for FailWriteWriter {
247        fn write(&mut self, _data: &[u8]) -> std::io::Result<usize> {
248            Err(Error::other("write failure"))
249        }
250
251        fn flush(&mut self) -> std::io::Result<()> {
252            Ok(())
253        }
254    }
255
256    #[derive(Default)]
257    struct FailFlushWriter(Vec<u8>);
258
259    impl Write for FailFlushWriter {
260        fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
261            self.0.extend_from_slice(data);
262            Ok(data.len())
263        }
264
265        fn flush(&mut self) -> std::io::Result<()> {
266            Err(Error::other("flush failure"))
267        }
268    }
269
270    struct AlwaysErrVisualize;
271
272    impl Visualize for AlwaysErrVisualize {
273        fn visualize<W: Write>(&self, _drawer: Drawer<W>) -> std::io::Result<Drawer<W>> {
274            Err(Error::other("visualize failure"))
275        }
276    }
277
278    #[test]
279    fn drawer_write_respects_indentation_levels() {
280        let mut writer = RecordingWriter::default();
281        let mut drawer = Drawer::new(&mut writer);
282        drawer.write(b"a\nb").expect("write at root level");
283        drawer.down();
284        drawer.write(b"\nc\nd").expect("write at level 1");
285        drawer.down();
286        drawer.write(b"\ne").expect("write at level 2");
287        drawer.up();
288        drawer.write(b"\nf").expect("write after up");
289
290        let got = String::from_utf8(writer.buf).expect("valid utf8");
291        assert_eq!(got, "ab\n   c\n   d\n       e\n   f");
292    }
293
294    #[test]
295    fn drawer_write_propagates_inner_write_errors() {
296        let mut drawer = Drawer::new(FailWriteWriter);
297        let err = drawer
298            .write(b"data")
299            .expect_err("must propagate writer error");
300        assert_eq!(err.kind(), ErrorKind::Other);
301    }
302
303    #[test]
304    fn drawer_flush_writes_trailing_newline_then_flushes() {
305        let mut writer = RecordingWriter::default();
306        let mut drawer = Drawer::new(&mut writer);
307        drawer.write(b"line").expect("write");
308        drawer.flush().expect("flush");
309
310        assert_eq!(String::from_utf8(writer.buf).expect("utf8"), "line\n");
311        assert_eq!(writer.flushes, 1);
312    }
313
314    #[test]
315    fn drawer_flush_propagates_flush_errors() {
316        let mut drawer = Drawer::new(FailFlushWriter::default());
317        let err = drawer.flush().expect_err("must propagate flush error");
318        assert_eq!(err.kind(), ErrorKind::Other);
319    }
320
321    #[test]
322    fn to_hex_returns_full_for_short_values() {
323        assert_eq!(to_hex(b""), "");
324        assert_eq!(to_hex(b"abc"), "616263");
325        assert_eq!(to_hex(&[1, 2, 3, 4, 5, 6, 7]), "01020304050607");
326    }
327
328    #[test]
329    fn to_hex_shortens_long_values() {
330        let bytes: Vec<u8> = (0..8).collect();
331        assert_eq!(to_hex(&bytes), "00010203..04050607");
332    }
333
334    #[test]
335    fn bytes_visualize_with_utf8_includes_string_part() {
336        let got = visualized(&b"hello"[..]);
337        assert_eq!(got, "[hex: 68656c6c6f, str: hello]");
338    }
339
340    #[test]
341    fn bytes_visualize_truncates_long_utf8_string() {
342        let input = vec![b'x'; 40];
343        let got = visualized(input.as_slice());
344        assert_eq!(
345            got,
346            format!(
347                "[hex: {}..{}, str: {}]",
348                "78787878",
349                "78787878",
350                "x".repeat(33)
351            )
352        );
353    }
354
355    #[test]
356    fn bytes_visualize_non_utf8_omits_string_part() {
357        let got = visualized(&[0xff, 0xfe, 0xfd][..]);
358        assert_eq!(got, "[hex: fffefd]");
359    }
360
361    #[test]
362    fn vec_and_reference_visualize_delegate_correctly() {
363        let vec_value = vec![1u8, 2, 3];
364        assert_eq!(
365            visualized(&vec_value),
366            "[hex: 010203, str: \u{1}\u{2}\u{3}]"
367        );
368
369        let slice: &[u8] = b"ab";
370        let reference = &slice;
371        assert_eq!(visualized(&reference), "[hex: 6162, str: ab]");
372    }
373
374    #[test]
375    fn option_visualize_handles_some_and_none() {
376        let some = Some(vec![0xabu8, 0xcdu8]);
377        let none: Option<Vec<u8>> = None;
378        assert_eq!(visualized(&some), "[hex: abcd]");
379        assert_eq!(visualized(&none), "None");
380    }
381
382    #[test]
383    fn debug_bytes_formats_using_visualization() {
384        let value = DebugBytes(b"test".to_vec());
385        assert_eq!(format!("{value:?}"), "[hex: 74657374, str: test]");
386    }
387
388    #[test]
389    fn debug_byte_vectors_formats_collection_with_elements() {
390        let value = DebugByteVectors(vec![b"a".to_vec(), vec![0xff]]);
391        assert_eq!(format!("{value:?}"), "[ [hex: 61, str: a], [hex: ff],  ]");
392    }
393
394    #[test]
395    fn debug_byte_vectors_formats_empty_collection() {
396        let value = DebugByteVectors(Vec::new());
397        assert_eq!(format!("{value:?}"), "[  ]");
398    }
399
400    #[test]
401    fn visualize_stdout_and_stderr_do_not_panic_for_valid_values() {
402        visualize_stdout(&b"ok"[..]);
403        visualize_stderr(&b"ok"[..]);
404    }
405
406    #[test]
407    #[should_panic(expected = "error while writing into slice")]
408    fn visualize_to_vec_panics_when_visualize_returns_error() {
409        let mut out = Vec::new();
410        visualize_to_vec(&mut out, &AlwaysErrVisualize);
411    }
412
413    #[test]
414    #[should_panic(expected = "IO error when trying to `visualize`")]
415    fn visualize_stdout_panics_when_visualize_returns_error() {
416        visualize_stdout(&AlwaysErrVisualize);
417    }
418
419    #[test]
420    #[should_panic(expected = "IO error when trying to `visualize`")]
421    fn visualize_stderr_panics_when_visualize_returns_error() {
422        visualize_stderr(&AlwaysErrVisualize);
423    }
424}