1use std::collections::BTreeMap;
4
5use miden_field_repr::FeltWriter;
6
7use crate::{
8 CodecRegistry, Error, Felt, NoteStorage, NoteStorageSchema, PrimitiveType, Result, SchemaField,
9 SchemaType, SchemaTypeKind,
10 codec::{parse_felt, parse_unsigned, write_repr},
11 schema::normalize_name,
12 value::validate_encoding,
13};
14
15pub struct NoteStorageBuilder<'a> {
17 schema: &'a NoteStorageSchema,
18 registry: &'a CodecRegistry,
19 values: BTreeMap<String, String>,
20}
21
22impl<'a> NoteStorageBuilder<'a> {
23 pub(crate) fn new(schema: &'a NoteStorageSchema, registry: &'a CodecRegistry) -> Self {
25 Self {
26 schema,
27 registry,
28 values: BTreeMap::new(),
29 }
30 }
31
32 pub fn set(mut self, path: &str, value: impl AsRef<str>) -> Result<Self> {
37 let segments = normalize_path(path)?;
38 let normalized = segments.join(".");
39 let ty = resolve_path(self.schema.root(), &segments)?;
40 let value = value.as_ref();
41 reject_unsupported_constructor_path(ty, &normalized, value, self.registry)?;
42
43 if let Some(conflict) = self.values.keys().find(|existing| {
44 *existing == &normalized
45 || existing.starts_with(&format!("{normalized}."))
46 || normalized.starts_with(&format!("{existing}."))
47 }) {
48 return Err(Error::new(format!(
49 "path `{normalized}` conflicts with the existing value at `{conflict}`"
50 )));
51 }
52 self.values.insert(normalized, value.to_owned());
53 Ok(self)
54 }
55
56 pub fn build(self) -> Result<NoteStorage> {
58 let mut felts = Vec::new();
59 encode_type(
60 self.schema.root(),
61 "",
62 &self.values,
63 self.registry,
64 &mut FeltWriter::new(&mut felts),
65 )?;
66 validate_encoding(self.schema.root(), &felts)
67 .map_err(|err| err.context("built note storage does not match its schema"))?;
68 NoteStorage::new(felts)
69 .map_err(|err| Error::new(format!("failed to create note storage: {err}")))
70 }
71}
72
73fn reject_unsupported_constructor_path(
75 ty: &SchemaType,
76 path: &str,
77 value: &str,
78 registry: &CodecRegistry,
79) -> Result<()> {
80 if ty.fqn().is_some_and(|fqn| registry.contains(fqn)) {
81 return Ok(());
82 }
83 match ty.kind() {
84 SchemaTypeKind::Option(payload)
85 if matches!(payload.kind(), SchemaTypeKind::Record(_))
86 && value.trim() != "none"
87 && !payload.fqn().is_some_and(|fqn| registry.contains(fqn)) =>
88 {
89 Err(Error::new(format!(
90 "path `{path}` selects an option with record payload `{}`; the string builder \
91 does not support nested record constructors without a codec for that payload",
92 payload.fqn().or(payload.name()).unwrap_or("<anonymous record>")
93 )))
94 }
95 SchemaTypeKind::Variant(cases) => {
96 let (case_name, _) = parse_constructor(value)?;
97 let case_name = normalize_name(case_name);
98 let unsupported = cases.iter().find(|case| case.name() == case_name).and_then(|case| {
99 let payload = case.payload()?;
100 (matches!(payload.kind(), SchemaTypeKind::Record(_))
101 && !payload.fqn().is_some_and(|fqn| registry.contains(fqn)))
102 .then_some(case.name())
103 });
104 if let Some(case_name) = unsupported {
105 Err(Error::new(format!(
106 "path `{path}` selects variant case `{case_name}` with a record payload; the \
107 string builder does not support nested record constructors without a codec \
108 for that payload"
109 )))
110 } else {
111 Ok(())
112 }
113 }
114 _ => Ok(()),
115 }
116}
117
118fn normalize_path(path: &str) -> Result<Vec<String>> {
120 let segments = path.split('.').map(normalize_name).collect::<Vec<_>>();
122 if segments.iter().any(String::is_empty) {
123 return Err(Error::new(format!("invalid empty note storage path `{path}`")));
124 }
125 Ok(segments)
126}
127
128fn resolve_path<'a>(mut ty: &'a SchemaType, segments: &[String]) -> Result<&'a SchemaType> {
130 let mut resolved = Vec::with_capacity(segments.len());
131 for segment in segments {
132 let SchemaTypeKind::Record(fields) = ty.kind() else {
133 return Err(Error::new(format!(
134 "path `{}` continues through non-record type `{}`",
135 resolved.join("."),
136 ty.fqn().or(ty.name()).unwrap_or("<anonymous>")
137 )));
138 };
139 let field = fields.iter().find(|field| field.name() == segment).ok_or_else(|| {
140 let prefix = if resolved.is_empty() {
141 "<root>".to_owned()
142 } else {
143 resolved.join(".")
144 };
145 Error::new(format!("record `{prefix}` has no field named `{segment}`"))
146 })?;
147 resolved.push(segment.clone());
148 ty = field.ty();
149 }
150 Ok(ty)
151}
152
153fn encode_type(
155 ty: &SchemaType,
156 path: &str,
157 values: &BTreeMap<String, String>,
158 registry: &CodecRegistry,
159 writer: &mut FeltWriter<'_>,
160) -> Result<()> {
161 if let Some(value) = values.get(path) {
162 let felts = encode_text_value(ty, value, registry)
163 .map_err(|err| err.context(format!("value at `{path}`")))?;
164 for felt in felts {
165 writer.write(felt);
166 }
167 return Ok(());
168 }
169
170 if let SchemaTypeKind::Record(fields) = ty.kind() {
171 let Some((fqn, codec)) = ty.fqn().and_then(|fqn| Some((fqn, registry.codec(fqn)?))) else {
172 return encode_record_fields(fields, path, values, registry, writer);
173 };
174 let mut subtree = Vec::new();
178 encode_record_fields(fields, path, values, registry, &mut FeltWriter::new(&mut subtree))?;
179 codec.validate(&subtree).map_err(|err| {
180 err.context(format!(
181 "codec `{fqn}` validation failed for the values assigned under `{}`",
182 if path.is_empty() { "<root>" } else { path }
183 ))
184 })?;
185 for felt in subtree {
186 writer.write(felt);
187 }
188 return Ok(());
189 }
190
191 Err(Error::new(format!(
192 "missing note storage value for `{}`",
193 if path.is_empty() { "<root>" } else { path }
194 )))
195}
196
197fn encode_record_fields(
199 fields: &[SchemaField],
200 path: &str,
201 values: &BTreeMap<String, String>,
202 registry: &CodecRegistry,
203 writer: &mut FeltWriter<'_>,
204) -> Result<()> {
205 for field in fields {
206 let field_path = if path.is_empty() {
207 field.name().to_owned()
208 } else {
209 format!("{path}.{}", field.name())
210 };
211 encode_type(field.ty(), &field_path, values, registry, writer)?;
212 }
213 Ok(())
214}
215
216fn encode_text_value(ty: &SchemaType, value: &str, registry: &CodecRegistry) -> Result<Vec<Felt>> {
218 if let Some(fqn) = ty.fqn()
219 && let Some(codec) = registry.codec(fqn)
220 {
221 let felts = codec.parse(value)?;
222 codec
223 .validate(&felts)
224 .map_err(|err| err.context(format!("codec `{fqn}` validation failed")))?;
225 validate_encoding(ty, &felts)
226 .map_err(|err| err.context(format!("codec `{fqn}` changed the structural layout")))?;
227 return Ok(felts);
228 }
229
230 match ty.kind() {
231 SchemaTypeKind::Felt => Ok(write_repr(&parse_felt(value)?)),
232 SchemaTypeKind::Primitive(primitive) => encode_primitive(*primitive, value),
233 SchemaTypeKind::Option(payload) => encode_option(payload, value, registry),
234 SchemaTypeKind::Variant(cases) => {
235 let (case_name, payload_text) = parse_constructor(value)?;
236 let case_name = normalize_name(case_name);
237 let (ordinal, case) =
238 cases.iter().enumerate().find(|(_, case)| case.name() == case_name).ok_or_else(
239 || {
240 Error::new(format!(
241 "variant `{}` has no case named `{case_name}`",
242 ty.fqn().or(ty.name()).unwrap_or("<anonymous>")
243 ))
244 },
245 )?;
246 let ordinal = u32::try_from(ordinal)
247 .map_err(|_| Error::new("variant has more than u32::MAX cases"))?;
248 let mut felts = write_repr(&ordinal);
249 match (case.payload(), payload_text) {
250 (None, None) => {}
251 (None, Some(_)) => {
252 return Err(Error::new(format!(
253 "variant case `{case_name}` does not accept a payload"
254 )));
255 }
256 (Some(_), None) => {
257 return Err(Error::new(format!("variant case `{case_name}` needs a payload")));
258 }
259 (Some(payload), Some(payload_text)) => {
260 felts.extend(encode_text_value(payload, payload_text, registry)?);
261 }
262 }
263 Ok(felts)
264 }
265 SchemaTypeKind::Record(_) => Err(Error::new(format!(
266 "type `{}` has no codec; set its leaf fields with dotted paths",
267 ty.fqn().or(ty.name()).unwrap_or("<anonymous record>")
268 ))),
269 }
270}
271
272fn encode_option(payload: &SchemaType, value: &str, registry: &CodecRegistry) -> Result<Vec<Felt>> {
274 let value = value.trim();
275 if value == "none" {
276 return Ok(write_repr(&0u32));
277 }
278 let (constructor, payload_text) = parse_constructor(value)?;
279 if normalize_name(constructor) != "some" {
280 return Err(Error::new("an option value must be `none` or `some(<value>)`"));
281 }
282 let payload_text =
283 payload_text.ok_or_else(|| Error::new("an option `some` value needs a payload"))?;
284 let mut felts = write_repr(&1u32);
285 felts.extend(encode_text_value(payload, payload_text, registry)?);
286 Ok(felts)
287}
288
289fn encode_primitive(primitive: PrimitiveType, value: &str) -> Result<Vec<Felt>> {
291 match primitive {
292 PrimitiveType::U64 => Ok(write_repr(&parse_unsigned(value, "u64")?)),
293 PrimitiveType::U32 => {
294 let value = u32::try_from(parse_unsigned(value, "u32")?)
295 .map_err(|_| Error::new(format!("u32 value `{value}` is out of range")))?;
296 Ok(write_repr(&value))
297 }
298 PrimitiveType::U8 => {
299 let value = u8::try_from(parse_unsigned(value, "u8")?)
300 .map_err(|_| Error::new(format!("u8 value `{value}` is out of range")))?;
301 Ok(write_repr(&value))
302 }
303 PrimitiveType::Bool => {
304 let value = match value.trim() {
305 "true" | "1" => true,
306 "false" | "0" => false,
307 value => {
308 return Err(Error::new(format!(
309 "invalid bool `{value}`; expected true, false, 1, or 0"
310 )));
311 }
312 };
313 Ok(write_repr(&value))
314 }
315 }
316}
317
318fn parse_constructor(value: &str) -> Result<(&str, Option<&str>)> {
320 let value = value.trim();
321 let Some(open) = value.find('(') else {
322 return Ok((value, None));
323 };
324 if !value.ends_with(')') {
325 return Err(Error::new(format!("value `{value}` has an unclosed payload")));
326 }
327 let name = value[..open].trim();
328 let payload = value[open + 1..value.len() - 1].trim();
329 if name.is_empty() || payload.is_empty() {
330 return Err(Error::new(format!("value `{value}` has an empty constructor or payload")));
331 }
332 Ok((name, Some(payload)))
333}