diff --git a/Cargo.lock b/Cargo.lock index 34bb135760..57c8f1433e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8506,6 +8506,7 @@ name = "torii-grpc" version = "0.3.14" dependencies = [ "bytes", + "crypto-bigint", "dojo-types", "futures", "futures-util", diff --git a/Cargo.toml b/Cargo.toml index 25ae200b19..a4085f9557 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ clap_complete = "4.3" colored = "2" console = "0.15.7" convert_case = "0.6.0" +crypto-bigint = { version = "0.5.3", features = [ "serde" ] } env_logger = "0.10.0" flate2 = "1.0.24" futures = "0.3.28" diff --git a/crates/dojo-types/src/primitive.rs b/crates/dojo-types/src/primitive.rs index 2e91d0fdbb..49d67fc9a5 100644 --- a/crates/dojo-types/src/primitive.rs +++ b/crates/dojo-types/src/primitive.rs @@ -1,10 +1,22 @@ use crypto_bigint::{Encoding, U256}; use serde::{Deserialize, Serialize}; use starknet::core::types::{FieldElement, ValueOutOfRangeError}; +use strum::IntoEnumIterator; use strum_macros::{AsRefStr, Display, EnumIter, EnumString}; #[derive( - AsRefStr, Display, EnumIter, EnumString, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, + AsRefStr, + Display, + EnumIter, + EnumString, + Copy, + Clone, + Debug, + Serialize, + Deserialize, + PartialEq, + Hash, + Eq, )] #[serde(tag = "scalar_type", content = "value")] #[strum(serialize_all = "lowercase")] @@ -101,6 +113,14 @@ impl Primitive { set_primitive!(set_class_hash, ClassHash, FieldElement); set_primitive!(set_contract_address, ContractAddress, FieldElement); + pub fn to_numeric(&self) -> usize { + Self::iter().position(|p| p == *self).unwrap() + } + + pub fn from_numeric(value: usize) -> Option { + Self::iter().nth(value) + } + pub fn to_sql_type(&self) -> SqlType { match self { Primitive::U8(_) diff --git a/crates/dojo-types/src/schema.rs b/crates/dojo-types/src/schema.rs index f791c7bb96..a1e42206b7 100644 --- a/crates/dojo-types/src/schema.rs +++ b/crates/dojo-types/src/schema.rs @@ -6,7 +6,7 @@ use strum_macros::AsRefStr; use crate::primitive::{Primitive, PrimitiveError}; /// Represents a model member. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Hash, Eq)] pub struct Member { pub name: String, #[serde(rename = "member_type")] @@ -31,7 +31,7 @@ pub struct ModelMetadata { } /// Represents all possible types in Cairo -#[derive(AsRefStr, Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(AsRefStr, Clone, Debug, Serialize, Deserialize, PartialEq, Hash, Eq)] #[serde(tag = "type", content = "content")] #[serde(rename_all = "lowercase")] pub enum Ty { @@ -216,7 +216,7 @@ impl std::fmt::Display for Ty { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Hash, Eq)] pub struct Struct { pub name: String, pub children: Vec, @@ -241,14 +241,14 @@ pub enum EnumError { OptionInvalid, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Hash, Eq)] pub struct Enum { pub name: String, pub option: Option, pub options: Vec, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Hash, Eq)] pub struct EnumOption { pub name: String, pub ty: Ty, diff --git a/crates/torii/client/Cargo.toml b/crates/torii/client/Cargo.toml index 4e4ab8e240..c777de0701 100644 --- a/crates/torii/client/Cargo.toml +++ b/crates/torii/client/Cargo.toml @@ -7,7 +7,7 @@ version.workspace = true [dependencies] async-trait.workspace = true -crypto-bigint = "0.5.3" +crypto-bigint.workspace = true dojo-types = { path = "../../dojo-types" } dojo-world = { path = "../../dojo-world", features = [ "contracts" ] } futures-util = "0.3.28" diff --git a/crates/torii/client/src/client/mod.rs b/crates/torii/client/src/client/mod.rs index ba713b5c5a..7bed643a32 100644 --- a/crates/torii/client/src/client/mod.rs +++ b/crates/torii/client/src/client/mod.rs @@ -17,7 +17,8 @@ use starknet::providers::JsonRpcClient; use starknet_crypto::FieldElement; use tokio::sync::RwLock as AsyncRwLock; use torii_grpc::client::EntityUpdateStreaming; -use torii_grpc::types::KeysClause; +use torii_grpc::proto::world::RetrieveEntitiesResponse; +use torii_grpc::types::{Entity, KeysClause, Query}; use self::error::{Error, ParseError}; use self::storage::ModelStorage; @@ -98,6 +99,18 @@ impl Client { self.subscribed_entities.entities_keys.read() } + /// Retrieves entities matching specified keys and/or model name in query parameter. + /// + /// The query can include keys and a model name, both optional. Without parameters, it fetches + /// all entities, which is less efficient as it requires additional queries for each + /// entity's model data. Specifying a model name optimizes the process by limiting the + /// retrieval to entities with that model, requiring just one query. + pub async fn entities(&self, query: Query) -> Result, Error> { + let mut grpc_client = self.inner.write().await; + let RetrieveEntitiesResponse { entities } = grpc_client.retrieve_entities(query).await?; + Ok(entities.into_iter().map(TryInto::try_into).collect::, _>>()?) + } + /// Returns the model value of an entity. /// /// This function will only return `None`, if `model` doesn't exist. If there is no entity with diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index c05831c664..883470e1b1 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -177,14 +177,9 @@ pub fn build_sql_query(model_schemas: &Vec) -> Result { } } - let primary_table = model_schemas[0].name(); let mut global_selections = Vec::new(); - let mut global_tables = model_schemas - .iter() - .enumerate() - .filter(|(index, _)| *index != 0) // primary_table don't `JOIN` itself - .map(|(_, schema)| schema.name()) - .collect::>(); + let mut global_tables = + model_schemas.iter().enumerate().map(|(_, schema)| schema.name()).collect::>(); for ty in model_schemas { let schema = ty.as_struct().expect("schema should be struct"); @@ -206,11 +201,11 @@ pub fn build_sql_query(model_schemas: &Vec) -> Result { let selections_clause = global_selections.join(", "); let join_clause = global_tables .into_iter() - .map(|table| format!(" LEFT JOIN {table} ON {primary_table}.entity_id = {table}.entity_id")) + .map(|table| format!(" LEFT JOIN {table} ON entities.id = {table}.entity_id")) .collect::>() .join(" "); - Ok(format!("SELECT {selections_clause} FROM {primary_table}{join_clause}")) + Ok(format!("SELECT entities.keys, {selections_clause} FROM entities{join_clause}")) } /// Populate the values of a Ty (schema) from SQLite row. @@ -509,12 +504,14 @@ mod tests { }); let query = build_sql_query(&vec![ty]).unwrap(); + println!("{query}"); assert_eq!( query, - "SELECT Position.external_name AS \"Position.name\", Position.external_age AS \ - \"Position.age\", Position$Vec2.external_x AS \"Position$Vec2.x\", \ - Position$Vec2.external_y AS \"Position$Vec2.y\" FROM Position LEFT JOIN \ - Position$Vec2 ON Position.entity_id = Position$Vec2.entity_id" + "SELECT entities.keys, Position.external_name AS \"Position.name\", \ + Position.external_age AS \"Position.age\", Position$Vec2.external_x AS \ + \"Position$Vec2.x\", Position$Vec2.external_y AS \"Position$Vec2.y\" FROM entities \ + LEFT JOIN Position ON entities.id = Position.entity_id LEFT JOIN Position$Vec2 ON \ + entities.id = Position$Vec2.entity_id" ); } } diff --git a/crates/torii/grpc/Cargo.toml b/crates/torii/grpc/Cargo.toml index 451f62d02a..b1101c82fc 100644 --- a/crates/torii/grpc/Cargo.toml +++ b/crates/torii/grpc/Cargo.toml @@ -19,6 +19,7 @@ torii-core = { path = "../core", optional = true } serde.workspace = true strum_macros.workspace = true +crypto-bigint.workspace = true # server hex.workspace = true diff --git a/crates/torii/grpc/proto/schema.proto b/crates/torii/grpc/proto/schema.proto new file mode 100644 index 0000000000..7c6e5a9cd9 --- /dev/null +++ b/crates/torii/grpc/proto/schema.proto @@ -0,0 +1,62 @@ +syntax = "proto3"; +package types; + +enum PrimitiveType { + U8 = 0; + U16 = 1; + U32 = 2; + U64 = 3; + U128 = 4; + U256 = 5; + USIZE = 6; + BOOL = 7; + FELT252 = 8; + CLASS_HASH = 9; + CONTRACT_ADDRESS = 10; +} + +message EnumOption { + string name = 1; + Ty ty = 2; +} + +message Enum { + string name = 1; + uint32 option = 2; + repeated EnumOption options = 3; +} + +message Primitive { + PrimitiveType type = 1; + Value value = 2; +} + +message Struct { + string name = 1; + repeated Member children = 2; +} + +message Ty { + oneof ty_type { + Primitive primitive = 2; + Enum enum = 3; + Struct struct = 4; + // TODO: Tuple + } +} + +message Member { + string name = 1; + Ty ty = 2; + bool key = 3; +} + +message Value { + oneof value_type { + string string_value = 2; + int64 int_value = 3; + uint64 uint_value = 4; + bool bool_value = 5; + bytes byte_value = 6; + } +} diff --git a/crates/torii/grpc/proto/types.proto b/crates/torii/grpc/proto/types.proto index 873cabd4b7..e413817e22 100644 --- a/crates/torii/grpc/proto/types.proto +++ b/crates/torii/grpc/proto/types.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package types; +import "schema.proto"; + message WorldMetadata { // The hex-encoded address of the world. string world_address = 1; @@ -29,31 +31,10 @@ message ModelMetadata { bytes schema = 6; } -message Enum { - // Variant - uint32 option = 1; - // Variants of the enum - repeated string options = 2; -} - -message Member { - // Name of the member - string name = 1; - // Type of member - oneof member_type { - Value value = 2; - Enum enum = 3; - Model struct = 4; - } -} - message Model { - // Name of the model string name = 1; - // Members of the model repeated Member members = 2; } - message Entity { // The entity key bytes key = 1; @@ -85,7 +66,7 @@ message EntityUpdate { EntityDiff entity_diff = 2; } -message EntityQuery { +message Query { Clause clause = 1; uint32 limit = 2; uint32 offset = 3; @@ -129,14 +110,4 @@ enum ComparisonOperator { GTE = 3; LT = 4; LTE = 5; -} - -message Value { - oneof value_type { - string string_value = 1; - int64 int_value = 2; - uint64 uint_value = 3; - bool bool_value = 4; - bytes byte_value = 5; - } -} +} \ No newline at end of file diff --git a/crates/torii/grpc/proto/world.proto b/crates/torii/grpc/proto/world.proto index ec6dccc469..2f55a2cc72 100644 --- a/crates/torii/grpc/proto/world.proto +++ b/crates/torii/grpc/proto/world.proto @@ -39,7 +39,7 @@ message SubscribeEntitiesResponse { message RetrieveEntitiesRequest { // The entities to retrieve - types.EntityQuery query = 1; + types.Query query = 1; } message RetrieveEntitiesResponse { diff --git a/crates/torii/grpc/src/client.rs b/crates/torii/grpc/src/client.rs index e4097849c3..0e2ef98e1b 100644 --- a/crates/torii/grpc/src/client.rs +++ b/crates/torii/grpc/src/client.rs @@ -1,13 +1,17 @@ //! Client implementation for the gRPC service. +use std::num::ParseIntError; + use futures_util::stream::MapOk; use futures_util::{Stream, StreamExt, TryStreamExt}; use proto::world::{world_client, SubscribeEntitiesRequest}; -use starknet::core::types::{FromStrError, StateUpdate}; +use starknet::core::types::{FromByteSliceError, FromStrError, StateUpdate}; use starknet_crypto::FieldElement; -use crate::proto::world::{MetadataRequest, SubscribeEntitiesResponse}; +use crate::proto::world::{ + MetadataRequest, RetrieveEntitiesRequest, RetrieveEntitiesResponse, SubscribeEntitiesResponse, +}; use crate::proto::{self}; -use crate::types::KeysClause; +use crate::types::{KeysClause, Query}; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -15,8 +19,14 @@ pub enum Error { Grpc(tonic::Status), #[error("Missing expected data")] MissingExpectedData, + #[error("Unsupported type")] + UnsupportedType, + #[error(transparent)] + ParseStr(FromStrError), + #[error(transparent)] + SliceError(FromByteSliceError), #[error(transparent)] - Parsing(FromStrError), + ParseInt(ParseIntError), #[cfg(not(target_arch = "wasm32"))] #[error(transparent)] @@ -61,7 +71,15 @@ impl WorldClient { .await .map_err(Error::Grpc) .and_then(|res| res.into_inner().metadata.ok_or(Error::MissingExpectedData)) - .and_then(|metadata| metadata.try_into().map_err(Error::Parsing)) + .and_then(|metadata| metadata.try_into().map_err(Error::ParseStr)) + } + + pub async fn retrieve_entities( + &mut self, + query: Query, + ) -> Result { + let request = RetrieveEntitiesRequest { query: Some(query.into()) }; + self.inner.retrieve_entities(request).await.map_err(Error::Grpc).map(|res| res.into_inner()) } /// Subscribe to the state diff for a set of entities of a World. diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index 398343b9c7..dccb730239 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -128,15 +128,21 @@ impl DojoWorld { .collect::, Error>>()?; let keys_pattern = keys.join("/") + "/%"; - let db_entities: Vec<(String, String)> = sqlx::query_as( - "SELECT id, model_names FROM entities WHERE keys LIKE ? ORDER BY event_id ASC LIMIT ? \ - OFFSET ?", - ) - .bind(&keys_pattern) - .bind(limit) - .bind(offset) - .fetch_all(&self.pool) - .await?; + let query = r#" + SELECT entities.id, group_concat(entity_model.model_id) as model_names + FROM entities + JOIN entity_model ON entities.id = entity_model.entity_id + WHERE entities.keys LIKE ? + GROUP BY entities.id + ORDER BY entities.event_id DESC + LIMIT ? OFFSET ? + "#; + let db_entities: Vec<(String, String)> = sqlx::query_as(query) + .bind(&keys_pattern) + .bind(limit) + .bind(offset) + .fetch_all(&self.pool) + .await?; let mut entities = Vec::new(); for (entity_id, models_str) in db_entities { @@ -153,7 +159,7 @@ impl DojoWorld { let mut models = Vec::new(); for schema in schemas { let struct_ty = schema.as_struct().expect("schema should be struct"); - models.push(Self::map_row_to_model(&schema.name(), struct_ty, &row)?); + models.push(Self::map_row_to_proto(&schema.name(), struct_ty, &row)?.into()); } let key = FieldElement::from_str(&entity_id).map_err(ParseError::FromStr)?; @@ -237,7 +243,7 @@ impl DojoWorld { async fn retrieve_entities( &self, - query: proto::types::EntityQuery, + query: proto::types::Query, ) -> Result { let clause_type = query .clause @@ -260,84 +266,95 @@ impl DojoWorld { Ok(RetrieveEntitiesResponse { entities }) } - fn map_row_to_model( + /// Helper function to map Sqlite row to proto::types::Struct + // TODO: refactor this to use `map_row_to_ty` from core and implement Ty to protobuf conversion + fn map_row_to_proto( path: &str, struct_ty: &Struct, row: &SqliteRow, - ) -> Result { - let members = struct_ty + ) -> Result { + let children = struct_ty .children .iter() .map(|member| { let column_name = format!("{}.{}", path, member.name); let name = member.name.clone(); - let member = match &member.ty { + let ty_type = match &member.ty { Ty::Primitive(primitive) => { let value_type = match primitive { - Primitive::Bool(_) => proto::types::value::ValueType::BoolValue( + Primitive::Bool(_) => Some(proto::types::value::ValueType::BoolValue( row.try_get::(&column_name)?, - ), + )), Primitive::U8(_) | Primitive::U16(_) | Primitive::U32(_) | Primitive::U64(_) | Primitive::USize(_) => { let value = row.try_get::(&column_name)?; - proto::types::value::ValueType::UintValue(value as u64) + Some(proto::types::value::ValueType::UintValue(value as u64)) } Primitive::U128(_) - | Primitive::U256(_) | Primitive::Felt252(_) | Primitive::ClassHash(_) | Primitive::ContractAddress(_) => { let value = row.try_get::(&column_name)?; - proto::types::value::ValueType::StringValue(value) + let felt = + FieldElement::from_str(&value).map_err(ParseError::FromStr)?; + Some(proto::types::value::ValueType::ByteValue( + felt.to_bytes_be().to_vec(), + )) + } + Primitive::U256(_) => { + let value = row.try_get::(&column_name)?; + Some(proto::types::value::ValueType::StringValue(value)) } }; - proto::types::Member { - name, - member_type: Some(proto::types::member::MemberType::Value( - proto::types::Value { value_type: Some(value_type) }, - )), - } + Some(proto::types::ty::TyType::Primitive(proto::types::Primitive { + value: Some(proto::types::Value { value_type }), + r#type: primitive.to_numeric() as i32, + })) } Ty::Enum(enum_ty) => { let value = row.try_get::(&column_name)?; let options = enum_ty .options .iter() - .map(|e| e.name.to_string()) - .collect::>(); + .map(|r#enum| proto::types::EnumOption { + name: r#enum.name.clone(), + ty: None, + }) + .collect::>(); let option = - options.iter().position(|o| o == &value).expect("wrong enum value") + options.iter().position(|o| o.name == value).expect("wrong enum value") as u32; - proto::types::Member { - name: enum_ty.name.clone(), - member_type: Some(proto::types::member::MemberType::Enum( - proto::types::Enum { option, options }, - )), - } + + Some(proto::types::ty::TyType::Enum(proto::types::Enum { + option, + options, + name: member.ty.name(), + })) } Ty::Struct(struct_ty) => { let path = [path, &struct_ty.name].join("$"); - proto::types::Member { - name, - member_type: Some(proto::types::member::MemberType::Struct( - Self::map_row_to_model(&path, struct_ty, row)?, - )), - } + Some(proto::types::ty::TyType::Struct(Self::map_row_to_proto( + &path, struct_ty, row, + )?)) } ty => { unimplemented!("unimplemented type_enum: {ty}"); } }; - Ok(member) + Ok(proto::types::Member { + name, + ty: Some(proto::types::Ty { ty_type }), + key: member.key, + }) }) .collect::, Error>>()?; - Ok(proto::types::Model { name: struct_ty.name.clone(), members }) + Ok(proto::types::Struct { name: struct_ty.name.clone(), children }) } } diff --git a/crates/torii/grpc/src/types.rs b/crates/torii/grpc/src/types.rs index c2c834cf42..632d2d763c 100644 --- a/crates/torii/grpc/src/types.rs +++ b/crates/torii/grpc/src/types.rs @@ -1,14 +1,28 @@ use std::collections::HashMap; use std::str::FromStr; -use dojo_types::schema::Ty; +use dojo_types::primitive::Primitive; +use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; use serde::{Deserialize, Serialize}; use starknet::core::types::{ ContractStorageDiffItem, FromByteSliceError, FromStrError, StateDiff, StateUpdate, StorageEntry, }; use starknet_crypto::FieldElement; -use crate::proto; +use crate::client::Error as ClientError; +use crate::proto::{self}; + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub struct Entity { + pub key: FieldElement, + pub models: Vec, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub struct Model { + pub name: String, + pub members: Vec, +} #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] pub struct Query { @@ -62,7 +76,13 @@ pub enum ComparisonOperator { } #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] -pub enum Value { +pub struct Value { + pub primitive_type: Primitive, + pub value_type: ValueType, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub enum ValueType { String(String), Int(i64), UInt(u64), @@ -105,7 +125,7 @@ impl TryFrom for dojo_types::WorldMetadata { } } -impl From for proto::types::EntityQuery { +impl From for proto::types::Query { fn from(value: Query) -> Self { Self { clause: Some(value.clause.into()), limit: value.limit, offset: value.offset } } @@ -150,6 +170,34 @@ impl TryFrom for KeysClause { } } +impl TryFrom for Entity { + type Error = ClientError; + fn try_from(entity: proto::types::Entity) -> Result { + Ok(Self { + key: FieldElement::from_byte_slice_be(&entity.key).map_err(ClientError::SliceError)?, + models: entity + .models + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + +impl TryFrom for Model { + type Error = ClientError; + fn try_from(model: proto::types::Model) -> Result { + Ok(Self { + name: model.name, + members: model + .members + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + impl From for proto::types::MemberClause { fn from(value: MemberClause) -> Self { Self { @@ -173,26 +221,137 @@ impl From for proto::types::CompositeClause { impl From for proto::types::Value { fn from(value: Value) -> Self { - match value { - Value::String(val) => { - Self { value_type: Some(proto::types::value::ValueType::StringValue(val)) } + let value_type = match value.value_type { + ValueType::String(val) => Some(proto::types::value::ValueType::StringValue(val)), + ValueType::Int(val) => Some(proto::types::value::ValueType::IntValue(val)), + ValueType::UInt(val) => Some(proto::types::value::ValueType::UintValue(val)), + ValueType::Bool(val) => Some(proto::types::value::ValueType::BoolValue(val)), + ValueType::Bytes(val) => Some(proto::types::value::ValueType::ByteValue(val)), + }; + + Self { value_type } + } +} + +impl From for EnumOption { + fn from(option: proto::types::EnumOption) -> Self { + EnumOption { name: option.name, ty: Ty::Tuple(vec![]) } + } +} + +impl From for Enum { + fn from(r#enum: proto::types::Enum) -> Self { + Enum { + name: r#enum.name.clone(), + option: Some(r#enum.option as u8), + options: r#enum.options.into_iter().map(Into::into).collect::>(), + } + } +} + +impl TryFrom for Struct { + type Error = ClientError; + fn try_from(r#struct: proto::types::Struct) -> Result { + Ok(Struct { + name: r#struct.name, + children: r#struct + .children + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + +impl From for proto::types::Model { + fn from(r#struct: proto::types::Struct) -> Self { + Self { name: r#struct.name, members: r#struct.children } + } +} + +// FIX: weird catch-22 issue - prost Enum has `try_from` trait we can use, however, using it results +// in wasm compile err about From missing. Implementing that trait results in clippy error +// about duplicate From... Workaround is to use deprecated `from_i32` and allow deprecation +// warning. +#[allow(deprecated)] +impl TryFrom for Primitive { + type Error = ClientError; + fn try_from(primitive: proto::types::Primitive) -> Result { + let primitive_type = primitive.r#type; + let value_type = primitive + .value + .ok_or(ClientError::MissingExpectedData)? + .value_type + .ok_or(ClientError::MissingExpectedData)?; + + let primitive = match &value_type { + proto::types::value::ValueType::BoolValue(bool) => Primitive::Bool(Some(*bool)), + proto::types::value::ValueType::UintValue(int) => { + match proto::types::PrimitiveType::from_i32(primitive_type) { + Some(proto::types::PrimitiveType::U8) => Primitive::U8(Some(*int as u8)), + Some(proto::types::PrimitiveType::U16) => Primitive::U16(Some(*int as u16)), + Some(proto::types::PrimitiveType::U32) => Primitive::U32(Some(*int as u32)), + Some(proto::types::PrimitiveType::U64) => Primitive::U64(Some(*int)), + Some(proto::types::PrimitiveType::Usize) => Primitive::USize(Some(*int as u32)), + _ => return Err(ClientError::UnsupportedType), + } } - Value::Int(val) => { - Self { value_type: Some(proto::types::value::ValueType::IntValue(val)) } + proto::types::value::ValueType::ByteValue(bytes) => { + match proto::types::PrimitiveType::from_i32(primitive_type) { + Some(proto::types::PrimitiveType::U128) + | Some(proto::types::PrimitiveType::Felt252) + | Some(proto::types::PrimitiveType::ClassHash) + | Some(proto::types::PrimitiveType::ContractAddress) => { + Primitive::Felt252(Some( + FieldElement::from_byte_slice_be(bytes) + .map_err(ClientError::SliceError)?, + )) + } + _ => return Err(ClientError::UnsupportedType), + } } - Value::UInt(val) => { - Self { value_type: Some(proto::types::value::ValueType::UintValue(val)) } + proto::types::value::ValueType::StringValue(_string) => { + match proto::types::PrimitiveType::from_i32(primitive_type) { + Some(proto::types::PrimitiveType::U256) => { + // TODO: Handle u256 + Primitive::U256(None) + } + _ => return Err(ClientError::UnsupportedType), + } } - Value::Bool(val) => { - Self { value_type: Some(proto::types::value::ValueType::BoolValue(val)) } + _ => { + return Err(ClientError::UnsupportedType); } - Value::Bytes(val) => { - Self { value_type: Some(proto::types::value::ValueType::ByteValue(val)) } + }; + + Ok(primitive) + } +} + +impl TryFrom for Ty { + type Error = ClientError; + fn try_from(ty: proto::types::Ty) -> Result { + match ty.ty_type.ok_or(ClientError::MissingExpectedData)? { + proto::types::ty::TyType::Primitive(primitive) => { + Ok(Ty::Primitive(primitive.try_into()?)) } + proto::types::ty::TyType::Struct(r#struct) => Ok(Ty::Struct(r#struct.try_into()?)), + proto::types::ty::TyType::Enum(r#enum) => Ok(Ty::Enum(r#enum.into())), } } } +impl TryFrom for Member { + type Error = ClientError; + fn try_from(member: proto::types::Member) -> Result { + Ok(Member { + name: member.name, + ty: member.ty.ok_or(ClientError::MissingExpectedData)?.try_into()?, + key: member.key, + }) + } +} + impl TryFrom for StorageEntry { type Error = FromStrError; fn try_from(value: proto::types::StorageEntry) -> Result {