array-bytes 6.0.0

A collection of array/bytes/hex utilities.
Documentation
#![allow(clippy::tabs_in_doc_comments)]
#![deny(missing_docs)]
#![no_std]

//! A collection of array/bytes/hex utilities.
//!
//! Completely optimized for blockchain development.
//! Especially the Substrate.

extern crate alloc;

#[cfg(test)] mod test;

// core
use core::{convert::TryInto, mem, result::Result as CoreResult};
// alloc
use alloc::{string::String, vec::Vec};
// crates.io
#[cfg(feature = "serde")] use serde::{de::Error as DeError, Deserialize, Deserializer};
// use thiserror::Error as ThisError;

/// The main result of array-bytes.
pub type Result<T> = CoreResult<T, Error>;

/// Simple and safe `T`/[`AsRef<str>`] conversions that may fail in a controlled way under some
/// circumstances.
pub trait TryFromHex
where
	Self: Sized,
{
	/// Try to convert the [`Self`] from hex.
	fn try_from_hex<H>(hex: H) -> Result<Self>
	where
		H: AsRef<str>;
}

macro_rules! impl_num_from_hex {
	($t:ty) => {
		impl TryFromHex for $t {
			fn try_from_hex<H>(hex: H) -> Result<Self>
			where
				H: AsRef<str>,
			{
				let hex = strip_0x(hex.as_ref());

				Self::from_str_radix(hex, 16).map_err(Error::ParseIntError)
			}
		}
	};
}
impl_num_from_hex!(isize);
impl_num_from_hex!(i8);
impl_num_from_hex!(i16);
impl_num_from_hex!(i32);
impl_num_from_hex!(i64);
impl_num_from_hex!(i128);
impl_num_from_hex!(usize);
impl_num_from_hex!(u8);
impl_num_from_hex!(u16);
impl_num_from_hex!(u32);
impl_num_from_hex!(u64);
impl_num_from_hex!(u128);

/// The main error of array-bytes.
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
	/// The length must not be odd.
	InvalidLength,
	/// Found the invalid character at `index`.
	InvalidCharacter {
		/// The invalid character.
		character: char,
		/// The invalid character's index.
		index: usize,
	},
	/// The data can not fit the array/slice length well.
	MismatchedLength {
		/// Expected length.
		expect: usize,
	},
	/// Failed to parse the hex number from hex string.
	ParseIntError(core::num::ParseIntError),
}

/// `&[T]` to `[T; N]`.
///
/// # Examples
/// ```
/// assert_eq!(array_bytes::slice2array::<8, _>(&[0; 8]), Ok([0; 8]));
/// ```
pub fn slice2array<const N: usize, T>(slice: &[T]) -> Result<[T; N]>
where
	T: Copy,
{
	slice.try_into().map_err(|_| Error::MismatchedLength { expect: N })
}

/// Just like [`slice2array`] but without the checking.
///
/// # Examples
/// ```
/// assert_eq!(array_bytes::slice2array_unchecked::<8, _>(&[0; 8]), [0; 8]);
/// ```
pub fn slice2array_unchecked<const N: usize, T>(slice: &[T]) -> [T; N]
where
	T: Copy,
{
	slice2array(slice).unwrap()
}

/// Convert `&[T]` to a type directly.
///
/// # Examples
/// ```
/// #[derive(Debug, PartialEq)]
/// struct LJF([u8; 17]);
/// impl From<[u8; 17]> for LJF {
/// 	fn from(array: [u8; 17]) -> Self {
/// 		Self(array)
/// 	}
/// }
///
/// assert_eq!(
/// 	array_bytes::slice_n_into::<17, u8, LJF>(b"Love Jane Forever"),
/// 	Ok(LJF(*b"Love Jane Forever"))
/// );
/// ```
pub fn slice_n_into<const N: usize, T, V>(slice: &[T]) -> Result<V>
where
	T: Copy,
	V: From<[T; N]>,
{
	Ok(slice2array(slice)?.into())
}

