diff --git a/crates/dojo-types/src/primitive.rs b/crates/dojo-types/src/primitive.rs index 49d67fc9a5..c2727e75e7 100644 --- a/crates/dojo-types/src/primitive.rs +++ b/crates/dojo-types/src/primitive.rs @@ -114,7 +114,19 @@ impl Primitive { set_primitive!(set_contract_address, ContractAddress, FieldElement); pub fn to_numeric(&self) -> usize { - Self::iter().position(|p| p == *self).unwrap() + match self { + Primitive::U8(_) => 0, + Primitive::U16(_) => 1, + Primitive::U32(_) => 2, + Primitive::U64(_) => 3, + Primitive::U128(_) => 4, + Primitive::U256(_) => 5, + Primitive::USize(_) => 6, + Primitive::Bool(_) => 7, + Primitive::Felt252(_) => 8, + Primitive::ClassHash(_) => 9, + Primitive::ContractAddress(_) => 10, + } } pub fn from_numeric(value: usize) -> Option { diff --git a/crates/torii/client/src/client/mod.rs b/crates/torii/client/src/client/mod.rs index 9520ef7782..08c2f1694d 100644 --- a/crates/torii/client/src/client/mod.rs +++ b/crates/torii/client/src/client/mod.rs @@ -18,7 +18,8 @@ use starknet_crypto::FieldElement; use tokio::sync::RwLock as AsyncRwLock; use torii_grpc::client::EntityUpdateStreaming; use torii_grpc::proto::world::RetrieveEntitiesResponse; -use torii_grpc::types::{Entity, KeysClause, Query}; +use torii_grpc::types::schema::Entity; +use torii_grpc::types::{KeysClause, Query}; use self::error::{Error, ParseError}; use self::storage::ModelStorage; diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index de83ad13cb..1e32ba5308 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -8,8 +8,7 @@ use std::pin::Pin; use std::str::FromStr; use std::sync::Arc; -use dojo_types::primitive::Primitive; -use dojo_types::schema::{Struct, Ty}; +use dojo_types::schema::Ty; use futures::Stream; use proto::world::{ MetadataRequest, MetadataResponse, RetrieveEntitiesRequest, RetrieveEntitiesResponse, @@ -28,7 +27,7 @@ use tonic::transport::Server; use tonic::{Request, Response, Status}; use torii_core::cache::ModelCache; use torii_core::error::{Error, ParseError, QueryError}; -use torii_core::model::build_sql_query; +use torii_core::model::{build_sql_query, map_row_to_ty}; use self::subscription::SubscribeRequest; use crate::proto::types::clause::ClauseType; @@ -132,11 +131,15 @@ impl DojoWorld { let entity_query = format!("{} WHERE entities.id = ?", build_sql_query(&schemas)?); let row = sqlx::query(&entity_query).bind(&entity_id).fetch_one(&self.pool).await?; - let mut models = Vec::with_capacity(schemas.len()); - for schema in schemas { - let struct_ty = schema.as_struct().expect("schema should be struct"); - models.push(Self::map_row_to_struct(&schema.name(), struct_ty, &row)?.into()); - } + let models = schemas + .iter() + .map(|s| { + let mut struct_ty = s.as_struct().expect("schema should be struct").to_owned(); + map_row_to_ty(&s.name(), &mut struct_ty, &row)?; + + Ok(struct_ty.try_into().unwrap()) + }) + .collect::, Error>>()?; let key = FieldElement::from_str(&entity_id).map_err(ParseError::FromStr)?; entities.push(proto::types::Entity { key: key.to_bytes_be().to_vec(), models }) @@ -310,103 +313,14 @@ impl DojoWorld { let models = schemas .iter() .map(|schema| { - let struct_ty = schema.as_struct().expect("schema should be struct"); - Self::map_row_to_struct(&schema.name(), struct_ty, row).map(Into::into) - }) - .collect::, Error>>()?; - - Ok(proto::types::Entity { key: key.to_bytes_be().to_vec(), models }) - } - - /// 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_struct( - path: &str, - struct_ty: &Struct, - row: &SqliteRow, - ) -> Result { - let children = struct_ty - .children - .iter() - .map(|member| { - let column_name = format!("{}.{}", path, member.name); - let name = member.name.clone(); - let ty_type = match &member.ty { - Ty::Primitive(primitive) => { - let value_type = match primitive { - 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)?; - Some(proto::types::value::ValueType::UintValue(value as u64)) - } - Primitive::U128(_) - | Primitive::Felt252(_) - | Primitive::ClassHash(_) - | Primitive::ContractAddress(_) => { - let value = row.try_get::(&column_name)?; - 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)) - } - }; - - 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(|r#enum| proto::types::EnumOption { - name: r#enum.name.clone(), - ty: None, - }) - .collect::>(); - let option = - options.iter().position(|o| o.name == value).expect("wrong enum value") - as u32; - - 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("$"); - Some(proto::types::ty::TyType::Struct(Self::map_row_to_struct( - &path, struct_ty, row, - )?)) - } - ty => { - unimplemented!("unimplemented type_enum: {ty}"); - } - }; + let mut struct_ty = schema.as_struct().expect("schema should be struct").to_owned(); + map_row_to_ty(&schema.name(), &mut struct_ty, row)?; - Ok(proto::types::Member { - name, - ty: Some(proto::types::Ty { ty_type }), - key: member.key, - }) + Ok(struct_ty.try_into().unwrap()) }) - .collect::, Error>>()?; + .collect::, Error>>()?; - Ok(proto::types::Struct { name: struct_ty.name.clone(), children }) + Ok(proto::types::Entity { key: key.to_bytes_be().to_vec(), models }) } } diff --git a/crates/torii/grpc/src/types.rs b/crates/torii/grpc/src/types/mod.rs similarity index 55% rename from crates/torii/grpc/src/types.rs rename to crates/torii/grpc/src/types/mod.rs index ddb6820cc2..25531536df 100644 --- a/crates/torii/grpc/src/types.rs +++ b/crates/torii/grpc/src/types/mod.rs @@ -2,27 +2,16 @@ use std::collections::HashMap; use std::str::FromStr; use dojo_types::primitive::Primitive; -use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; +use dojo_types::schema::Ty; use serde::{Deserialize, Serialize}; use starknet::core::types::{ ContractStorageDiffItem, FromByteSliceError, FromStrError, StateDiff, StateUpdate, StorageEntry, }; use starknet_crypto::FieldElement; -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, -} +pub mod schema; #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] pub struct Query { @@ -170,34 +159,6 @@ 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 { @@ -233,125 +194,6 @@ impl From for proto::types::Value { } } -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), - } - } - 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), - } - } - 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), - } - } - _ => { - return Err(ClientError::UnsupportedType); - } - }; - - 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 { diff --git a/crates/torii/grpc/src/types/schema.rs b/crates/torii/grpc/src/types/schema.rs new file mode 100644 index 0000000000..72047b6f06 --- /dev/null +++ b/crates/torii/grpc/src/types/schema.rs @@ -0,0 +1,267 @@ +use dojo_types::primitive::Primitive; +use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; +use serde::{Deserialize, Serialize}; +use starknet_crypto::FieldElement; + +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, +} + +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 TryFrom for proto::types::Ty { + type Error = ClientError; + fn try_from(ty: Ty) -> Result { + let ty_type = match ty { + Ty::Primitive(primitive) => { + Some(proto::types::ty::TyType::Primitive(primitive.try_into()?)) + } + Ty::Enum(r#enum) => Some(proto::types::ty::TyType::Enum(r#enum.into())), + Ty::Struct(r#struct) => Some(proto::types::ty::TyType::Struct(r#struct.try_into()?)), + Ty::Tuple(_) => unimplemented!("unimplemented typle type"), + }; + + Ok(proto::types::Ty { ty_type }) + } +} + +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 proto::types::Member { + type Error = ClientError; + fn try_from(member: Member) -> Result { + Ok(proto::types::Member { + name: member.name, + ty: Some(member.ty.try_into()?), + key: member.key, + }) + } +} + +impl From for EnumOption { + fn from(option: proto::types::EnumOption) -> Self { + EnumOption { name: option.name, ty: Ty::Tuple(vec![]) } + } +} + +impl From for proto::types::EnumOption { + fn from(option: EnumOption) -> Self { + proto::types::EnumOption { name: option.name, ty: None } + } +} + +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 From for proto::types::Enum { + fn from(r#enum: Enum) -> Self { + proto::types::Enum { + name: r#enum.name, + option: r#enum.option.expect("option value") as u32, + 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 TryFrom for proto::types::Struct { + type Error = ClientError; + fn try_from(r#struct: Struct) -> Result { + Ok(proto::types::Struct { + name: r#struct.name, + children: r#struct + .children + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + +impl TryFrom for proto::types::Model { + type Error = ClientError; + fn try_from(r#struct: Struct) -> Result { + let r#struct: proto::types::Struct = r#struct.try_into()?; + + Ok(r#struct.into()) + } +} + +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), + } + } + 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), + } + } + 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), + } + } + _ => { + return Err(ClientError::UnsupportedType); + } + }; + + Ok(primitive) + } +} + +impl TryFrom for proto::types::Primitive { + type Error = ClientError; + fn try_from(primitive: Primitive) -> Result { + use proto::types::value::ValueType; + + let value_type = match primitive { + Primitive::Bool(bool) => bool.map(ValueType::BoolValue), + Primitive::U8(u8) => u8.map(|val| ValueType::UintValue(val as u64)), + Primitive::U16(u16) => u16.map(|val| ValueType::UintValue(val as u64)), + Primitive::U32(u32) => u32.map(|val| ValueType::UintValue(val as u64)), + Primitive::U64(u64) => u64.map(ValueType::UintValue), + Primitive::USize(usize) => usize.map(|val| ValueType::UintValue(val as u64)), + Primitive::U128(u128) => { + u128.map(|val| ValueType::ByteValue(val.to_be_bytes().to_vec())) + } + Primitive::U256(u256) => u256.map(|val| ValueType::StringValue(val.to_string())), + Primitive::Felt252(felt) => { + felt.map(|val| ValueType::ByteValue(val.to_bytes_be().to_vec())) + } + Primitive::ClassHash(class) => { + class.map(|val| ValueType::ByteValue(val.to_bytes_be().to_vec())) + } + Primitive::ContractAddress(contract) => { + contract.map(|val| ValueType::ByteValue(val.to_bytes_be().to_vec())) + } + } + .expect("value expected"); + + Ok(proto::types::Primitive { + value: Some(proto::types::Value { value_type: Some(value_type) }), + r#type: primitive.to_numeric() as i32, + }) + } +} + +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())), + } + } +}