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 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}