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            // Preserve the historical 33-byte preview, ending before any
150            // character that would cross its byte boundary.
151            let str_part = &str_repr[..str_repr.floor_char_boundary(STR_LEN + 1)];
152            drawer.write(format!(", str: {str_part}").as_bytes())?;
153        }
154        drawer.write(b"]")?;
155        Ok(drawer)
156    }
157}
158
159impl Visualize for Vec<u8> {
160    fn visualize<W: Write>(&self, drawer: Drawer<W>) -> Result<Drawer<W>> {
161        self.as_slice().visualize(drawer)
162    }
163}
164
165impl<T: Visualize + ?Sized> Visualize for &T {
166    fn visualize<'a, W: Write>(&self, drawer: Drawer<W>) -> Result<Drawer<W>> {
167        (*self).visualize(drawer)
168    }
169}
170
171impl<T: Visualize> Visualize for Option<T> {
172    fn visualize<'a, W: Write>(&self, mut drawer: Drawer<W>) -> Result<Drawer<W>> {
173        Ok(if let Some(v) = self {
174            v.visualize(drawer)?
175        } else {
176            drawer.write(b"None")?;
177            drawer
178        })
179    }
180}
181
182/// `visualize` shortcut to write straight into stderr offhand
183pub fn visualize_stderr<T: Visualize + ?Sized>(value: &T) {
184    let mut out = std::io::stderr();
185    let drawer = Drawer::new(&mut out);
186    value
187        .visualize(drawer)
188        .expect("IO error when trying to `visualize`");
189}
190
191/// `visualize` shortcut to write straight into stdout offhand
192pub fn visualize_stdout<T: Visualize + ?Sized>(value: &T) {
193    let mut out = std::io::stdout();
194    let drawer = Drawer::new(&mut out);
195    value
196        .visualize(drawer)
197        .expect("IO error when trying to `visualize`");
198}
199
200/// `visualize` shortcut to write into provided buffer, should be a `Vec` not a
201/// slice because slices won't grow if needed.
202pub fn visualize_to_vec<T: Visualize + ?Sized>(v: &mut Vec<u8>, value: &T) {
203    let drawer = Drawer::new(v);
204    value
205        .visualize(drawer)
206        .expect("error while writing into slice");
207}
208
209#[cfg(test)]
210mod tests {
211    use std::io::{Error, ErrorKind, Write};
212
213    use super::{
214        to_hex, visualize_stderr, visualize_stdout, visualize_to_vec, DebugByteVectors, DebugBytes,
215        Drawer, Visualize,
216    };
217
218    fn visualized<T: Visualize + ?Sized>(value: &T) -> String {
219        let mut out = Vec::new();
220        visualize_to_vec(&mut out, value);
221        String::from_utf8(out).expect("visualization is utf8")
222    }
223
224    #[derive(Default)]
225    struct RecordingWriter {
226        buf: Vec<u8>,
227        flushes: usize,
228    }
229
230    impl Write for RecordingWriter {
231        fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
232            self.buf.extend_from_slice(data);
233            Ok(data.len())
234        }
235
236        fn flush(&mut self) -> std::io::Result<()> {
237            self.flushes += 1;
238            Ok(())
239        }
240    }
241
242    struct FailWriteWriter;
243
244    impl Write for FailWriteWriter {
245        fn write(&mut self, _data: &[u8]) -> std::io::Result<usize> {
246            Err(Error::other("write failure"))
247        }
248
249        fn flush(&mut self) -> std::io::Result<()> {
250            Ok(())
251        }
252    }
253
254    #[derive(Default)]
255    struct FailFlushWriter(Vec<u8>);
256
257    impl Write for FailFlushWriter {
258        fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
259            self.0.extend_from_slice(data);
260            Ok(data.len())
261        }
262
263        fn flush(&mut self) -> std::io::Result<()> {
264            Err(Error::other("flush failure"))
265        }
266    }
267
268    struct AlwaysErrVisualize;
269
270    impl Visualize for AlwaysErrVisualize {
271        fn visualize<W: Write>(&self, _drawer: Drawer<W>) -> std::io::Result<Drawer<W>> {
272            Err(Error::other("visualize failure"))
273        }
274    }
275
276    #[test]
277    fn drawer_write_respects_indentation_levels() {
278        let mut writer = RecordingWriter::default();
279        let mut drawer = Drawer::new(&mut writer);
280        drawer.write(b"a\nb").expect("write at root level");
281        drawer.down();
282        drawer.write(b"\nc\nd").expect("write at level 1");
283        drawer.down();
284        drawer.write(b"\ne").expect("write at level 2");
285        drawer.up();
286        drawer.write(b"\nf").expect("write after up");
287
288        let got = String::from_utf8(writer.buf).expect("valid utf8");
289        assert_eq!(got, "ab\n   c\n   d\n       e\n   f");
290    }
291
292    #[test]
293    fn drawer_write_propagates_inner_write_errors() {
294        let mut drawer = Drawer::new(FailWriteWriter);
295        let err = drawer
296            .write(b"data")
297            .expect_err("must propagate writer error");
298        assert_eq!(err.kind(), ErrorKind::Other);
299    }
300
301    #[test]
302    fn drawer_flush_writes_trailing_newline_then_flushes() {
303        let mut writer = RecordingWriter::default();
304        let mut drawer = Drawer::new(&mut writer);
305        drawer.write(b"line").expect("write");
306        drawer.flush().expect("flush");
307
308        assert_eq!(String::from_utf8(writer.buf).expect("utf8"), "line\n");
309        assert_eq!(writer.flushes, 1);
310    }
311
312    #[test]
313    fn drawer_flush_propagates_flush_errors() {
314        let mut drawer = Drawer::new(FailFlushWriter::default());
315        let err = drawer.flush().expect_err("must propagate flush error");
316        assert_eq!(err.kind(), ErrorKind::Other);
317    }
318
319    #[test]
320    fn to_hex_returns_full_for_short_values() {
321        assert_eq!(to_hex(b""), "");
322        assert_eq!(to_hex(b"abc"), "616263");
323        assert_eq!(to_hex(&[1, 2, 3, 4, 5, 6, 7]), "01020304050607");
324    }
325
326    #[test]
327    fn to_hex_shortens_long_values() {
328        let bytes: Vec<u8> = (0..8).collect();
329        assert_eq!(to_hex(&bytes), "00010203..04050607");
330    }
331
332    #[test]
333    fn bytes_visualize_with_utf8_includes_string_part() {
334        let got = visualized(&b"hello"[..]);
335        assert_eq!(got, "[hex: 68656c6c6f, str: hello]");
336    }
337
338    #[test]
339    fn bytes_visualize_truncates_long_utf8_string() {
340        let input = vec![b'x'; 40];
341        let got = visualized(input.as_slice());
342        assert_eq!(
343            got,
344            format!(
345                "[hex: {}..{}, str: {}]",
346                "78787878",
347                "78787878",
348                "x".repeat(33)
349            )
350        );
351    }
352
353    #[test]
354    fn bytes_visualize_non_utf8_omits_string_part() {
355        let got = visualized(&[0xff, 0xfe, 0xfd][..]);
356        assert_eq!(got, "[hex: fffefd]");
357    }
358
359    #[test]
360    fn bytes_visualize_preserves_complete_utf8_characters_at_preview_boundary() {
361        let cases = [
362            ("é".repeat(16), "é".repeat(16)),
363            ("x".repeat(33), "x".repeat(33)),
364            ("é".repeat(17), "é".repeat(16)),
365            (format!("{}é", "a".repeat(32)), "a".repeat(32)),
366            (format!("{}界", "a".repeat(31)), "a".repeat(31)),
367            (format!("{}🦀", "a".repeat(30)), "a".repeat(30)),
368            (format!("{}x", "界".repeat(11)), "界".repeat(11)),
369        ];
370        for (input, preview) in cases {
371            assert_eq!(
372                visualized(input.as_bytes()),
373                format!("[hex: {}, str: {preview}]", to_hex(input.as_bytes()))
374            );
375        }
376    }
377
378    #[test]
379    fn debug_wrappers_handle_multibyte_path_segments() {
380        let bytes = "é".repeat(17).into_bytes();
381        let expected = format!("[hex: {}, str: {}]", to_hex(&bytes), "é".repeat(16));
382        assert_eq!(format!("{:?}", DebugBytes(bytes.clone())), expected);
383        assert_eq!(
384            format!("{:?}", DebugByteVectors(vec![bytes, vec![0xff; 34]])),
385            format!("[ {expected}, [hex: ffffffff..ffffffff],  ]")
386        );
387    }
388
389    #[test]
390    fn vec_and_reference_visualize_delegate_correctly() {
391        let vec_value = vec![1u8, 2, 3];
392        assert_eq!(
393            visualized(&vec_value),
394            "[hex: 010203, str: \u{1}\u{2}\u{3}]"
395        );
396
397        let slice: &[u8] = b"ab";
398        let reference = &slice;
399        assert_eq!(visualized(&reference), "[hex: 6162, str: ab]");
400    }
401
402    #[test]
403    fn option_visualize_handles_some_and_none() {
404        let some = Some(vec![0xabu8, 0xcdu8]);
405        let none: Option<Vec<u8>> = None;
406        assert_eq!(visualized(&some), "[hex: abcd]");
407        assert_eq!(visualized(&none), "None");
408    }
409
410    #[test]
411    fn debug_bytes_formats_using_visualization() {
412        let value = DebugBytes(b"test".to_vec());
413        assert_eq!(format!("{value:?}"), "[hex: 74657374, str: test]");
414    }
415
416    #[test]
417    fn debug_byte_vectors_formats_collection_with_elements() {
418        let value = DebugByteVectors(vec![b"a".to_vec(), vec![0xff]]);
419        assert_eq!(format!("{value:?}"), "[ [hex: 61, str: a], [hex: ff],  ]");
420    }
421
422    #[test]
423    fn debug_byte_vectors_formats_empty_collection() {
424        let value = DebugByteVectors(Vec::new());
425        assert_eq!(format!("{value:?}"), "[  ]");
426    }
427
428    #[test]
429    fn visualize_stdout_and_stderr_do_not_panic_for_valid_values() {
430        visualize_stdout(&b"ok"[..]);
431        visualize_stderr(&b"ok"[..]);
432    }
433
434    #[test]
435    #[should_panic(expected = "error while writing into slice")]
436    fn visualize_to_vec_panics_when_visualize_returns_error() {
437        let mut out = Vec::new();
438        visualize_to_vec(&mut out, &AlwaysErrVisualize);
439    }
440
441    #[test]
442    #[should_panic(expected = "IO error when trying to `visualize`")]
443    fn visualize_stdout_panics_when_visualize_returns_error() {
444        visualize_stdout(&AlwaysErrVisualize);
445    }
446
447    #[test]
448    #[should_panic(expected = "IO error when trying to `visualize`")]
449    fn visualize_stderr_panics_when_visualize_returns_error() {
450        visualize_stderr(&AlwaysErrVisualize);
451    }
452}