/// Just like [`slice_n_into`] but without the checking.
///
/// # Examples
/// ```
/// #[derive(Debug, PartialEq)]
/// struct LJF([u8; 17]);
/// impl From<[u8; 17]> for LJF {
/// 	fn from(array: [u8; 17]) -> Self {
/// 		Self(array)
/// 	}
/// }
///
/// assert_eq!(
/// 	array_bytes::slice_n_into_unchecked::<17, u8, LJF>(b"Love Jane Forever"),
/// 	LJF(*b"Love Jane Forever")
/// );
/// ```
pub fn slice_n_into_unchecked<const N: usize, T, V>(slice: &[T]) -> V
where
	T: Copy,
	V: From<[T; N]>,
{
	slice2array_unchecked(slice).into()
}

/// [`Vec<T>`] to `[T; N]`.
///
/// # Examples
/// ```
/// assert_eq!(array_bytes::vec2array::<8, _>(vec![0; 8]), Ok([0; 8]));
/// ```
pub fn vec2array<const N: usize, T>(vec: Vec<T>) -> Result<[T; N]> {
	vec.try_into().map_err(|_| Error::MismatchedLength { expect: N })
}

/// Just like [`vec2array`] but without the checking.
///
/// # Examples
/// ```
/// assert_eq!(array_bytes::vec2array_unchecked::<8, _>(vec![0; 8]), [0; 8]);
/// ```
pub fn vec2array_unchecked<const N: usize, T>(vec: Vec<T>) -> [T; N] {
	vec2array(vec).unwrap()
}

/// Convert [`Vec<T>`] to a type directly.
///
/// # Examples
///
/// ```
/// #[derive(Debug, PartialEq)]
/// struct LJF([u8; 17]);
/// impl From<[u8; 17]> for LJF {
/// 	fn from(array: [u8; 17]) -> Self {
/// 		Self(array)
/// 	}
/// }
///
/// assert_eq!(
/// 	array_bytes::vec_n_into::<17, u8, LJF>(b"Love Jane Forever".to_vec()),
/// 	Ok(LJF(*b"Love Jane Forever"))
/// );
/// ```
pub fn vec_n_into<const N: usize, T, V>(vec: Vec<T>) -> Result<V>
where
	V: From<[T; N]>,
{
	Ok(vec2array(vec)?.into())
}

/// Just like [`vec_n_into`] but without the checking.
///
/// # Examples
/// ```
/// #[derive(Debug, PartialEq)]
/// struct LJF([u8; 17]);
/// impl From<[u8; 17]> for LJF {
/// 	fn from(array: [u8; 17]) -> Self {
/// 		Self(array)
/// 	}
/// }
///
/// assert_eq!(
/// 	array_bytes::vec_n_into_unchecked::<17, u8, LJF>(b"Love Jane Forever".to_vec()),
/// 	LJF(*b"Love Jane Forever")
/// );
/// ```
pub fn vec_n_into_unchecked<const N: usize, T, V>(vec: Vec<T>) -> V
where
	V: From<[T; N]>,
{
	vec2array_unchecked(vec).into()
}

/// Convert hex bytes to hex string.
///
/// This is useful when you are interacting with the IO.
///
/// # Examples
/// ```
/// assert_eq!(
/// 	array_bytes::hex_bytes2hex_str(b"0x4c6f7665204a616e6520466f7265766572"),
/// 	Ok("0x4c6f7665204a616e6520466f7265766572"),
/// );
/// ```
pub fn hex_bytes2hex_str(bytes: &[u8]) -> Result<&str> {
	for (i, byte) in bytes.iter().enumerate().skip(if bytes.starts_with(b"0x") { 2 } else { 0 }) {
		if !is_hex_ascii(byte) {
			Err(Error::InvalidCharacter { character: *byte as _, index: i })?;
		}
	}

	Ok(unsafe {
		// Validated in previous step, never fails here; qed.
		#[allow(clippy::transmute_bytes_to_str)]
		mem::transmute(bytes)
	})
}

