miden_protocol/account/component/storage/
value_name.rs1use 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#[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 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 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 pub fn slot_name(&self) -> &StorageSlotName {
55 &self.slot_name
56 }
57
58 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 (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 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 TryFrom<&str> for StorageValueName {
162 type Error = StorageValueNameError;
163
164 fn try_from(value: &str) -> Result<Self, Self::Error> {
165 value.parse()
166 }
167}
168
169impl From<StorageValueName> for String {
170 fn from(value: StorageValueName) -> Self {
171 value.to_string()
172 }
173}
174
175impl From<&StorageSlotName> for StorageValueName {
176 fn from(value: &StorageSlotName) -> Self {
177 StorageValueName::from_slot_name(value)
178 }
179}
180
181impl Display for StorageValueName {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 match &self.element_field {
184 None => f.write_str(self.slot_name.as_str()),
185 Some(field) => {
186 f.write_str(self.slot_name.as_str())?;
187 f.write_str(".")?;
188 f.write_str(field)
189 },
190 }
191 }
192}
193
194impl Serializable for StorageValueName {
195 fn write_into<W: ByteWriter>(&self, target: &mut W) {
196 let key = self.to_string();
197 target.write(&key);
198 }
199}
200
201impl Deserializable for StorageValueName {
202 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
203 let key: String = source.read()?;
204 key.parse().map_err(|err: StorageValueNameError| {
205 DeserializationError::InvalidValue(err.to_string())
206 })
207 }
208}
209
210#[derive(Debug, Error)]
211pub enum StorageValueNameError {
212 #[error("key suffix is empty")]
213 EmptySuffix,
214 #[error("key segment '{part}' contains invalid character '{character}'")]
215 InvalidCharacter { part: String, character: char },
216 #[error("invalid storage slot name")]
217 InvalidSlotName(#[source] StorageSlotNameError),
218}