Skip to main content

miden_note_schema/
value.rs

1//! Named values decoded from note storage.
2
3use core::fmt;
4
5use miden_field_repr::{FeltReader, FromFeltRepr};
6
7use crate::{
8    CodecRegistry, Error, Felt, NoteStorage, PrimitiveType, Result, SchemaType, SchemaTypeKind,
9    schema::normalize_name,
10};
11
12/// A named value decoded from note storage.
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct DecodedValue {
15    name: Option<String>,
16    docs: Option<String>,
17    fqn: Option<String>,
18    kind: DecodedValueKind,
19}
20
21impl DecodedValue {
22    /// Returns the field or root type name.
23    pub fn name(&self) -> Option<&str> {
24        self.name.as_deref()
25    }
26
27    /// Returns the field-level or root type documentation.
28    pub fn docs(&self) -> Option<&str> {
29        self.docs.as_deref()
30    }
31
32    /// Returns the canonical WIT FQN when this value has one.
33    pub fn fqn(&self) -> Option<&str> {
34        self.fqn.as_deref()
35    }
36
37    /// Returns the decoded value kind.
38    pub const fn kind(&self) -> &DecodedValueKind {
39        &self.kind
40    }
41
42    /// Finds a direct record field with kebab-case or snake_case spelling.
43    pub fn field(&self, name: &str) -> Option<&Self> {
44        let DecodedValueKind::Record(fields) = &self.kind else {
45            return None;
46        };
47        let name = normalize_name(name);
48        fields.iter().find(|field| field.name.as_deref() == Some(name.as_str()))
49    }
50}
51
52/// The structural value stored in a decoded node.
53#[derive(Clone, Debug, Eq, PartialEq)]
54pub enum DecodedValueKind {
55    /// A codec-rendered or primitive leaf with its encoded felts.
56    Leaf {
57        /// The structural felt representation.
58        felts: Vec<Felt>,
59        /// The registry-backed or primitive display text.
60        display: String,
61    },
62    /// Record fields in declaration order.
63    Record(Vec<DecodedValue>),
64    /// An optional value.
65    Option(Option<Box<DecodedValue>>),
66    /// A selected variant case and its optional payload.
67    Variant {
68        /// The selected case name.
69        case: String,
70        /// The decoded case payload.
71        value: Option<Box<DecodedValue>>,
72    },
73}
74
75impl fmt::Display for DecodedValue {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        match &self.kind {
78            DecodedValueKind::Leaf { display, .. } => f.write_str(display),
79            DecodedValueKind::Record(fields) => {
80                f.write_str("{")?;
81                for (index, field) in fields.iter().enumerate() {
82                    if index != 0 {
83                        f.write_str(", ")?;
84                    }
85                    f.write_str(field.name.as_deref().unwrap_or("<unnamed>"))?;
86                    f.write_str(": ")?;
87                    field.fmt(f)?;
88                }
89                f.write_str("}")
90            }
91            DecodedValueKind::Option(None) => f.write_str("none"),
92            DecodedValueKind::Option(Some(value)) => write!(f, "some({value})"),
93            DecodedValueKind::Variant { case, value: None } => f.write_str(case),
94            DecodedValueKind::Variant {
95                case,
96                value: Some(value),
97            } => write!(f, "{case}({value})"),
98        }
99    }
100}
101
102/// Decodes a root storage value and rejects trailing felts.
103pub(crate) fn decode(
104    root: &SchemaType,
105    storage: &NoteStorage,
106    registry: &CodecRegistry,
107) -> Result<DecodedValue> {
108    let (value, consumed) = decode_type(
109        root,
110        storage.items(),
111        Some(registry),
112        root.name().map(str::to_owned),
113        root.docs().map(str::to_owned),
114    )?;
115    if consumed != storage.items().len() {
116        return Err(Error::new(format!(
117            "note storage has {} trailing felt(s) after the schema root",
118            storage.items().len() - consumed
119        )));
120    }
121    Ok(value)
122}
123
124/// Validates one complete structural encoding without applying codecs.
125pub(crate) fn validate_encoding(ty: &SchemaType, felts: &[Felt]) -> Result<()> {
126    let (_, consumed) = decode_type(ty, felts, None, None, None)?;
127    if consumed != felts.len() {
128        return Err(Error::new(format!(
129            "value has {} trailing felt(s) after its structural encoding",
130            felts.len() - consumed
131        )));
132    }
133    Ok(())
134}
135
136/// Decodes one value prefix and returns the consumed felt count.
137fn decode_type(
138    ty: &SchemaType,
139    input: &[Felt],
140    registry: Option<&CodecRegistry>,
141    name: Option<String>,
142    docs: Option<String>,
143) -> Result<(DecodedValue, usize)> {
144    if let (Some(registry), Some(fqn)) = (registry, ty.fqn())
145        && let Some(codec) = registry.codec(fqn)
146    {
147        let (_, consumed) = decode_type(ty, input, None, None, None)?;
148        let felts = input[..consumed].to_vec();
149        codec
150            .validate(&felts)
151            .map_err(|err| err.context(format!("codec `{fqn}` rejected decoded value")))?;
152        let display = codec
153            .display(&felts)
154            .map_err(|err| err.context(format!("codec `{fqn}` failed to display decoded value")))?;
155        return Ok((
156            DecodedValue {
157                name,
158                docs,
159                fqn: Some(fqn.to_owned()),
160                kind: DecodedValueKind::Leaf { display, felts },
161            },
162            consumed,
163        ));
164    }
165
166    let fqn = ty.fqn().map(str::to_owned);
167    let (kind, consumed) = match ty.kind() {
168        SchemaTypeKind::Felt => {
169            let mut reader = FeltReader::new(input);
170            let felt = reader
171                .read()
172                .map_err(|err| Error::new(format!("invalid felt representation: {err}")))?;
173            (
174                DecodedValueKind::Leaf {
175                    felts: vec![felt],
176                    display: felt.as_canonical_u64().to_string(),
177                },
178                reader.pos(),
179            )
180        }
181        SchemaTypeKind::Primitive(primitive) => decode_primitive(*primitive, input)?,
182        SchemaTypeKind::Record(fields) => {
183            let mut decoded = Vec::with_capacity(fields.len());
184            let mut offset = 0usize;
185            for field in fields {
186                let (value, consumed) = decode_type(
187                    field.ty(),
188                    &input[offset..],
189                    registry,
190                    Some(field.name().to_owned()),
191                    field.docs().map(str::to_owned),
192                )
193                .map_err(|err| err.context(format!("field `{}`", field.name())))?;
194                offset = offset
195                    .checked_add(consumed)
196                    .ok_or_else(|| Error::new("decoded record width is too large"))?;
197                decoded.push(value);
198            }
199            (DecodedValueKind::Record(decoded), offset)
200        }
201        SchemaTypeKind::Option(payload) => {
202            let mut reader = FeltReader::new(input);
203            let tag = reader
204                .read()
205                .map_err(|err| Error::new(format!("invalid option representation: {err}")))?
206                .as_canonical_u64();
207            match tag {
208                0 => (DecodedValueKind::Option(None), reader.pos()),
209                1 => {
210                    let (value, consumed) =
211                        decode_type(payload, &input[reader.pos()..], registry, None, None)?;
212                    (DecodedValueKind::Option(Some(Box::new(value))), reader.pos() + consumed)
213                }
214                tag => {
215                    return Err(Error::new(format!("invalid option tag {tag}; expected 0 or 1")));
216                }
217            }
218        }
219        SchemaTypeKind::Variant(cases) => {
220            let mut reader = FeltReader::new(input);
221            let tag = reader
222                .read_u32()
223                .map_err(|err| Error::new(format!("invalid variant representation: {err}")))?
224                as usize;
225            let case = cases.get(tag).ok_or_else(|| {
226                Error::new(format!(
227                    "invalid variant tag {tag}; expected a declaration ordinal below {}",
228                    cases.len()
229                ))
230            })?;
231            let (value, consumed) = match case.payload() {
232                Some(payload) => {
233                    let (value, consumed) =
234                        decode_type(payload, &input[reader.pos()..], registry, None, None)?;
235                    (Some(Box::new(value)), reader.pos() + consumed)
236                }
237                None => (None, reader.pos()),
238            };
239            (
240                DecodedValueKind::Variant {
241                    case: case.name().to_owned(),
242                    value,
243                },
244                consumed,
245            )
246        }
247    };
248
249    Ok((
250        DecodedValue {
251            name,
252            docs,
253            fqn,
254            kind,
255        },
256        consumed,
257    ))
258}
259
260/// Decodes a supported primitive through `miden-field-repr`.
261fn decode_primitive(primitive: PrimitiveType, input: &[Felt]) -> Result<(DecodedValueKind, usize)> {
262    match primitive {
263        PrimitiveType::U64 => read_primitive::<u64>(input),
264        PrimitiveType::U32 => read_primitive::<u32>(input),
265        PrimitiveType::U8 => read_primitive::<u8>(input),
266        PrimitiveType::Bool => read_primitive::<bool>(input),
267    }
268}
269
270/// Decodes and displays one primitive value.
271fn read_primitive<T>(input: &[Felt]) -> Result<(DecodedValueKind, usize)>
272where
273    T: FromFeltRepr + fmt::Display,
274{
275    let mut reader = FeltReader::new(input);
276    let value = T::from_felt_repr(&mut reader)
277        .map_err(|err| Error::new(format!("invalid primitive representation: {err}")))?;
278    Ok((
279        DecodedValueKind::Leaf {
280            felts: input[..reader.pos()].to_vec(),
281            display: value.to_string(),
282        },
283        reader.pos(),
284    ))
285}