/// Just like [`hex_bytes2hex_str`] but without the checking.
///
/// # Safety
/// See the [`mem::transmute`].
///
/// # Examples
/// ```
/// unsafe {
/// 	assert_eq!(
/// 		array_bytes::hex_bytes2hex_str_unchecked(b"0x4c6f7665204a616e6520466f7265766572"),
/// 		"0x4c6f7665204a616e6520466f7265766572",
/// 	);
/// }
/// ```
pub unsafe fn hex_bytes2hex_str_unchecked(bytes: &[u8]) -> &str {
	#[allow(clippy::transmute_bytes_to_str)]
	mem::transmute(bytes)
}

/// `AsRef<[u8]>` to [`String`].
///
/// # Examples
/// ```
/// assert_eq!(
/// 	array_bytes::bytes2hex("0x", b"Love Jane Forever"),
/// 	String::from("0x4c6f7665204a616e6520466f7265766572")
/// );
/// ```
pub fn bytes2hex<B>(prefix: &str, bytes: B) -> String
where
	B: AsRef<[u8]>,
{
	let bytes = bytes.as_ref();
	let mut hex = String::with_capacity(prefix.len() + bytes.len() * 2);

	prefix.chars().for_each(|byte| hex.push(byte));
	bytes.iter().for_each(|byte| {
		hex.push(char::from_digit((byte >> 4) as _, 16).unwrap());
		hex.push(char::from_digit((byte & 0xf) as _, 16).unwrap());
	});

	hex
}

/// Just like [`hex2bytes`] but to a fixed length array.
///
/// # Examples
/// ```
/// assert_eq!(
/// 	array_bytes::hex2array("0x4c6f7665204a616e6520466f7265766572"),
/// 	Ok(*b"Love Jane Forever")
/// );
/// ```
pub fn hex2array<H, const N: usize>(hex: H) -> Result<[u8; N]>
where
	H: AsRef<[u8]>,
{
	vec2array(hex2bytes(hex.as_ref())?)
}

/// Just like [`hex2array`] but without the checking.
///
/// # Examples
/// ```
/// assert_eq!(
/// 	array_bytes::hex2array_unchecked("0x4c6f7665204a616e6520466f7265766572"),
/// 	*b"Love Jane Forever"
/// );
/// ```
pub fn hex2array_unchecked<H, const N: usize>(hex: H) -> [u8; N]
where
	H: AsRef<[u8]>,
{
	hex2bytes_unchecked(hex).try_into().unwrap()
}

/// `AsRef<[u8]>` to [`Vec<u8>`].
///
/// Return error if:
/// - length is odd
/// - encounter invalid hex ascii
///
/// # Examples
/// ```
/// assert_eq!(
/// 	array_bytes::hex2bytes("0x4c6f7665204a616e6520466f7265766572"),
/// 	Ok(b"Love Jane Forever".to_vec())
/// );
/// ```
pub fn hex2bytes<H>(hex: H) -> Result<Vec<u8>>
where
	H: AsRef<[u8]>,
{
	let hex = strip_0x_bytes(hex.as_ref());

	if hex.len() % 2 != 0 {
		Err(Error::InvalidLength)?;
	}

	let mut bytes = Vec::new();

	for i in (0..hex.len()).step_by(2) {
		bytes.push(hex2byte((&hex[i], i), (&hex[i + 1], i + 1))?);
	}

	Ok(bytes)
}

/// Just like [`hex2bytes`] but without checking.
///
/// # Examples
/// ```
/// assert_eq!(
/// 	array_bytes::hex2bytes_unchecked("0x4c6f7665204a616e6520466f7265766572"),
/// 	*b"Love Jane Forever"
/// );
/// ```
pub fn hex2bytes_unchecked<H>(hex: H) -> Vec<u8>
where
	H: AsRef<[u8]>,
{
	let hex = strip_0x_bytes(hex.as_ref());

	(0..hex.len()).step_by(2).map(|i| hex2byte_unchecked(&hex[i], &hex[i + 1])).collect()
}

