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