serde-hex 0.1.0

Hexadecimal encoding/decoding with serde.
Documentation
//! macros which are only useful inside of this crate.

/// Implement `SerHex` for a unsigned integer with a size equivalent
/// to `$bytes`.  Currently just offloads conversion to the appropriately
/// sized byte-array logic, and then does a endianness-aware transmute to
/// the target type.  TODO: benchmark this and determine if it is worth
/// writing a custom impl instead.
macro_rules! impl_serhex_uint {
    ($type: ty, $bytes: expr) => {
        impl_serhex_seq!($type, $bytes);
        impl<C> $crate::SerHex<C> for $type
        where
            C: $crate::HexConf,
        {
            type Error = $crate::types::Error;
            fn into_hex_raw<D>(&self, mut dst: D) -> ::std::result::Result<(), Self::Error>
            where
                D: ::std::io::Write,
            {
                let bytes: [u8; $bytes] = unsafe { ::std::mem::transmute(self.to_be()) };
                into_hex_bytearray!(bytes, dst, $bytes)?;
                Ok(())
            }
            fn from_hex_raw<S>(src: S) -> ::std::result::Result<Self, Self::Error>
            where
                S: AsRef<[u8]>,
            {
                let rslt: ::std::result::Result<[u8; $bytes], Self::Error> =
                    from_hex_bytearray!(src, $bytes);
                match rslt {
                    Ok(buf) => {
                        let val: $type = unsafe { ::std::mem::transmute(buf) };
                        Ok(Self::from_be(val))
                    }
                    Err(e) => Err(e),
                }
            }
        }
    };
}

/// implement `SerHexSeq` for an array of elements which implement
/// `SerHexSeq`.
macro_rules! impl_serhex_seq_array {
    ($conf:ty,$len:expr) => {
        impl<T, E> $crate::SerHexSeq<$conf> for [T; $len]
        where
            E: From<$crate::types::Error> + ::std::error::Error,
            T: $crate::SerHexSeq<$conf>
                + $crate::SerHex<$crate::Strict, Error = E>
                + $crate::SerHex<$crate::StrictPfx, Error = E>
                + $crate::SerHex<$crate::StrictCap, Error = E>
                + $crate::SerHex<$crate::StrictCapPfx, Error = E>,
        {
            fn size() -> usize {
                <T as $crate::SerHexSeq<$conf>>::size() * $len
            }
        }
    };
}

/// generate a blanket impl for a strict variant of `SerHex` for
/// an any array of `[T;$len]` where `T` meets the trait bound:
/// `SerHex<Strict> + SerHex<StrictCap>`.  this macro is invoked
/// by the `impl_serhex_strict_array` macro for all `Strict`
/// variants, so prefer that macro over calling this one directly.
/// *TODO*: the `from_hex_raw` impl generated by this macro takes
/// twice as long for the worst `Err` case as it does for the `Ok`
/// case.  This needs to be updated.
macro_rules! impl_serhex_strictconf_array {
    ($conf:ty,$len:expr) => {
        impl_serhex_seq_array!($conf, $len);

        impl<T, E> $crate::SerHex<$conf> for [T; $len]
        where
            E: From<$crate::types::Error> + ::std::error::Error,
            T: $crate::SerHex<$crate::Strict, Error = E>
                + $crate::SerHex<$crate::StrictPfx, Error = E>
                + $crate::SerHex<$crate::StrictCap, Error = E>
                + $crate::SerHex<$crate::StrictCapPfx, Error = E>,
        {
            type Error = E;

            fn into_hex_raw<D>(&self, mut dst: D) -> Result<(), Self::Error>
            where
                D: io::Write,
            {
                let src: &[T] = self.as_ref();
                debug_assert!(src.len() == $len);
                let mut items = src.iter();
                // first element is serialized with `$conf` to allow prefixing if specified.
                // plus this has the handy side-effect of preventing impls of `Compact` variants,
                // since they are not part of the constraints on `T`.
                match items.next() {
                    Some(itm) => <T as $crate::SerHex<$conf>>::into_hex_raw(itm, &mut dst)?,
                    None => {
                        // should only happen in the `[T;0]` case.
                        debug_assert!($len == 0);
                        return Ok(());
                    }
                }
                if <$conf as $crate::HexConf>::withcap() {
                    for itm in items {
                        <T as SerHex<$crate::StrictCap>>::into_hex_raw(&itm, &mut dst)?;
                    }
                } else {
                    for itm in items {
                        <T as SerHex<$crate::Strict>>::into_hex_raw(&itm, &mut dst)?;
                    }
                }
                Ok(())
            }

            fn from_hex_raw<S>(src: S) -> Result<Self, Self::Error>
            where
                S: AsRef<[u8]>,
            {
                let raw: &[u8] = src.as_ref();
                let hex = if <$conf as $crate::HexConf>::withpfx() {
                    let pfx = "0x".as_bytes();
                    if raw.starts_with(pfx) {
                        &raw[2..]
                    } else {
                        raw
                    }
                } else {
                    raw
                };
                // get iterator over chunks of expected size.  the underlying
                // `SerHex<Strict>` implementation must raise an appropriate
                // error if chunks are not of the proper size.
                let chunks = hex.chunks(hex.len() / $len);
                let values =
                    chunks.filter_map(
                        |chunk| match <T as $crate::SerHex<$crate::Strict>>::from_hex(chunk) {
                            Ok(val) => Some(val),
                            Err(_) => None,
                        },
                    );
                match array_init::from_iter(values) {
                    Some(rslt) => Ok(rslt),
                    None => {
                        // TODO: currently just repeats above process until
                        // error is found.  this is a workaround for the fact
                        // that `aray_init::from_iter` consumes the iterator.
                        // need to find a better workaround.
                        let chunks = hex.chunks(hex.len() / $len);
                        let mut errors = chunks.filter_map(|chunk| {
                            match <T as $crate::SerHex<$crate::Strict>>::from_hex(chunk) {
                                Ok(_) => None,
                                Err(e) => Some(e),
                            }
                        });
                        if let Some(err) = errors.next() {
                            Err(err.into())
                        } else {
                            let expect = $len;
                            let actual = hex.len() / $len;
                            let inner = $crate::types::ParseHexError::Size { expect, actual };
                            let error = $crate::types::Error::from(inner);
                            Err(error.into())
                        }
                    }
                }
            }
        }
    };
}

/// generate blanket impls for all strict variants of `SerHex` for
/// an any array of `[T;$len]` where `T: SerHex`.  We only implement
/// strict variants since the implementation assumes no framing/separators
/// (meaning that separation must be knowable by offset).  If you would
/// like to Serialize/Deserialize to arrays of non-strict hex, this is
/// best handled elsewhere; `SerHex` is intended to operate on types
/// representable as contiguous hex.
macro_rules! impl_serhex_strict_array {
    ( $($len: expr),+ ) => {
        $(
            impl_serhex_strictconf_array!($crate::Strict,$len);
            impl_serhex_strictconf_array!($crate::StrictPfx,$len);
            impl_serhex_strictconf_array!($crate::StrictCap,$len);
            impl_serhex_strictconf_array!($crate::StrictCapPfx,$len);
        )+
    }
}