/// `AsRef<[u8]>` to `&[u8]`.
///
/// This function will modify the given slice's source and return the revised result.
///
/// Return error if:
/// - length is odd
/// - encounter invalid hex ascii
/// - mismatched slice size
///
/// # Examples
/// ```
/// assert_eq!(
/// 	array_bytes::hex2bytes("0x4c6f7665204a616e6520466f7265766572"),
/// 	Ok(b"Love Jane Forever".to_vec())
/// );
/// ```
pub fn hex2slice<H>(hex: H, slice: &mut [u8]) -> Result<&[u8]>
where
	H: AsRef<[u8]>,
{
	let hex = strip_0x_bytes(hex.as_ref());

	if hex.len() % 2 != 0 {
		Err(Error::InvalidLength)?;
	}

	let expected_len = hex.len() >> 1;

	if expected_len != slice.len() {
		Err(Error::MismatchedLength { expect: expected_len })?;
	}

	for (byte, i) in slice.iter_mut().zip((0..hex.len()).step_by(2)) {
		*byte = hex2byte((&hex[i], i), (&hex[i + 1], i + 1))?;
	}

	Ok(slice)
}

/// Just like [`hex2slice`] but without checking.
///
/// # Examples
/// ```
/// assert_eq!(
/// 	array_bytes::hex2bytes("0x4c6f7665204a616e6520466f7265766572"),
/// 	Ok(b"Love Jane Forever".to_vec())
/// );
/// ```
pub fn hex2slice_unchecked<H>(hex: H, slice: &mut [u8]) -> &[u8]
where
	H: AsRef<[u8]>,
{
	let hex = strip_0x_bytes(hex.as_ref());

	slice
		.iter_mut()
		.zip((0..hex.len()).step_by(2))
		.for_each(|(byte, i)| *byte = hex2byte_unchecked(&hex[i], &hex[i + 1]));

	slice
}

/// Try to convert `AsRef<[u8]>` to `T` directly, where `T: From<Vec<u8>>`.
///
/// # Examples
/// ```
/// #[derive(Debug, PartialEq)]
/// struct LJF(Vec<u8>);
/// impl From<Vec<u8>> for LJF {
/// 	fn from(vec: Vec<u8>) -> Self {
/// 		Self(vec)
/// 	}
/// }
///
/// assert_eq!(
/// 	array_bytes::hex_into::<_, LJF>("0x4c6f7665204a616e6520466f7265766572"),
/// 	Ok(LJF(b"Love Jane Forever".to_vec()))
/// );
/// ```
pub fn hex_into<H, T>(hex: H) -> Result<T>
where
	H: AsRef<[u8]>,
	T: From<Vec<u8>>,
{
	Ok(hex2bytes(hex.as_ref())?.into())
}

/// Just like [`hex_into`] but without the checking.
///
/// # Examples
/// ```
/// #[derive(Debug, PartialEq)]
/// struct LJF(Vec<u8>);
/// impl From<Vec<u8>> for LJF {
/// 	fn from(vec: Vec<u8>) -> Self {
/// 		Self(vec)
/// 	}
/// }
///
/// assert_eq!(
/// 	array_bytes::hex_into_unchecked::<_, LJF>("0x4c6f7665204a616e6520466f7265766572"),
/// 	LJF(b"Love Jane Forever".to_vec())
/// );
/// ```
pub fn hex_into_unchecked<H, T>(hex: H) -> T
where
	H: AsRef<[u8]>,
	T: From<Vec<u8>>,
{
	hex2bytes_unchecked(hex).into()
}

/// Try to convert `AsRef<[u8]>` to `T` directly, where `T: From<[u8; N]>`.
///
/// # Examples
/// ```
/// #[derive(Debug, PartialEq)]
/// struct LJF([u8; 17]);
/// impl From<[u8; 17]> for LJF {
/// 	fn from(array: [u8; 17]) -> Self {
/// 		Self(array)
/// 	}
/// }
///
/// assert_eq!(
/// 	array_bytes::hex_n_into::<_, LJF, 17>("0x4c6f7665204a616e6520466f7265766572"),
/// 	Ok(LJF(*b"Love Jane Forever"))
/// );
/// ```
pub fn hex_n_into<H, T, const N: usize>(hex: H) -> Result<T>
where
	H: AsRef<[u8]>,
	T: From<[u8; N]>,
{
	Ok(hex2array(hex)?.into())
}

