Skip to main content

miden_protocol/account/component/storage/
value_name.rs

1use alloc::string::{String, ToString};
2use core::cmp::Ordering;
3use core::fmt::{self, Display};
4use core::str::FromStr;
5
6use miden_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable};
7use miden_processor::DeserializationError;
8use thiserror::Error;
9
10use crate::account::StorageSlotName;
11use crate::errors::StorageSlotNameError;
12
13/// A simple wrapper type around a string key that identifies init-provided values.
14///
15/// A storage value name is a string that identifies values supplied during component
16/// instantiation (via [`InitStorageData`](super::InitStorageData)).
17///
18/// Each name is either a storage slot name, or a storage slot name with a suffixed identifier for
19/// composite types (where the suffix identifies the inner type).
20#[derive(Clone, Debug)]
21#[cfg_attr(feature = "std", derive(::serde::Deserialize, ::serde::Serialize))]
22#[cfg_attr(feature = "std", serde(try_from = "String", into = "String"))]
23pub struct StorageValueName {
24    slot_name: StorageSlotName,
25    element_field: Option<String>,
26}
27
28impl StorageValueName {
29    /// Creates a [`StorageValueName`] for the given storage slot.
30    pub fn from_slot_name(slot_name: &StorageSlotName) -> Self {
31        StorageValueName {
32            slot_name: slot_name.clone(),
33            element_field: None,
34        }
35    }
36
37    /// Creates a [`StorageValueName`] for the given storage slot and field suffix.
38    ///
39    /// A suffixed slot name is used to identify a specific field element's type in a schema
40    /// (e.g., `miden::contracts::fungible_faucets::token_metadata.max_supply` can specify the
41    /// `max_supply` element in the `token_metadata` storage slot)
42    pub fn from_slot_name_with_suffix(
43        slot_name: &StorageSlotName,
44        suffix: &str,
45    ) -> Result<StorageValueName, StorageValueNameError> {
46        Self::validate_field_segment(suffix)?;
47        Ok(StorageValueName {
48            slot_name: slot_name.clone(),
49            element_field: Some(suffix.to_string()),
50        })
51    }
52
53    /// Returns the storage slot name prefix of this value name.
54    pub fn slot_name(&self) -> &StorageSlotName {
55        &self.slot_name
56    }
57
58    /// Returns the optional field suffix of this value name.
59    pub fn field_name(&self) -> Option<&str> {
60        self.element_field.as_deref()
61    }
62
63    fn validate_field_segment(segment: &str) -> Result<(), StorageValueNameError> {
64        if segment.is_empty() {
65            return Err(StorageValueNameError::EmptySuffix);
66        }
67
68        if let Some(offending_char) =
69            segment.chars().find(|&c| !(c.is_ascii_alphanumeric() || c == '_' || c == '-'))
70        {
71            return Err(StorageValueNameError::InvalidCharacter {
72                part: segment.to_string(),
73                character: offending_char,
74            });
75        }
76
77        Ok(())
78    }
79}
80
81impl PartialEq for StorageValueName {
82    fn eq(&self, other: &Self) -> bool {
83        self.slot_name.as_str() == other.slot_name.as_str()
84            && self.element_field.as_deref() == other.element_field.as_deref()
85    }
86}
87
88impl Eq for StorageValueName {}
89
90impl PartialOrd for StorageValueName {
91    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
92        Some(self.cmp(other))
93    }
94}
95
96impl Ord for StorageValueName {
97    fn cmp(&self, other: &Self) -> Ordering {
98        let slot_cmp = self.slot_name.as_str().cmp(other.slot_name.as_str());
99        if slot_cmp != Ordering::Equal {
100            return slot_cmp;
101        }
102
103        match (self.element_field.as_deref(), other.element_field.as_deref()) {
104            (None, None) => Ordering::Equal,
105
106            // "<slot>" is a prefix of "<slot>.<field>", so it sorts first.
107            (None, Some(_)) => Ordering::Less,
108            (Some(_), None) => Ordering::Greater,
109
110            (Some(a), Some(b)) => a.cmp(b),
111        }
112    }
113}
114
115impl FromStr for StorageValueName {
116    type Err = StorageValueNameError;
117
118    fn from_str(value: &str) -> Result<Self, Self::Err> {
119        if value.is_empty() {
120            return Err(StorageValueNameError::EmptySuffix);
121        }
122
123        // `StorageValueName` represents:
124        // - a storage slot name (`StorageSlotName`), or
125        // - a fully-qualified storage slot field key (`named::slot.field`).
126        let (slot, field) = match value.split_once('.') {
127            Some((slot, field)) => {
128                Self::validate_field_segment(field)?;
129
130                if slot.is_empty() || field.is_empty() {
131                    return Err(StorageValueNameError::EmptySuffix);
132                }
133
134                (slot, Some(field))
135            },
136            None => (value, None),
137        };
138
139        let slot_name =
140            StorageSlotName::new(slot).map_err(StorageValueNameError::InvalidSlotName)?;
141        let field = match field {
142            Some(field) => {
143                Self::validate_field_segment(field)?;
144                Some(field.to_string())
145            },
146            None => None,
147        };
148
149        Ok(Self { slot_name, element_field: field })
150    }
151}
152
153impl TryFrom<String> for StorageValueName {
154    type Error = StorageValueNameError;
155
156    fn try_from(value: String) -> Result<Self, Self::Error> {
157        value.parse()
158    }
159}
160
161impl From<StorageValueName> for String {
162    fn from(value: StorageValueName) -> Self {
163        value.to_string()
164    }
165}
166
167impl From<&StorageSlotName> for StorageValueName {
168    fn from(value: &StorageSlotName) -> Self {
169        StorageValueName::from_slot_name(value)
170    }
171}
172
173impl Display for StorageValueName {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        match &self.element_field {
176            None => f.write_str(self.slot_name.as_str()),
177            Some(field) => {
178                f.write_str(self.slot_name.as_str())?;
179                f.write_str(".")?;
180                f.write_str(field)
181            },
182        }
183    }
184}
185
186impl Serializable for StorageValueName {
187    fn write_into<W: ByteWriter>(&self, target: &mut W) {
188        let key = self.to_string();
189        target.write(&key);
190    }
191}
192
193impl Deserializable for StorageValueName {
194    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
195        let key: String = source.read()?;
196        key.parse().map_err(|err: StorageValueNameError| {
197            DeserializationError::InvalidValue(err.to_string())
198        })
199    }
200}
201
202#[derive(Debug, Error)]
203pub enum StorageValueNameError {
204    #[error("key suffix is empty")]
205    EmptySuffix,
206    #[error("key segment '{part}' contains invalid character '{character}'")]
207    InvalidCharacter { part: String, character: char },
208    #[error("invalid storage slot name")]
209    InvalidSlotName(#[source] StorageSlotNameError),
210}