use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use super::super::type_registry::{SCHEMA_TYPE_REGISTRY, SchemaRequirement, SchemaType};
use super::super::{InitStorageData, StorageValueName, WordValue};
use super::validate_description_ascii;
use crate::Felt;
use crate::account::StorageSlotName;
use crate::errors::ComponentMetadataError;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FeltSchema {
name: Option<String>,
description: Option<String>,
r#type: SchemaType,
default_value: Option<Felt>,
}
impl FeltSchema {
pub fn new_typed(r#type: SchemaType, name: impl Into<String>) -> Self {
FeltSchema {
name: Some(name.into()),
description: None,
r#type,
default_value: None,
}
}
pub fn new_typed_with_default(
r#type: SchemaType,
name: impl Into<String>,
default_value: Felt,
) -> Self {
FeltSchema {
name: Some(name.into()),
description: None,
r#type,
default_value: Some(default_value),
}
}
pub fn new_void() -> Self {
FeltSchema {
name: None,
description: None,
r#type: SchemaType::void(),
default_value: None,
}
}
pub fn felt(name: impl Into<String>) -> Self {
Self::new_typed(SchemaType::native_felt(), name)
}
pub fn word(name: impl Into<String>) -> Self {
Self::new_typed(SchemaType::native_word(), name)
}
pub fn u8(name: impl Into<String>) -> Self {
Self::new_typed(SchemaType::u8(), name)
}
pub fn u16(name: impl Into<String>) -> Self {
Self::new_typed(SchemaType::u16(), name)
}
pub fn u32(name: impl Into<String>) -> Self {
Self::new_typed(SchemaType::u32(), name)
}
pub fn bool(name: impl Into<String>) -> Self {
Self::new_typed(SchemaType::bool(), name)
}
pub fn with_default(self, default_value: Felt) -> Self {
FeltSchema {
default_value: Some(default_value),
..self
}
}
pub fn with_description(self, description: impl Into<String>) -> Self {
FeltSchema {
description: Some(description.into()),
..self
}
}
pub fn felt_type(&self) -> SchemaType {
self.r#type.clone()
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn description(&self) -> Option<&String> {
self.description.as_ref()
}
pub fn default_value(&self) -> Option<Felt> {
self.default_value
}
pub(super) fn collect_init_value_requirements(
&self,
slot_prefix: StorageValueName,
requirements: &mut BTreeMap<StorageValueName, SchemaRequirement>,
) -> Result<(), ComponentMetadataError> {
if self.r#type == SchemaType::void() {
return Ok(());
}
let Some(name) = self.name.as_deref() else {
return Err(ComponentMetadataError::InvalidSchema(
"non-void felt elements must be named".into(),
));
};
let value_name =
StorageValueName::from_slot_name_with_suffix(slot_prefix.slot_name(), name)
.map_err(|err| ComponentMetadataError::InvalidSchema(err.to_string()))?;
let default_value = self
.default_value
.map(|felt| SCHEMA_TYPE_REGISTRY.display_felt(&self.r#type, felt));
if requirements
.insert(
value_name.clone(),
SchemaRequirement {
description: self.description.clone(),
r#type: self.r#type.clone(),
default_value,
},
)
.is_some()
{
return Err(ComponentMetadataError::DuplicateInitValueName(value_name));
}
Ok(())
}
pub(crate) fn try_build_felt(
&self,
init_storage_data: &InitStorageData,
slot_name: &StorageSlotName,
) -> Result<Felt, ComponentMetadataError> {
let value_name = match self.name.as_deref() {
Some(name) => Some(
StorageValueName::from_slot_name_with_suffix(slot_name, name)
.map_err(|err| ComponentMetadataError::InvalidSchema(err.to_string()))?,
),
None => None,
};
if let Some(value_name) = value_name.clone()
&& let Some(raw_value) = init_storage_data.value_entry(&value_name)
{
match raw_value {
WordValue::Atomic(raw) => {
let felt = SCHEMA_TYPE_REGISTRY
.try_parse_felt(&self.r#type, raw)
.map_err(ComponentMetadataError::StorageValueParsingError)?;
return Ok(felt);
},
WordValue::Elements(_) => {
return Err(ComponentMetadataError::InvalidInitStorageValue(
value_name,
"expected an atomic value, got a 4-element array".into(),
));
},
WordValue::FullyTyped(_) => {
return Err(ComponentMetadataError::InvalidInitStorageValue(
value_name,
"expected an atomic value, got a word".into(),
));
},
}
}
if self.r#type == SchemaType::void() {
return Ok(Felt::ZERO);
}
if let Some(default_value) = self.default_value {
return Ok(default_value);
}
let Some(value_name) = value_name else {
return Err(ComponentMetadataError::InvalidSchema(
"non-void felt elements must be named".into(),
));
};
Err(ComponentMetadataError::InitValueNotProvided(value_name))
}
pub(super) fn validate(&self) -> Result<(), ComponentMetadataError> {
if let Some(description) = self.description.as_deref() {
validate_description_ascii(description)?;
}
let type_exists = SCHEMA_TYPE_REGISTRY.contains_felt_type(&self.felt_type());
if !type_exists {
return Err(ComponentMetadataError::InvalidType(
self.felt_type().to_string(),
"Felt".into(),
));
}
if self.r#type == SchemaType::void() {
if self.name.is_some() {
return Err(ComponentMetadataError::InvalidSchema(
"void felt elements must be unnamed".into(),
));
}
if self.default_value.is_some() {
return Err(ComponentMetadataError::InvalidSchema(
"void felt elements cannot define `default-value`".into(),
));
}
return Ok(());
}
if self.name.is_none() {
return Err(ComponentMetadataError::InvalidSchema(
"non-void felt elements must be named".into(),
));
}
if let Some(value) = self.default_value {
SCHEMA_TYPE_REGISTRY
.validate_felt_value(&self.felt_type(), value)
.map_err(ComponentMetadataError::StorageValueParsingError)?;
}
Ok(())
}
pub(super) fn write_into_with_optional_defaults<W: ByteWriter>(
&self,
target: &mut W,
include_defaults: bool,
) {
target.write(&self.name);
target.write(&self.description);
target.write(&self.r#type);
let default_value = if include_defaults { self.default_value } else { None };
target.write(default_value);
}
}
impl Serializable for FeltSchema {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.write_into_with_optional_defaults(target, true);
}
}
impl Deserializable for FeltSchema {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let name = Option::<String>::read_from(source)?;
let description = Option::<String>::read_from(source)?;
let r#type = SchemaType::read_from(source)?;
let default_value = Option::<Felt>::read_from(source)?;
Ok(FeltSchema { name, description, r#type, default_value })
}
}