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 = &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
182pub 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
191pub 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
200pub 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}