format-bytes 0.4.0

A macro to format bytestrings
Documentation
use format_bytes::{DisplayBytes, Utf8, Visit, join};
use std::io;

/// Only what is required
#[derive(Default)]
struct Minimal(Vec<String>);

impl Visit for Minimal {
    fn bytes(&mut self, value: &[u8]) -> io::Result<()> {
        self.0.push(format!("bytes {:?}", value));
        Ok(())
    }

    fn str(&mut self, value: &str) -> io::Result<()> {
        self.0.push(format!("str {}", value));
        Ok(())
    }
}

/// Keeps types, and sequences as such
#[derive(Debug, PartialEq)]
enum Value {
    Bytes(Vec<u8>),
    Str(String),
    Int(i128),
    Uint(u128),
    Seq(Vec<Value>),
}

#[derive(Default)]
struct Typed(Vec<Value>);

impl Visit for Typed {
    fn bytes(&mut self, value: &[u8]) -> io::Result<()> {
        self.0.push(Value::Bytes(value.to_vec()));
        Ok(())
    }

    fn str(&mut self, value: &str) -> io::Result<()> {
        self.0.push(Value::Str(value.to_owned()));
        Ok(())
    }

    fn int(&mut self, value: i128) -> io::Result<()> {
        self.0.push(Value::Int(value));
        Ok(())
    }

    fn uint(&mut self, value: u128) -> io::Result<()> {
        self.0.push(Value::Uint(value));
        Ok(())
    }

    fn seq(
        &mut self,
        items: &[&dyn DisplayBytes],
        _separator: &dyn DisplayBytes,
    ) -> io::Result<()> {
        let items = items.iter().flat_map(|item| typed(item)).collect();
        self.0.push(Value::Seq(items));
        Ok(())
    }
}

fn typed(value: &dyn DisplayBytes) -> Vec<Value> {
    let mut visitor = Typed::default();
    value.visit(&mut visitor).unwrap();
    visitor.0
}

fn minimal(value: &dyn DisplayBytes) -> Vec<String> {
    let mut visitor = Minimal::default();
    value.visit(&mut visitor).unwrap();
    visitor.0
}

#[test]
fn types() {
    assert_eq!(typed(b"caf\xe9"), [Value::Bytes(b"caf\xe9".to_vec())]);
    assert_eq!(typed(&Utf8("é")), [Value::Str("é".to_owned())]);
    assert_eq!(typed(&-1_i8), [Value::Int(-1)]);
    assert_eq!(typed(&u64::MAX), [Value::Uint(u64::MAX.into())]);
    assert_eq!(typed(&0.5_f64), [Value::Str("0.5".to_owned())]);
    assert_eq!(typed(&Box::new(&&3_usize)), [Value::Uint(3)]);
}

#[test]
fn numbers_display_as_usual() {
    assert_eq!(minimal(&-1_i8), ["str -1"]);
    assert_eq!(minimal(&u8::MAX), ["str 255"]);
}

/// Not overriding `visit` gives the output of `display_bytes`
#[test]
fn default_visit() {
    struct Point(u8, u8);

    impl DisplayBytes for Point {
        fn display_bytes(&self, out: &mut dyn io::Write) -> io::Result<()> {
            format_bytes::write_bytes!(out, "{}x{}", self.0, self.1)
        }
    }

    assert_eq!(typed(&Point(1, 2)), [Value::Bytes(b"1x2".to_vec())]);
}

#[test]
fn sequences() {
    let paths: [&[u8]; 3] = [b"a", b"caf\xe9", b"b, c"];
    assert_eq!(
        typed(&join(paths, b", ")),
        [Value::Seq(vec![
            Value::Bytes(b"a".to_vec()),
            Value::Bytes(b"caf\xe9".to_vec()),
            Value::Bytes(b"b, c".to_vec()),
        ])]
    );
    assert_eq!(
        minimal(&join([1, 2], b" and ")),
        [format!("bytes {:?}", b"1 and 2")]
    );
}