use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use super::super::type_registry::{SCHEMA_TYPE_REGISTRY, SchemaRequirement, SchemaType};
use super::super::{InitStorageData, StorageValueName};
use super::FeltSchema;
use crate::account::StorageSlotName;
use crate::errors::ComponentMetadataError;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use crate::{Felt, Word};
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(clippy::large_enum_variant)]
pub enum WordSchema {
Simple {
r#type: SchemaType,
default_value: Option<Word>,
},
Composite { value: [FeltSchema; 4] },
}
impl WordSchema {
pub fn new_simple(r#type: SchemaType) -> Self {
WordSchema::Simple { r#type, default_value: None }
}
pub fn new_simple_with_default(r#type: SchemaType, default_value: Word) -> Self {
WordSchema::Simple {
r#type,
default_value: Some(default_value),
}
}
pub fn new_value(value: impl Into<[FeltSchema; 4]>) -> Self {
WordSchema::Composite { value: value.into() }
}
pub fn value(&self) -> Option<&[FeltSchema; 4]> {
match self {
WordSchema::Composite { value } => Some(value),
WordSchema::Simple { .. } => None,
}
}
pub fn word_type(&self) -> SchemaType {
match self {
WordSchema::Simple { r#type, .. } => r#type.clone(),
WordSchema::Composite { .. } => SchemaType::native_word(),
}
}
pub(super) fn collect_init_value_requirements(
&self,
value_name: StorageValueName,
description: Option<String>,
requirements: &mut BTreeMap<StorageValueName, SchemaRequirement>,
) -> Result<(), ComponentMetadataError> {
match self {
WordSchema::Simple { r#type, default_value } => {
if *r#type == SchemaType::void() {
return Ok(());
}
let default_value = default_value.map(|word| {
SCHEMA_TYPE_REGISTRY.display_word(r#type, word).value().to_string()
});
if requirements
.insert(
value_name.clone(),
SchemaRequirement {
description,
r#type: r#type.clone(),
default_value,
},
)
.is_some()
{
return Err(ComponentMetadataError::DuplicateInitValueName(value_name));
}
Ok(())
},
WordSchema::Composite { value } => {
for felt in value.iter() {
felt.collect_init_value_requirements(value_name.clone(), requirements)?;
}
Ok(())
},
}
}
pub(super) fn validate(&self) -> Result<(), ComponentMetadataError> {
let type_exists = SCHEMA_TYPE_REGISTRY.contains_word_type(&self.word_type());
if !type_exists {
return Err(ComponentMetadataError::InvalidType(
self.word_type().to_string(),
"Word".into(),
));
}
if let WordSchema::Simple {
r#type,
default_value: Some(default_value),
} = self
{
SCHEMA_TYPE_REGISTRY
.validate_word_value(r#type, *default_value)
.map_err(ComponentMetadataError::StorageValueParsingError)?;
}
if let Some(felts) = self.value() {
for felt in felts {
felt.validate()?;
}
}
Ok(())
}
pub(crate) fn try_build_word(
&self,
init_storage_data: &InitStorageData,
slot_name: &StorageSlotName,
) -> Result<Word, ComponentMetadataError> {
let slot_prefix = StorageValueName::from_slot_name(slot_name);
let slot_value = init_storage_data.slot_value_entry(slot_name);
let has_fields = init_storage_data.has_field_entries_for_slot(slot_name);
if init_storage_data.map_entries(slot_name).is_some() {
return Err(ComponentMetadataError::InvalidInitStorageValue(
slot_prefix,
"expected a value, got a map".into(),
));
}
match self {
WordSchema::Simple { r#type, default_value } => {
if has_fields {
return Err(ComponentMetadataError::InvalidInitStorageValue(
slot_prefix,
"expected a value, got field entries".into(),
));
}
match slot_value {
Some(value) => {
super::parse_storage_value_with_schema(self, value, &slot_prefix)
},
None => {
if *r#type == SchemaType::void() {
Ok(Word::empty())
} else {
default_value.as_ref().copied().ok_or_else(|| {
ComponentMetadataError::InitValueNotProvided(slot_prefix)
})
}
},
}
},
WordSchema::Composite { value } => {
if let Some(value) = slot_value {
if has_fields {
return Err(ComponentMetadataError::InvalidInitStorageValue(
slot_prefix,
"expected a single value, got both value and field entries".into(),
));
}
return super::parse_storage_value_with_schema(self, value, &slot_prefix);
}
let mut result = [Felt::ZERO; 4];
for (index, felt_schema) in value.iter().enumerate() {
result[index] = felt_schema.try_build_felt(init_storage_data, slot_name)?;
}
Ok(Word::from(result))
},
}
}
pub(crate) fn validate_word_value(
&self,
slot_prefix: &StorageValueName,
label: &str,
word: Word,
) -> Result<(), ComponentMetadataError> {
match self {
WordSchema::Simple { r#type, .. } => {
SCHEMA_TYPE_REGISTRY.validate_word_value(r#type, word).map_err(|err| {
ComponentMetadataError::InvalidInitStorageValue(
slot_prefix.clone(),
format!("{label} does not match `{}`: {err}", r#type),
)
})
},
WordSchema::Composite { value } => {
for (index, felt_schema) in value.iter().enumerate() {
let felt_type = felt_schema.felt_type();
SCHEMA_TYPE_REGISTRY.validate_felt_value(&felt_type, word[index]).map_err(
|err| {
ComponentMetadataError::InvalidInitStorageValue(
slot_prefix.clone(),
format!("{label}[{index}] does not match `{felt_type}`: {err}"),
)
},
)?;
}
Ok(())
},
}
}
pub(super) fn write_into_with_optional_defaults<W: ByteWriter>(
&self,
target: &mut W,
include_defaults: bool,
) {
match self {
WordSchema::Simple { r#type, default_value } => {
target.write_u8(0);
target.write(r#type);
let default_value = if include_defaults { *default_value } else { None };
target.write(default_value);
},
WordSchema::Composite { value } => {
target.write_u8(1);
for felt in value.iter() {
felt.write_into_with_optional_defaults(target, include_defaults);
}
},
}
}
}
impl Serializable for WordSchema {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.write_into_with_optional_defaults(target, true);
}
}
impl Deserializable for WordSchema {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let tag = source.read_u8()?;
match tag {
0 => {
let r#type = SchemaType::read_from(source)?;
let default_value = Option::<Word>::read_from(source)?;
Ok(WordSchema::Simple { r#type, default_value })
},
1 => {
let value = <[FeltSchema; 4]>::read_from(source)?;
Ok(WordSchema::Composite { value })
},
other => Err(DeserializationError::InvalidValue(format!(
"unknown tag '{other}' for WordSchema"
))),
}
}
}
impl From<SchemaType> for WordSchema {
fn from(r#type: SchemaType) -> Self {
WordSchema::new_simple(r#type)
}
}
impl From<[FeltSchema; 4]> for WordSchema {
fn from(value: [FeltSchema; 4]) -> Self {
WordSchema::new_value(value)
}
}
impl From<[Felt; 4]> for WordSchema {
fn from(value: [Felt; 4]) -> Self {
WordSchema::new_simple_with_default(SchemaType::native_word(), Word::from(value))
}
}