use alloc::collections::{BTreeMap, BTreeSet};
use alloc::string::{String, ToString};
use core::str::FromStr;
use miden_mast_package::{Package, SectionId};
use semver::Version;
use super::{AccountType, SchemaRequirement, StorageSchema, StorageValueName};
use crate::errors::AccountError;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "std", serde(rename_all = "kebab-case"))]
pub struct AccountComponentMetadata {
name: String,
description: String,
version: Version,
supported_types: BTreeSet<AccountType>,
#[cfg_attr(feature = "std", serde(rename = "storage"))]
storage_schema: StorageSchema,
}
impl AccountComponentMetadata {
pub fn new(
name: impl Into<String>,
supported_types: impl IntoIterator<Item = AccountType>,
) -> Self {
Self {
name: name.into(),
description: String::new(),
version: Version::new(1, 0, 0),
supported_types: supported_types.into_iter().collect(),
storage_schema: StorageSchema::default(),
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
pub fn with_version(mut self, version: Version) -> Self {
self.version = version;
self
}
pub fn with_storage_schema(mut self, schema: StorageSchema) -> Self {
self.storage_schema = schema;
self
}
pub fn schema_requirements(&self) -> BTreeMap<StorageValueName, SchemaRequirement> {
self.storage_schema.schema_requirements().expect("storage schema is validated")
}
pub fn name(&self) -> &str {
&self.name
}
pub fn description(&self) -> &str {
&self.description
}
pub fn version(&self) -> &Version {
&self.version
}
pub fn supported_types(&self) -> &BTreeSet<AccountType> {
&self.supported_types
}
pub fn storage_schema(&self) -> &StorageSchema {
&self.storage_schema
}
}
impl TryFrom<&Package> for AccountComponentMetadata {
type Error = AccountError;
fn try_from(package: &Package) -> Result<Self, Self::Error> {
package
.sections
.iter()
.find_map(|section| {
(section.id == SectionId::ACCOUNT_COMPONENT_METADATA).then(|| {
AccountComponentMetadata::read_from_bytes(§ion.data).map_err(|err| {
AccountError::other_with_source(
"failed to deserialize account component metadata",
err,
)
})
})
})
.transpose()?
.ok_or_else(|| {
AccountError::other(
"package does not contain account component metadata section - packages without explicit metadata may be intended for other purposes (e.g., note scripts, transaction scripts)",
)
})
}
}
impl Serializable for AccountComponentMetadata {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.name.write_into(target);
self.description.write_into(target);
self.version.to_string().write_into(target);
self.supported_types.write_into(target);
self.storage_schema.write_into(target);
}
}
impl Deserializable for AccountComponentMetadata {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let name = String::read_from(source)?;
let description = String::read_from(source)?;
if !description.is_ascii() {
return Err(DeserializationError::InvalidValue(
"description must contain only ASCII characters".to_string(),
));
}
let version = semver::Version::from_str(&String::read_from(source)?)
.map_err(|err: semver::Error| DeserializationError::InvalidValue(err.to_string()))?;
let supported_types = BTreeSet::<AccountType>::read_from(source)?;
let storage_schema = StorageSchema::read_from(source)?;
Ok(Self {
name,
description,
version,
supported_types,
storage_schema,
})
}
}