1#![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
42pub trait Visualize {
44 fn visualize<W: Write>(&self, drawer: Drawer<W>) -> Result<Drawer<W>>;
46}
47
48#[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#[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
85pub struct Drawer<W: Write> {
87 level: usize,
88 write: W,
89}
90
91impl<W: Write> Drawer<W> {
92 pub fn new(write: W) -> Self {
94 Drawer { level: 0, write }
95 }
96
97 pub fn down(&mut self) {
99 self.level += 1;
100 }
101
102 pub fn up(&mut self) {
104 self.level -= 1;
105 }
106
107 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 pub fn flush(&mut self) -> Result<()> {
126 self.write.write_all(b"\n")?;
127 self.write.flush()?;
128 Ok(())
129 }
130}
131
132pub 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
184pub 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
193pub 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
202pub 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}