/// Just like [`hex_n_into`] but without the checking.
///
/// # Examples
/// ```
/// #[derive(Debug, PartialEq)]
/// struct LJF([u8; 17]);
/// impl From<[u8; 17]> for LJF {
/// 	fn from(array: [u8; 17]) -> Self {
/// 		Self(array)
/// 	}
/// }
///
/// assert_eq!(
/// 	array_bytes::hex_n_into_unchecked::<_, LJF, 17>("0x4c6f7665204a616e6520466f7265766572"),
/// 	LJF(*b"Love Jane Forever")
/// );
/// ```
pub fn hex_n_into_unchecked<H, T, const N: usize>(hex: H) -> T
where
	H: AsRef<[u8]>,
	T: From<[u8; N]>,
{
	hex2array_unchecked(hex).into()
}

/// Deserialize hex to `T`, where `T: From<Vec<u8>>`.
///
/// # Examples
/// ```
/// use serde::Deserialize;
///
/// #[derive(Debug, PartialEq)]
/// struct LJF(Vec<u8>);
/// impl From<Vec<u8>> for LJF {
/// 	fn from(vec: Vec<u8>) -> Self {
/// 		Self(vec)
/// 	}
/// }
///
/// #[derive(Debug, PartialEq, Deserialize)]
/// struct WrappedLJF {
/// 	#[serde(deserialize_with = "array_bytes::hex_deserialize_into")]
/// 	ljf: LJF,
/// }
///
/// assert_eq!(
/// 	serde_json::from_str::<WrappedLJF>(r#"{
/// 		"ljf": "0x4c6f7665204a616e6520466f7265766572"
/// 	}"#).unwrap(),
/// 	WrappedLJF {
/// 		ljf: LJF(b"Love Jane Forever".to_vec())
/// 	}
/// );
#[cfg(feature = "serde")]
pub fn hex_deserialize_into<'de, D, T>(hex: D) -> CoreResult<T, D::Error>
where
	D: Deserializer<'de>,
	T: From<Vec<u8>>,
{
	Ok(hex2bytes_unchecked(<&str>::deserialize(hex)?).into())
}

/// Deserialize hex to `T`, where `T: From<[u8; N]>`.
///
/// # Examples
/// ```
/// use serde::Deserialize;
///
/// #[derive(Debug, PartialEq)]
/// struct LJF([u8; 17]);
/// impl From<[u8; 17]> for LJF {
/// 	fn from(array: [u8; 17]) -> Self {
/// 		Self(array)
/// 	}
/// }
///
/// #[derive(Debug, PartialEq, Deserialize)]
/// struct WrappedLJF {
/// 	#[serde(deserialize_with = "array_bytes::hex_deserialize_n_into")]
/// 	ljf: LJF,
/// }
///
/// assert_eq!(
/// 	serde_json::from_str::<WrappedLJF>(r#"{
/// 		"ljf": "0x4c6f7665204a616e6520466f7265766572"
/// 	}"#).unwrap(),
/// 	WrappedLJF {
/// 		ljf: LJF(*b"Love Jane Forever")
/// 	}
/// );
#[cfg(feature = "serde")]
pub fn hex_deserialize_n_into<'de, D, T, const N: usize>(hex: D) -> CoreResult<T, D::Error>
where
	D: Deserializer<'de>,
	T: From<[u8; N]>,
{
	Ok(hex2array_unchecked(<&str>::deserialize(hex)?).into())
}

