use alloc::string::{String, ToString};
use alloc::sync::Arc;
use core::fmt::Display;
use core::str::FromStr;
use crate::account::name_validation::{self, NameValidationError};
use crate::account::storage::slot::StorageSlotId;
use crate::errors::StorageSlotNameError;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct StorageSlotName {
name: Arc<str>,
id: StorageSlotId,
}
impl StorageSlotName {
pub(crate) const MIN_NUM_COMPONENTS: usize = name_validation::MIN_NUM_COMPONENTS;
pub(crate) const MAX_LENGTH: usize = name_validation::MAX_LENGTH;
pub fn new(name: impl Into<Arc<str>>) -> Result<Self, StorageSlotNameError> {
let name: Arc<str> = name.into();
Self::validate(&name)?;
let id = StorageSlotId::from_str(&name);
Ok(Self { name, id })
}
pub fn as_str(&self) -> &str {
&self.name
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> u8 {
debug_assert!(self.name.len() <= Self::MAX_LENGTH);
self.name.len() as u8
}
pub fn id(&self) -> StorageSlotId {
self.id
}
const fn validate(name: &str) -> Result<(), StorageSlotNameError> {
match name_validation::validate(name) {
Ok(()) => Ok(()),
Err(NameValidationError::TooShort) => Err(StorageSlotNameError::TooShort),
Err(NameValidationError::TooLong) => Err(StorageSlotNameError::TooLong),
Err(NameValidationError::UnexpectedColon) => Err(StorageSlotNameError::UnexpectedColon),
Err(NameValidationError::UnexpectedUnderscore) => {
Err(StorageSlotNameError::UnexpectedUnderscore)
},
Err(NameValidationError::InvalidCharacter) => {
Err(StorageSlotNameError::InvalidCharacter)
},
}
}
}
impl Ord for StorageSlotName {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.id().cmp(&other.id())
}
}
impl PartialOrd for StorageSlotName {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Display for StorageSlotName {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for StorageSlotName {
type Err = StorageSlotNameError;
fn from_str(string: &str) -> Result<Self, Self::Err> {
StorageSlotName::new(string)
}
}
impl TryFrom<&str> for StorageSlotName {
type Error = StorageSlotNameError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
value.parse()
}
}
impl TryFrom<String> for StorageSlotName {
type Error = StorageSlotNameError;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.parse()
}
}
impl From<StorageSlotName> for String {
fn from(slot_name: StorageSlotName) -> Self {
slot_name.name.to_string()
}
}
impl Serializable for StorageSlotName {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_u8(self.len());
target.write_many(self.as_str().as_bytes())
}
fn get_size_hint(&self) -> usize {
1 + self.as_str().len()
}
}
impl Deserializable for StorageSlotName {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let len = source.read_u8()?;
let name = source.read_many_iter(len as usize)?.collect::<Result<_, _>>()?;
String::from_utf8(name)
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))
.and_then(|name| {
Self::new(name).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde_slot_name() -> anyhow::Result<()> {
let slot_name = StorageSlotName::new("miden::faucet0::fungible_1::b4sic::metadata")?;
assert_eq!(slot_name, StorageSlotName::read_from_bytes(&slot_name.to_bytes())?);
Ok(())
}
#[test]
fn serde_max_length_slot_name() -> anyhow::Result<()> {
let slot_name = StorageSlotName::new(name_validation::tests::get_max_length_name())?;
assert_eq!(slot_name, StorageSlotName::read_from_bytes(&slot_name.to_bytes())?);
Ok(())
}
}