miden_mast_package/package/
section.rs

1use alloc::{
2    borrow::{Cow, ToOwned},
3    format,
4    string::ToString,
5};
6use core::{fmt, str::FromStr};
7
8use miden_assembly_syntax::DisplayHex;
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// A unique identifier for optional sections of the Miden package format
13#[derive(Debug, Clone, PartialEq, Eq)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15#[cfg_attr(feature = "serde", serde(transparent))]
16#[repr(transparent)]
17pub struct SectionId(Cow<'static, str>);
18
19impl SectionId {
20    /// The section containing debug information (source locations, spans)
21    pub const DEBUG_INFO: Self = Self(Cow::Borrowed("debug_info"));
22    /// This section provides the encoded metadata for a compiled account component
23    ///
24    /// Currently, this corresponds to the serialized representation of
25    /// `miden-objects::account::AccountComponentMetadata`, i.e. name, descrioption, storage, that
26    /// is associated with this package.
27    pub const ACCOUNT_COMPONENT_METADATA: Self = Self(Cow::Borrowed("account_component_metadata"));
28
29    /// Construct a user-defined (i.e. "custom") section identifier
30    ///
31    /// Section identifiers must be either an ASCII alphanumeric, or one of the following
32    /// characters: `.`, `_`, `-`. Additionally, the identifier must start with an ASCII alphabetic
33    /// character or `_`.
34    pub fn custom(name: impl AsRef<str>) -> Result<Self, InvalidSectionIdError> {
35        let name = name.as_ref();
36        if !name.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_') {
37            return Err(InvalidSectionIdError::InvalidStart);
38        }
39        if name.contains(|c: char| !c.is_ascii_alphanumeric() && !matches!(c, '.' | '_' | '-')) {
40            return Err(InvalidSectionIdError::InvalidCharacter);
41        }
42        Ok(Self(name.to_string().into()))
43    }
44
45    /// Get this section identifier as a string
46    #[inline]
47    pub fn as_str(&self) -> &str {
48        self.0.as_ref()
49    }
50}
51
52#[derive(Debug, thiserror::Error)]
53pub enum InvalidSectionIdError {
54    #[error("invalid section id: cannot be empty")]
55    Empty,
56    #[error(
57        "invalid section id: contains invalid characters, only the set [a-z0-9._-] are allowed"
58    )]
59    InvalidCharacter,
60    #[error("invalid section id: must start with a character in the set [a-z_]")]
61    InvalidStart,
62}
63
64impl FromStr for SectionId {
65    type Err = InvalidSectionIdError;
66    fn from_str(s: &str) -> Result<Self, Self::Err> {
67        match s {
68            "debug_info" => Ok(Self::DEBUG_INFO),
69            "account_component_metadata" => Ok(Self::ACCOUNT_COMPONENT_METADATA),
70            custom => Self::custom(custom),
71        }
72    }
73}
74
75impl fmt::Display for SectionId {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        f.write_str(self.as_str())
78    }
79}
80
81#[derive(Clone, PartialEq, Eq)]
82#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
83pub struct Section {
84    pub id: SectionId,
85    pub data: Cow<'static, [u8]>,
86}
87
88impl fmt::Debug for Section {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        let verbose = f.alternate();
91        let mut builder = f.debug_struct("Section");
92        builder.field("id", &format_args!("{}", &self.id));
93        if verbose {
94            builder.field("data", &format_args!("{}", DisplayHex(&self.data))).finish()
95        } else {
96            builder.field("data", &format_args!("{} bytes", self.data.len())).finish()
97        }
98    }
99}
100
101impl Section {
102    pub fn new<B>(id: SectionId, data: B) -> Self
103    where
104        B: Into<Cow<'static, [u8]>>,
105    {
106        Self { id, data: data.into() }
107    }
108
109    /// Returns true if this section is empty, i.e. has no data
110    pub fn is_empty(&self) -> bool {
111        self.data.is_empty()
112    }
113
114    /// Returns the size in bytes of this section's data
115    pub fn len(&self) -> usize {
116        self.data.len()
117    }
118}
119
120impl miden_core::utils::Serializable for Section {
121    fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
122        let id = self.id.as_str();
123        target.write_usize(id.len());
124        target.write_bytes(id.as_bytes());
125        target.write_usize(self.len());
126        target.write_bytes(&self.data);
127    }
128}
129
130impl miden_core::utils::Deserializable for Section {
131    fn read_from<R: miden_core::utils::ByteReader>(
132        source: &mut R,
133    ) -> Result<Self, miden_core::utils::DeserializationError> {
134        let id_len = source.read_usize()?;
135        let id_bytes = source.read_slice(id_len)?;
136        let id_str = core::str::from_utf8(id_bytes).map_err(|err| {
137            miden_core::utils::DeserializationError::InvalidValue(format!(
138                "invalid utf-8 in section name: {err}"
139            ))
140        })?;
141        let id = SectionId(Cow::Owned(id_str.to_owned()));
142
143        let len = source.read_usize()?;
144        let bytes = source.read_slice(len)?;
145        Ok(Section { id, data: Cow::Owned(bytes.to_owned()) })
146    }
147}