use super::{ConversionError, DecodeHexError, byte_char_to_digit};
use crate::stable_hash;
use core::fmt::{Debug, Display, Formatter, Write};
use core::ops::{Deref, DerefMut};
use serde::Deserialize;
use std::str::FromStr;
#[cfg(feature = "ethereum")]
mod sealed {
use super::*;
use ethereum_types::{H32, H64, H128, H160, H256, H264, H512, H520};
macro_rules! impl_conversion {
($($name: ident: $size: expr),+$(,)?) => {
$(
impl From<$name> for H<$size> {
fn from(h: $name) -> Self {
Self(h.0)
}
}
impl From<H<$size>> for $name {
fn from(h: H<$size>) -> Self {
Self(h.0)
}
}
)*
};
}
impl_conversion!(
H32: 4,
H64: 8,
H128: 16,
H160: 20,
H256: 32,
H264: 33,
H512: 64,
H520: 65,
);
}
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct H<const N: usize>([u8; N]);
impl<const N: usize> Default for H<N> {
fn default() -> Self {
Self([0; N])
}
}
impl<const N: usize> From<[u8; N]> for H<N> {
fn from(array: [u8; N]) -> Self {
Self::new(array)
}
}
impl<const N: usize> From<H<N>> for [u8; N] {
fn from(h: H<N>) -> Self {
h.into_inner()
}
}
impl<const N: usize> From<H<N>> for Vec<u8> {
fn from(h: H<N>) -> Self {
h.into_inner().into()
}
}
impl<const N: usize> TryFrom<&[u8]> for H<N> {
type Error = ConversionError;
fn try_from(source: &[u8]) -> Result<Self, Self::Error> {
if source.len() != N {
Err(ConversionError::WrongSize {
expected: N,
actual: source.len(),
})
} else {
let mut output: [u8; N] = [0; N];
output.copy_from_slice(source);
Ok(Self(output))
}
}
}
impl<const N: usize> FromStr for H<N> {
type Err = DecodeHexError;
fn from_str(mut s: &str) -> Result<Self, Self::Err> {
let extra_len = if s.starts_with("0x") || s.starts_with("0X") {
s = &s[2..];
2
} else {
0
};
if s.len() != N * 2 {
return Err(DecodeHexError::WrongSize {
expected: N * 2 + extra_len,
actual: s.len(),
});
}
let mut output = Self::default();
for (chars, byte) in s.as_bytes().chunks_exact(2).zip(output.iter_mut()) {
let (l, r) = (chars[0] as char, chars[1] as char);
match (l.to_digit(16), r.to_digit(16)) {
(Some(l), Some(r)) => *byte = (l as u8) << 4 | r as u8,
(_, _) => return Err(DecodeHexError::WrongCharacter(l, r)),
};
}
Ok(output)
}
}
impl<const N: usize> H<N> {
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub const fn zero() -> Self {
Self::new([0; N])
}
pub const fn new(array: [u8; N]) -> Self {
Self(array)
}
pub const fn into_inner(self) -> [u8; N] {
self.0
}
pub const fn into_option(self) -> Option<Self> {
let mut i = 0;
loop {
if self.0[i] != 0 {
return Some(self);
}
i += 1;
if i == N {
return None;
}
}
}
pub const fn from_hex_str(s: &str) -> Self {
let s_bytes = s.as_bytes();
if s_bytes.len() & 1 == 1 {
panic!("odd length str");
}
let skip_bytes = if s_bytes[0] == b'0' && (s_bytes[1] == b'x' || s_bytes[1] == b'X') {
2
} else {
0
};
if N * 2 + skip_bytes != s_bytes.len() {
panic!("Invalid string length");
}
let mut bytes = [0_u8; N];
let mut count = 0;
while count < N {
let offset = count * 2 + skip_bytes;
let left = s_bytes[offset];
let right = s_bytes[offset + 1];
bytes[count] = byte_char_to_digit(left) << 4 | byte_char_to_digit(right);
count += 1;
}
Self::new(bytes)
}
pub fn to_hex_string(&self) -> String {
let mut output = String::with_capacity(N * 2 + 2);
write!(output, "{self:?}").unwrap();
output
}
pub fn is_zero(&self) -> bool {
self.0.iter().all(|v| *v == 0)
}
pub fn friendly_name() -> String {
format!("H{}", N * 8)
}
}
impl<const N: usize> Debug for H<N> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "0x")?;
for v in self.0 {
write!(f, "{:02x}", v)?;
}
Ok(())
}
}
impl<const N: usize> Display for H<N> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "0x")?;
if N > 8 {
for v in &self.0[..4] {
write!(f, "{:02x}", v)?;
}
write!(f, "..")?;
for v in &self.0[self.0.len() - 4..] {
write!(f, "{:02x}", v)?;
}
} else {
for v in self.0 {
write!(f, "{:02x}", v)?;
}
}
Ok(())
}
}
impl<const N: usize> ::core::fmt::Binary for H<N> {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
for byte in self.0 {
write!(f, "{:08b}", byte)?;
}
Ok(())
}
}
impl<const N: usize> ::core::fmt::LowerHex for H<N> {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
for byte in self.0 {
write!(f, "{:02x}", byte)?;
}
Ok(())
}
}
impl<const N: usize> ::core::fmt::UpperHex for H<N> {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
for byte in self.0 {
write!(f, "{:02X}", byte)?;
}
Ok(())
}
}
impl<const N: usize> AsRef<[u8]> for H<N> {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<const N: usize> AsMut<[u8]> for H<N> {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
impl<const N: usize> AsRef<[u8; N]> for H<N> {
fn as_ref(&self) -> &[u8; N] {
&self.0
}
}
impl<const N: usize> AsMut<[u8; N]> for H<N> {
fn as_mut(&mut self) -> &mut [u8; N] {
&mut self.0
}
}
impl<const N: usize> DerefMut for H<N> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<const N: usize> Deref for H<N> {
type Target = [u8; N];
fn deref(&self) -> &Self::Target {
&self.0
}
}
mod sql {
use super::*;
use sqlx::{
Decode, Encode, Postgres, Type,
postgres::{PgHasArrayType, PgTypeInfo},
};
impl<const N: usize> Type<Postgres> for H<N> {
fn type_info() -> PgTypeInfo {
<[u8] as sqlx::Type<Postgres>>::type_info()
}
fn compatible(ty: &PgTypeInfo) -> bool {
<[u8] as sqlx::Type<Postgres>>::compatible(ty)
}
}
impl<'a, const N: usize> Encode<'a, Postgres> for H<N> {
fn encode_by_ref(
&self,
buf: &mut <Postgres as sqlx::Database>::ArgumentBuffer<'a>,
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync + 'static>> {
<&[u8] as sqlx::Encode<Postgres>>::encode_by_ref(&self.as_slice(), buf)
}
}
impl<'a, const N: usize> Decode<'a, Postgres> for H<N> {
fn decode(
value: <Postgres as sqlx::Database>::ValueRef<'a>,
) -> Result<Self, sqlx::error::BoxDynError> {
let decoded = <&'a [u8] as sqlx::Decode<Postgres>>::decode(value)?;
Ok(Self::try_from(decoded)?)
}
}
impl<const N: usize> PgHasArrayType for H<N> {
fn array_type_info() -> PgTypeInfo {
<Vec<u8> as PgHasArrayType>::array_type_info()
}
}
}
mod graphql {
use super::*;
use std::borrow::Cow;
impl<const N: usize> async_graphql::OutputType for H<N> {
fn type_name() -> std::borrow::Cow<'static, str> {
Self::friendly_name().into()
}
fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
registry.create_output_type::<Self, _>(async_graphql::registry::MetaTypeId::Scalar, |_| {
async_graphql::registry::MetaType::Scalar {
name: Self::type_name().to_string(),
description: None,
visible: None,
is_valid: None,
specified_by_url: None,
inaccessible: false,
tags: vec![],
directive_invocations: vec![],
}
})
}
async fn resolve(
&self,
_ctx: &async_graphql::ContextSelectionSet<'_>,
_field: &async_graphql::Positioned<async_graphql::parser::types::Field>,
) -> async_graphql::ServerResult<async_graphql::Value> {
Ok(if N == 0 {
async_graphql::Value::String("0x0".into())
} else {
async_graphql::Value::String(self.to_hex_string())
})
}
}
impl<const N: usize> async_graphql::InputType for H<N> {
type RawValueType = Self;
fn type_name() -> Cow<'static, str> {
Self::friendly_name().into()
}
fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
registry.create_input_type::<Self, _>(async_graphql::registry::MetaTypeId::Scalar, |_| {
async_graphql::registry::MetaType::Scalar {
name: Self::type_name().to_string(),
description: None,
visible: None,
is_valid: None,
specified_by_url: None,
inaccessible: false,
tags: vec![],
directive_invocations: vec![],
}
})
}
fn parse(value: Option<async_graphql::Value>) -> async_graphql::InputValueResult<Self> {
match value.unwrap_or_default() {
async_graphql::Value::String(v) => v
.parse::<Self>()
.map_err(async_graphql::InputValueError::custom),
async_graphql::Value::Binary(bytes) => {
Self::try_from(&*bytes).map_err(async_graphql::InputValueError::custom)
}
_ => Err(async_graphql::InputValueError::custom(
"Only supports hex strings or byte arrays",
)),
}
}
fn to_value(&self) -> async_graphql::Value {
if N == 0 {
async_graphql::Value::String("0x0".into())
} else {
async_graphql::Value::String(self.to_hex_string())
}
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
Some(self)
}
}
}
impl<const N: usize> serde::Serialize for H<N> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
let size = N * 2 + 2;
super::TRANSIENT_STRING_SERIALIZER.with(|s| {
let mut s = s.borrow_mut();
s.clear();
s.reserve(size);
write!(s, "{:?}", self).unwrap();
s.serialize(serializer)
})
} else {
self.0.serialize(serializer)
}
}
}
impl<'de, const N: usize> Deserialize<'de> for H<N> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
if deserializer.is_human_readable() {
let s = <&str>::deserialize(deserializer)?;
s.parse::<Self>().map_err(D::Error::custom)
} else {
let x = <&[u8]>::deserialize(deserializer)?;
Self::try_from(x).map_err(D::Error::custom)
}
}
}
impl<const N: usize> stable_hash::StableHash for H<N> {
fn stable_hash<H: stable_hash::StableHasher>(&self, field_address: H::Addr, state: &mut H) {
stable_hash::utils::AsBytes(self.0.as_ref()).stable_hash(field_address, state);
}
}