pub trait ToHexString {
    fn to_hex_string(&self) -> String;
}

impl ToHexString for [u8] {
    fn to_hex_string(&self) -> String {
        let mut s = String::new();
        for b in self.iter() {
            s.push_str(&format!("{:02x}", b));
        }
        s
    }
}

#[test]
fn test_to_hex_string() {
    let bytes = [0x01, 0x67, 0xcd];
    let test = bytes.to_hex_string();
    let expected = "0167cd";
    assert_eq!(test, expected);
}

#[doc(hidden)]
#[macro_export]
macro_rules! __impl_u8_array {
    ($name:ident) => {
        /// Shows bytes in hexadecimal.
        impl fmt::Display for $name {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                self.0.to_hex_string().fmt(f)
            }
        }

        impl AsRef<[u8]> for $name {
            fn as_ref(&self) -> &[u8] {
                &self.0
            }
        }
    };
}

// based on newtype_array crate
// https://github.com/derekdreery/newtype_array-rs/blob/master/src/lib.rs
#[doc(hidden)]
#[macro_export]
macro_rules! __impl_index {
    ($name:ident, $field:tt, $ftype:ty) => {
        impl ::std::ops::Index<usize> for $name {
            type Output = $ftype;

            fn index(&self, idx: usize) -> &$ftype {
                &self.$field[idx]
            }
        }

        impl ::std::ops::Index<::std::ops::Range<usize>> for $name {
            type Output = [$ftype];

            fn index(&self, index: ::std::ops::Range<usize>) -> &[$ftype] {
                &self.$field[index]
            }
        }

        impl ::std::ops::Index<::std::ops::RangeFrom<usize>> for $name {
            type Output = [$ftype];

            fn index(&self, index: ::std::ops::RangeFrom<usize>) -> &[$ftype] {
                &self.$field[index]
            }
        }

        impl ::std::ops::Index<::std::ops::RangeTo<usize>> for $name {
            type Output = [$ftype];

            fn index(&self, index: ::std::ops::RangeTo<usize>) -> &[$ftype] {
                &self.$field[index]
            }
        }

        impl ::std::ops::Index<::std::ops::RangeInclusive<usize>> for $name {
            type Output = [$ftype];

            fn index(&self, index: ::std::ops::RangeInclusive<usize>) -> &[$ftype] {
                &self.$field[index]
            }
        }

        impl ::std::ops::Index<::std::ops::RangeToInclusive<usize>> for $name {
            type Output = [$ftype];

            fn index(&self, index: ::std::ops::RangeToInclusive<usize>) -> &[$ftype] {
                &self.$field[index]
            }
        }

        impl ::std::ops::Index<::std::ops::RangeFull> for $name {
            type Output = [$ftype];

            fn index(&self, index: ::std::ops::RangeFull) -> &[$ftype] {
                &self.$field[index]
            }
        }
    };
}
