fashex 0.0.11

Hexadecimal string encoding and decoding with best-effort SIMD acceleration.
Documentation
//! [`Hex`]

use crate::buffer::{Buffer, StringBuffer};
use crate::error::InvalidInput;
use crate::{decode, encode};

trait HexPriv {}

#[allow(private_bounds, reason = "Sealed trait.")]
/// A trait for encoding / decoding a hexadecimal string (bytes) from / into the
/// raw bytes.
///
/// ```rust
/// use fashex::Hex;
/// let bytes = &[0xde, 0xad, 0xbe, 0xef];
///
/// assert_eq!(bytes.hex::<String, false>().unwrap(), "deadbeef");
/// assert_eq!(bytes.hex::<String, true>().unwrap(), "DEADBEEF");
/// ```
///
/// ```rust
/// use fashex::Hex;
/// let bytes = b"deadbeef";
///
/// assert_eq!(
///     &bytes.unhex::<Vec<u8>>().unwrap()[..],
///     &[0xde, 0xad, 0xbe, 0xef]
/// );
/// ```
pub trait Hex: HexPriv {
    #[doc(alias = "encode")]
    /// [`encode()`] the current raw bytes slice into a hexadecimal string.
    ///
    /// ## Errors
    ///
    /// 1. The target buffer type has a capacity limit, and cannot accommodate
    ///    the encoded output, like [`[u8; N]`](core::primitive).
    ///
    /// For heap-allocated string types, the encoding should never fail.
    fn hex<O: StringBuffer, const UPPER: bool>(&self) -> Result<O, InvalidInput>;

    #[doc(alias = "decode")]
    /// [`decode()`] the current hexadecimal string into raw bytes.
    ///
    /// ## Errors
    ///
    /// 1. The target buffer type has a capacity limit, and cannot accommodate
    ///    the decoded output, like [`[u8; N]`](core::primitive).
    /// 1. The input string is not a valid hexadecimal string (e.g., has an odd
    ///    length, or contains non-hex chars, etc).
    fn unhex<O: Buffer>(&self) -> Result<O, InvalidInput>;
}

impl<T: AsRef<[u8]>> HexPriv for T {}

impl<T: AsRef<[u8]>> Hex for T {
    fn hex<O: StringBuffer, const UPPER: bool>(&self) -> Result<O, InvalidInput> {
        let this = self.as_ref();

        let mut buf = O::Bytes::uninit(this.len() * 2)?;

        let written = encode::<UPPER>(this, &mut buf)?;

        #[allow(unsafe_code, reason = "XXX")]
        let buf = unsafe {
            debug_assert!(written.len() == this.len() * 2);

            O::Bytes::assume_init(buf)
        };

        #[allow(
            unsafe_code,
            reason = "Hexadecimal output contains only ASCII bytes, which are valid UTF-8."
        )]
        let buf = unsafe { O::from_utf8_unchecked(buf) };

        Ok(buf)
    }

    fn unhex<O: Buffer>(&self) -> Result<O, InvalidInput> {
        let this = self.as_ref();

        if this.len() % 2 != 0 {
            return Err(InvalidInput);
        }

        let mut buf = O::uninit(this.len() / 2)?;

        let written = decode(this, &mut buf)?;

        #[allow(unsafe_code, reason = "XXX")]
        let buf = unsafe {
            debug_assert!(written.len() == this.len() / 2);

            O::assume_init(buf)
        };

        Ok(buf)
    }
}

#[cfg(test)]
mod smoking {
    use alloc::boxed::Box;
    use alloc::rc::Rc;
    use alloc::string::String;
    use alloc::sync::Arc;
    use alloc::vec::Vec;

    use super::*;

    #[test]
    fn test_encode() {
        let bytes = &[0xde, 0xad, 0xbe, 0xef];

        macro_rules! test {
            ($ty:ty, $expected:expr) => {
                assert!(
                    bytes
                        .hex::<$ty, false>()
                        .unwrap()
                        .eq_ignore_ascii_case($expected)
                );
            };
        }

        test!([u8; 8], b"deadbeef");
        test!(Vec<u8>, b"deadbeef");
        test!(Box<[u8]>, b"deadbeef");
        test!(Arc<[u8]>, b"deadbeef");
        test!(Rc<[u8]>, b"deadbeef");
        test!(String, "deadbeef");
        test!(Box<str>, "deadbeef");
        test!(Arc<str>, "deadbeef");
        test!(Rc<str>, "deadbeef");
    }

    #[test]
    fn test_decode() {
        let hex = b"deadbeef";

        macro_rules! test {
            ($ty:ty, $expected:expr) => {
                assert_eq!(&hex.unhex::<$ty>().unwrap()[..], $expected);
            };
        }

        test!([u8; 4], b"\xde\xad\xbe\xef");
        test!(Vec<u8>, b"\xde\xad\xbe\xef");
        test!(Box<[u8]>, b"\xde\xad\xbe\xef");
        test!(Arc<[u8]>, b"\xde\xad\xbe\xef");
        test!(Rc<[u8]>, b"\xde\xad\xbe\xef");
    }
}