use core::fmt;
use miden_field_repr::{FeltReader, FromFeltRepr};
use crate::{
CodecRegistry, Error, Felt, NoteStorage, PrimitiveType, Result, SchemaType, SchemaTypeKind,
schema::normalize_name,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DecodedValue {
name: Option<String>,
docs: Option<String>,
fqn: Option<String>,
kind: DecodedValueKind,
}
impl DecodedValue {
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn docs(&self) -> Option<&str> {
self.docs.as_deref()
}
pub fn fqn(&self) -> Option<&str> {
self.fqn.as_deref()
}
pub const fn kind(&self) -> &DecodedValueKind {
&self.kind
}
pub fn field(&self, name: &str) -> Option<&Self> {
let DecodedValueKind::Record(fields) = &self.kind else {
return None;
};
let name = normalize_name(name);
fields.iter().find(|field| field.name.as_deref() == Some(name.as_str()))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DecodedValueKind {
Leaf {
felts: Vec<Felt>,
display: String,
},
Record(Vec<DecodedValue>),
Option(Option<Box<DecodedValue>>),
Variant {
case: String,
value: Option<Box<DecodedValue>>,
},
}
impl fmt::Display for DecodedValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
DecodedValueKind::Leaf { display, .. } => f.write_str(display),
DecodedValueKind::Record(fields) => {
f.write_str("{")?;
for (index, field) in fields.iter().enumerate() {
if index != 0 {
f.write_str(", ")?;
}
f.write_str(field.name.as_deref().unwrap_or("<unnamed>"))?;
f.write_str(": ")?;
field.fmt(f)?;
}
f.write_str("}")
}
DecodedValueKind::Option(None) => f.write_str("none"),
DecodedValueKind::Option(Some(value)) => write!(f, "some({value})"),
DecodedValueKind::Variant { case, value: None } => f.write_str(case),
DecodedValueKind::Variant {
case,
value: Some(value),
} => write!(f, "{case}({value})"),
}
}
}
pub(crate) fn decode(
root: &SchemaType,
storage: &NoteStorage,
registry: &CodecRegistry,
) -> Result<DecodedValue> {
let (value, consumed) = decode_type(
root,
storage.items(),
Some(registry),
root.name().map(str::to_owned),
root.docs().map(str::to_owned),
)?;
if consumed != storage.items().len() {
return Err(Error::new(format!(
"note storage has {} trailing felt(s) after the schema root",
storage.items().len() - consumed
)));
}
Ok(value)
}
pub(crate) fn validate_encoding(ty: &SchemaType, felts: &[Felt]) -> Result<()> {
let (_, consumed) = decode_type(ty, felts, None, None, None)?;
if consumed != felts.len() {
return Err(Error::new(format!(
"value has {} trailing felt(s) after its structural encoding",
felts.len() - consumed
)));
}
Ok(())
}
fn decode_type(
ty: &SchemaType,
input: &[Felt],
registry: Option<&CodecRegistry>,
name: Option<String>,
docs: Option<String>,
) -> Result<(DecodedValue, usize)> {
if let (Some(registry), Some(fqn)) = (registry, ty.fqn())
&& let Some(codec) = registry.codec(fqn)
{
let (_, consumed) = decode_type(ty, input, None, None, None)?;
let felts = input[..consumed].to_vec();
codec
.validate(&felts)
.map_err(|err| err.context(format!("codec `{fqn}` rejected decoded value")))?;
let display = codec
.display(&felts)
.map_err(|err| err.context(format!("codec `{fqn}` failed to display decoded value")))?;
return Ok((
DecodedValue {
name,
docs,
fqn: Some(fqn.to_owned()),
kind: DecodedValueKind::Leaf { display, felts },
},
consumed,
));
}
let fqn = ty.fqn().map(str::to_owned);
let (kind, consumed) = match ty.kind() {
SchemaTypeKind::Felt => {
let mut reader = FeltReader::new(input);
let felt = reader
.read()
.map_err(|err| Error::new(format!("invalid felt representation: {err}")))?;
(
DecodedValueKind::Leaf {
felts: vec![felt],
display: felt.as_canonical_u64().to_string(),
},
reader.pos(),
)
}
SchemaTypeKind::Primitive(primitive) => decode_primitive(*primitive, input)?,
SchemaTypeKind::Record(fields) => {
let mut decoded = Vec::with_capacity(fields.len());
let mut offset = 0usize;
for field in fields {
let (value, consumed) = decode_type(
field.ty(),
&input[offset..],
registry,
Some(field.name().to_owned()),
field.docs().map(str::to_owned),
)
.map_err(|err| err.context(format!("field `{}`", field.name())))?;
offset = offset
.checked_add(consumed)
.ok_or_else(|| Error::new("decoded record width is too large"))?;
decoded.push(value);
}
(DecodedValueKind::Record(decoded), offset)
}
SchemaTypeKind::Option(payload) => {
let mut reader = FeltReader::new(input);
let tag = reader
.read()
.map_err(|err| Error::new(format!("invalid option representation: {err}")))?
.as_canonical_u64();
match tag {
0 => (DecodedValueKind::Option(None), reader.pos()),
1 => {
let (value, consumed) =
decode_type(payload, &input[reader.pos()..], registry, None, None)?;
(DecodedValueKind::Option(Some(Box::new(value))), reader.pos() + consumed)
}
tag => {
return Err(Error::new(format!("invalid option tag {tag}; expected 0 or 1")));
}
}
}
SchemaTypeKind::Variant(cases) => {
let mut reader = FeltReader::new(input);
let tag = reader
.read_u32()
.map_err(|err| Error::new(format!("invalid variant representation: {err}")))?
as usize;
let case = cases.get(tag).ok_or_else(|| {
Error::new(format!(
"invalid variant tag {tag}; expected a declaration ordinal below {}",
cases.len()
))
})?;
let (value, consumed) = match case.payload() {
Some(payload) => {
let (value, consumed) =
decode_type(payload, &input[reader.pos()..], registry, None, None)?;
(Some(Box::new(value)), reader.pos() + consumed)
}
None => (None, reader.pos()),
};
(
DecodedValueKind::Variant {
case: case.name().to_owned(),
value,
},
consumed,
)
}
};
Ok((
DecodedValue {
name,
docs,
fqn,
kind,
},
consumed,
))
}
fn decode_primitive(primitive: PrimitiveType, input: &[Felt]) -> Result<(DecodedValueKind, usize)> {
match primitive {
PrimitiveType::U64 => read_primitive::<u64>(input),
PrimitiveType::U32 => read_primitive::<u32>(input),
PrimitiveType::U8 => read_primitive::<u8>(input),
PrimitiveType::Bool => read_primitive::<bool>(input),
}
}
fn read_primitive<T>(input: &[Felt]) -> Result<(DecodedValueKind, usize)>
where
T: FromFeltRepr + fmt::Display,
{
let mut reader = FeltReader::new(input);
let value = T::from_felt_repr(&mut reader)
.map_err(|err| Error::new(format!("invalid primitive representation: {err}")))?;
Ok((
DecodedValueKind::Leaf {
felts: input[..reader.pos()].to_vec(),
display: value.to_string(),
},
reader.pos(),
))
}