Skip to main content

ic_query/icrc/model/
subaccount.rs

1//! Module: icrc::model::subaccount
2//!
3//! Responsibility: validate and normalize ICRC subaccount hex values.
4//! Does not own: command parsing, account construction, or report fields.
5//! Boundary: preserves absent values or produces exactly 32 validated bytes and canonical hex.
6
7use super::IcrcError;
8use crate::hex::hex_bytes;
9
10/// Validates and normalizes a 32-byte ICRC subaccount hex string.
11pub fn normalize_subaccount_hex(value: &str) -> Result<String, IcrcError> {
12    let bytes = subaccount_bytes_from_hex(value)?;
13    Ok(hex_bytes(&bytes))
14}
15
16#[cfg(feature = "host")]
17pub(in crate::icrc) fn normalize_optional_subaccount_hex(
18    value: Option<&str>,
19) -> Result<Option<String>, IcrcError> {
20    value.map(normalize_subaccount_hex).transpose()
21}
22
23pub(in crate::icrc) fn subaccount_bytes_from_hex(value: &str) -> Result<Vec<u8>, IcrcError> {
24    let value = value.trim();
25    if !value.len().is_multiple_of(2) {
26        return Err(IcrcError::InvalidSubaccountHex {
27            reason: "hex string must contain an even number of characters".to_string(),
28        });
29    }
30    let bytes = (0..value.len())
31        .step_by(2)
32        .map(|index| {
33            u8::from_str_radix(&value[index..index + 2], 16).map_err(|err| {
34                IcrcError::InvalidSubaccountHex {
35                    reason: err.to_string(),
36                }
37            })
38        })
39        .collect::<Result<Vec<_>, _>>()?;
40    if bytes.len() != 32 {
41        return Err(IcrcError::InvalidSubaccountLength { bytes: bytes.len() });
42    }
43    Ok(bytes)
44}