use crate::{Principal, SchemaContractError, atom::deserialize_fixed_bytes};
use serde::{Deserialize, Deserializer, Serialize};
use std::fmt::{self, Display};
type SubaccountBytes = [u8; 32];
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Subaccount(SubaccountBytes);
impl Subaccount {
pub const MIN: Self = Self::from_array([0x00; 32]);
pub const MAX: Self = Self::from_array([0xFF; 32]);
#[must_use]
pub const fn to_array(&self) -> [u8; 32] {
self.0
}
#[must_use]
pub const fn from_array(array: [u8; 32]) -> Self {
Self(array)
}
pub fn from_hex(text: &str) -> Result<Self, SchemaContractError> {
let text = text.strip_prefix("0x").unwrap_or(text);
if text.len() != 64 || !text.bytes().all(|byte| byte.is_ascii_hexdigit()) {
return Err(SchemaContractError::InvalidLiteral);
}
let mut bytes = [0; 32];
for (index, pair) in text.as_bytes().as_chunks::<2>().0.iter().enumerate() {
let pair =
std::str::from_utf8(pair).map_err(|_| SchemaContractError::InvalidLiteral)?;
bytes[index] =
u8::from_str_radix(pair, 16).map_err(|_| SchemaContractError::InvalidLiteral)?;
}
Ok(Self::from_array(bytes))
}
#[must_use]
pub const fn as_slice(&self) -> &[u8] {
&self.0
}
#[must_use]
pub const fn to_bytes(self) -> [u8; 32] {
self.0
}
}
impl<'de> Deserialize<'de> for Subaccount {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
return SubaccountBytes::deserialize(deserializer).map(Self);
}
deserialize_fixed_bytes(deserializer).map(Self::from_array)
}
}
impl Display for Subaccount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in &self.0 {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
#[expect(clippy::cast_possible_truncation)]
impl From<Principal> for Subaccount {
fn from(principal: Principal) -> Self {
let mut bytes = [0u8; 32];
let p = principal.as_slice();
let len = p.len().min(31); bytes[0] = len as u8;
bytes[1..=len].copy_from_slice(&p[..len]);
Self(bytes)
}
}
impl Serialize for Subaccount {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
self.0.serialize(serializer)
} else {
serializer.serialize_bytes(&self.0)
}
}
}
#[cfg(test)]
mod tests;