/// Deserialize hex to any Rust primitive num types.
///
/// # Examples
/// ```
/// use serde::Deserialize;
///
/// #[derive(Debug, PartialEq, Deserialize)]
/// struct LJF {
/// 	#[serde(deserialize_with = "array_bytes::de_hex2num")]
/// 	_0: u8,
/// 	#[serde(deserialize_with = "array_bytes::de_hex2num")]
/// 	_1: u8,
/// 	#[serde(deserialize_with = "array_bytes::de_hex2num")]
/// 	_2: u8,
/// 	#[serde(deserialize_with = "array_bytes::de_hex2num")]
/// 	_3: u32,
/// }
///
/// assert_eq!(
/// 	serde_json::from_str::<LJF>(
/// 		r#"{
/// 		"_0": "0x5",
/// 		"_1": "0x2",
/// 		"_2": "0x0",
/// 		"_3": "0x522"
/// 	}"#
/// 	)
/// 	.unwrap(),
/// 	LJF { _0: 5, _1: 2, _2: 0, _3: 1314 }
/// );
/// ```
#[cfg(feature = "serde")]
pub fn de_hex2num<'de, D, T>(hex: D) -> CoreResult<T, D::Error>
where
	D: Deserializer<'de>,
	T: TryFromHex,
{
	let hex = <&str>::deserialize(hex)?;

	T::try_from_hex(hex).map_err(|_| D::Error::custom(alloc::format!("Invalid hex str `{}`", hex)))
}

/// Deserialize hex to [`Vec<u8>`].
///
/// # Examples
/// ```
/// use serde::Deserialize;
///
/// #[derive(Debug, PartialEq, Deserialize)]
/// struct LJF {
/// 	#[serde(deserialize_with = "array_bytes::de_hex2bytes")]
/// 	ljf: Vec<u8>,
/// }
///
/// assert_eq!(
/// 	serde_json::from_str::<LJF>(
/// 		r#"{
/// 		"ljf": "0x4c6f7665204a616e6520466f7265766572"
/// 	}"#
/// 	)
/// 	.unwrap(),
/// 	LJF { ljf: (*b"Love Jane Forever").to_vec() }
/// );
/// ```
#[cfg(feature = "serde")]
pub fn de_hex2bytes<'de, D>(hex: D) -> CoreResult<Vec<u8>, D::Error>
where
	D: Deserializer<'de>,
{
	let hex = <&str>::deserialize(hex)?;

	hex2bytes(hex).map_err(|_| D::Error::custom(alloc::format!("Invalid hex str `{}`", hex)))
}

fn strip_0x(hex: &str) -> &str {
	if let Some(hex) = hex.strip_prefix("0x") {
		hex
	} else {
		hex
	}
}

fn strip_0x_bytes(hex: &[u8]) -> &[u8] {
	if let Some(hex) = hex.strip_prefix(b"0x") {
		hex
	} else {
		hex
	}
}

fn is_hex_ascii(byte: &u8) -> bool {
	// Convert to lowercase.
	let byte = byte | 0b10_0000;

	matches!(byte, b'0'..=b'9' | b'a'..=b'f')
}

fn hex_ascii2digit(hex_ascii: &u8) -> Option<u8> {
	// Convert to lowercase.
	let hex_ascii = hex_ascii | 0b10_0000;

	match hex_ascii {
		b'0'..=b'9' => Some(hex_ascii - b'0'),
		b'a'..=b'f' => Some(hex_ascii - b'a' + 10),
		_ => None,
	}
}

fn hex2byte(hex_ascii_1: (&u8, usize), hex_ascii_2: (&u8, usize)) -> Result<u8> {
	let byte = hex_ascii2digit(hex_ascii_1.0)
		.ok_or(Error::InvalidCharacter { character: *hex_ascii_1.0 as _, index: hex_ascii_1.1 })?
		<< 4 | hex_ascii2digit(hex_ascii_2.0)
		.ok_or(Error::InvalidCharacter { character: *hex_ascii_2.0 as _, index: hex_ascii_2.1 })?;

	Ok(byte)
}

fn hex2byte_unchecked(hex_ascii_1: &u8, hex_ascii_2: &u8) -> u8 {
	hex_ascii2digit(hex_ascii_1).unwrap() << 4 | hex_ascii2digit(hex_ascii_2).unwrap()
}