diff --git a/crates/dojo/bindgen/src/plugins/mod.rs b/crates/dojo/bindgen/src/plugins/mod.rs index 4470028a65..3b1b481482 100644 --- a/crates/dojo/bindgen/src/plugins/mod.rs +++ b/crates/dojo/bindgen/src/plugins/mod.rs @@ -74,15 +74,6 @@ impl Buffer { } } - /// Inserts string at the specified index. - /// - /// * `s` - The string to insert. - /// * `pos` - The position to insert the string at. - /// * `idx` - The index of the string to insert at. - pub fn insert_at_index(&mut self, s: String, idx: usize) { - self.0.insert(idx, s); - } - /// Finds position of the given string in the inner vec. /// /// * `pos` - The string to search for. diff --git a/crates/dojo/bindgen/src/plugins/typescript/generator/constants.rs b/crates/dojo/bindgen/src/plugins/typescript/generator/constants.rs index ce064ed6f9..735ca02792 100644 --- a/crates/dojo/bindgen/src/plugins/typescript/generator/constants.rs +++ b/crates/dojo/bindgen/src/plugins/typescript/generator/constants.rs @@ -10,24 +10,21 @@ pub const CAIRO_U256: &str = "u256"; pub const CAIRO_U256_STRUCT: &str = "U256"; pub const CAIRO_I128: &str = "i128"; pub const CAIRO_BOOL: &str = "bool"; +pub const CAIRO_OPTION: &str = "Option"; + +pub(crate) const CAIRO_OPTION_DEFAULT_VALUE: &str = "new CairoOption(CairoOptionVariant.None)"; pub const JS_BOOLEAN: &str = "boolean"; pub const JS_STRING: &str = "string"; pub const JS_BIGNUMBERISH: &str = "BigNumberish"; -pub(crate) const BIGNUMNERISH_IMPORT: &str = "import type { BigNumberish } from 'starknet';\n"; -pub(crate) const CAIRO_OPTION_IMPORT: &str = "import type { CairoOption } from 'starknet';\n"; -pub(crate) const CAIRO_ENUM_IMPORT: &str = "import type { CairoCustomEnum } from 'starknet';\n"; +pub(crate) const BIGNUMNERISH_IMPORT: &str = "import { BigNumberish } from 'starknet';\n"; +pub(crate) const CAIRO_OPTION_IMPORT: &str = "import { CairoOption } from 'starknet';\n"; +pub(crate) const CAIRO_ENUM_IMPORT: &str = "import { CairoCustomEnum } from 'starknet';\n"; pub(crate) const CAIRO_OPTION_TYPE_PATH: &str = "core::option::Option"; pub(crate) const SN_IMPORT_SEARCH: &str = "} from 'starknet';"; -pub(crate) const CAIRO_OPTION_TOKEN: &str = "CairoOption,"; +pub(crate) const CAIRO_OPTION_TOKEN: &str = "CairoOption, CairoOptionVariant,"; pub(crate) const CAIRO_ENUM_TOKEN: &str = "CairoCustomEnum,"; -pub(crate) const REMOVE_FIELD_ORDER_TYPE_DEF: &str = "type RemoveFieldOrder = T extends object - ? Omit< - { - [K in keyof T]: T[K] extends object ? RemoveFieldOrder : T[K]; - }, - 'fieldOrder' - > - : T;"; +pub(crate) const WITH_FIELD_ORDER_TYPE_DEF: &str = + "type WithFieldOrder = T & { fieldOrder: string[] };\n"; diff --git a/crates/dojo/bindgen/src/plugins/typescript/generator/enum.rs b/crates/dojo/bindgen/src/plugins/typescript/generator/enum.rs index a80df627c4..b72df23295 100644 --- a/crates/dojo/bindgen/src/plugins/typescript/generator/enum.rs +++ b/crates/dojo/bindgen/src/plugins/typescript/generator/enum.rs @@ -3,13 +3,9 @@ use cainome::parser::tokens::{Composite, CompositeType}; use super::constants::{CAIRO_ENUM_IMPORT, CAIRO_ENUM_TOKEN, SN_IMPORT_SEARCH}; use super::token_is_custom_enum; use crate::error::BindgenResult; -use crate::plugins::typescript::generator::JsType; +use crate::plugins::typescript::generator::JsPrimitiveType; use crate::plugins::{BindgenModelGenerator, Buffer}; -const CAIRO_ENUM_TYPE_IMPL: &str = "export type TypedCairoEnum = CairoCustomEnum & \ - {\n\tvariant: { [K in keyof T]: T[K] | undefined \ - };\n\tunwrap(): T[keyof T];\n}\n"; - pub(crate) struct TsEnumGenerator; impl TsEnumGenerator { @@ -24,10 +20,6 @@ impl TsEnumGenerator { buffer.insert_after(format!(" {CAIRO_ENUM_TOKEN}"), SN_IMPORT_SEARCH, "{", 1); } } - if !buffer.has(CAIRO_ENUM_TYPE_IMPL) { - let pos = buffer.pos(SN_IMPORT_SEARCH).unwrap(); - buffer.insert_at_index(CAIRO_ENUM_TYPE_IMPL.to_owned(), pos + 1); - } } } @@ -44,14 +36,16 @@ impl BindgenModelGenerator for TsEnumGenerator { export type {name} = {{ {variants} }} -export type {name}Enum = TypedCairoEnum<{name}>; +export type {name}Enum = CairoCustomEnum; ", path = token.type_path, name = token.type_name(), variants = token .inners .iter() - .map(|inner| { format!("\t{}: {};", inner.name, JsType::from(&inner.token)) }) + .map(|inner| { + format!("\t{}: {};", inner.name, JsPrimitiveType::from(&inner.token)) + }) .collect::>() .join("\n") ) diff --git a/crates/dojo/bindgen/src/plugins/typescript/generator/erc.rs b/crates/dojo/bindgen/src/plugins/typescript/generator/erc.rs deleted file mode 100644 index b731dcfe3c..0000000000 --- a/crates/dojo/bindgen/src/plugins/typescript/generator/erc.rs +++ /dev/null @@ -1,116 +0,0 @@ -use cainome::parser::tokens::Composite; - -use super::get_namespace_and_path; -use crate::error::BindgenResult; -use crate::plugins::{BindgenModelGenerator, Buffer}; - -const ERC_TORII_TPL: &str = "// Type definition for ERC__Balance struct -export type ERC__Type = 'ERC20' | 'ERC721'; -export interface ERC__Balance { - fieldOrder: string[]; - balance: string; - type: string; - tokenMetadata: ERC__Token; -} -export interface ERC__Token { - fieldOrder: string[]; - name: string; - symbol: string; - tokenId: string; - decimals: string; - contractAddress: string; -} -export interface ERC__Transfer { - fieldOrder: string[]; - from: string; - to: string; - amount: string; - type: string; - executedAt: string; - tokenMetadata: ERC__Token; - transactionHash: string; -}"; -const ERC_TORII_TYPES: &str = "\n\t\tERC__Balance: ERC__Balance,\n\t\tERC__Token: \ - ERC__Token,\n\t\tERC__Transfer: ERC__Transfer,"; -const ERC_TORII_INIT: &str = " -\t\tERC__Balance: { -\t\t\tfieldOrder: ['balance', 'type', 'tokenmetadata'], -\t\t\tbalance: '', -\t\t\ttype: 'ERC20', -\t\t\ttokenMetadata: { -\t\t\t\tfieldOrder: ['name', 'symbol', 'tokenId', 'decimals', 'contractAddress'], -\t\t\t\tname: '', -\t\t\t\tsymbol: '', -\t\t\t\ttokenId: '', -\t\t\t\tdecimals: '', -\t\t\t\tcontractAddress: '', -\t\t\t}, -\t\t}, -\t\tERC__Token: { -\t\t\tfieldOrder: ['name', 'symbol', 'tokenId', 'decimals', 'contractAddress'], -\t\t\tname: '', -\t\t\tsymbol: '', -\t\t\ttokenId: '', -\t\t\tdecimals: '', -\t\t\tcontractAddress: '', -\t\t}, -\t\tERC__Transfer: { -\t\t\tfieldOrder: ['from', 'to', 'amount', 'type', 'executed', 'tokenMetadata'], -\t\t\tfrom: '', -\t\t\tto: '', -\t\t\tamount: '', -\t\t\ttype: 'ERC20', -\t\t\texecutedAt: '', -\t\t\ttokenMetadata: { -\t\t\t\tfieldOrder: ['name', 'symbol', 'tokenId', 'decimals', 'contractAddress'], -\t\t\t\tname: '', -\t\t\t\tsymbol: '', -\t\t\t\ttokenId: '', -\t\t\t\tdecimals: '', -\t\t\t\tcontractAddress: '', -\t\t\t}, -\t\t\ttransactionHash: '', -\t\t}, -"; - -pub(crate) struct TsErcGenerator; - -impl TsErcGenerator { - fn add_schema_type(&self, buffer: &mut Buffer, token: &Composite) { - let (_, namespace, _) = get_namespace_and_path(token); - let schema_type = format!("export interface {namespace}SchemaType extends SchemaType"); - if buffer.has(&schema_type) { - if buffer.has(ERC_TORII_TYPES) { - return; - } - - buffer.insert_after(ERC_TORII_TYPES.to_owned(), &schema_type, ",", 2); - } - } - - fn add_schema_type_init(&self, buffer: &mut Buffer, token: &Composite) { - let (_, namespace, _) = get_namespace_and_path(token); - let const_type = format!("export const schema: {namespace}SchemaType"); - if buffer.has(&const_type) { - if buffer.has(ERC_TORII_INIT) { - return; - } - buffer.insert_after(ERC_TORII_INIT.to_owned(), &const_type, ",", 2); - } - } -} - -impl BindgenModelGenerator for TsErcGenerator { - fn generate(&self, token: &Composite, buffer: &mut Buffer) -> BindgenResult { - if buffer.has(ERC_TORII_TPL) { - return Ok(String::new()); - } - - // As this generator is separated from schema.rs we need to check if schema is present in - // buffer and also adding torii types to schema to query this data through grpc - self.add_schema_type(buffer, token); - self.add_schema_type_init(buffer, token); - - Ok(ERC_TORII_TPL.to_owned()) - } -} diff --git a/crates/dojo/bindgen/src/plugins/typescript/generator/function.rs b/crates/dojo/bindgen/src/plugins/typescript/generator/function.rs index e678ed5b60..28c9a6988a 100644 --- a/crates/dojo/bindgen/src/plugins/typescript/generator/function.rs +++ b/crates/dojo/bindgen/src/plugins/typescript/generator/function.rs @@ -3,7 +3,7 @@ use convert_case::{Case, Casing}; use dojo_world::contracts::naming; use super::constants::JS_BIGNUMBERISH; -use super::JsType; +use super::{token_is_custom_enum, JsPrimitiveInputType}; use crate::error::BindgenResult; use crate::plugins::{BindgenContractGenerator, Buffer}; use crate::DojoContract; @@ -16,7 +16,8 @@ impl TsFunctionGenerator { buffer.insert( 1, format!( - "import {{ Account, AccountInterface, {} }} from \"starknet\";", + "import {{ Account, AccountInterface, {}, CairoOption, CairoCustomEnum, \ + ByteArray }} from \"starknet\";", JS_BIGNUMBERISH ), ); @@ -25,7 +26,7 @@ impl TsFunctionGenerator { } fn setup_function_wrapper_start(&self, buffer: &mut Buffer) -> usize { - let fn_wrapper = "export async function setupWorld(provider: DojoProvider) {\n"; + let fn_wrapper = "export function setupWorld(provider: DojoProvider) {\n"; if !buffer.has(fn_wrapper) { buffer.push(fn_wrapper.to_owned()); @@ -93,24 +94,26 @@ impl TsFunctionGenerator { .fold(inputs, |mut acc, input| { let prefix = match &input.1 { Token::Composite(t) => { - if t.r#type == CompositeType::Enum { - "models." - } else if t.r#type == CompositeType::Struct - && !t.type_path.starts_with("core") + if !token_is_custom_enum(t) + && (t.r#type == CompositeType::Enum + || (t.r#type == CompositeType::Struct + && !t.type_path.starts_with("core")) + || t.r#type == CompositeType::Unknown) { - "models.Input" + "models." } else { "" } } _ => "", }; - acc.push(format!( - "{}: {}{}", - input.0.to_case(Case::Camel), - prefix, - JsType::from(&input.1) - )); + let mut type_input = JsPrimitiveInputType::from(&input.1).to_string(); + if type_input.contains("<") { + type_input = type_input.replace("<", format!("<{}", prefix).as_str()); + } else { + type_input = format!("{}{}", prefix, type_input); + } + acc.push(format!("{}: {}", input.0.to_case(Case::Camel), type_input)); acc }) .join(", ") @@ -231,7 +234,10 @@ impl BindgenContractGenerator for TsFunctionGenerator { #[cfg(test)] mod tests { - use cainome::parser::tokens::{CoreBasic, Function, Token}; + use cainome::parser::tokens::{ + Array, Composite, CompositeInner, CompositeInnerKind, CompositeType, CoreBasic, Function, + Token, + }; use cainome::parser::TokenizedAbi; use dojo_world::contracts::naming; @@ -317,6 +323,23 @@ mod tests { assert_eq!(expected, generator.format_function_inputs(&function)) } + #[test] + fn test_format_function_inputs_cairo_option() { + let generator = TsFunctionGenerator {}; + let function = create_function_with_option_param(); + let expected = + "snAccount: Account | AccountInterface, value: CairoOption"; + assert_eq!(expected, generator.format_function_inputs(&function)) + } + + #[test] + fn test_format_function_inputs_cairo_enum() { + let generator = TsFunctionGenerator {}; + let function = create_function_with_custom_enum(); + let expected = "snAccount: Account | AccountInterface, value: models.GatedType"; + assert_eq!(expected, generator.format_function_inputs(&function)) + } + #[test] fn test_format_function_calldata() { let generator = TsFunctionGenerator {}; @@ -488,4 +511,162 @@ mod tests { systems: vec![], } } + + fn create_function_with_option_param() -> Function { + Function { + name: "cairo_option".to_owned(), + state_mutability: cainome::parser::tokens::StateMutability::External, + inputs: vec![("value".to_owned(), Token::Composite(Composite { +type_path: "core::option::Option".to_owned(), + inners: vec![], + generic_args: vec![ + ( + "A".to_owned(), + Token::Composite( + Composite { + type_path: "tournament::ls15_components::models::tournament::GatedType".to_owned(), + inners: vec![ + CompositeInner { + index: 0, + name: "token".to_owned(), + kind: CompositeInnerKind::NotUsed, + token: Token::Composite( + Composite { + type_path: "tournament::ls15_components::models::tournament::GatedToken".to_owned(), + inners: vec![], + generic_args: vec![], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }, + ), + }, + CompositeInner { + index: 1, + name: "tournament".to_owned(), + kind: CompositeInnerKind::NotUsed, + token: Token::Array( + Array { + type_path: "core::array::Span::".to_owned(), + inner: Box::new(Token::CoreBasic( + CoreBasic { + type_path: "core::integer::u64".to_owned(), + }, + )), + is_legacy: false, + }, + ), + }, + CompositeInner { + index: 2, + name: "address".to_owned(), + kind: CompositeInnerKind::NotUsed, + token: Token::Array( + Array { + type_path: "core::array::Span::".to_owned(), + inner: Box::new(Token::CoreBasic( + CoreBasic { + type_path: "core::starknet::contract_address::ContractAddress".to_owned(), + }, + )) , + is_legacy: false, + }, + ), + } + ], + generic_args: vec![], + r#type: CompositeType::Unknown, + is_event: false, + alias: None + } + ) + ), + ], + r#type: CompositeType::Unknown, + is_event: false, + alias: None + }))], + outputs: vec![], + named_outputs: vec![], + } + } + fn create_function_with_custom_enum() -> Function { + Function { + name: "cairo_enum".to_owned(), + state_mutability: cainome::parser::tokens::StateMutability::External, + inputs: vec![("value".to_owned(), Token::Composite(Composite { + type_path: "tournament::ls15_components::models::tournament::GatedType".to_owned(), + inners: vec![ + CompositeInner { + index: 0, + name: "token".to_owned(), + kind: CompositeInnerKind::NotUsed, + token: Token::Composite( + Composite { + type_path: "tournament::ls15_components::models::tournament::GatedType".to_owned(), + inners: vec![ + CompositeInner { + index: 0, + name: "token".to_owned(), + kind: CompositeInnerKind::NotUsed, + token: Token::Composite( + Composite { + type_path: "tournament::ls15_components::models::tournament::GatedToken".to_owned(), + inners: vec![], + generic_args: vec![], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }, + ), + }, + CompositeInner { + index: 1, + name: "tournament".to_owned(), + kind: CompositeInnerKind::NotUsed, + token: Token::Array( + Array { + type_path: "core::array::Span::".to_owned(), + inner: Box::new(Token::CoreBasic( + CoreBasic { + type_path: "core::integer::u64".to_owned(), + }, + )), + is_legacy: false, + }, + ), + }, + CompositeInner { + index: 2, + name: "address".to_owned(), + kind: CompositeInnerKind::NotUsed, + token: Token::Array( + Array { + type_path: "core::array::Span::".to_owned(), + inner: Box::new(Token::CoreBasic( + CoreBasic { + type_path: "core::starknet::contract_address::ContractAddress".to_owned(), + }, + )) , + is_legacy: false, + }, + ), + } + ], + generic_args: vec![], + r#type: CompositeType::Unknown, + is_event: false, + alias: None + }), + } + ], + generic_args: vec![], + r#type: CompositeType::Unknown, + is_event: false, + alias: None + }))], + outputs: vec![], + named_outputs: vec![], + } + } } diff --git a/crates/dojo/bindgen/src/plugins/typescript/generator/interface.rs b/crates/dojo/bindgen/src/plugins/typescript/generator/interface.rs index 15defd7bcd..d4ae4670c2 100644 --- a/crates/dojo/bindgen/src/plugins/typescript/generator/interface.rs +++ b/crates/dojo/bindgen/src/plugins/typescript/generator/interface.rs @@ -1,9 +1,9 @@ use cainome::parser::tokens::{Composite, CompositeType, Token}; use super::constants::{ - BIGNUMNERISH_IMPORT, CAIRO_OPTION_IMPORT, REMOVE_FIELD_ORDER_TYPE_DEF, SN_IMPORT_SEARCH, + BIGNUMNERISH_IMPORT, CAIRO_OPTION_IMPORT, SN_IMPORT_SEARCH, WITH_FIELD_ORDER_TYPE_DEF, }; -use super::{token_is_option, JsType}; +use super::{token_is_option, JsPrimitiveType}; use crate::error::BindgenResult; use crate::plugins::typescript::generator::constants::CAIRO_OPTION_TOKEN; use crate::plugins::{BindgenModelGenerator, Buffer}; @@ -29,8 +29,8 @@ impl TsInterfaceGenerator { } fn add_input_type(&self, buffer: &mut Buffer) { - if !buffer.has(REMOVE_FIELD_ORDER_TYPE_DEF) { - buffer.push(REMOVE_FIELD_ORDER_TYPE_DEF.to_owned()); + if !buffer.has(WITH_FIELD_ORDER_TYPE_DEF) { + buffer.push(WITH_FIELD_ORDER_TYPE_DEF.to_owned()); } } } @@ -52,10 +52,8 @@ impl BindgenModelGenerator for TsInterfaceGenerator { Ok(format!( "// Type definition for `{path}` struct export interface {name} {{ -\tfieldOrder: string[]; {fields} }} -export type Input{name} = RemoveFieldOrder<{name}>; ", path = token.type_path, name = token.type_name(), @@ -69,7 +67,7 @@ export type Input{name} = RemoveFieldOrder<{name}>; } } - format!("\t{}: {};", inner.name, JsType::from(&inner.token)) + format!("\t{}: {};", inner.name, JsPrimitiveType::from(&inner.token)) }) .collect::>() .join("\n") @@ -128,9 +126,7 @@ mod tests { assert_eq!( result, "// Type definition for `core::test::TestStruct` struct\nexport interface TestStruct \ - {\n\tfieldOrder: string[];\n\tfield1: BigNumberish;\n\tfield2: \ - BigNumberish;\n\tfield3: BigNumberish;\n}\nexport type InputTestStruct = \ - RemoveFieldOrder;\n" + {\n\tfield1: BigNumberish;\n\tfield2: BigNumberish;\n\tfield3: BigNumberish;\n}\n" ); } diff --git a/crates/dojo/bindgen/src/plugins/typescript/generator/mod.rs b/crates/dojo/bindgen/src/plugins/typescript/generator/mod.rs index 9d080322c2..6e7dff6f67 100644 --- a/crates/dojo/bindgen/src/plugins/typescript/generator/mod.rs +++ b/crates/dojo/bindgen/src/plugins/typescript/generator/mod.rs @@ -1,8 +1,8 @@ -use cainome::parser::tokens::{Composite, CompositeType, Token}; +use cainome::parser::tokens::{Composite, CompositeInner, CompositeType, Token}; use constants::{ - CAIRO_BOOL, CAIRO_BYTE_ARRAY, CAIRO_CONTRACT_ADDRESS, CAIRO_FELT252, CAIRO_I128, CAIRO_U128, - CAIRO_U16, CAIRO_U256, CAIRO_U256_STRUCT, CAIRO_U32, CAIRO_U64, CAIRO_U8, JS_BIGNUMBERISH, - JS_BOOLEAN, JS_STRING, + CAIRO_BOOL, CAIRO_BYTE_ARRAY, CAIRO_CONTRACT_ADDRESS, CAIRO_FELT252, CAIRO_I128, CAIRO_OPTION, + CAIRO_OPTION_DEFAULT_VALUE, CAIRO_U128, CAIRO_U16, CAIRO_U256, CAIRO_U256_STRUCT, CAIRO_U32, + CAIRO_U64, CAIRO_U8, JS_BIGNUMBERISH, JS_BOOLEAN, JS_STRING, }; use convert_case::{Case, Casing}; @@ -10,9 +10,9 @@ use crate::plugins::typescript::generator::constants::CAIRO_OPTION_TYPE_PATH; pub(crate) mod constants; pub(crate) mod r#enum; -pub(crate) mod erc; pub(crate) mod function; pub(crate) mod interface; +pub(crate) mod models; pub(crate) mod schema; /// Get the namespace and path of a model @@ -37,21 +37,7 @@ pub(crate) fn generate_type_init(token: &Composite) -> String { token .inners .iter() - .map(|i| { - match i.token.to_composite() { - Ok(c) => { - format!("\t\t\t{}: {},", i.name, JsDefaultValue::from(c)) - } - Err(_) => { - // this will fail on core types as - // `core::starknet::contract_address::ContractAddress` - // `core::felt252` - // `core::integer::u64` - // and so son - format!("\t\t\t{}: {},", i.name, JsDefaultValue::from(&i.token)) - } - } - }) + .map(build_composite_inner_primitive_default_value) .collect::>() .join("\n") ) @@ -63,45 +49,59 @@ pub(crate) fn token_is_option(token: &Composite) -> bool { token.type_path.starts_with(CAIRO_OPTION_TYPE_PATH) } +/// Checks if token has inner composite +/// * token - The token to check +fn token_has_inner_composite(token: &Composite) -> bool { + token.inners.iter().any(|inner| match &inner.token { + Token::Array(array) => array.inner.to_composite().is_ok(), + Token::Tuple(tuple) => tuple.inners.iter().any(|t| matches!(t, Token::Composite(_))), + Token::Composite(_) => true, + _ => false, + }) +} + /// Checks if Token::Composite is an custom enum (enum with nested Composite types) /// * token - The token to check pub(crate) fn token_is_custom_enum(token: &Composite) -> bool { - token.r#type == CompositeType::Enum - && token.inners.iter().any(|inner| inner.token.to_composite().is_ok()) + token.r#type == CompositeType::Enum && token_has_inner_composite(token) } +/// Type used to map cainome `Token` into javascript types in interface definition #[derive(Debug)] -pub(crate) struct JsType(String); -impl From<&str> for JsType { +pub(crate) struct JsPrimitiveType(String); + +impl From<&str> for JsPrimitiveType { fn from(value: &str) -> Self { match value { - CAIRO_FELT252 => JsType(JS_BIGNUMBERISH.to_owned()), - CAIRO_CONTRACT_ADDRESS => JsType(JS_STRING.to_owned()), - CAIRO_BYTE_ARRAY => JsType(JS_STRING.to_owned()), - CAIRO_U8 => JsType(JS_BIGNUMBERISH.to_owned()), - CAIRO_U16 => JsType(JS_BIGNUMBERISH.to_owned()), - CAIRO_U32 => JsType(JS_BIGNUMBERISH.to_owned()), - CAIRO_U64 => JsType(JS_BIGNUMBERISH.to_owned()), - CAIRO_U128 => JsType(JS_BIGNUMBERISH.to_owned()), - CAIRO_U256 => JsType(JS_BIGNUMBERISH.to_owned()), - CAIRO_U256_STRUCT => JsType(JS_BIGNUMBERISH.to_owned()), - CAIRO_I128 => JsType(JS_BIGNUMBERISH.to_owned()), - CAIRO_BOOL => JsType(JS_BOOLEAN.to_owned()), - _ => JsType(value.to_owned()), + CAIRO_FELT252 => JsPrimitiveType(JS_BIGNUMBERISH.to_owned()), + CAIRO_CONTRACT_ADDRESS => JsPrimitiveType(JS_STRING.to_owned()), + CAIRO_BYTE_ARRAY => JsPrimitiveType(JS_STRING.to_owned()), + CAIRO_U8 => JsPrimitiveType(JS_BIGNUMBERISH.to_owned()), + CAIRO_U16 => JsPrimitiveType(JS_BIGNUMBERISH.to_owned()), + CAIRO_U32 => JsPrimitiveType(JS_BIGNUMBERISH.to_owned()), + CAIRO_U64 => JsPrimitiveType(JS_BIGNUMBERISH.to_owned()), + CAIRO_U128 => JsPrimitiveType(JS_BIGNUMBERISH.to_owned()), + CAIRO_U256 => JsPrimitiveType(JS_BIGNUMBERISH.to_owned()), + CAIRO_U256_STRUCT => JsPrimitiveType(JS_BIGNUMBERISH.to_owned()), + CAIRO_I128 => JsPrimitiveType(JS_BIGNUMBERISH.to_owned()), + CAIRO_BOOL => JsPrimitiveType(JS_BOOLEAN.to_owned()), + _ => JsPrimitiveType(value.to_owned()), } } } -impl From<&Token> for JsType { +impl From<&Token> for JsPrimitiveType { fn from(value: &Token) -> Self { match value { - Token::Array(a) => JsType::from(format!("Array<{}>", JsType::from(&*a.inner)).as_str()), - Token::Tuple(t) => JsType::from( + Token::Array(a) => JsPrimitiveType::from( + format!("Array<{}>", JsPrimitiveType::from(&*a.inner)).as_str(), + ), + Token::Tuple(t) => JsPrimitiveType::from( format!( "[{}]", t.inners .iter() - .map(|i| JsType::from(i.type_name().as_str()).to_string()) + .map(|i| JsPrimitiveType::from(i.type_name().as_str()).to_string()) .collect::>() .join(", ") .as_str() @@ -110,12 +110,12 @@ impl From<&Token> for JsType { ), Token::Composite(c) => { if token_is_option(c) { - return JsType::from( + return JsPrimitiveType::from( format!( "CairoOption<{}>", c.generic_args .iter() - .map(|(_, t)| JsType::from(t.type_name().as_str()).to_string()) + .map(|(_, t)| JsPrimitiveType::from(t).to_string()) .collect::>() .join(", ") ) @@ -124,104 +124,226 @@ impl From<&Token> for JsType { } if token_is_custom_enum(c) { // we defined a type wrapper with Enum suffix let's use it there - return JsType::from(format!("{}Enum", value.type_name()).as_str()); + return JsPrimitiveType::from(format!("{}Enum", c.type_name()).as_str()); } - return JsType::from(value.type_name().as_str()); + return JsPrimitiveType::from(value.type_name().as_str()); } - _ => JsType::from(value.type_name().as_str()), + _ => JsPrimitiveType::from(value.type_name().as_str()), } } } -impl std::fmt::Display for JsType { +impl std::fmt::Display for JsPrimitiveType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) } } +/// Type used to map cainome `Token` into javascript types in function definition #[derive(Debug)] -pub(crate) struct JsDefaultValue(String); -impl From<&str> for JsDefaultValue { +pub(crate) struct JsPrimitiveInputType(String); +impl std::fmt::Display for JsPrimitiveInputType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } +} +impl From<&str> for JsPrimitiveInputType { fn from(value: &str) -> Self { match value { - CAIRO_FELT252 => JsDefaultValue("0".to_string()), - CAIRO_CONTRACT_ADDRESS => JsDefaultValue("\"\"".to_string()), - CAIRO_BYTE_ARRAY => JsDefaultValue("\"\"".to_string()), - CAIRO_U8 => JsDefaultValue("0".to_string()), - CAIRO_U16 => JsDefaultValue("0".to_string()), - CAIRO_U32 => JsDefaultValue("0".to_string()), - CAIRO_U64 => JsDefaultValue("0".to_string()), - CAIRO_U128 => JsDefaultValue("0".to_string()), - CAIRO_U256 => JsDefaultValue("0".to_string()), - CAIRO_U256_STRUCT => JsDefaultValue("0".to_string()), - CAIRO_I128 => JsDefaultValue("0".to_string()), - CAIRO_BOOL => JsDefaultValue("false".to_string()), - _ => JsDefaultValue(value.to_string()), + CAIRO_FELT252 => JsPrimitiveInputType(JS_BIGNUMBERISH.to_owned()), + CAIRO_CONTRACT_ADDRESS => JsPrimitiveInputType(JS_STRING.to_owned()), + CAIRO_BYTE_ARRAY => JsPrimitiveInputType(CAIRO_BYTE_ARRAY.to_owned()), + CAIRO_U8 => JsPrimitiveInputType(JS_BIGNUMBERISH.to_owned()), + CAIRO_U16 => JsPrimitiveInputType(JS_BIGNUMBERISH.to_owned()), + CAIRO_U32 => JsPrimitiveInputType(JS_BIGNUMBERISH.to_owned()), + CAIRO_U64 => JsPrimitiveInputType(JS_BIGNUMBERISH.to_owned()), + CAIRO_U128 => JsPrimitiveInputType(JS_BIGNUMBERISH.to_owned()), + CAIRO_U256 => JsPrimitiveInputType(CAIRO_U256_STRUCT.to_owned()), + CAIRO_U256_STRUCT => JsPrimitiveInputType(CAIRO_U256_STRUCT.to_owned()), + CAIRO_I128 => JsPrimitiveInputType(JS_BIGNUMBERISH.to_owned()), + CAIRO_BOOL => JsPrimitiveInputType(JS_BOOLEAN.to_owned()), + _ => JsPrimitiveInputType(value.to_owned()), } } } -impl From<&Composite> for JsDefaultValue { + +impl From<&Token> for JsPrimitiveInputType { + fn from(value: &Token) -> Self { + match value { + Token::Array(a) => JsPrimitiveInputType::from( + format!("Array<{}>", JsPrimitiveInputType::from(&*a.inner)).as_str(), + ), + Token::Tuple(t) => JsPrimitiveInputType::from( + format!( + "[{}]", + t.inners + .iter() + .map(|i| JsPrimitiveInputType::from(i.type_name().as_str()).to_string()) + .collect::>() + .join(", ") + .as_str() + ) + .as_str(), + ), + Token::Composite(c) => { + if token_is_option(c) { + return JsPrimitiveInputType::from( + format!( + "CairoOption<{}>", + c.generic_args + .iter() + .map(|(_, t)| JsPrimitiveInputType::from(t).to_string()) + .collect::>() + .join(", ") + ) + .as_str(), + ); + } + if token_is_custom_enum(c) { + // Use CairoCustomEnum type from starknetjs + return JsPrimitiveInputType::from("CairoCustomEnum"); + } + return JsPrimitiveInputType::from(value.type_name().as_str()); + } + _ => JsPrimitiveInputType::from(value.type_name().as_str()), + } + } +} + +#[derive(Debug)] +pub(crate) struct JsPrimitiveDefaultValue(String); +impl From<&str> for JsPrimitiveDefaultValue { + fn from(value: &str) -> Self { + match value { + CAIRO_FELT252 => JsPrimitiveDefaultValue("0".to_string()), + CAIRO_CONTRACT_ADDRESS => JsPrimitiveDefaultValue("\"\"".to_string()), + CAIRO_BYTE_ARRAY => JsPrimitiveDefaultValue("\"\"".to_string()), + CAIRO_U8 => JsPrimitiveDefaultValue("0".to_string()), + CAIRO_U16 => JsPrimitiveDefaultValue("0".to_string()), + CAIRO_U32 => JsPrimitiveDefaultValue("0".to_string()), + CAIRO_U64 => JsPrimitiveDefaultValue("0".to_string()), + CAIRO_U128 => JsPrimitiveDefaultValue("0".to_string()), + CAIRO_U256 => JsPrimitiveDefaultValue("0".to_string()), + CAIRO_U256_STRUCT => JsPrimitiveDefaultValue("0".to_string()), + CAIRO_I128 => JsPrimitiveDefaultValue("0".to_string()), + CAIRO_BOOL => JsPrimitiveDefaultValue("false".to_string()), + CAIRO_OPTION => JsPrimitiveDefaultValue(CAIRO_OPTION_DEFAULT_VALUE.to_string()), + _ => JsPrimitiveDefaultValue(value.to_string()), + } + } +} +impl From<&Composite> for JsPrimitiveDefaultValue { fn from(value: &Composite) -> Self { match value.r#type { cainome::parser::tokens::CompositeType::Enum => { + if token_is_custom_enum(value) { + return JsPrimitiveDefaultValue(build_custom_enum_default_value(value)); + } match value.inners[0].token.to_composite() { - Ok(c) => JsDefaultValue::from(c), - Err(_) => { - JsDefaultValue(format!("{}.{}", value.type_name(), value.inners[0].name)) - } + Ok(c) => JsPrimitiveDefaultValue::from(c), + Err(_) => JsPrimitiveDefaultValue(format!( + "{}.{}", + value.type_name(), + value.inners[0].name + )), } } - cainome::parser::tokens::CompositeType::Struct => JsDefaultValue(format!( - "{{ fieldOrder: [{}], {} }}", - value.inners.iter().map(|i| format!("'{}'", i.name)).collect::>().join(", "), - value - .inners - .iter() - .map(|i| format!( - "{}: {},", - i.name, - match i.token.to_composite() { - Ok(c) => { - JsDefaultValue::from(c) - } - Err(_) => { - JsDefaultValue::from(&i.token) - } - } - )) - .collect::>() - .join(" ") - )), - _ => JsDefaultValue::from(value.type_name().as_str()), + cainome::parser::tokens::CompositeType::Struct => { + JsPrimitiveDefaultValue(build_struct_default_value(value)) + } + _ => JsPrimitiveDefaultValue::from(value.type_name().as_str()), + } + } +} + +/// Builds the default value for an enum variant +/// * token - The enum token to build the default value for +fn build_default_enum_variant(token: &Composite) -> String { + let default_value = token.inners.iter().take(1).fold(String::new(), |_acc, i| { + format!("\n\t\t{}", build_composite_inner_primitive_default_value(i)) + }); + let undefined = token.inners.iter().skip(1).fold(String::new(), |acc, i| { + format!("{acc}\n\t\t\t\t{}: undefined,", i.name.to_case(Case::Camel)) + }); + + default_value + &undefined +} + +/// Builds JsPrimitiveDefaultValue from CompositeInne token +/// * inner - CompositeInner +fn build_composite_inner_primitive_default_value(inner: &CompositeInner) -> String { + match inner.token.to_composite() { + Ok(c) => { + format!("\t\t{}: {},", inner.name, JsPrimitiveDefaultValue::from(c)) + } + Err(_) => { + // this will fail on core types as + // `core::starknet::contract_address::ContractAddress` + // `core::felt252` + // `core::integer::u64` + // and so son + format!("\t\t\t{}: {},", inner.name, JsPrimitiveDefaultValue::from(&inner.token)) } } } -impl From<&Token> for JsDefaultValue { +/// Builds the default value for an enum variant +/// * token - The enum token to build the default value for +fn build_custom_enum_default_value(token: &Composite) -> String { + format!("new CairoCustomEnum({{ {} }})", build_default_enum_variant(token),) +} + +/// Builds the default value for a struct +/// * token - The enum token to build the default value for +fn build_struct_default_value(token: &Composite) -> String { + format!( + "{{ fieldOrder: [{}], {} }}", + token.inners.iter().map(|i| format!("'{}'", i.name)).collect::>().join(", "), + token + .inners + .iter() + .map(|i| format!( + "{}: {},", + i.name, + match i.token.to_composite() { + Ok(c) => { + JsPrimitiveDefaultValue::from(c) + } + Err(_) => { + JsPrimitiveDefaultValue::from(&i.token) + } + } + )) + .collect::>() + .join(" ") + ) +} + +impl From<&Token> for JsPrimitiveDefaultValue { fn from(value: &Token) -> Self { match value { - Token::Array(a) => { - JsDefaultValue::from(format!("[{}]", JsDefaultValue::from(&*a.inner)).as_str()) - } - Token::Tuple(t) => JsDefaultValue::from( + Token::Array(a) => JsPrimitiveDefaultValue::from( + format!("[{}]", JsPrimitiveDefaultValue::from(&*a.inner)).as_str(), + ), + Token::Tuple(t) => JsPrimitiveDefaultValue::from( format!( "[{}]", t.inners .iter() - .map(|i| JsDefaultValue::from(i.type_name().as_str()).to_string()) + .map(|i| JsPrimitiveDefaultValue::from(i.type_name().as_str()).to_string()) .collect::>() .join(", ") .as_str() ) .as_str(), ), - Token::Composite(c) => JsDefaultValue::from(c), - _ => JsDefaultValue::from(value.type_name().as_str()), + Token::Composite(c) => JsPrimitiveDefaultValue::from(c), + _ => JsPrimitiveDefaultValue::from(value.type_name().as_str()), } } } -impl std::fmt::Display for JsDefaultValue { +impl std::fmt::Display for JsPrimitiveDefaultValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) } @@ -235,16 +357,18 @@ mod tests { }; use crate::plugins::typescript::generator::constants::JS_BIGNUMBERISH; - use crate::plugins::typescript::generator::{generate_type_init, JsDefaultValue, JsType}; + use crate::plugins::typescript::generator::{ + generate_type_init, JsPrimitiveDefaultValue, JsPrimitiveType, + }; - impl PartialEq for &str { - fn eq(&self, other: &JsType) -> bool { + impl PartialEq for &str { + fn eq(&self, other: &JsPrimitiveType) -> bool { self == &other.0.as_str() } } - impl PartialEq for &str { - fn eq(&self, other: &JsDefaultValue) -> bool { + impl PartialEq for &str { + fn eq(&self, other: &JsPrimitiveDefaultValue) -> bool { self == &other.0.as_str() } } @@ -253,13 +377,15 @@ mod tests { fn test_js_type_basics() { assert_eq!( JS_BIGNUMBERISH, - JsType::from(&Token::CoreBasic(CoreBasic { + JsPrimitiveType::from(&Token::CoreBasic(CoreBasic { type_path: "core::integer::u8".to_owned() })) ); assert_eq!( JS_BIGNUMBERISH, - JsType::from(&Token::CoreBasic(CoreBasic { type_path: "core::felt252".to_owned() })) + JsPrimitiveType::from(&Token::CoreBasic(CoreBasic { + type_path: "core::felt252".to_owned() + })) ) } @@ -267,7 +393,7 @@ mod tests { fn test_tuple_type() { assert_eq!( format!("[{}, {}]", JS_BIGNUMBERISH, JS_BIGNUMBERISH).as_str(), - JsType::from(&Token::Tuple(Tuple { + JsPrimitiveType::from(&Token::Tuple(Tuple { type_path: "(core::integer::u8,core::integer::u128)".to_owned(), inners: vec![ Token::CoreBasic(CoreBasic { type_path: "core::integer::u8".to_owned() }), @@ -281,7 +407,7 @@ mod tests { fn test_array_type() { assert_eq!( format!("Array<[{}, {}]>", JS_BIGNUMBERISH, JS_BIGNUMBERISH).as_str(), - JsType::from(&Token::Array(Array { + JsPrimitiveType::from(&Token::Array(Array { type_path: "core::array::Span<(core::integer::u8,core::integer::u128)>".to_owned(), inner: Box::new(Token::Tuple(Tuple { type_path: "(core::integer::u8,core::integer::u128)".to_owned(), @@ -299,7 +425,7 @@ mod tests { fn test_option_type() { assert_eq!( "CairoOption", - JsType::from(&Token::Composite(Composite { + JsPrimitiveType::from(&Token::Composite(Composite { type_path: "core::option::Option".to_owned(), inners: vec![], generic_args: vec![ @@ -375,13 +501,13 @@ mod tests { fn test_default_value_basics() { assert_eq!( "0", - JsDefaultValue::from(&Token::CoreBasic(CoreBasic { + JsPrimitiveDefaultValue::from(&Token::CoreBasic(CoreBasic { type_path: "core::integer::u8".to_owned() })) ); assert_eq!( "0", - JsDefaultValue::from(&Token::CoreBasic(CoreBasic { + JsPrimitiveDefaultValue::from(&Token::CoreBasic(CoreBasic { type_path: "core::felt252".to_owned() })) ) @@ -391,7 +517,7 @@ mod tests { fn test_tuple_default_value() { assert_eq!( "[0, 0]", - JsDefaultValue::from(&Token::Tuple(Tuple { + JsPrimitiveDefaultValue::from(&Token::Tuple(Tuple { type_path: "(core::integer::u8,core::integer::u128)".to_owned(), inners: vec![ Token::CoreBasic(CoreBasic { type_path: "core::integer::u8".to_owned() }), @@ -405,7 +531,7 @@ mod tests { fn test_array_default_value() { assert_eq!( "[[0, 0]]", - JsDefaultValue::from(&Token::Array(Array { + JsPrimitiveDefaultValue::from(&Token::Array(Array { type_path: "core::array::Span<(core::integer::u8,core::integer::u128)>".to_owned(), inner: Box::new(Token::Tuple(Tuple { type_path: "(core::integer::u8,core::integer::u128)".to_owned(), @@ -423,7 +549,7 @@ mod tests { fn test_enum_default_value() { assert_eq!( "Direction.Up", - JsDefaultValue::from(&Token::Composite(Composite { + JsPrimitiveDefaultValue::from(&Token::Composite(Composite { type_path: "dojo_starter::Direction".to_owned(), inners: vec![ CompositeInner { @@ -462,8 +588,9 @@ mod tests { #[test] fn test_cairo_custom_enum_default_value() { assert_eq!( - "{ fieldOrder: ['id', 'xp'], id: 0, xp: 0, }", - JsDefaultValue::from(&Token::Composite(Composite { + "new CairoCustomEnum({ \n\t\t\t\titem: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, \ + },\n\t\t\t\taddress: undefined, })", + JsPrimitiveDefaultValue::from(&Token::Composite(Composite { type_path: "dojo_starter::Direction".to_owned(), inners: vec![ CompositeInner { @@ -515,7 +642,7 @@ mod tests { fn test_composite_default_value() { assert_eq!( "{ fieldOrder: ['id', 'xp'], id: 0, xp: 0, }", - JsDefaultValue::from(&Token::Composite(Composite { + JsPrimitiveDefaultValue::from(&Token::Composite(Composite { type_path: "dojo_starter::Item".to_owned(), inners: vec![ CompositeInner { @@ -548,7 +675,7 @@ mod tests { assert_eq!( "{ fieldOrder: ['id', 'xp', 'item'], id: 0, xp: 0, item: { fieldOrder: ['id', 'xp', \ 'item'], id: 0, xp: 0, item: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, }, }", - JsDefaultValue::from(&Token::Composite(Composite { + JsPrimitiveDefaultValue::from(&Token::Composite(Composite { type_path: "dojo_starter::Item".to_owned(), inners: vec![ CompositeInner { @@ -636,6 +763,38 @@ mod tests { ) } + #[test] + fn test_cairo_option_default_value() { + assert_eq!( + "new CairoOption(CairoOptionVariant.None)", + JsPrimitiveDefaultValue::from(&Token::Composite(Composite { + type_path: "core::option::Option".to_owned(), + inners: vec![ + CompositeInner { + index: 0, + name: "id".to_owned(), + kind: CompositeInnerKind::NotUsed, + token: Token::CoreBasic(CoreBasic { + type_path: "core::felt252".to_owned(), + }) + }, + CompositeInner { + index: 1, + name: "xp".to_owned(), + kind: CompositeInnerKind::NotUsed, + token: Token::CoreBasic(CoreBasic { + type_path: "core::felt252".to_owned(), + }) + }, + ], + generic_args: vec![], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + })) + ) + } + #[test] fn test_generate_type_init() { let token = create_test_struct_token("TestStruct"); @@ -652,6 +811,13 @@ mod tests { \t\t}"; assert_eq!(expected, init_type); } + + #[test] + fn test_option_custom_enum_type() { + let token = create_option_custom_enum_token(); + assert_eq!("{ fieldOrder: [], }", JsPrimitiveDefaultValue::from(&Token::Composite(token))) + } + fn create_test_struct_token(name: &str) -> Composite { Composite { type_path: format!("onchain_dash::{name}"), @@ -681,4 +847,54 @@ mod tests { alias: None, } } + + fn create_option_custom_enum_token() -> Composite { + Composite { + type_path: "core::option::Option".to_owned(), + inners: vec![], + generic_args: vec![( + "A".to_owned(), + Token::Composite(Composite { + type_path: "core::test::CustomEnum".to_owned(), + inners: vec![ + CompositeInner { + index: 0, + name: "Variant1".to_owned(), + kind: CompositeInnerKind::NotUsed, + token: Token::CoreBasic(CoreBasic { + type_path: "core::felt252".to_owned(), + }), + }, + CompositeInner { + index: 1, + name: "Variant2".to_owned(), + kind: CompositeInnerKind::NotUsed, + token: Token::Composite(Composite { + type_path: "core::test::NestedStruct".to_owned(), + inners: vec![CompositeInner { + index: 0, + name: "nested_field".to_owned(), + kind: CompositeInnerKind::Key, + token: Token::CoreBasic(CoreBasic { + type_path: "core::felt252".to_owned(), + }), + }], + generic_args: vec![], + r#type: CompositeType::Struct, + is_event: false, + alias: None, + }), + }, + ], + generic_args: vec![], + r#type: CompositeType::Enum, + is_event: false, + alias: None, + }), + )], + r#type: CompositeType::Struct, + is_event: false, + alias: None, + } + } } diff --git a/crates/dojo/bindgen/src/plugins/typescript/generator/models.rs b/crates/dojo/bindgen/src/plugins/typescript/generator/models.rs new file mode 100644 index 0000000000..d36704ea14 --- /dev/null +++ b/crates/dojo/bindgen/src/plugins/typescript/generator/models.rs @@ -0,0 +1,29 @@ +use cainome::parser::tokens::Composite; + +use super::get_namespace_and_path; +use crate::error::BindgenResult; +use crate::plugins::{BindgenModelGenerator, Buffer}; + +pub(crate) struct TsModelsGenerator; + +impl BindgenModelGenerator for TsModelsGenerator { + fn generate(&self, token: &Composite, buffer: &mut Buffer) -> BindgenResult { + let (ns, _namespace, type_name) = get_namespace_and_path(token); + let models_mapping = "export enum ModelsMapping {"; + if !buffer.has(models_mapping) { + buffer.push(format!( + "export enum ModelsMapping {{\n\t{type_name} = '{ns}-{type_name}',\n}}", + )); + return Ok("".to_owned()); + } + + let gen = format!("\n\t{type_name} = '{ns}-{type_name}',"); + if buffer.has(&gen) { + return Ok("".to_owned()); + } + + buffer.insert_after(gen, models_mapping, ",", 1); + + Ok("".to_owned()) + } +} diff --git a/crates/dojo/bindgen/src/plugins/typescript/generator/schema.rs b/crates/dojo/bindgen/src/plugins/typescript/generator/schema.rs index 91cc83b0f8..ccd4a9eb1b 100644 --- a/crates/dojo/bindgen/src/plugins/typescript/generator/schema.rs +++ b/crates/dojo/bindgen/src/plugins/typescript/generator/schema.rs @@ -27,7 +27,7 @@ impl TsSchemaGenerator { if !buffer.has(schema_type) { buffer.push(format!( "export interface SchemaType extends ISchemaType {{\n\t{ns}: {{\n\t\t{}: \ - {},\n\t}},\n}}", + WithFieldOrder<{}>,\n\t}},\n}}", type_name, type_name )); return; @@ -36,13 +36,13 @@ impl TsSchemaGenerator { // check if namespace is defined in interface. if not, add it. // next, find where namespace was defined in interface and add property to it. if !self.namespace_is_defined(buffer, &ns) { - let gen = format!("\n\t{ns}: {{\n\t\t{type_name}: {type_name},\n\t}},"); + let gen = format!("\n\t{ns}: {{\n\t\t{type_name}: WithFieldOrder<{type_name}>,\n\t}},"); buffer.insert_after(gen, schema_type, ",", 1); return; } // type has already been initialized - let gen = format!("\n\t\t{type_name}: {type_name},"); + let gen = format!("\n\t\t{type_name}: WithFieldOrder<{type_name}>,"); if buffer.has(&gen) { return; } @@ -171,7 +171,7 @@ mod tests { let _result = generator.generate(&token, &mut buffer); assert_eq!( "export interface SchemaType extends ISchemaType {\n\tonchain_dash: \ - {\n\t\tTestStruct: TestStruct,\n\t},\n}", + {\n\t\tTestStruct: WithFieldOrder,\n\t},\n}", buffer[1] ); } @@ -187,7 +187,7 @@ mod tests { assert_ne!(0, buffer.len()); assert_eq!( "export interface SchemaType extends ISchemaType {\n\tonchain_dash: \ - {\n\t\tTestStruct: TestStruct,\n\t},\n}", + {\n\t\tTestStruct: WithFieldOrder,\n\t},\n}", buffer[0] ); @@ -195,23 +195,26 @@ mod tests { generator.handle_schema_type(&token_2, &mut buffer); assert_eq!( "export interface SchemaType extends ISchemaType {\n\tonchain_dash: \ - {\n\t\tTestStruct: TestStruct,\n\t\tAvailableTheme: AvailableTheme,\n\t},\n}", + {\n\t\tTestStruct: WithFieldOrder,\n\t\tAvailableTheme: \ + WithFieldOrder,\n\t},\n}", buffer[0] ); let token_3 = create_test_struct_token("Player", "combat"); generator.handle_schema_type(&token_3, &mut buffer); assert_eq!( "export interface SchemaType extends ISchemaType {\n\tonchain_dash: \ - {\n\t\tTestStruct: TestStruct,\n\t\tAvailableTheme: AvailableTheme,\n\t},\n\tcombat: \ - {\n\t\tPlayer: Player,\n\t},\n}", + {\n\t\tTestStruct: WithFieldOrder,\n\t\tAvailableTheme: \ + WithFieldOrder,\n\t},\n\tcombat: {\n\t\tPlayer: \ + WithFieldOrder,\n\t},\n}", buffer[0] ); let token_4 = create_test_struct_token("Position", "combat"); generator.handle_schema_type(&token_4, &mut buffer); assert_eq!( "export interface SchemaType extends ISchemaType {\n\tonchain_dash: \ - {\n\t\tTestStruct: TestStruct,\n\t\tAvailableTheme: AvailableTheme,\n\t},\n\tcombat: \ - {\n\t\tPlayer: Player,\n\t\tPosition: Position,\n\t},\n}", + {\n\t\tTestStruct: WithFieldOrder,\n\t\tAvailableTheme: \ + WithFieldOrder,\n\t},\n\tcombat: {\n\t\tPlayer: \ + WithFieldOrder,\n\t\tPosition: WithFieldOrder,\n\t},\n}", buffer[0] ); } diff --git a/crates/dojo/bindgen/src/plugins/typescript/mod.rs b/crates/dojo/bindgen/src/plugins/typescript/mod.rs index d9d7f57e1d..b8058ee725 100644 --- a/crates/dojo/bindgen/src/plugins/typescript/mod.rs +++ b/crates/dojo/bindgen/src/plugins/typescript/mod.rs @@ -3,9 +3,9 @@ use std::path::PathBuf; use async_trait::async_trait; use generator::r#enum::TsEnumGenerator; -use generator::erc::TsErcGenerator; use generator::function::TsFunctionGenerator; use generator::interface::TsInterfaceGenerator; +use generator::models::TsModelsGenerator; use generator::schema::TsSchemaGenerator; use writer::{TsFileContractWriter, TsFileWriter}; @@ -31,7 +31,7 @@ impl TypescriptPlugin { Box::new(TsInterfaceGenerator {}), Box::new(TsEnumGenerator {}), Box::new(TsSchemaGenerator {}), - Box::new(TsErcGenerator {}), + Box::new(TsModelsGenerator {}), ], )), Box::new(TsFileContractWriter::new( diff --git a/crates/dojo/bindgen/src/plugins/typescript/writer.rs b/crates/dojo/bindgen/src/plugins/typescript/writer.rs index e34313d994..b97febcadc 100644 --- a/crates/dojo/bindgen/src/plugins/typescript/writer.rs +++ b/crates/dojo/bindgen/src/plugins/typescript/writer.rs @@ -20,11 +20,9 @@ impl TsFileWriter { impl BindgenWriter for TsFileWriter { fn write(&self, path: &str, data: &DojoData) -> BindgenResult<(PathBuf, Vec)> { let models_path = Path::new(path).to_owned(); - let mut models = data.models.values().collect::>(); + let models = data.models.values().collect::>(); - // Sort models based on their tag to ensure deterministic output. - models.sort_by(|a, b| a.tag.cmp(&b.tag)); - let composites = models + let mut composites = models .iter() .flat_map(|m| { let mut composites: Vec<&Composite> = Vec::new(); @@ -46,14 +44,15 @@ impl BindgenWriter for TsFileWriter { .filter(|c| !(c.type_path.starts_with("dojo::") || c.type_path.starts_with("core::"))) .collect::>(); + // Sort models based on their tag to ensure deterministic output. + // models.sort_by(|a, b| a.tag.cmp(&b.tag)); + composites.sort_by(|a, b| a.type_path.cmp(&b.type_path)); + let code = self .generators .iter() .fold(Buffer::new(), |mut acc, g| { composites.iter().for_each(|c| { - // println!("Generating code for model {}", c.type_path); - // println!("{:#?}", c); - // println!("====================="); match g.generate(c, &mut acc) { Ok(code) => { if !code.is_empty() {