Skip to main content

miden_protocol/account/storage/slot/
slot_name.rs

1use alloc::string::{String, ToString};
2use alloc::sync::Arc;
3use core::fmt::Display;
4use core::str::FromStr;
5
6use crate::account::name_validation::{self, NameValidationError};
7use crate::account::storage::slot::StorageSlotId;
8use crate::errors::StorageSlotNameError;
9use crate::utils::serde::{
10    ByteReader,
11    ByteWriter,
12    Deserializable,
13    DeserializationError,
14    Serializable,
15};
16
17/// The name of an account storage slot.
18///
19/// A typical slot name looks like this:
20///
21/// ```text
22/// miden::standards::fungible_faucets::metadata
23/// ```
24///
25/// The double-colon (`::`) serves as a separator and the strings in between the separators are
26/// called components.
27///
28/// It is generally recommended that slot names have at least three components and follow this
29/// structure:
30///
31/// ```text
32/// project_name::component_name::slot_name
33/// ```
34///
35/// ## Requirements
36///
37/// For a string to be a valid slot name it needs to satisfy the following criteria:
38/// - Its length must be less than 255.
39/// - It needs to have at least 2 components.
40/// - Each component must consist of at least one character.
41/// - Each component must only consist of the characters `a` to `z`, `A` to `Z`, `0` to `9` or `_`
42///   (underscore).
43/// - Each component must not start with an underscore.
44#[derive(Debug, Clone, PartialEq, Eq, Hash)]
45pub struct StorageSlotName {
46    name: Arc<str>,
47    id: StorageSlotId,
48}
49
50impl StorageSlotName {
51    // CONSTANTS
52    // --------------------------------------------------------------------------------------------
53
54    /// The minimum number of components that a slot name must contain.
55    pub(crate) const MIN_NUM_COMPONENTS: usize = name_validation::MIN_NUM_COMPONENTS;
56
57    /// The maximum number of characters in a slot name.
58    pub(crate) const MAX_LENGTH: usize = name_validation::MAX_LENGTH;
59
60    // CONSTRUCTORS
61    // --------------------------------------------------------------------------------------------
62
63    /// Constructs a new [`StorageSlotName`] from a string.
64    ///
65    /// # Errors
66    ///
67    /// Returns an error if:
68    /// - the slot name is invalid (see the type-level docs for the requirements).
69    pub fn new(name: impl Into<Arc<str>>) -> Result<Self, StorageSlotNameError> {
70        let name: Arc<str> = name.into();
71        Self::validate(&name)?;
72        let id = StorageSlotId::from_str(&name);
73        Ok(Self { name, id })
74    }
75
76    // ACCESSORS
77    // --------------------------------------------------------------------------------------------
78
79    /// Returns the slot name as a string slice.
80    pub fn as_str(&self) -> &str {
81        &self.name
82    }
83
84    /// Returns the slot name as a string slice.
85    // allow is_empty to be missing because it would always return false since slot names are
86    // enforced to have a length greater than zero, so it does not have much use.
87    #[allow(clippy::len_without_is_empty)]
88    pub fn len(&self) -> u8 {
89        // SAFETY: Slot name validation should enforce length fits into a u8.
90        debug_assert!(self.name.len() <= Self::MAX_LENGTH);
91        self.name.len() as u8
92    }
93
94    /// Returns the [`StorageSlotId`] derived from the slot name.
95    pub fn id(&self) -> StorageSlotId {
96        self.id
97    }
98
99    // HELPERS
100    // --------------------------------------------------------------------------------------------
101
102    /// Validates a slot name against the shared name validation rules.
103    const fn validate(name: &str) -> Result<(), StorageSlotNameError> {
104        match name_validation::validate(name) {
105            Ok(()) => Ok(()),
106            Err(NameValidationError::TooShort) => Err(StorageSlotNameError::TooShort),
107            Err(NameValidationError::TooLong) => Err(StorageSlotNameError::TooLong),
108            Err(NameValidationError::UnexpectedColon) => Err(StorageSlotNameError::UnexpectedColon),
109            Err(NameValidationError::UnexpectedUnderscore) => {
110                Err(StorageSlotNameError::UnexpectedUnderscore)
111            },
112            Err(NameValidationError::InvalidCharacter) => {
113                Err(StorageSlotNameError::InvalidCharacter)
114            },
115        }
116    }
117}
118
119impl Ord for StorageSlotName {
120    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
121        self.id().cmp(&other.id())
122    }
123}
124
125impl PartialOrd for StorageSlotName {
126    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
127        Some(self.cmp(other))
128    }
129}
130
131impl Display for StorageSlotName {
132    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
133        f.write_str(self.as_str())
134    }
135}
136
137impl FromStr for StorageSlotName {
138    type Err = StorageSlotNameError;
139
140    fn from_str(string: &str) -> Result<Self, Self::Err> {
141        StorageSlotName::new(string)
142    }
143}
144
145impl TryFrom<&str> for StorageSlotName {
146    type Error = StorageSlotNameError;
147
148    fn try_from(value: &str) -> Result<Self, Self::Error> {
149        value.parse()
150    }
151}
152
153impl TryFrom<String> for StorageSlotName {
154    type Error = StorageSlotNameError;
155
156    fn try_from(value: String) -> Result<Self, Self::Error> {
157        value.parse()
158    }
159}
160
161impl From<StorageSlotName> for String {
162    fn from(slot_name: StorageSlotName) -> Self {
163        slot_name.name.to_string()
164    }
165}
166
167impl Serializable for StorageSlotName {
168    fn write_into<W: ByteWriter>(&self, target: &mut W) {
169        target.write_u8(self.len());
170        target.write_many(self.as_str().as_bytes())
171    }
172
173    fn get_size_hint(&self) -> usize {
174        // Slot name length + slot name bytes
175        1 + self.as_str().len()
176    }
177}
178
179impl Deserializable for StorageSlotName {
180    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
181        let len = source.read_u8()?;
182        let name = source.read_many_iter(len as usize)?.collect::<Result<_, _>>()?;
183        String::from_utf8(name)
184            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
185            .and_then(|name| {
186                Self::new(name).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
187            })
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    //! Note: Most tests live in crate::account::name_validation.
194
195    use super::*;
196
197    // Serialization tests
198    // --------------------------------------------------------------------------------------------
199
200    #[test]
201    fn serde_slot_name() -> anyhow::Result<()> {
202        let slot_name = StorageSlotName::new("miden::faucet0::fungible_1::b4sic::metadata")?;
203        assert_eq!(slot_name, StorageSlotName::read_from_bytes(&slot_name.to_bytes())?);
204        Ok(())
205    }
206
207    #[test]
208    fn serde_max_length_slot_name() -> anyhow::Result<()> {
209        let slot_name = StorageSlotName::new(name_validation::tests::get_max_length_name())?;
210        assert_eq!(slot_name, StorageSlotName::read_from_bytes(&slot_name.to_bytes())?);
211        Ok(())
212    }
213}