miden_objects/account/component/template/storage/
placeholder.rs

1use alloc::string::{String, ToString};
2
3use thiserror::Error;
4use vm_core::{
5    utils::{ByteReader, ByteWriter, Deserializable, Serializable},
6    Felt, Word,
7};
8use vm_processor::DeserializationError;
9
10use crate::account::{component::template::AccountComponentTemplateError, StorageMap};
11
12// STORAGE PLACEHOLDER
13// ================================================================================================
14
15/// A simple wrapper type around a string key that enables templating.
16///
17/// A storage placeholder is a string that identifies dynamic values within a component's metadata
18/// storage entries. Storage placeholders are serialized as "{{key}}" and can be used as
19/// placeholders in map keys, map values, or individual [Felt]s within a [Word].
20///
21/// At component instantiation, a map of keys to [StorageValue] must be provided to dynamically
22/// replace these placeholders with the instance’s actual values.
23#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
24pub struct StoragePlaceholder {
25    key: String,
26}
27
28/// An identifier for the expected type for a storage placeholder.
29/// These indicate which variant of [StorageValue] should be provided when instantiating a
30/// component.
31#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
32pub enum PlaceholderType {
33    Felt,
34    Map,
35    Word,
36}
37
38impl core::fmt::Display for PlaceholderType {
39    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40        match self {
41            PlaceholderType::Felt => f.write_str("Felt"),
42            PlaceholderType::Map => f.write_str("Map"),
43            PlaceholderType::Word => f.write_str("Word"),
44        }
45    }
46}
47
48impl StoragePlaceholder {
49    /// Creates a new [StoragePlaceholder] from the provided string.
50    ///
51    /// A [StoragePlaceholder] serves as an identifier for storage values that are determined at
52    /// instantiation time of an [AccountComponentTemplate](super::super::AccountComponentTemplate).
53    ///
54    /// The key can consist of one or more segments separated by dots (`.`).  
55    /// Each segment must be non-empty and may contain only alphanumeric characters, underscores
56    /// (`_`), or hyphens (`-`).
57    ///
58    /// # Errors
59    ///
60    /// This method returns an error if:
61    /// - Any segment (or the whole key) is empty.
62    /// - Any segment contains invalid characters.
63    pub fn new(key: impl Into<String>) -> Result<Self, StoragePlaceholderError> {
64        let key: String = key.into();
65        Self::validate(&key)?;
66        Ok(Self { key })
67    }
68
69    /// Returns the key name
70    pub fn inner(&self) -> &str {
71        &self.key
72    }
73
74    /// Checks if the given string is a valid key.
75    /// A storage placeholder is valid if it's made of one or more segments that are non-empty
76    /// alphanumeric strings.
77    fn validate(key: &str) -> Result<(), StoragePlaceholderError> {
78        if key.is_empty() {
79            return Err(StoragePlaceholderError::EmptyKey);
80        }
81
82        for segment in key.split('.') {
83            if segment.is_empty() {
84                return Err(StoragePlaceholderError::EmptyKey);
85            }
86
87            for c in segment.chars() {
88                if !(c.is_ascii_alphanumeric() || c == '_' || c == '-') {
89                    return Err(StoragePlaceholderError::InvalidChar(key.into(), c));
90                }
91            }
92        }
93
94        Ok(())
95    }
96}
97
98impl TryFrom<&str> for StoragePlaceholder {
99    type Error = StoragePlaceholderError;
100
101    fn try_from(value: &str) -> Result<Self, Self::Error> {
102        if value.starts_with("{{") && value.ends_with("}}") {
103            let inner = &value[2..value.len() - 2];
104            Self::validate(inner)?;
105
106            Ok(StoragePlaceholder { key: inner.to_string() })
107        } else {
108            Err(StoragePlaceholderError::FormatError(value.into()))
109        }
110    }
111}
112
113impl TryFrom<&String> for StoragePlaceholder {
114    type Error = StoragePlaceholderError;
115
116    fn try_from(value: &String) -> Result<Self, Self::Error> {
117        Self::try_from(value.as_str())
118    }
119}
120
121impl core::fmt::Display for StoragePlaceholder {
122    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
123        write!(f, "{{{{{}}}}}", self.key)
124    }
125}
126
127#[derive(Debug, Error)]
128pub enum StoragePlaceholderError {
129    #[error("entire key and key segments cannot be empty")]
130    EmptyKey,
131    #[error("key `{0}` is invalid (expected string in {{...}} format)")]
132    FormatError(String),
133    #[error(
134        "key `{0}` contains invalid character ({1}) (must be alphanumeric, underscore, or hyphen)"
135    )]
136    InvalidChar(String, char),
137}
138
139// SERIALIZATION
140// ================================================================================================
141
142impl Serializable for StoragePlaceholder {
143    fn write_into<W: ByteWriter>(&self, target: &mut W) {
144        target.write(&self.key);
145    }
146}
147
148impl Deserializable for StoragePlaceholder {
149    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
150        let key: String = source.read()?;
151        StoragePlaceholder::new(key)
152            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
153    }
154}
155
156// STORAGE VALUE
157// ================================================================================================
158
159/// Represents a value used within a templating context.
160///
161/// A [StorageValue] can be one of:
162/// - `Felt(Felt)`: a single [Felt] value
163/// - `Word(Word)`: a single [Word] value
164/// - `Map(StorageMap)`: a storage map
165///
166/// These values are used to resolve dynamic placeholders at component instantiation.
167#[derive(Clone, Debug)]
168pub enum StorageValue {
169    Felt(Felt),
170    Word(Word),
171    Map(StorageMap),
172}
173
174impl StorageValue {
175    /// Returns `Some(&Felt)` if the variant is `Felt`, otherwise errors.
176    pub fn as_felt(&self) -> Result<&Felt, AccountComponentTemplateError> {
177        if let StorageValue::Felt(felt) = self {
178            Ok(felt)
179        } else {
180            Err(AccountComponentTemplateError::IncorrectStorageValue("Felt".into()))
181        }
182    }
183
184    /// Returns `Ok(&Word)` if the variant is `Word`, otherwise errors.
185    pub fn as_word(&self) -> Result<&Word, AccountComponentTemplateError> {
186        if let StorageValue::Word(word) = self {
187            Ok(word)
188        } else {
189            Err(AccountComponentTemplateError::IncorrectStorageValue("Word".into()))
190        }
191    }
192
193    /// Returns `Ok(&StorageMap>` if the variant is `Map`, otherwise errors.
194    pub fn as_map(&self) -> Result<&StorageMap, AccountComponentTemplateError> {
195        if let StorageValue::Map(map) = self {
196            Ok(map)
197        } else {
198            Err(AccountComponentTemplateError::IncorrectStorageValue("Map".into()))
199        }
200    }
201}