diff --git a/Cargo.lock b/Cargo.lock index ebe370ac1..3c7e46e4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -562,13 +562,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" dependencies = [ "askama_parser", - "basic-toml", "memchr", "proc-macro2", "quote", "rustc-hash 2.1.1", - "serde", - "serde_derive", "syn 2.0.104", ] @@ -579,8 +576,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" dependencies = [ "memchr", - "serde", - "serde_derive", "winnow 0.7.1", ] @@ -806,15 +801,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "basic-toml" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" -dependencies = [ - "serde", -] - [[package]] name = "beef" version = "0.5.2" @@ -6183,6 +6169,7 @@ version = "0.9.2" dependencies = [ "ping-pong-bench-app", "sails-client-gen", + "sails-idl-gen", ] [[package]] @@ -7228,15 +7215,15 @@ dependencies = [ name = "sails-idl-gen" version = "0.9.2" dependencies = [ + "askama", "convert_case 0.7.1", "gprimitives", - "handlebars", "insta", + "quote", "sails-idl-meta", - "sails-idl-parser", + "sails-idl-parser-v2", "scale-info", - "serde", - "serde_json", + "syn 2.0.104", "thiserror 2.0.12", ] diff --git a/Cargo.toml b/Cargo.toml index 545d380ce..b2d99a478 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ members = [ # The order matches with publishing order. sails-idl-meta = { path = "rs/idl-meta" } sails-idl-parser = { path = "rs/idl-parser" } -sails-idl-parser-v2 = { path = "rs/idl-parser-v2" } +sails-idl-parser-v2 = { path = "rs/idl-parser-v2", default-features = false } sails-idl-gen = { path = "rs/idl-gen" } sails-client-gen = { path = "rs/client-gen" } sails-macros-core = { path = "rs/macros/core" } @@ -75,7 +75,7 @@ gwasm-builder = { version = "=1.9.2", package = "gear-wasm-builder" } alloy-primitives = { version = "0.8.19", default-features = false } alloy-sol-types = { version = "0.8.19", default-features = false } anyhow = "1" -askama = "0.14" +askama = { version = "0.14", default-features = false } cargo-generate = "0.23" cargo_metadata = "0.19" clap = "4.5" diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 381001251..0d0f7b6d4 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -1,32 +1,32 @@ { "compute": { - "median": 450513692764 + "median": 450514036959 }, "alloc": { - "0": 563797174, - "12": 567303766, - "143": 726672361, - "986": 827658128, - "10945": 2018422654, - "46367": 6403684137, - "121392": 16858375895, - "317810": 43146695232 + "0": 564376739, + "12": 567864696, + "143": 727298127, + "986": 828290311, + "10945": 2019067671, + "46367": 6404351520, + "121392": 16859084038, + "317810": 43147409792 }, "counter": { - "async_call": 850478213, - "sync_call": 677855029 + "async_call": 851480134, + "sync_call": 678859589 }, "cross_program": { - "median": 2433669984 + "median": 2437527818 }, "redirect": { - "median": 3469451691 + "median": 3474613683 }, "message_stack": { - "0": 797238065, - "1": 3762055850, - "5": 15662630147, - "10": 29532802161, - "20": 63872681625 + "0": 799115685, + "1": 3769584063, + "5": 15693086906, + "10": 29592077575, + "20": 63989367411 } } \ No newline at end of file diff --git a/benchmarks/idls/alloc_stress_program.idl b/benchmarks/idls/alloc_stress_program.idl index 298ab3b02..40f148273 100644 --- a/benchmarks/idls/alloc_stress_program.idl +++ b/benchmarks/idls/alloc_stress_program.idl @@ -1,9 +1,8 @@ -program AllocStress { - constructors { - NewForBench(); - } - services { - AllocStress, +!@sails: 0.9.2 + +service AllocStress { + functions { + AllocStress(n: u32) -> AllocStressResult; } types { struct AllocStressResult { @@ -12,8 +11,11 @@ program AllocStress { } } -service AllocStress { - functions { - AllocStress(n: u32) -> AllocStressResult; +program AllocStress { + constructors { + NewForBench(); } -} \ No newline at end of file + services { + AllocStress, + } +} diff --git a/benchmarks/idls/compute_stress_program.idl b/benchmarks/idls/compute_stress_program.idl index 8d271b317..1e62c183f 100644 --- a/benchmarks/idls/compute_stress_program.idl +++ b/benchmarks/idls/compute_stress_program.idl @@ -5,15 +5,15 @@ program ComputeStress { services { ComputeStress, } - types { - struct ComputeStressResult { - res: u32, - } - } } service ComputeStress { functions { ComputeStress(n: u32) -> ComputeStressResult; } -} \ No newline at end of file + types { + struct ComputeStressResult { + res: u32, + } + } +} diff --git a/benchmarks/ping-pong/app/src/lib.rs b/benchmarks/ping-pong/app/src/lib.rs index a71c81f36..74ca4d36f 100644 --- a/benchmarks/ping-pong/app/src/lib.rs +++ b/benchmarks/ping-pong/app/src/lib.rs @@ -28,13 +28,13 @@ impl PingPongService { PingPongPayload::Start(actor_id) => { let mut api = client::PingPongProgram::client(actor_id).ping_pong_service(); let result = api - .ping(client::PingPongPayload::Ping) + .ping(client::ping_pong_service::PingPongPayload::Ping) .await .unwrap_or_else(|e| { panic!("Failed to receiving successful ping result: {e:?}") }); - if matches!(result, client::PingPongPayload::Pong) { + if matches!(result, client::ping_pong_service::PingPongPayload::Pong) { PingPongPayload::Finished } else { panic!("Unexpected payload received: {result:?}") diff --git a/benchmarks/ping-pong/app/src/ping_pong_client.rs b/benchmarks/ping-pong/app/src/ping_pong_client.rs index 4549bd880..523c84aea 100644 --- a/benchmarks/ping-pong/app/src/ping_pong_client.rs +++ b/benchmarks/ping-pong/app/src/ping_pong_client.rs @@ -38,19 +38,19 @@ pub mod io { use super::*; sails_rs::io_struct_impl!(NewForBench () -> ()); } -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub enum PingPongPayload { - Start(ActorId), - Ping, - Pong, - Finished, -} pub mod ping_pong_service { use super::*; + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub enum PingPongPayload { + Start(ActorId), + Ping, + Pong, + Finished, + } pub trait PingPongService { type Env: sails_rs::client::GearEnv; fn ping( diff --git a/benchmarks/ping-pong/client/Cargo.toml b/benchmarks/ping-pong/client/Cargo.toml index dba7deff1..cfaedb042 100644 --- a/benchmarks/ping-pong/client/Cargo.toml +++ b/benchmarks/ping-pong/client/Cargo.toml @@ -9,5 +9,4 @@ repository.workspace = true [build-dependencies] ping-pong-bench-app = { path = "../app" } sails-client-gen.workspace = true -# TODO: Switch to new IDL generator crate -# sails-idl-gen.workspace = true +sails-idl-gen.workspace = true diff --git a/benchmarks/ping-pong/client/build.rs b/benchmarks/ping-pong/client/build.rs index ed2609c9b..2937308dd 100644 --- a/benchmarks/ping-pong/client/build.rs +++ b/benchmarks/ping-pong/client/build.rs @@ -1,12 +1,12 @@ -// use ping_pong_bench_app::PingPongProgram; +use ping_pong_bench_app::PingPongProgram; fn main() { let idl_file_path = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) .join("ping_pong.idl"); - // Generate IDL file for the `AllocStress` app - // TODO: uncomment - // sails_idl_gen::generate_idl_to_file::(&idl_file_path).unwrap(); + // Generate IDL file for the `PingPongProgram` app + sails_idl_gen::generate_idl_to_file::(Some("PingPong"), &idl_file_path) + .unwrap(); // Generate client code from IDL file sails_client_gen::ClientGenerator::from_idl_path(&idl_file_path) diff --git a/benchmarks/ping-pong/client/ping_pong.idl b/benchmarks/ping-pong/client/ping_pong.idl index 2d6d12f2f..da5eb8d78 100644 --- a/benchmarks/ping-pong/client/ping_pong.idl +++ b/benchmarks/ping-pong/client/ping_pong.idl @@ -1,9 +1,9 @@ -program PingPongProgram { - constructors { - NewForBench(); - } - services { - PingPongService, + +!@sails: 0.9.2 + +service PingPongService { + functions { + Ping(payload: PingPongPayload) -> PingPongPayload; } types { enum PingPongPayload { @@ -15,8 +15,11 @@ program PingPongProgram { } } -service PingPongService { - functions { - Ping(payload: PingPongPayload) -> PingPongPayload; +program PingPong { + constructors { + NewForBench(); } -} \ No newline at end of file + services { + PingPongService, + } +} diff --git a/benchmarks/src/alloc_stress_program.rs b/benchmarks/src/alloc_stress_program.rs index a94895f21..8413a96a7 100644 --- a/benchmarks/src/alloc_stress_program.rs +++ b/benchmarks/src/alloc_stress_program.rs @@ -1,33 +1,31 @@ // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; -pub struct AllocStressProgramProgram; -impl sails_rs::client::Program for AllocStressProgramProgram {} -pub trait AllocStressProgram { +pub struct AllocStressProgram; +impl sails_rs::client::Program for AllocStressProgram {} +pub trait AllocStress { type Env: sails_rs::client::GearEnv; fn alloc_stress(&self) -> sails_rs::client::Service; } -impl AllocStressProgram - for sails_rs::client::Actor -{ +impl AllocStress for sails_rs::client::Actor { type Env = E; fn alloc_stress(&self) -> sails_rs::client::Service { self.service(stringify!(AllocStress)) } } -pub trait AllocStressProgramCtors { +pub trait AllocStressCtors { type Env: sails_rs::client::GearEnv; fn new_for_bench( self, - ) -> sails_rs::client::PendingCtor; + ) -> sails_rs::client::PendingCtor; } -impl AllocStressProgramCtors - for sails_rs::client::Deployment +impl AllocStressCtors + for sails_rs::client::Deployment { type Env = E; fn new_for_bench( self, - ) -> sails_rs::client::PendingCtor { + ) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } @@ -36,16 +34,16 @@ pub mod io { use super::*; sails_rs::io_struct_impl!(NewForBench () -> ()); } -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub struct AllocStressResult { - pub inner: Vec, -} pub mod alloc_stress { use super::*; + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub struct AllocStressResult { + pub inner: Vec, + } pub trait AllocStress { type Env: sails_rs::client::GearEnv; fn alloc_stress( diff --git a/benchmarks/src/benchmarks.rs b/benchmarks/src/benchmarks.rs index 0e95e4014..e9da1f802 100644 --- a/benchmarks/src/benchmarks.rs +++ b/benchmarks/src/benchmarks.rs @@ -18,13 +18,13 @@ //! ``` use crate::clients::{ - alloc_stress_client::{AllocStressProgram, AllocStressProgramCtors, alloc_stress::*}, - compute_stress_client::{ComputeStressProgram, ComputeStressProgramCtors, compute_stress::*}, - counter_bench_client::{CounterBenchProgram, CounterBenchProgramCtors, counter_bench::*}, + alloc_stress_client::{AllocStress as _, AllocStressCtors, alloc_stress::*}, + compute_stress_client::{ComputeStress as _, ComputeStressCtors, compute_stress::*}, + counter_bench_client::{CounterBench as _, CounterBenchCtors, counter_bench::*}, }; use gtest::{System, constants::DEFAULT_USER_ALICE}; use itertools::{Either, Itertools}; -use ping_pong_bench_app::client::{PingPong, PingPongCtors, PingPongPayload, ping_pong_service::*}; +use ping_pong_bench_app::client::{PingPong, PingPongCtors, ping_pong_service::*}; use redirect_client::{RedirectClient, RedirectClientCtors, redirect::*}; use redirect_proxy_client::{RedirectProxyClient, RedirectProxyClientCtors, proxy::*}; use sails_rs::{client::*, prelude::*}; @@ -57,10 +57,7 @@ async fn alloc_stress_bench() { async fn compute_stress_bench() { let wasm_path = "../target/wasm32-gear/release/compute_stress.opt.wasm"; let env = create_env(); - let program = deploy_for_bench(&env, wasm_path, |d| { - ComputeStressProgramCtors::new_for_bench(d) - }) - .await; + let program = deploy_for_bench(&env, wasm_path, |d| ComputeStressCtors::new_for_bench(d)).await; let mut service = program.compute_stress(); let input_value = 30; @@ -91,10 +88,7 @@ async fn compute_stress_bench() { async fn counter_bench() { let wasm_path = "../target/wasm32-gear/release/counter_bench.opt.wasm"; let env = create_env(); - let program = deploy_for_bench(&env, wasm_path, |d| { - CounterBenchProgramCtors::new_for_bench(d) - }) - .await; + let program = deploy_for_bench(&env, wasm_path, |d| CounterBenchCtors::new_for_bench(d)).await; let mut service = program.counter_bench(); let mut expected_value = 0; @@ -270,10 +264,7 @@ async fn alloc_stress_test(n: u32) -> (usize, u64) { // Path taken from the .binpath file let wasm_path = "../target/wasm32-gear/release/alloc_stress.opt.wasm"; let env = create_env(); - let program = deploy_for_bench(&env, wasm_path, |d| { - AllocStressProgramCtors::new_for_bench(d) - }) - .await; + let program = deploy_for_bench(&env, wasm_path, |d| AllocStressCtors::new_for_bench(d)).await; let mut service = program.alloc_stress(); let message_id = service.alloc_stress(n).send_one_way().unwrap(); diff --git a/benchmarks/src/compute_stress_program.rs b/benchmarks/src/compute_stress_program.rs index dbf759422..0dab756e6 100644 --- a/benchmarks/src/compute_stress_program.rs +++ b/benchmarks/src/compute_stress_program.rs @@ -1,16 +1,16 @@ // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; -pub struct ComputeStressProgramProgram; -impl sails_rs::client::Program for ComputeStressProgramProgram {} -pub trait ComputeStressProgram { +pub struct ComputeStressProgram; +impl sails_rs::client::Program for ComputeStressProgram {} +pub trait ComputeStress { type Env: sails_rs::client::GearEnv; fn compute_stress( &self, ) -> sails_rs::client::Service; } -impl ComputeStressProgram - for sails_rs::client::Actor +impl ComputeStress + for sails_rs::client::Actor { type Env = E; fn compute_stress( @@ -19,20 +19,19 @@ impl ComputeStressProgram self.service(stringify!(ComputeStress)) } } -pub trait ComputeStressProgramCtors { +pub trait ComputeStressCtors { type Env: sails_rs::client::GearEnv; fn new_for_bench( self, - ) -> sails_rs::client::PendingCtor; + ) -> sails_rs::client::PendingCtor; } -impl ComputeStressProgramCtors - for sails_rs::client::Deployment +impl ComputeStressCtors + for sails_rs::client::Deployment { type Env = E; fn new_for_bench( self, - ) -> sails_rs::client::PendingCtor - { + ) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } @@ -41,16 +40,16 @@ pub mod io { use super::*; sails_rs::io_struct_impl!(NewForBench () -> ()); } -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub struct ComputeStressResult { - pub res: u32, -} pub mod compute_stress { use super::*; + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub struct ComputeStressResult { + pub res: u32, + } pub trait ComputeStress { type Env: sails_rs::client::GearEnv; fn compute_stress( diff --git a/benchmarks/src/counter_bench_program.rs b/benchmarks/src/counter_bench_program.rs index 632002229..50560441e 100644 --- a/benchmarks/src/counter_bench_program.rs +++ b/benchmarks/src/counter_bench_program.rs @@ -1,16 +1,16 @@ // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; -pub struct CounterBenchProgramProgram; -impl sails_rs::client::Program for CounterBenchProgramProgram {} -pub trait CounterBenchProgram { +pub struct CounterBenchProgram; +impl sails_rs::client::Program for CounterBenchProgram {} +pub trait CounterBench { type Env: sails_rs::client::GearEnv; fn counter_bench( &self, ) -> sails_rs::client::Service; } -impl CounterBenchProgram - for sails_rs::client::Actor +impl CounterBench + for sails_rs::client::Actor { type Env = E; fn counter_bench( @@ -19,19 +19,19 @@ impl CounterBenchProgram self.service(stringify!(CounterBench)) } } -pub trait CounterBenchProgramCtors { +pub trait CounterBenchCtors { type Env: sails_rs::client::GearEnv; fn new_for_bench( self, - ) -> sails_rs::client::PendingCtor; + ) -> sails_rs::client::PendingCtor; } -impl CounterBenchProgramCtors - for sails_rs::client::Deployment +impl CounterBenchCtors + for sails_rs::client::Deployment { type Env = E; fn new_for_bench( self, - ) -> sails_rs::client::PendingCtor { + ) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } diff --git a/examples/demo/client/build.rs b/examples/demo/client/build.rs index 9a86a45ac..d3ab1bcb6 100644 --- a/examples/demo/client/build.rs +++ b/examples/demo/client/build.rs @@ -1,17 +1,10 @@ -use sails_rs::ClientGenerator; -use std::{env, path::PathBuf}; - fn main() { // Generate IDL file for the `Demo` app and client code from IDL file - // sails_rs::ClientBuilder::::from_env().build_idl(); - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let idl_file_path = manifest_dir.join("demo_client.idl"); - let client_rs_file_path = manifest_dir.join("src/demo_client.rs"); - - ClientGenerator::from_idl_path(&idl_file_path) + sails_rs::ClientBuilder::::from_env() + .build_idl() .with_mocks("with_mocks") .with_external_type("NonZeroU8", "core::num::NonZeroU8") .with_external_type("NonZeroU32", "core::num::NonZeroU32") - .generate_to(client_rs_file_path) + .generate() .unwrap(); } diff --git a/examples/demo/client/demo_client.idl b/examples/demo/client/demo_client.idl index f3a6baf60..fdd7e0923 100644 --- a/examples/demo/client/demo_client.idl +++ b/examples/demo/client/demo_client.idl @@ -142,7 +142,7 @@ service Chaos { } } -program ProgramToDo { +program DemoClient { constructors { /// Program constructor (called once at the very beginning of the program lifetime) Default(); diff --git a/examples/demo/client/src/demo_client.rs b/examples/demo/client/src/demo_client.rs index 348e51cab..67bed37c4 100644 --- a/examples/demo/client/src/demo_client.rs +++ b/examples/demo/client/src/demo_client.rs @@ -1,14 +1,13 @@ // Code generated by sails-client-gen. DO NOT EDIT. +#[cfg(feature = "with_mocks")] +#[cfg(not(target_arch = "wasm32"))] +extern crate std; #[allow(unused_imports)] use core::num::NonZeroU8; #[allow(unused_imports)] use core::num::NonZeroU32; #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; - -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -extern crate std; pub struct DemoClientProgram; impl sails_rs::client::Program for DemoClientProgram {} pub trait DemoClient { diff --git a/examples/no-svcs-prog/wasm/build.rs b/examples/no-svcs-prog/wasm/build.rs index 5cb10f108..3fb4913e3 100644 --- a/examples/no-svcs-prog/wasm/build.rs +++ b/examples/no-svcs-prog/wasm/build.rs @@ -1,16 +1,5 @@ fn main() { sails_rs::build_wasm(); - // sails_rs::build_client::(); - - use sails_rs::ClientGenerator; - use std::{env, path::PathBuf}; - - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let idl_file_path = manifest_dir.join("no_svcs_prog.idl"); - let client_rs_file_path = manifest_dir.join("src/no_svcs_prog.rs"); - - ClientGenerator::from_idl_path(&idl_file_path) - .generate_to(client_rs_file_path) - .unwrap(); + sails_rs::build_client::(); } diff --git a/examples/no-svcs-prog/wasm/no_svcs_prog.idl b/examples/no-svcs-prog/wasm/no_svcs_prog.idl index 443f67a40..429b45ddd 100644 --- a/examples/no-svcs-prog/wasm/no_svcs_prog.idl +++ b/examples/no-svcs-prog/wasm/no_svcs_prog.idl @@ -1,7 +1,7 @@ !@sails: 0.9.2 -program ProgramToDo { +program NoSvcsProg { constructors { Create(); } diff --git a/examples/proxy/src/main-idl-gen.rs b/examples/proxy/src/main-idl-gen.rs index 71e37f3e7..bb6a5e3a9 100644 --- a/examples/proxy/src/main-idl-gen.rs +++ b/examples/proxy/src/main-idl-gen.rs @@ -3,10 +3,9 @@ use std::path::PathBuf; fn main() { // TODO: Switch to new IDL generator crate - /* - sails_idl_gen::generate_idl_to_file::( - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("proxy.idl"), - ) - .unwrap(); - */ + // sails_rename::generate_idl_to_file::( + // Some("ProxyProgram"), + // PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("proxy.idl"), + // ) + // .unwrap(); } diff --git a/examples/redirect/client/build.rs b/examples/redirect/client/build.rs index 1910aca45..91b9e35ea 100644 --- a/examples/redirect/client/build.rs +++ b/examples/redirect/client/build.rs @@ -1,14 +1,3 @@ -use sails_rs::ClientGenerator; -use std::{env, path::PathBuf}; - fn main() { - // sails_rs::build_client::(); - - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let idl_file_path = manifest_dir.join("redirect_client.idl"); - let client_rs_file_path = manifest_dir.join("src/redirect_client.rs"); - - ClientGenerator::from_idl_path(&idl_file_path) - .generate_to(client_rs_file_path) - .unwrap(); + sails_rs::build_client::(); } diff --git a/examples/redirect/client/redirect_client.idl b/examples/redirect/client/redirect_client.idl index d1769e7d7..475104d88 100644 --- a/examples/redirect/client/redirect_client.idl +++ b/examples/redirect/client/redirect_client.idl @@ -11,7 +11,7 @@ service Redirect { } } -program ProgramToDo { +program RedirectClient { constructors { New(); } diff --git a/examples/redirect/proxy-client/build.rs b/examples/redirect/proxy-client/build.rs index 34b82c0f4..c101ea40b 100644 --- a/examples/redirect/proxy-client/build.rs +++ b/examples/redirect/proxy-client/build.rs @@ -1,12 +1,3 @@ -use sails_rs::ClientGenerator; -use std::{env, path::PathBuf}; - fn main() { - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let idl_file_path = manifest_dir.join("redirect_proxy_client.idl"); - let client_rs_file_path = manifest_dir.join("src/redirect_proxy_client.rs"); - - ClientGenerator::from_idl_path(&idl_file_path) - .generate_to(client_rs_file_path) - .unwrap(); + sails_rs::build_client::(); } diff --git a/examples/redirect/proxy-client/redirect_proxy_client.idl b/examples/redirect/proxy-client/redirect_proxy_client.idl index 758e4307e..4b2634273 100644 --- a/examples/redirect/proxy-client/redirect_proxy_client.idl +++ b/examples/redirect/proxy-client/redirect_proxy_client.idl @@ -9,7 +9,7 @@ service Proxy { } } -program ProgramToDo { +program RedirectProxyClient { constructors { /// Proxy Program's constructor New(target: ActorId); diff --git a/examples/rmrk/catalog/wasm/rmrk-catalog.idl b/examples/rmrk/catalog/wasm/rmrk-catalog.idl index de4ca9807..1932654fd 100644 --- a/examples/rmrk/catalog/wasm/rmrk-catalog.idl +++ b/examples/rmrk/catalog/wasm/rmrk-catalog.idl @@ -49,7 +49,7 @@ service RmrkCatalog { } } -program ProgramToDo { +program RmrkCatalog { constructors { New(); } diff --git a/examples/rmrk/resource/app/src/rmrk_catalog.rs b/examples/rmrk/resource/app/src/rmrk_catalog.rs index d60605d4d..fc847ad85 100644 --- a/examples/rmrk/resource/app/src/rmrk_catalog.rs +++ b/examples/rmrk/resource/app/src/rmrk_catalog.rs @@ -1,10 +1,9 @@ // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "mockall")] #[cfg(not(target_arch = "wasm32"))] extern crate std; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct RmrkCatalogProgram; impl sails_rs::client::Program for RmrkCatalogProgram {} pub trait RmrkCatalog { diff --git a/rs/Cargo.toml b/rs/Cargo.toml index b8e6ebe1c..e4537943d 100644 --- a/rs/Cargo.toml +++ b/rs/Cargo.toml @@ -64,5 +64,5 @@ gtest = ["std", "dep:gtest", "dep:log", "dep:tokio-stream"] idl-gen = ["dep:sails-idl-gen"] client-builder = ["std", "idl-gen", "dep:sails-client-gen", "dep:convert_case"] mockall = ["std", "dep:mockall"] -std = ["futures/std"] +std = ["futures/std", "sails-idl-gen?/std"] wasm-builder = ["dep:gwasm-builder"] diff --git a/rs/cli/Cargo.toml b/rs/cli/Cargo.toml index 97542cfba..fa117a1a5 100644 --- a/rs/cli/Cargo.toml +++ b/rs/cli/Cargo.toml @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] anyhow.workspace = true -askama.workspace = true +askama = { workspace = true, features = ["std"] } cargo-generate.workspace = true cargo_metadata.workspace = true clap = { workspace = true, features = ["derive"] } diff --git a/rs/client-gen/src/ctor_generators.rs b/rs/client-gen/src/ctor_generators.rs index a1fe19b10..9da4056c2 100644 --- a/rs/client-gen/src/ctor_generators.rs +++ b/rs/client-gen/src/ctor_generators.rs @@ -5,7 +5,7 @@ use rust::Tokens; use sails_idl_parser_v2::{ast, visitor::Visitor}; pub(crate) struct CtorGenerator<'ast> { - service_name: &'ast str, + program_name: &'ast str, sails_path: &'ast str, ctor_tokens: Tokens, io_tokens: Tokens, @@ -13,9 +13,9 @@ pub(crate) struct CtorGenerator<'ast> { } impl<'ast> CtorGenerator<'ast> { - pub(crate) fn new(service_name: &'ast str, sails_path: &'ast str) -> Self { + pub(crate) fn new(program_name: &'ast str, sails_path: &'ast str) -> Self { Self { - service_name, + program_name, sails_path, ctor_tokens: Tokens::new(), io_tokens: Tokens::new(), @@ -25,12 +25,12 @@ impl<'ast> CtorGenerator<'ast> { pub(crate) fn finalize(self) -> Tokens { quote! { - pub trait $(self.service_name)Ctors { + pub trait $(self.program_name)Ctors { type Env: $(self.sails_path)::client::GearEnv; $(self.trait_ctors_tokens) } - impl $(self.service_name)Ctors for $(self.sails_path)::client::Deployment<$(self.service_name)Program, E> { + impl $(self.program_name)Ctors for $(self.sails_path)::client::Deployment<$(self.program_name)Program, E> { type Env = E; $(self.ctor_tokens) } @@ -63,12 +63,12 @@ impl<'ast> Visitor<'ast> for CtorGenerator<'ast> { quote_in! { self.trait_ctors_tokens => $['\r'] - fn $fn_name_snake (self, $params_with_types) -> $(self.sails_path)::client::PendingCtor<$(self.service_name)Program, io::$fn_name, Self::Env>; + fn $fn_name_snake (self, $params_with_types) -> $(self.sails_path)::client::PendingCtor<$(self.program_name)Program, io::$fn_name, Self::Env>; }; quote_in! { self.ctor_tokens => $['\r'] - fn $fn_name_snake (self, $params_with_types) -> $(self.sails_path)::client::PendingCtor<$(self.service_name)Program, io::$fn_name, Self::Env> { + fn $fn_name_snake (self, $params_with_types) -> $(self.sails_path)::client::PendingCtor<$(self.program_name)Program, io::$fn_name, Self::Env> { self.pending_ctor($args) } }; diff --git a/rs/client-gen/src/lib.rs b/rs/client-gen/src/lib.rs index 2805c821c..a26c37ea6 100644 --- a/rs/client-gen/src/lib.rs +++ b/rs/client-gen/src/lib.rs @@ -1,8 +1,7 @@ use anyhow::{Context, Result}; -use convert_case::{Case, Casing}; use root_generator::RootGenerator; use sails_idl_parser_v2::{parse_idl, visitor}; -use std::{collections::HashMap, ffi::OsStr, fs, io::Write, path::Path}; +use std::{collections::HashMap, fs, io::Write, path::Path}; mod ctor_generators; mod events_generator; @@ -106,11 +105,8 @@ impl<'ast> ClientGenerator<'ast, IdlPath<'ast>> { let idl = fs::read_to_string(idl_path) .with_context(|| format!("Failed to open {} for reading", idl_path.display()))?; - let file_name = idl_path.file_stem().unwrap_or(OsStr::new("service")); - let service_name = file_name.to_string_lossy().to_case(Case::Pascal); - self.with_idl(&idl) - .generate_to(&service_name, client_path) + .generate_to(client_path) .context("failed to generate client")?; Ok(()) } @@ -121,11 +117,8 @@ impl<'ast> ClientGenerator<'ast, IdlPath<'ast>> { let idl = fs::read_to_string(idl_path) .with_context(|| format!("Failed to open {} for reading", idl_path.display()))?; - let file_name = idl_path.file_stem().unwrap_or(OsStr::new("service")); - let service_name = file_name.to_string_lossy().to_case(Case::Pascal); - self.with_idl(&idl) - .generate_to(&service_name, out_path) + .generate_to(out_path) .context("failed to generate client")?; Ok(()) } @@ -156,11 +149,10 @@ impl<'ast> ClientGenerator<'ast, IdlString<'ast>> { } } - pub fn generate(self, anonymous_service_name: &str) -> Result { + pub fn generate(self) -> Result { let idl = self.idl.0; let sails_path = self.sails_path.unwrap_or(SAILS); let mut generator = RootGenerator::new( - anonymous_service_name, self.mocks_feature_name, sails_path, self.external_types, @@ -177,15 +169,9 @@ impl<'ast> ClientGenerator<'ast, IdlString<'ast>> { Ok(code) } - pub fn generate_to( - self, - anonymous_service_name: &str, - out_path: impl AsRef, - ) -> Result<()> { + pub fn generate_to(self, out_path: impl AsRef) -> Result<()> { let out_path = out_path.as_ref(); - let code = self - .generate(anonymous_service_name) - .context("failed to generate client")?; + let code = self.generate().context("failed to generate client")?; fs::write(out_path, code).with_context(|| { format!("Failed to write generated client to {}", out_path.display()) diff --git a/rs/client-gen/src/root_generator.rs b/rs/client-gen/src/root_generator.rs index a05eb8b7b..d7987b7af 100644 --- a/rs/client-gen/src/root_generator.rs +++ b/rs/client-gen/src/root_generator.rs @@ -12,7 +12,7 @@ pub(crate) struct RootGenerator<'ast> { tokens: Tokens, service_impl_tokens: Tokens, service_trait_tokens: Tokens, - anonymous_service_name: &'ast str, + program_name: Option<&'ast str>, mocks_feature_name: Option<&'ast str>, sails_path: &'ast str, external_types: HashMap<&'ast str, &'ast str>, @@ -23,17 +23,16 @@ pub(crate) struct RootGenerator<'ast> { impl<'ast> RootGenerator<'ast> { pub(crate) fn new( - anonymous_service_name: &'ast str, mocks_feature_name: Option<&'ast str>, sails_path: &'ast str, external_types: HashMap<&'ast str, &'ast str>, no_derive_traits: bool, ) -> Self { Self { - anonymous_service_name, tokens: Tokens::new(), service_impl_tokens: Tokens::new(), service_trait_tokens: Tokens::new(), + program_name: None, mocks_feature_name, sails_path, external_types, @@ -44,7 +43,7 @@ impl<'ast> RootGenerator<'ast> { } pub(crate) fn finalize(self, with_no_std: bool) -> String { - let extern_std = if let Some(mocks_feature_name) = self.mocks_feature_name { + let mut tokens = if let Some(mocks_feature_name) = self.mocks_feature_name { quote! { $['\n'] #[cfg(feature = $(quoted(mocks_feature_name)))] @@ -55,7 +54,7 @@ impl<'ast> RootGenerator<'ast> { Tokens::new() }; - let mut tokens = quote! { + quote_in! { tokens => #[allow(unused_imports)] use $(self.sails_path)::{client::*, collections::*, prelude::*}; }; @@ -67,26 +66,25 @@ impl<'ast> RootGenerator<'ast> { }; } - let program_name = &self.anonymous_service_name.to_case(Case::Pascal); - quote_in! { tokens => - $extern_std + if let Some(program_name) = self.program_name { + quote_in! { tokens => + pub struct $(program_name)Program; - pub struct $(program_name)Program; + impl $(self.sails_path)::client::Program for $(program_name)Program {} - impl $(self.sails_path)::client::Program for $(program_name)Program {} + pub trait $program_name { + type Env: $(self.sails_path)::client::GearEnv; + $(self.service_trait_tokens) + } - pub trait $program_name { - type Env: $(self.sails_path)::client::GearEnv; - $(self.service_trait_tokens) - } - - impl $program_name for $(self.sails_path)::client::Actor<$(program_name)Program, E> { - type Env = E; - $(self.service_impl_tokens) - } + impl $program_name for $(self.sails_path)::client::Actor<$(program_name)Program, E> { + type Env = E; + $(self.service_impl_tokens) + } + }; + } - $(self.tokens) - }; + tokens.extend(self.tokens); let mut result = tokens.to_file_string().unwrap(); @@ -105,7 +103,9 @@ impl<'ast> RootGenerator<'ast> { impl<'ast> Visitor<'ast> for RootGenerator<'ast> { fn visit_program_unit(&mut self, program: &'ast ast::ProgramUnit) { - let mut ctor_gen = CtorGenerator::new(self.anonymous_service_name, self.sails_path); + self.program_name = Some(&program.name); + + let mut ctor_gen = CtorGenerator::new(&program.name, self.sails_path); ctor_gen.visit_program_unit(program); self.tokens.extend(ctor_gen.finalize()); @@ -118,14 +118,8 @@ impl<'ast> Visitor<'ast> for RootGenerator<'ast> { } fn visit_service_unit(&mut self, service: &'ast ast::ServiceUnit) { - let service_name = if service.name.is_empty() { - self.anonymous_service_name - } else { - &service.name - }; - let mut client_gen = ServiceGenerator::new( - service_name, + &service.name, self.sails_path, &self.external_types, self.mocks_feature_name, diff --git a/rs/client-gen/tests/generator.rs b/rs/client-gen/tests/generator.rs index 2831353ef..8e1ddedba 100644 --- a/rs/client-gen/tests/generator.rs +++ b/rs/client-gen/tests/generator.rs @@ -4,42 +4,42 @@ use sails_client_gen::ClientGenerator; fn test_basic_works() { let idl = include_str!("idls/basic_works.idl"); - insta::assert_snapshot!(gen_client(idl, "Basic")); + insta::assert_snapshot!(gen_client(idl)); } #[test] fn test_complex_type_generation_works() { const IDL: &str = include_str!("idls/complex_type_generation_works.idl"); - insta::assert_snapshot!(gen_client(IDL, "ComplexTypesProgram")); + insta::assert_snapshot!(gen_client(IDL)); } #[test] fn test_scope_resolution() { const IDL: &str = include_str!("idls/scope_test.idl"); - insta::assert_snapshot!(gen_client(IDL, "MyProgram")); + insta::assert_snapshot!(gen_client(IDL)); } #[test] fn test_multiple_services() { let idl = include_str!("idls/multiple_services.idl"); - insta::assert_snapshot!(gen_client(idl, "Multiple")); + insta::assert_snapshot!(gen_client(idl)); } #[test] fn test_rmrk_works() { const IDL: &str = include_str!("idls/rmrk_works.idl"); - insta::assert_snapshot!(gen_client(IDL, "RmrkCatalog")); + insta::assert_snapshot!(gen_client(IDL)); } #[test] fn test_events_works() { let idl = include_str!("idls/events_works.idl"); - insta::assert_snapshot!(gen_client(idl, "ServiceWithEvents")); + insta::assert_snapshot!(gen_client(idl)); } #[test] @@ -48,7 +48,7 @@ fn full_with_sails_path() { let code = ClientGenerator::from_idl(IDL) .with_sails_crate("my_crate::sails") - .generate("FullCoverageProgram") // Use new program name + .generate() // Use new program name .expect("generate client"); insta::assert_snapshot!(code); } @@ -61,14 +61,14 @@ fn test_external_types() { .with_sails_crate("my_crate::sails") .with_external_type("MyParam", "my_crate::MyParam") .with_no_derive_traits() - .generate("Service") + .generate() .expect("generate client"); insta::assert_snapshot!(code); } -fn gen_client(program: &str, service_name: &str) -> String { +fn gen_client(program: &str) -> String { ClientGenerator::from_idl(program) .with_mocks("with_mocks") - .generate(service_name) + .generate() .expect("generate client") } diff --git a/rs/client-gen/tests/snapshots/generator__basic_works.snap b/rs/client-gen/tests/snapshots/generator__basic_works.snap index 9e41253b0..e6d174636 100644 --- a/rs/client-gen/tests/snapshots/generator__basic_works.snap +++ b/rs/client-gen/tests/snapshots/generator__basic_works.snap @@ -1,22 +1,13 @@ --- source: rs/client-gen/tests/generator.rs -expression: "gen_client(idl, \"Basic\")" +expression: gen_client(idl) --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; -pub struct BasicProgram; -impl sails_rs::client::Program for BasicProgram {} -pub trait Basic { - type Env: sails_rs::client::GearEnv; -} -impl Basic for sails_rs::client::Actor { - type Env = E; -} +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub mod basic { use super::*; diff --git a/rs/client-gen/tests/snapshots/generator__complex_type_generation_works.snap b/rs/client-gen/tests/snapshots/generator__complex_type_generation_works.snap index 283896a87..6e33ef0bf 100644 --- a/rs/client-gen/tests/snapshots/generator__complex_type_generation_works.snap +++ b/rs/client-gen/tests/snapshots/generator__complex_type_generation_works.snap @@ -1,14 +1,13 @@ --- source: rs/client-gen/tests/generator.rs -expression: "gen_client(IDL, \"ComplexTypesProgram\")" +expression: gen_client(IDL) --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct ComplexTypesProgramProgram; impl sails_rs::client::Program for ComplexTypesProgramProgram {} pub trait ComplexTypesProgram { diff --git a/rs/client-gen/tests/snapshots/generator__events_works.snap b/rs/client-gen/tests/snapshots/generator__events_works.snap index dbdb62077..713d84a00 100644 --- a/rs/client-gen/tests/snapshots/generator__events_works.snap +++ b/rs/client-gen/tests/snapshots/generator__events_works.snap @@ -1,24 +1,13 @@ --- source: rs/client-gen/tests/generator.rs -expression: "gen_client(idl, \"ServiceWithEvents\")" +expression: gen_client(idl) --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; -pub struct ServiceWithEventsProgram; -impl sails_rs::client::Program for ServiceWithEventsProgram {} -pub trait ServiceWithEvents { - type Env: sails_rs::client::GearEnv; -} -impl ServiceWithEvents - for sails_rs::client::Actor -{ - type Env = E; -} +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub mod service_with_events { use super::*; diff --git a/rs/client-gen/tests/snapshots/generator__external_types.snap b/rs/client-gen/tests/snapshots/generator__external_types.snap index 8f3fe6d09..11fd38016 100644 --- a/rs/client-gen/tests/snapshots/generator__external_types.snap +++ b/rs/client-gen/tests/snapshots/generator__external_types.snap @@ -7,16 +7,6 @@ expression: code use my_crate::MyParam; #[allow(unused_imports)] use my_crate::sails::{client::*, collections::*, prelude::*}; -pub struct ServiceProgram; -impl my_crate::sails::client::Program for ServiceProgram {} -pub trait Service { - type Env: my_crate::sails::client::GearEnv; -} -impl Service - for my_crate::sails::client::Actor -{ - type Env = E; -} pub mod service { use super::*; diff --git a/rs/client-gen/tests/snapshots/generator__multiple_services.snap b/rs/client-gen/tests/snapshots/generator__multiple_services.snap index bb804da0a..6318f2b8e 100644 --- a/rs/client-gen/tests/snapshots/generator__multiple_services.snap +++ b/rs/client-gen/tests/snapshots/generator__multiple_services.snap @@ -1,22 +1,13 @@ --- source: rs/client-gen/tests/generator.rs -expression: "gen_client(idl, \"Multiple\")" +expression: gen_client(idl) --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; -pub struct MultipleProgram; -impl sails_rs::client::Program for MultipleProgram {} -pub trait Multiple { - type Env: sails_rs::client::GearEnv; -} -impl Multiple for sails_rs::client::Actor { - type Env = E; -} +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub mod multiple { use super::*; diff --git a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap index e19dfe451..71639eda9 100644 --- a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap +++ b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap @@ -1,14 +1,13 @@ --- source: rs/client-gen/tests/generator.rs -expression: "gen_client(IDL, \"RmrkCatalog\")" +expression: gen_client(IDL) --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct RmrkCatalogProgram; impl sails_rs::client::Program for RmrkCatalogProgram {} pub trait RmrkCatalog { diff --git a/rs/client-gen/tests/snapshots/generator__scope_resolution.snap b/rs/client-gen/tests/snapshots/generator__scope_resolution.snap index 52b38c88c..a27d0c6ba 100644 --- a/rs/client-gen/tests/snapshots/generator__scope_resolution.snap +++ b/rs/client-gen/tests/snapshots/generator__scope_resolution.snap @@ -1,14 +1,13 @@ --- source: rs/client-gen/tests/generator.rs -expression: "gen_client(IDL, \"MyProgram\")" +expression: gen_client(IDL) --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct MyProgramProgram; impl sails_rs::client::Program for MyProgramProgram {} pub trait MyProgram { diff --git a/rs/ethexe/Cargo.lock b/rs/ethexe/Cargo.lock index e5a3fc975..306aaf97f 100644 --- a/rs/ethexe/Cargo.lock +++ b/rs/ethexe/Cargo.lock @@ -442,6 +442,43 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "askama" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" +dependencies = [ + "askama_parser", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "syn 2.0.100", +] + +[[package]] +name = "askama_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" +dependencies = [ + "memchr", + "winnow 0.7.3", +] + [[package]] name = "async-trait" version = "0.1.87" @@ -2451,20 +2488,6 @@ dependencies = [ "tracing-subscriber 0.3.19", ] -[[package]] -name = "handlebars" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" -dependencies = [ - "log", - "pest", - "pest_derive", - "serde", - "serde_json", - "thiserror 1.0.69", -] - [[package]] name = "hash-db" version = "0.16.0" @@ -3633,51 +3656,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pest" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" -dependencies = [ - "memchr", - "thiserror 2.0.12", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "pest_meta" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" -dependencies = [ - "once_cell", - "pest", - "sha2 0.10.8", -] - [[package]] name = "pin-project-lite" version = "0.2.16" @@ -4159,6 +4137,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -4239,13 +4223,13 @@ dependencies = [ name = "sails-idl-gen" version = "0.9.2" dependencies = [ + "askama", "convert_case", "gprimitives", - "handlebars", + "quote", "sails-idl-meta", "scale-info", - "serde", - "serde_json", + "syn 2.0.100", "thiserror 2.0.12", ] @@ -4253,6 +4237,7 @@ dependencies = [ name = "sails-idl-meta" version = "0.9.2" dependencies = [ + "askama", "parity-scale-codec", "scale-info", ] @@ -5763,12 +5748,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - [[package]] name = "uint" version = "0.9.5" diff --git a/rs/ethexe/ethapp_with_events/tests/insta.rs b/rs/ethexe/ethapp_with_events/tests/insta.rs index 6aa8a27a8..ae09282ae 100644 --- a/rs/ethexe/ethapp_with_events/tests/insta.rs +++ b/rs/ethexe/ethapp_with_events/tests/insta.rs @@ -1,7 +1,6 @@ #[test] fn ethapp_with_events_generate_idl() { - let mut idl = Vec::new(); - sails_rs::generate_idl::(&mut idl).unwrap(); - let idl = String::from_utf8(idl).unwrap(); + let mut idl = String::new(); + sails_rs::generate_idl::(Some("MyProgram"), &mut idl).unwrap(); insta::assert_snapshot!(idl); } diff --git a/rs/ethexe/ethapp_with_events/tests/snapshots/insta__ethapp_with_events_generate_idl.snap b/rs/ethexe/ethapp_with_events/tests/snapshots/insta__ethapp_with_events_generate_idl.snap index 60937b75b..7c02a8bd6 100644 --- a/rs/ethexe/ethapp_with_events/tests/snapshots/insta__ethapp_with_events_generate_idl.snap +++ b/rs/ethexe/ethapp_with_events/tests/snapshots/insta__ethapp_with_events_generate_idl.snap @@ -2,24 +2,36 @@ source: ethapp_with_events/tests/insta.rs expression: idl --- -constructor { - Create : (); -}; +!@sails: 0.9.2 service Svc1 { - DoThis : (p1: u32, p2: str) -> u32; - query This : (p1: bool) -> bool; - - events { - DoThisEvent: struct { - /// Some u32 value - /// #[indexed] - p1: u32, - p2: str, - }; - } -}; + events { + DoThisEvent { + /// Some u32 value + /// #[indexed] + p1: u32, + p2: String, + }, + } + functions { + DoThis(p1: u32, p2: String) -> u32; + @query + This(p1: bool) -> bool; + } +} service Svc2 { - DoThis : (p1: u32, p2: str) -> u32; -}; + functions { + DoThis(p1: u32, p2: String) -> u32; + } +} + +program MyProgram { + constructors { + Create(); + } + services { + Svc1, + Svc2, + } +} diff --git a/rs/idl-gen/Cargo.toml b/rs/idl-gen/Cargo.toml index 6bbd529d9..311cb3017 100644 --- a/rs/idl-gen/Cargo.toml +++ b/rs/idl-gen/Cargo.toml @@ -10,15 +10,18 @@ repository.workspace = true rust-version.workspace = true [dependencies] +askama = { workspace = true, features = ["alloc", "derive"] } convert_case.workspace = true gprimitives.workspace = true -handlebars.workspace = true -sails-idl-meta.workspace = true +sails-idl-meta = { workspace = true, features = ["ast", "templates"] } scale-info = { workspace = true, features = ["derive", "docs", "serde"] } -serde = { workspace = true, features = ["derive"] } -serde-json.workspace = true thiserror.workspace = true +syn = { workspace = true, features = ["parsing", "full", "fold"] } +quote.workspace = true + +[features] +std = [] [dev-dependencies] insta.workspace = true -sails-idl-parser.workspace = true +sails-idl-parser-v2.workspace = true diff --git a/rs/idl-gen/hbs/composite.hbs b/rs/idl-gen/hbs/composite.hbs deleted file mode 100644 index 15cd6492f..000000000 --- a/rs/idl-gen/hbs/composite.hbs +++ /dev/null @@ -1,11 +0,0 @@ -{{#each docs}} -/// {{{this}}} -{{/each}} -type {{{lookup @root/type_names id}}} = struct { - {{#each fields}} - {{#each docs}} - /// {{{this}}} - {{/each}} - {{#if name}}{{name}}: {{/if~}}{{{lookup @root/type_names type}}}, - {{/each}} -}; diff --git a/rs/idl-gen/hbs/idl.hbs b/rs/idl-gen/hbs/idl.hbs deleted file mode 100644 index 94711ef61..000000000 --- a/rs/idl-gen/hbs/idl.hbs +++ /dev/null @@ -1,65 +0,0 @@ -{{#each types as |type|}} - {{#each type.type.def}} - {{~> (deref @key) id=type.id docs=type.type.docs}} - {{/each}} - -{{/each}} -{{#if (len ctors)}} -constructor { -{{#each ctors}} - {{#each ./[2]}} - /// {{{this}}} - {{/each}} - {{./[0]}} : ({{#each ./[1]}}{{#if @index}}, {{/if}}{{name}}: {{{lookup @root/type_names type}}}{{/each}}); -{{/each}} -}; - -{{/if}} -{{#each services}} -service {{#if name}}{{name}} {{/if}}{ -{{#each commands}} - {{#each ./[3]}} - /// {{{this}}} - {{/each}} - {{./[0]}} : ({{#each ./[1]}}{{#if @index}}, {{/if}}{{name}}: {{{lookup @root/type_names type}}}{{/each}}) -> {{{lookup @root/type_names ./[2]}}}; -{{/each}} -{{#each queries}} - {{#each ./[3]}} - /// {{{this}}} - {{/each}} - query {{./[0]}} : ({{#each ./[1]}}{{#if @index}}, {{/if}}{{name}}: {{{lookup @root/type_names type}}}{{/each}}) -> {{{lookup @root/type_names ./[2]}}}; -{{/each}} -{{#if (len events)}} - - events { - {{#each events}} - {{#each docs}} - /// {{{this}}} - {{/each}} - {{name}} - {{~#if fields.[1]}}: struct { - {{#each fields}} - {{#each docs}} - /// {{{this}}} - {{/each}} - {{#if name}}{{name}}: {{/if~}}{{{lookup @root/type_names type}}}, - {{/each}} - } {{~else}} - {{~#if fields.[0]}}: {{#with fields.[0]}} {{~#if name~}} struct { - {{#each docs}} - /// {{{this}}} - {{/each}} - {{name}}: {{{lookup @root/type_names type}}} - }{{else~}} - {{{lookup @root/type_names type}}} - {{~/if}} - {{~/with~}} - {{~/if~}} - {{~/if~}} - ; - {{/each}} - } -{{/if}} -}; - -{{/each}} \ No newline at end of file diff --git a/rs/idl-gen/hbs/variant.hbs b/rs/idl-gen/hbs/variant.hbs deleted file mode 100644 index 672df3f05..000000000 --- a/rs/idl-gen/hbs/variant.hbs +++ /dev/null @@ -1,31 +0,0 @@ -{{#each docs}} -/// {{{this}}} -{{/each}} -type {{{lookup @root/type_names id}}} = enum { -{{#each variants}} - {{#each docs}} - /// {{{this}}} - {{/each}} - {{name}} - {{~#if fields.[1]}}: struct { - {{#each fields~}} - {{#each docs}} - /// {{{this}}} - {{/each}} - {{#if name}}{{name}}: {{/if~}}{{{lookup @root/type_names type}}}, - {{/each}} - } {{~else}} - {{~#if fields.[0]}}: {{#with fields.[0]}} {{~#if name~}} struct { - {{#each docs}} - /// {{{this}}} - {{/each}} - {{name}}: {{{lookup @root/type_names type}}} - }{{else~}} - {{{lookup @root/type_names type}}} - {{~/if}} - {{~/with~}} - {{~/if~}} - {{~/if~}} - , -{{/each}} -}; diff --git a/rs/idl-gen/src/builder.rs b/rs/idl-gen/src/builder.rs new file mode 100644 index 000000000..a0332bc0b --- /dev/null +++ b/rs/idl-gen/src/builder.rs @@ -0,0 +1,2245 @@ +use super::*; +use crate::type_resolver::TypeResolver; +use scale_info::*; + +pub struct ProgramBuilder { + registry: PortableRegistry, + ctors_type_id: u32, + services_expo: Vec, +} + +impl ProgramBuilder { + pub fn new() -> Self { + let mut service_ids: BTreeMap = BTreeMap::new(); + let mut registry = Registry::new(); + let ctors = P::constructors(); + let ctors_type_id = registry.register_type(&ctors).id; + let services_expo = P::services() + .map(|(service_name, meta)| { + // TEMP: dedup by interface_id + // will not be needed after routring by interface_id + let key = u64::from_le_bytes(meta.interface_id().0); + let (name, route) = if let Some(name) = service_ids.get(&key) { + (name.to_string(), Some(service_name.to_string())) + } else { + service_ids.insert(key, service_name); + (service_name.to_string(), None) + }; + ServiceExpo { + name, + route, + docs: vec![], + annotations: vec![], + } + }) + .collect::>(); + let registry = PortableRegistry::from(registry); + Self { + registry, + ctors_type_id, + services_expo, + } + } + + fn ctor_funcs(&self, resolver: &TypeResolver) -> Result> { + any_funcs(&self.registry, self.ctors_type_id)? + .map(|c| { + if c.fields.len() != 1 { + Err(Error::FuncMetaIsInvalid(format!( + "ctor `{}` has invalid number of fields", + c.name + ))) + } else { + let params_type_id = c.fields[0].ty.id; + let params_type = &self + .registry + .resolve(params_type_id) + .ok_or(Error::TypeIdIsUnknown(params_type_id))?; + if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { + let params = params_type + .fields + .iter() + .map(|f| -> Result<_> { + let name = f.name.as_ref().ok_or_else(|| { + Error::FuncMetaIsInvalid(format!( + "ctor `{}` param is missing a name", + c.name + )) + })?; + let type_decl = resolver + .get(f.ty.id) + .cloned() + .ok_or(Error::TypeIdIsUnknown(f.ty.id))?; + Ok(FuncParam { + name: name.to_string(), + type_decl, + }) + }) + .collect::>>()?; + Ok(CtorFunc { + name: c.name.to_string(), + params, + docs: c.docs.iter().map(|s| s.to_string()).collect(), + annotations: vec![], + }) + } else { + Err(Error::FuncMetaIsInvalid(format!( + "ctor `{}` params type is not a composite", + c.name + ))) + } + } + }) + .collect() + } + + pub fn build(self, name: String) -> Result { + let mut exclude = BTreeSet::new(); + exclude.insert(self.ctors_type_id); + exclude.extend(any_funcs_ids(&self.registry, self.ctors_type_id)?); + let resolver = TypeResolver::try_from(&self.registry, exclude)?; + let ctors = self.ctor_funcs(&resolver)?; + let services = self.services_expo; + let types = resolver.into_types(); + + Ok(ProgramUnit { + name, + ctors, + services, + types, + docs: vec![], + annotations: vec![], + }) + } +} + +fn any_funcs( + registry: &PortableRegistry, + func_type_id: u32, +) -> Result>> { + let funcs = registry + .resolve(func_type_id) + .ok_or(Error::TypeIdIsUnknown(func_type_id))?; + if let scale_info::TypeDef::Variant(variant) = &funcs.type_def { + Ok(variant.variants.iter()) + } else { + Err(Error::FuncMetaIsInvalid(format!( + "func type id {func_type_id} references a type that is not a variant" + ))) + } +} + +fn any_funcs_ids(registry: &PortableRegistry, func_type_id: u32) -> Result> { + any_funcs(registry, func_type_id)? + .map(|variant| { + variant + .fields + .first() + .map(|field| field.ty.id) + .ok_or_else(|| { + Error::FuncMetaIsInvalid(format!("func `{}` has no fields", variant.name)) + }) + }) + .collect::>>() +} + +pub struct ServiceBuilder<'a> { + name: &'a str, + meta: &'a AnyServiceMeta, + registry: PortableRegistry, + commands_type_id: u32, + queries_type_id: u32, + events_type_id: u32, +} + +impl<'a> ServiceBuilder<'a> { + pub fn new(name: &'a str, meta: &'a AnyServiceMeta) -> Self { + let mut registry = Registry::new(); + let commands_type_id = registry.register_type(meta.commands()).id; + let queries_type_id = registry.register_type(meta.queries()).id; + let events_type_id = registry.register_type(meta.events()).id; + let registry = PortableRegistry::from(registry); + Self { + name, + meta, + registry, + commands_type_id, + queries_type_id, + events_type_id, + } + } + + pub fn build(self) -> Result> { + let mut services = Vec::new(); + let mut extends = Vec::new(); + for (name, meta) in self.meta.base_services() { + extends.push(name.to_string()); + // TODO: dedup base services based on `interface_id` + services.extend(ServiceBuilder::new(name, meta).build()?); + } + + let exclude = BTreeSet::from_iter(self.exclude_type_ids()?); + let resolver = TypeResolver::try_from(&self.registry, exclude)?; + let commands = self.commands(&resolver)?; + let queries = self.queries(&resolver)?; + let events = self.events(&resolver)?; + let types = resolver.into_types(); + + services.push(ServiceUnit { + name: self.name.to_string(), + extends, + funcs: [commands, queries].concat(), + events, + types, + docs: vec![], + annotations: vec![], + }); + Ok(services) + } + + fn exclude_type_ids(&self) -> Result> { + let base = vec![ + self.commands_type_id, + self.queries_type_id, + self.events_type_id, + ] + .into_iter(); + let command_ids = any_funcs_ids(&self.registry, self.commands_type_id)?; + let query_ids = any_funcs_ids(&self.registry, self.queries_type_id)?; + Ok(base.chain(command_ids).chain(query_ids)) + } + + fn commands(&self, resolver: &TypeResolver) -> Result> { + any_funcs(&self.registry, self.commands_type_id)? + .map(|c| { + if c.fields.len() != 2 { + Err(Error::FuncMetaIsInvalid(format!( + "command `{}` has invalid number of fields", + c.name + ))) + } else { + let params_type_id = c.fields[0].ty.id; + let params_type = self + .registry + .resolve(params_type_id) + .ok_or(Error::TypeIdIsUnknown(params_type_id))?; + let output_type_id = c.fields[1].ty.id; + let mut output = resolver + .get(output_type_id) + .cloned() + .ok_or(Error::TypeIdIsUnknown(output_type_id))?; + let mut throws = None; + // TODO: unwrap result param + if let Some((ok, err)) = TypeDecl::result_type_decl(&output) { + output = ok; + throws = Some(err); + }; + if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { + let params = params_type + .fields + .iter() + .map(|f| -> Result<_> { + let name = f.name.as_ref().ok_or_else(|| { + Error::FuncMetaIsInvalid(format!( + "command `{}` param is missing a name", + c.name + )) + })?; + let type_decl = resolver + .get(f.ty.id) + .cloned() + .ok_or(Error::TypeIdIsUnknown(f.ty.id))?; + Ok(FuncParam { + name: name.to_string(), + type_decl, + }) + }) + .collect::>>()?; + Ok(ServiceFunc { + name: c.name.to_string(), + params, + output, + throws, + kind: FunctionKind::Command, + docs: c.docs.iter().map(|s| s.to_string()).collect(), + annotations: vec![], + }) + } else { + Err(Error::FuncMetaIsInvalid(format!( + "command `{}` params type is not a composite", + c.name + ))) + } + } + }) + .collect() + } + + fn queries(&self, resolver: &TypeResolver) -> Result> { + any_funcs(&self.registry, self.queries_type_id)? + .map(|c| { + if c.fields.len() != 2 { + Err(Error::FuncMetaIsInvalid(format!( + "query `{}` has invalid number of fields", + c.name + ))) + } else { + let params_type_id = c.fields[0].ty.id; + let params_type = self + .registry + .resolve(params_type_id) + .ok_or(Error::TypeIdIsUnknown(params_type_id))?; + let output_type_id = c.fields[1].ty.id; + let mut output = resolver + .get(output_type_id) + .cloned() + .ok_or(Error::TypeIdIsUnknown(output_type_id))?; + let mut throws = None; + // TODO: unwrap result param + if let Some((ok, err)) = TypeDecl::result_type_decl(&output) { + output = ok; + throws = Some(err); + }; + if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { + let params = params_type + .fields + .iter() + .map(|f| -> Result<_> { + let name = f.name.as_ref().ok_or_else(|| { + Error::FuncMetaIsInvalid(format!( + "query `{}` param is missing a name", + c.name + )) + })?; + let type_decl = resolver + .get(f.ty.id) + .cloned() + .ok_or(Error::TypeIdIsUnknown(f.ty.id))?; + Ok(FuncParam { + name: name.to_string(), + type_decl, + }) + }) + .collect::>>()?; + Ok(ServiceFunc { + name: c.name.to_string(), + params, + output, + // TODO: Throws type + throws, + kind: FunctionKind::Query, + docs: c.docs.iter().map(|s| s.to_string()).collect(), + annotations: vec![("query".to_string(), None)], + }) + } else { + Err(Error::FuncMetaIsInvalid(format!( + "query `{}` params type is not a composite", + c.name + ))) + } + } + }) + .collect() + } + + fn events(&self, resolver: &TypeResolver) -> Result> { + any_funcs(&self.registry, self.events_type_id)? + .map(|v| { + let fields = v + .fields + .iter() + .map(|field| -> Result<_> { + let type_decl = resolver + .get(field.ty.id) + .cloned() + .ok_or(Error::TypeIdIsUnknown(field.ty.id))?; + Ok(StructField { + name: field.name.as_ref().map(|s| s.to_string()), + type_decl, + docs: field.docs.iter().map(|d| d.to_string()).collect(), + annotations: vec![], + }) + }) + .collect::>>()?; + + Ok(ServiceEvent { + name: v.name.to_string(), + def: StructDef { fields }, + docs: v.docs.iter().map(|d| d.to_string()).collect(), + annotations: vec![], + }) + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::marker::PhantomData; + use core::num::NonZeroU128; + use gprimitives::{ActorId, CodeId, H160, H256, MessageId, NonZeroU256, U256}; + use scale_info::TypeInfo; + + mod utils { + use super::*; + + #[derive(TypeInfo)] + #[allow(unused)] + pub(super) enum SimpleCtors { + SimpleCtor(SimpleCtorParams), + } + + #[derive(TypeInfo)] + #[allow(unused)] + pub(super) struct SimpleCtorParams { + f1: u32, + } + + #[derive(TypeInfo)] + #[allow(unused)] + pub(super) struct SimpleFunctionParams { + f1: u32, + } + + #[derive(TypeInfo)] + #[allow(unused)] + pub(super) enum NoCommands {} + + #[derive(TypeInfo)] + #[allow(unused)] + pub(super) enum NoQueries {} + + #[derive(TypeInfo)] + #[allow(unused)] + pub(super) enum NoEvents {} + } + + fn test_program_unit() -> Result { + struct TestProgram(PhantomData); + impl ProgramMeta for TestProgram { + type ConstructorsMeta = C; + const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + } + + ProgramBuilder::new::>().build("TestProgram".to_string()) + } + + fn test_service_units(service_name: &'static str) -> Result> { + let meta = AnyServiceMeta::new::(); + ServiceBuilder::new(service_name, &meta).build() + } + + // ------------------------------------------------------------------------------------ + // -------------------------- Program section related tests --------------------------- + // ------------------------------------------------------------------------------------ + + /// Test various constructor validation errors + #[test] + fn ctor_validation_errors() { + // Define all constructor error test types + #[derive(TypeInfo)] + #[allow(unused)] + enum NonCompositeArgsCtors { + CtorWithInvalidArgTypes(u32), // u32 is not composite, should cause error + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum NamelessFieldsCtors { + CtorWithNamelessArgs(NamelessFieldParams), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct NamelessFieldParams(u32, String); + + #[derive(TypeInfo)] + #[allow(unused)] + enum NoArgsCtors { + CtorWithNoArgs, + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum TooManyArgsCtors { + CtorWithResult(ValidParams, String), // Should have exactly 1 field, not 2 + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct ValidParams { + pub param1: u32, + } + + // Helper function to test constructor validation errors + fn test_ctor_error(expected_error_msg: &str) { + let result = test_program_unit::(); + + assert!(result.is_err()); + let Err(Error::FuncMetaIsInvalid(msg)) = result else { + panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + }; + assert_eq!(msg.as_str(), expected_error_msg); + } + + // Test all error scenarios + test_ctor_error::( + "ctor `CtorWithInvalidArgTypes` params type is not a composite", + ); + + test_ctor_error::( + "ctor `CtorWithNamelessArgs` param is missing a name", + ); + + test_ctor_error::("func `CtorWithNoArgs` has no fields"); + + test_ctor_error::("ctor `CtorWithResult` has invalid number of fields"); + } + + /// Test that returned program meta has result_ty == None for all constructors in program IDL section + #[test] + fn ctors_build_works() { + #[derive(TypeInfo)] + #[allow(unused)] + enum ValidConstructors { + Zero(ZeroParams), + One(OneParam), + Three(ThreeParams), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct ZeroParams {} + + #[derive(TypeInfo)] + #[allow(unused)] + struct OneParam { + pub actor: ActorId, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct ThreeParams { + pub code: CodeId, + pub name: String, + pub num: NonZeroU256, + } + + let meta = test_program_unit::().expect("ProgramBuilder error"); + + // Check that all constructors have parsed + assert_eq!(meta.ctors.len(), 3); + } + + /// Test successful creation with valid constructors and services + #[test] + fn ctor_simple_positive_test() { + use TypeDecl::*; + + #[derive(TypeInfo)] + #[allow(unused)] + enum Ctors { + Ctor(InitParams), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct InitParams { + pub initial_value: u32, + } + + let meta = test_program_unit::().expect("ProgramBuilder error"); + + assert_eq!( + meta.ctors, + vec![CtorFunc { + name: "Ctor".to_string(), + params: vec![FuncParam { + name: "initial_value".to_string(), + type_decl: Primitive(PrimitiveType::U32) + }], + docs: vec![], + annotations: vec![] + }] + ); + } + + #[test] + fn program_has_services() { + struct TestService; + impl ServiceMeta for TestService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, sails_idl_meta::AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::zero(); + } + + struct TestProgram; + impl ProgramMeta for TestProgram { + type ConstructorsMeta = utils::SimpleCtors; + const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + ("TestService1", AnyServiceMeta::new::), + ("TestService2", AnyServiceMeta::new::), + ("TestService3", AnyServiceMeta::new::), + ]; + const ASYNC: bool = false; + } + + let meta = ProgramBuilder::new::() + .build("TestProgram".to_string()) + .expect("ProgramBuilder error"); + + assert_eq!(meta.services.len(), 3); + assert_eq!( + meta.services[0], + ServiceExpo { + name: "TestService1".to_string(), + route: None, + docs: vec![], + annotations: vec![] + } + ); + assert_eq!( + meta.services[1], + ServiceExpo { + name: "TestService1".to_string(), + route: Some("TestService2".to_string()), + docs: vec![], + annotations: vec![] + } + ); + assert_eq!( + meta.services[2], + ServiceExpo { + name: "TestService1".to_string(), + route: Some("TestService3".to_string()), + docs: vec![], + annotations: vec![] + } + ); + } + + // #[test] + // #[ignore = "TODO"] + // fn program_has_same_name_services() { + // struct TestService; + // impl ServiceMeta for TestService { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::zero(); + // } + + // struct TestProgram; + // impl ProgramMeta for TestProgram { + // type ConstructorsMeta = utils::SimpleCtors; + // const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + // ("TestService", AnyServiceMeta::new::), + // ("TestService", AnyServiceMeta::new::), + // ]; + // const ASYNC: bool = false; + // } + + // let meta = ProgramBuilder::new::() + // .build("TestProgram".to_string()) + // .expect("ProgramBuilder error"); + + // assert_eq!(meta.services.len(), 2); + // assert_eq!( + // meta.services[0], + // ServiceExpo { + // name: "TestService".to_string(), + // route: None, + // docs: vec![], + // annotations: vec![] + // } + // ); + // assert_eq!( + // meta.services[1], + // ServiceExpo { + // name: "TestService".to_string(), + // route: Some("TestService".to_string()), + // docs: vec![], + // annotations: vec![] + // } + // ); + // } + + #[test] + fn program_section_has_types_section() { + #[derive(TypeInfo)] + #[allow(unused)] + enum Ctors { + Ctor1(Ctor1Params), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct Ctor1Params { + pub param1: u32, + pub param2: ActorId, + pub param3: CtorType, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct CtorType(String); + + let meta = test_program_unit::().expect("ProgramBuilder error"); + + assert_eq!(meta.types.len(), 1); + assert!(matches!( + meta.types.first(), + Some(sails_idl_meta::Type { name, .. }) if name == "CtorType" + )); + } + + // ------------------------------------------------------------------------------------ + // -------------------- Extension and base services related tests --------------------- + // ------------------------------------------------------------------------------------ + + #[test] + fn base_service_entities_doesnt_automatically_occur() { + struct BaseServiceMeta; + impl ServiceMeta for BaseServiceMeta { + type CommandsMeta = BaseServiceCommands; + type QueriesMeta = BaseServiceQueries; + type EventsMeta = BaseServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(1u64); + } + + struct ExtendedServiceMeta; + impl ServiceMeta for ExtendedServiceMeta { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = ExtendedServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + &[("BaseServiceMeta", AnyServiceMeta::new::)]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(2u64); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BaseServiceCommands { + BaseCmd(BaseServiceFunctionParams, String), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BaseServiceQueries { + BaseQuery(BaseServiceFunctionParams, ActorId), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BaseServiceEvents { + BaseEvent(NonZeroU128), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct BaseServiceFunctionParams { + param: SomeBaseServiceType, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct SomeBaseServiceType(ActorId); + + #[derive(TypeInfo)] + #[allow(unused)] + enum ExtendedServiceEvents { + ExtendedEvent(SomeExtendedServiceType), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct SomeExtendedServiceType(CodeId); + + let services = test_service_units::("ExtendedService") + .expect("ServiceBuilder error"); + + assert_eq!(services.len(), 2); + let base_service = &services[0]; + let extended_service = &services[1]; + + assert_eq!(base_service.name.as_str(), "BaseServiceMeta"); + assert_eq!(extended_service.name.as_str(), "ExtendedService"); + + assert_eq!( + extended_service.extends, + vec!["BaseServiceMeta".to_string()] + ); + + assert_eq!(base_service.funcs.len(), 2); + assert!( + base_service + .funcs + .iter() + .any(|f| f.kind == FunctionKind::Command && f.name == "BaseCmd") + ); + assert!( + base_service + .funcs + .iter() + .any(|f| f.kind == FunctionKind::Query && f.name == "BaseQuery") + ); + + assert!(extended_service.funcs.is_empty()); + + let base_events: Vec<&str> = base_service + .events + .iter() + .map(|e| e.name.as_str()) + .collect(); + let extended_events: Vec<&str> = extended_service + .events + .iter() + .map(|e| e.name.as_str()) + .collect(); + assert_eq!(base_events, vec!["BaseEvent"]); + assert_eq!(extended_events, vec!["ExtendedEvent"]); + + let base_types: Vec<&str> = base_service.types.iter().map(|t| t.name.as_str()).collect(); + let extended_types: Vec<&str> = extended_service + .types + .iter() + .map(|t| t.name.as_str()) + .collect(); + assert_eq!(base_types, vec!["NonZeroU128", "SomeBaseServiceType"]); + assert_eq!(extended_types, vec!["SomeExtendedServiceType"]); + } + + #[test] + fn service_extension_with_conflicting_names() { + struct BaseServiceMeta; + impl ServiceMeta for BaseServiceMeta { + type CommandsMeta = BaseServiceCommands; + type QueriesMeta = BaseServiceQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(10u64); + } + + struct ExtendedServiceMeta; + impl ServiceMeta for ExtendedServiceMeta { + type CommandsMeta = ExtendedServiceCommands; + type QueriesMeta = ExtendedServiceQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + &[("BaseService", AnyServiceMeta::new::)]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(11u64); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BaseServiceCommands { + ConflictingCmd(utils::SimpleFunctionParams, String), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BaseServiceQueries { + ConflictingQuery(utils::SimpleFunctionParams, u32), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum ExtendedServiceCommands { + ConflictingCmd(utils::SimpleFunctionParams, bool), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum ExtendedServiceQueries { + ConflictingQuery(utils::SimpleFunctionParams, String), + } + + let services = test_service_units::("ExtendedService") + .expect("ServiceBuilder error"); + + assert_eq!(services.len(), 2); + let base_service = &services[0]; + let extended_service = &services[1]; + + assert_eq!(base_service.name.as_str(), "BaseService"); + assert_eq!(extended_service.name.as_str(), "ExtendedService"); + + let base_cmd = base_service + .funcs + .iter() + .find(|f| f.kind == FunctionKind::Command && f.name == "ConflictingCmd") + .expect("missing base command"); + let extended_cmd = extended_service + .funcs + .iter() + .find(|f| f.kind == FunctionKind::Command && f.name == "ConflictingCmd") + .expect("missing extended command"); + + assert_eq!(base_cmd.output, TypeDecl::Primitive(PrimitiveType::String)); + assert_eq!( + extended_cmd.output, + TypeDecl::Primitive(PrimitiveType::Bool) + ); + + let base_query = base_service + .funcs + .iter() + .find(|f| f.kind == FunctionKind::Query && f.name == "ConflictingQuery") + .expect("missing base query"); + let extended_query = extended_service + .funcs + .iter() + .find(|f| f.kind == FunctionKind::Query && f.name == "ConflictingQuery") + .expect("missing extended query"); + + assert_eq!(base_query.output, TypeDecl::Primitive(PrimitiveType::U32)); + assert_eq!( + extended_query.output, + TypeDecl::Primitive(PrimitiveType::String) + ); + } + + #[test] + fn service_extension_with_conflicting_events() { + struct BaseService; + impl ServiceMeta for BaseService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = BaseServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(20u64); + } + + struct ExtendedService; + impl ServiceMeta for ExtendedService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = ExtendedServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + &[("BaseService", AnyServiceMeta::new::)]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(21u64); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BaseServiceEvents { + ConflictingEvent(u32), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum ExtendedServiceEvents { + ConflictingEvent(String), + } + + let services = + test_service_units::("ExtendedService").expect("ServiceBuilder error"); + + assert_eq!(services.len(), 2); + let base_service = &services[0]; + let extended_service = &services[1]; + + assert_eq!(base_service.name.as_str(), "BaseService"); + assert_eq!(extended_service.name.as_str(), "ExtendedService"); + + let base_event = base_service + .events + .iter() + .find(|e| e.name == "ConflictingEvent") + .expect("missing base event"); + let extended_event = extended_service + .events + .iter() + .find(|e| e.name == "ConflictingEvent") + .expect("missing extended event"); + + assert_eq!(base_event.def.fields.len(), 1); + assert_eq!( + base_event.def.fields[0].type_decl, + TypeDecl::Primitive(PrimitiveType::U32) + ); + + assert_eq!(extended_event.def.fields.len(), 1); + assert_eq!( + extended_event.def.fields[0].type_decl, + TypeDecl::Primitive(PrimitiveType::String) + ); + } + + #[test] + fn service_extension_with_conflicting_types() { + struct ServiceBase; + impl ServiceMeta for ServiceBase { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = BaseServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(30u64); + } + + #[allow(unused)] + #[derive(TypeInfo)] + enum BaseServiceEvents { + BaseEvent(GenericConstStruct<8>), + } + + struct ExtensionService; + impl ServiceMeta for ExtensionService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = ExtendedServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + &[("ServiceBase", AnyServiceMeta::new::)]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(31u64); + } + + #[allow(unused)] + #[derive(TypeInfo)] + enum ExtendedServiceEvents { + ExtEvent(GenericConstStruct<16>), + } + + #[allow(unused)] + #[derive(TypeInfo)] + struct GenericConstStruct([u8; N]); + + let services = test_service_units::("ExtensionService") + .expect("ServiceBuilder error"); + + assert_eq!(services.len(), 2); + let base_service = &services[0]; + let ext_service = &services[1]; + + assert_eq!(base_service.types.len(), 1); + assert_eq!(ext_service.types.len(), 1); + + let base_ty = &base_service.types[0]; + assert!(base_ty.name.starts_with("GenericConstStruct")); + let sails_idl_meta::TypeDef::Struct(base_struct_def) = &base_ty.def else { + panic!("expected struct type"); + }; + assert_eq!(base_struct_def.fields.len(), 1); + assert_eq!(base_struct_def.fields[0].type_decl.to_string(), "[u8; 8]"); + + let ext_ty = &ext_service.types[0]; + assert!(ext_ty.name.starts_with("GenericConstStruct")); + let sails_idl_meta::TypeDef::Struct(ext_struct_def) = &ext_ty.def else { + panic!("expected struct type"); + }; + assert_eq!(ext_struct_def.fields.len(), 1); + assert_eq!(ext_struct_def.fields[0].type_decl.to_string(), "[u8; 16]"); + } + + #[test] + fn service_extension_order() { + struct ServiceA1; + impl ServiceMeta for ServiceA1 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(40u64); + } + + struct ServiceA2; + impl ServiceMeta for ServiceA2 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(41u64); + } + + struct ServiceB2; + impl ServiceMeta for ServiceB2 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + ("ServiceA1", AnyServiceMeta::new::), + ("ServiceA2", AnyServiceMeta::new::), + ]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(42u64); + } + + struct ServiceB1; + impl ServiceMeta for ServiceB1 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(43u64); + } + + struct ServiceC; + impl ServiceMeta for ServiceC { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + ("ServiceB1", AnyServiceMeta::new::), + ("ServiceB2", AnyServiceMeta::new::), + ]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(44u64); + } + + let services = test_service_units::("ServiceC").expect("ServiceBuilder error"); + + assert_eq!(services.len(), 5); + let names: Vec<&str> = services.iter().map(|s| s.name.as_str()).collect(); + assert_eq!( + names, + vec![ + "ServiceB1", + "ServiceA1", + "ServiceA2", + "ServiceB2", + "ServiceC" + ] + ); + } + + // #[test] + // #[ignore = "TODO [future]: Must be 3 services when Sails binary protocol is implemented"] + // fn no_repeated_base_services() { + // struct BaseService; + // impl ServiceMeta for BaseService { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(50u64); + // } + + // struct Service1; + // impl ServiceMeta for Service1 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + // &[("BaseService", AnyServiceMeta::new::)]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(51u64); + // } + + // struct Service2; + // impl ServiceMeta for Service2 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + // &[("BaseService", AnyServiceMeta::new::)]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(52u64); + // } + + // struct TestProgram; + // impl ProgramMeta for TestProgram { + // type ConstructorsMeta = utils::SimpleCtors; + // const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + // ("Service1", AnyServiceMeta::new::), + // ("Service2", AnyServiceMeta::new::), + // ]; + // const ASYNC: bool = false; + // } + + // let doc = build_program_ast::(None).unwrap(); + // assert_eq!(doc.services.len(), 3); + // } + + // #[test] + // #[ignore = "TODO [future]: Must be 3 services when Sails binary protocol is implemented"] + // fn no_repeated_base_services_with_renaming() { + // struct BaseService; + // impl ServiceMeta for BaseService { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(60u64); + // } + + // struct Service1; + // impl ServiceMeta for Service1 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + // &[("BaseService", AnyServiceMeta::new::)]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(61u64); + // } + + // struct Service2; + // impl ServiceMeta for Service2 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + // &[("RenamedBaseService", AnyServiceMeta::new::)]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(62u64); + // } + + // struct TestProgram; + // impl ProgramMeta for TestProgram { + // type ConstructorsMeta = utils::SimpleCtors; + // const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + // ("Service1", AnyServiceMeta::new::), + // ("Service2", AnyServiceMeta::new::), + // ]; + // const ASYNC: bool = false; + // } + + // let doc = build_program_ast::(None).unwrap(); + // assert_eq!(doc.services.len(), 3); + // } + + // #[test] + // #[ignore = "TODO [future]: Must be error when Sails binary protocol is implemented"] + // fn no_same_service_in_base_services() { + // struct ServiceA; + // impl ServiceMeta for ServiceA { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(70u64); + // } + + // struct ServiceB; + // impl ServiceMeta for ServiceB { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + // ("ServiceA", AnyServiceMeta::new::), + // ("ServiceA", AnyServiceMeta::new::), + // ]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(71u64); + // } + + // assert!(test_service_units::("ServiceB").is_err()); + + // struct ServiceC; + // impl ServiceMeta for ServiceC { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + // ("ServiceA", AnyServiceMeta::new::), + // ("RenamedServiceA", AnyServiceMeta::new::), + // ]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(72u64); + // } + + // assert!(test_service_units::("ServiceC").is_err()); + // } + + // ------------------------------------------------------------------------------------ + // ------------------------------ Events related tests -------------------------------- + // ------------------------------------------------------------------------------------ + + #[test] + fn invalid_events_type() { + struct InvalidEventsService; + impl ServiceMeta for InvalidEventsService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = InvalidEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(80u64); + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct InvalidEvents { + pub field: u32, + } + + let res = test_service_units::("InvalidEventsService"); + + assert!(res.is_err()); + let Err(Error::FuncMetaIsInvalid(msg)) = res else { + panic!("Expected FuncMetaIsInvalid error, got {res:?}"); + }; + assert!(msg.contains("references a type that is not a variant")); + } + + #[test] + fn service_events_positive_test() { + use TypeDecl::*; + + struct EventService; + impl ServiceMeta for EventService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = EventServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(81u64); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum EventServiceEvents { + Zero, + One(u32), + Two(EventTwoParams), + Three { field1: ActorId, field2: String }, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct EventTwoParams { + pub field1: ActorId, + pub field2: String, + } + + let services = + test_service_units::("EventService").expect("ServiceBuilder error"); + + assert_eq!(services.len(), 1); + let service = &services[0]; + + assert_eq!( + service.events, + vec![ + ServiceEvent { + name: "Zero".to_string(), + def: StructDef { fields: vec![] }, + docs: vec![], + annotations: vec![], + }, + ServiceEvent { + name: "One".to_string(), + def: StructDef { + fields: vec![StructField { + name: None, + type_decl: Primitive(PrimitiveType::U32), + docs: vec![], + annotations: vec![], + }], + }, + docs: vec![], + annotations: vec![], + }, + ServiceEvent { + name: "Two".to_string(), + def: StructDef { + fields: vec![StructField { + name: None, + type_decl: TypeDecl::named("EventTwoParams".to_string()), + docs: vec![], + annotations: vec![], + }], + }, + docs: vec![], + annotations: vec![], + }, + ServiceEvent { + name: "Three".to_string(), + def: StructDef { + fields: vec![ + StructField { + name: Some("field1".to_string()), + type_decl: Primitive(PrimitiveType::ActorId), + docs: vec![], + annotations: vec![], + }, + StructField { + name: Some("field2".to_string()), + type_decl: Primitive(PrimitiveType::String), + docs: vec![], + annotations: vec![], + }, + ], + }, + docs: vec![], + annotations: vec![], + }, + ] + ); + + assert_eq!(service.types.len(), 1); + assert!(matches!( + service.types.first(), + Some(sails_idl_meta::Type { name, .. }) if name == "EventTwoParams" + )); + } + + // ------------------------------------------------------------------------------------ + // ----------------------------- Functions related tests ------------------------------ + // ------------------------------------------------------------------------------------ + + /// Test error when commands/queries are not variant types + #[test] + fn service_functions_non_variant_error() { + struct NotVariantCommandsService; + impl ServiceMeta for NotVariantCommandsService { + type CommandsMeta = NotVariantCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(90u64); + } + + struct NotVariantQueriesService; + impl ServiceMeta for NotVariantQueriesService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = NotVariantQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(91u64); + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct NotVariantCommands { + pub field: u32, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct NotVariantQueries(u32); + + let internal_check = |result: Result>| { + assert!(result.is_err()); + let Err(Error::FuncMetaIsInvalid(msg)) = result else { + panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + }; + assert!(msg.contains("references a type that is not a variant")); + }; + + internal_check(test_service_units::( + "TestService", + )); + internal_check(test_service_units::( + "TestService", + )); + } + + /// Test error when service variant doesn't have exactly 2 fields + #[test] + fn service_variant_field_count_error() { + struct InvalidCommandsService1; + impl ServiceMeta for InvalidCommandsService1 { + type CommandsMeta = BadCommands1; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(100u64); + } + + struct InvalidCommandsService2; + impl ServiceMeta for InvalidCommandsService2 { + type CommandsMeta = BadCommands2; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(101u64); + } + + struct InvalidQueriesService1; + impl ServiceMeta for InvalidQueriesService1 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = BadQueries1; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(102u64); + } + + struct InvalidQueriesService2; + impl ServiceMeta for InvalidQueriesService2 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = BadQueries2; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(103u64); + } + + // Commands/queries with wrong number of fields + #[derive(TypeInfo)] + #[allow(unused)] + enum BadCommands1 { + OneField(u32), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BadCommands2 { + ThreeFields(u32, String, bool), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BadQueries1 { + OneField(u32), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BadQueries2 { + ThreeFields(u32, String, bool), + } + + let internal_check = |result: Result>, expected_msg: &str| { + assert!(result.is_err()); + let Err(Error::FuncMetaIsInvalid(msg)) = result else { + panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + }; + assert_eq!(msg.as_str(), expected_msg); + }; + + internal_check( + test_service_units::("TestService"), + "command `OneField` has invalid number of fields", + ); + internal_check( + test_service_units::("TestService"), + "query `OneField` has invalid number of fields", + ); + internal_check( + test_service_units::("TestService"), + "command `ThreeFields` has invalid number of fields", + ); + internal_check( + test_service_units::("TestService"), + "query `ThreeFields` has invalid number of fields", + ); + } + + /// Test error when service method params are not composite + #[test] + fn service_params_non_composite_error() { + struct TestServiceMeta; + impl ServiceMeta for TestServiceMeta { + type CommandsMeta = BadCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(110u64); + } + + // Commands where the first field (params) is not composite + #[derive(TypeInfo)] + #[allow(unused)] + enum BadCommands { + BadCmd(u32, String), + } + + let result = test_service_units::("TestService"); + + assert!(result.is_err()); + let Err(Error::FuncMetaIsInvalid(msg)) = result else { + panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + }; + assert_eq!( + msg.as_str(), + "command `BadCmd` params type is not a composite" + ); + } + + /// Test error when service method params have nameless fields + #[test] + fn service_params_nameless_fields_error() { + struct BadServiceMeta; + impl ServiceMeta for BadServiceMeta { + type CommandsMeta = BadCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(111u64); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BadCommands { + BadCmd(NamelessParams, String), + } + + // Tuple struct with nameless fields for params + #[derive(TypeInfo)] + #[allow(unused)] + struct NamelessParams(u32, String); + + let result = test_service_units::("TestService"); + + assert!(result.is_err()); + let Err(Error::FuncMetaIsInvalid(msg)) = result else { + panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + }; + assert_eq!(msg.as_str(), "command `BadCmd` param is missing a name"); + } + + #[test] + fn service_fns_result_ty() { + struct TestServiceMeta; + impl ServiceMeta for TestServiceMeta { + type CommandsMeta = TestCommands; + type QueriesMeta = TestQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(120u64); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum TestCommands { + Unit(utils::SimpleFunctionParams, ()), + NonUnit(utils::SimpleFunctionParams, String), + WithUnit(utils::SimpleFunctionParams, Result<(), u32>), + Result(utils::SimpleFunctionParams, Result), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum TestQueries { + Unit(utils::SimpleFunctionParams, ()), + NonUnit(utils::SimpleFunctionParams, u32), + WithUnit(utils::SimpleFunctionParams, Result<(), u32>), + Result(utils::SimpleFunctionParams, Result), + } + + let services = + test_service_units::("TestService").expect("ServiceBuilder error"); + assert_eq!(services.len(), 1); + let service = &services[0]; + + let get = |name: &str, kind: FunctionKind| -> &ServiceFunc { + service + .funcs + .iter() + .find(|f| f.name == name && f.kind == kind) + .unwrap_or_else(|| panic!("missing {kind:?} {name}")) + }; + + assert_eq!( + get("Unit", FunctionKind::Command).output, + TypeDecl::Primitive(PrimitiveType::Void) + ); + assert_eq!(get("Unit", FunctionKind::Command).throws, None); + + assert_eq!( + get("NonUnit", FunctionKind::Command).output, + TypeDecl::Primitive(PrimitiveType::String) + ); + assert_eq!(get("NonUnit", FunctionKind::Command).throws, None); + + assert_eq!( + get("WithUnit", FunctionKind::Command).output, + TypeDecl::Primitive(PrimitiveType::Void) + ); + assert_eq!( + get("WithUnit", FunctionKind::Command).throws, + Some(TypeDecl::Primitive(PrimitiveType::U32)) + ); + + assert_eq!( + get("Result", FunctionKind::Command).output, + TypeDecl::Primitive(PrimitiveType::U32) + ); + assert_eq!( + get("Result", FunctionKind::Command).throws, + Some(TypeDecl::Primitive(PrimitiveType::String)) + ); + + assert_eq!( + get("Unit", FunctionKind::Query).output, + TypeDecl::Primitive(PrimitiveType::Void) + ); + assert_eq!(get("Unit", FunctionKind::Query).throws, None); + + assert_eq!( + get("NonUnit", FunctionKind::Query).output, + TypeDecl::Primitive(PrimitiveType::U32) + ); + assert_eq!(get("NonUnit", FunctionKind::Query).throws, None); + + assert_eq!( + get("WithUnit", FunctionKind::Query).output, + TypeDecl::Primitive(PrimitiveType::Void) + ); + assert_eq!( + get("WithUnit", FunctionKind::Query).throws, + Some(TypeDecl::Primitive(PrimitiveType::U32)) + ); + + assert_eq!( + get("Result", FunctionKind::Query).output, + TypeDecl::Primitive(PrimitiveType::U32) + ); + assert_eq!( + get("Result", FunctionKind::Query).throws, + Some(TypeDecl::Primitive(PrimitiveType::String)) + ); + } + + #[test] + fn service_function_variations_positive_test() { + struct ServiceWithOneCommand; + impl ServiceMeta for ServiceWithOneCommand { + type CommandsMeta = OneFunction; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(130u64); + } + + struct ServiceWithOneQuery; + impl ServiceMeta for ServiceWithOneQuery { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = OneFunction; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(131u64); + } + + struct ServiceWithNoFunctions; + impl ServiceMeta for ServiceWithNoFunctions { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(132u64); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum OneFunction { + Fn1(utils::SimpleFunctionParams, String), + } + + let internal_check = |service: &ServiceUnit, + expected_commands_count: usize, + expected_queries_count: usize| { + let commands_count = service + .funcs + .iter() + .filter(|f| f.kind == FunctionKind::Command) + .count(); + let queries_count = service + .funcs + .iter() + .filter(|f| f.kind == FunctionKind::Query) + .count(); + + assert_eq!(commands_count, expected_commands_count); + assert_eq!(queries_count, expected_queries_count); + + if expected_commands_count > 0 { + assert!( + service + .funcs + .iter() + .any(|f| f.kind == FunctionKind::Command && f.name == "Fn1") + ); + } + if expected_queries_count > 0 { + assert!( + service + .funcs + .iter() + .any(|f| f.kind == FunctionKind::Query && f.name == "Fn1") + ); + } + }; + + let svc = test_service_units::("TestService").unwrap(); + internal_check(&svc[0], 1, 0); + let svc = test_service_units::("TestService").unwrap(); + internal_check(&svc[0], 0, 1); + let svc = test_service_units::("TestService").unwrap(); + internal_check(&svc[0], 0, 0); + + struct Service; + impl ServiceMeta for Service { + type CommandsMeta = ServiceCommands; + type QueriesMeta = ServiceQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(133u64); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum ServiceCommands { + NoArgs(NoArgs, String), + OneArg(OneArg, u32), + MultiArgs(MultiArgs, bool), + NoResult(OneArg, ()), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum ServiceQueries { + NoArgs(NoArgs, String), + OneArg(OneArg, u32), + MultiArgs(MultiArgs, bool), + NoResult(OneArg, ()), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct NoArgs; + + #[derive(TypeInfo)] + #[allow(unused)] + struct OneArg { + pub arg1: u32, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct MultiArgs { + pub arg1: u32, + pub arg2: String, + pub arg3: bool, + } + + let service = test_service_units::("TestService").unwrap(); + let service = &service[0]; + + let get = |name: &str, kind: FunctionKind| -> &ServiceFunc { + service + .funcs + .iter() + .find(|f| f.name == name && f.kind == kind) + .unwrap_or_else(|| panic!("missing {kind:?} {name}")) + }; + + for kind in [FunctionKind::Command, FunctionKind::Query] { + assert_eq!(get("NoArgs", kind).params.len(), 0); + + let one_arg = get("OneArg", kind); + assert_eq!(one_arg.params.len(), 1); + assert_eq!(one_arg.params[0].name, "arg1"); + + let multi_args = get("MultiArgs", kind); + assert_eq!(multi_args.params.len(), 3); + assert_eq!(multi_args.params[0].name, "arg1"); + assert_eq!(multi_args.params[1].name, "arg2"); + assert_eq!(multi_args.params[2].name, "arg3"); + + let no_result = get("NoResult", kind); + assert_eq!(no_result.output, TypeDecl::Primitive(PrimitiveType::Void)); + assert_eq!(no_result.throws, None); + } + } + + // ------------------------------------------------------------------------------------ + // --------------------------- Types section related tests ---------------------------- + // ------------------------------------------------------------------------------------ + + /// Test that services with only primitive/builtin types have empty types sections + #[test] + fn service_non_user_defined_types_excluded() { + struct Service1; + impl ServiceMeta for Service1 { + type CommandsMeta = CommandsWithNonUserDefinedArgs; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(140u64); + } + + struct Service2; + impl ServiceMeta for Service2 { + type CommandsMeta = CommandWithUserDefinedArgs; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(141u64); + } + + struct Service3; + impl ServiceMeta for Service3 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = CommandsWithNonUserDefinedArgs; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(142u64); + } + + struct Service4; + impl ServiceMeta for Service4 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = CommandWithUserDefinedArgs; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(143u64); + } + + struct Service5; + impl ServiceMeta for Service5 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = EventsWithNonUserDefinedArgs; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(144u64); + } + + struct Service6; + impl ServiceMeta for Service6 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = EventsWithUserDefinedArgs; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(145u64); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum CommandWithUserDefinedArgs { + Cmd1(UserDefinedParams, String), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct UserDefinedParams { + pub arg1: NonUserDefinedArgs, + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum CommandsWithNonUserDefinedArgs { + Cmd1(NonUserDefinedArgs, String), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum EventsWithNonUserDefinedArgs { + Event1 { + number: u32, + flag: bool, + text: String, + actor: ActorId, + option_val: Option, + result_val: Result, + map: BTreeMap, + code: CodeId, + message: MessageId, + h160: H160, + h256: H256, + u256: U256, + // non_zero_u8: NonZeroU8, + // non_zero_u16: NonZeroU16, + // non_zero_u32: NonZeroU32, + // non_zero_u64: NonZeroU64, + // non_zero_u128: NonZeroU128, + // non_zero_u256: NonZeroU256, + }, + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum EventsWithUserDefinedArgs { + Event1(NonUserDefinedArgs), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct NonUserDefinedArgs { + pub number: u32, + pub flag: bool, + pub text: String, + pub actor: ActorId, + pub option_val: Option, + pub result_val: Result, + pub map: BTreeMap, + pub code: CodeId, + pub message: MessageId, + pub h160: H160, + pub h256: H256, + pub u256: U256, + // pub non_zero_u8: NonZeroU8, + // pub non_zero_u16: NonZeroU16, + // pub non_zero_u32: NonZeroU32, + // pub non_zero_u64: NonZeroU64, + // pub non_zero_u128: NonZeroU128, + // pub non_zero_u256: NonZeroU256, + } + + let check = |service: &ServiceUnit, expected_type_count: usize| { + assert_eq!(service.types.len(), expected_type_count); + if expected_type_count == 1 { + assert!(matches!( + service.types.first(), + Some(sails_idl_meta::Type { name, .. }) if name == "NonUserDefinedArgs" + )); + } + }; + + check(&test_service_units::("Service1").unwrap()[0], 0); + check(&test_service_units::("Service2").unwrap()[0], 1); + + check(&test_service_units::("Service3").unwrap()[0], 0); + check(&test_service_units::("Service4").unwrap()[0], 1); + + check(&test_service_units::("Service5").unwrap()[0], 0); + check(&test_service_units::("Service6").unwrap()[0], 1); + } + + #[test] + fn ctor_non_user_defined_types_excluded() { + #[derive(TypeInfo)] + #[allow(unused)] + enum CtorsWithNonUserDefinedArgs { + Ctor1(NonUserDefinedCtorArgs), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum CtorsWithUserDefinedArgs { + Ctor2(UserDefinedCtorArgs), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct NonUserDefinedCtorArgs { + pub number: u32, + pub flag: bool, + pub text: String, + pub actor: ActorId, + pub option_val: Option, + pub result_val: Result, + pub map: BTreeMap, + pub code: CodeId, + pub message: MessageId, + pub h160: H160, + pub h256: H256, + pub u256: U256, + // pub non_zero_u8: NonZeroU8, + // pub non_zero_u16: NonZeroU16, + // pub non_zero_u32: NonZeroU32, + // pub non_zero_u64: NonZeroU64, + // pub non_zero_u128: NonZeroU128, + // pub non_zero_u256: NonZeroU256, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct UserDefinedCtorArgs { + pub custom: CustomType, + pub number: u32, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct CustomType { + pub value: String, + } + + let meta1 = + test_program_unit::().expect("ProgramBuilder error"); + assert!(meta1.types.is_empty()); + + let meta2 = test_program_unit::().expect("ProgramBuilder error"); + assert_eq!(meta2.types.len(), 1); + assert!(matches!( + meta2.types.first(), + Some(sails_idl_meta::Type { name, .. }) if name == "CustomType" + )); + } + + // -------------------------------------------------------------------------------- + // ------------------------------ Miscellaneous tests ----------------------------- + // -------------------------------------------------------------------------------- + + #[test] + fn shared_and_same_name_types_across_services() { + struct Service1Meta; + impl ServiceMeta for Service1Meta { + type CommandsMeta = Service1Commands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(150u64); + } + + struct Service2Meta; + impl ServiceMeta for Service2Meta { + type CommandsMeta = Service2Commands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(151u64); + } + + // First service using both shared types + #[derive(TypeInfo)] + #[allow(unused)] + enum Service1Commands { + Cmd1(ServiceCommandParams, String), + Cmd2(ServiceCommandParams, SharedCustomType), + } + + // Second service using both shared types + #[derive(TypeInfo)] + #[allow(unused)] + enum Service2Commands { + Cmd3(ServiceCommandParams, String), + Cmd4(ServiceCommandParams, SharedCustomType), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct ServiceCommandParams { + param1: SimpleFunctionParams, + param2: utils::SimpleFunctionParams, + } + + // Define SimpleFunctionParams in local scope + #[derive(TypeInfo)] + #[allow(unused)] + struct SimpleFunctionParams { + f1: SharedCustomType, + } + + // Define a custom type to be reused across services + #[derive(TypeInfo)] + #[allow(unused)] + struct SharedCustomType; + + let service_1 = &test_service_units::("Service1").unwrap()[0]; + let service_2 = &test_service_units::("Service2").unwrap()[0]; + + assert_eq!(service_1.types.len(), 3); + assert_eq!(service_2.types.len(), 3); + + let type_names = |svc: &ServiceUnit| -> BTreeSet { + svc.types.iter().map(|t| t.name.clone()).collect() + }; + assert_eq!(type_names(service_1), type_names(service_2)); + assert!(type_names(service_1).contains("SharedCustomType")); + + let simple_params = service_1 + .types + .iter() + .filter(|t| t.name.contains("SimpleFunctionParams")) + .collect::>(); + assert_eq!(simple_params.len(), 2); + + let mut has_u32_field = false; + let mut has_shared_custom_type_field = false; + for ty in simple_params { + let sails_idl_meta::TypeDef::Struct(def) = &ty.def else { + continue; + }; + if def + .fields + .iter() + .any(|f| f.type_decl == TypeDecl::Primitive(PrimitiveType::U32)) + { + has_u32_field = true; + } + if def.fields.iter().any(|f| { + matches!(&f.type_decl, TypeDecl::Named { name, .. } if name == "SharedCustomType") + }) { + has_shared_custom_type_field = true; + } + } + + assert!(has_u32_field); + assert!(has_shared_custom_type_field); + } + + // #[test] + // #[ignore = "TODO [future]: Must be 3 services when Sails binary protocol is implemented"] + // fn no_repeated_services() { + // struct Service1; + // impl ServiceMeta for Service1 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(160u64); + // } + + // struct Service2; + // impl ServiceMeta for Service2 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(161u64); + // } + + // struct Service3; + // impl ServiceMeta for Service3 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(162u64); + // } + + // struct TestProgram; + // impl ProgramMeta for TestProgram { + // type ConstructorsMeta = utils::SimpleCtors; + // const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + // ("Service11", AnyServiceMeta::new::), + // ("Service12", AnyServiceMeta::new::), + // ("Service13", AnyServiceMeta::new::), + // ("Service21", AnyServiceMeta::new::), + // ("Service22", AnyServiceMeta::new::), + // ("Service31", AnyServiceMeta::new::), + // ]; + // const ASYNC: bool = false; + // } + + // let doc = build_program_ast::(None).unwrap(); + // assert_eq!(doc.services.len(), 3); + // } +} diff --git a/rs/idl-gen/src/errors.rs b/rs/idl-gen/src/errors.rs index 97988d871..3c4056005 100644 --- a/rs/idl-gen/src/errors.rs +++ b/rs/idl-gen/src/errors.rs @@ -1,3 +1,5 @@ +use super::*; + pub type Result = core::result::Result; #[derive(thiserror::Error, Debug)] @@ -13,9 +15,8 @@ pub enum Error { #[error("type `{0}` is not supported")] TypeIsUnsupported(String), #[error(transparent)] - TemplateIsBroken(#[from] Box), - #[error(transparent)] - RenderingFailed(#[from] Box), + Template(#[from] askama::Error), + #[cfg(feature = "std")] #[error(transparent)] - IoFailed(#[from] std::io::Error), + Io(#[from] std::io::Error), } diff --git a/rs/idl-gen/src/generic_resolver.rs b/rs/idl-gen/src/generic_resolver.rs new file mode 100644 index 000000000..0f0a50e8c --- /dev/null +++ b/rs/idl-gen/src/generic_resolver.rs @@ -0,0 +1,327 @@ +use super::*; + +pub(crate) fn resolve_generic_type_decl( + type_decl: &TypeDecl, + type_name: &str, + type_params: &[sails_idl_meta::TypeParameter], +) -> Result<(TypeDecl, BTreeSet)> { + let (generic_decl, suffixes) = syn_resolver::try_resolve(type_name, type_decl, type_params) + .ok_or_else(|| { + Error::TypeIsUnsupported(format!( + "Generic type {type_name} not resolved from decl {type_decl}" + )) + })?; + + Ok((generic_decl, suffixes)) +} + +mod syn_resolver { + use super::*; + use quote::ToTokens; + use syn::{ + GenericArgument, PathArguments, Type, TypeArray, TypeParen, TypePath, TypeReference, + TypeSlice, TypeTuple, + }; + + pub(super) fn try_resolve( + type_name: &str, + type_decl: &TypeDecl, + type_params: &[sails_idl_meta::TypeParameter], + ) -> Option<(TypeDecl, BTreeSet)> { + let syn_type = syn::parse_str::(type_name).ok()?; + let mut suffixes = BTreeSet::new(); + syn_resolve(&syn_type, type_decl, type_params, &mut suffixes).map(|td| (td, suffixes)) + } + + fn syn_resolve( + ty: &Type, + td: &TypeDecl, + type_params: &[sails_idl_meta::TypeParameter], + suffixes: &mut BTreeSet, + ) -> Option { + use TypeDecl::*; + + match (ty, td) { + ( + Type::Array(TypeArray { + elem, + len: len_expr, + .. + }), + Array { item, len }, + ) => { + // const generic arguments support limited to Array type + let len = if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Int(lit_int), + .. + }) = len_expr + && let Ok(len) = lit_int.base10_parse::() + { + len + } else { + let len_str = len_expr.to_token_stream().to_string(); + suffixes.insert(format!("{len_str}{len}")); + *len + }; + let item = syn_resolve(elem, item, type_params, suffixes)?; + Some(Array { + item: Box::new(item), + len, + }) + } + (Type::Slice(TypeSlice { elem, .. }), Slice { item }) => { + let item = syn_resolve(elem, item, type_params, suffixes)?; + Some(Slice { + item: Box::new(item), + }) + } + (Type::Tuple(TypeTuple { elems, .. }), Tuple { types }) + if elems.len() == types.len() => + { + let types: Option> = elems + .iter() + .zip(types) + .map(|(ty, td)| syn_resolve(ty, td, type_params, suffixes)) + .collect(); + Some(Tuple { types: types? }) + } + (Type::Reference(TypeReference { elem, .. }), _) => { + syn_resolve(elem, td, type_params, suffixes) + } + // No paren types in the final output. Only single value tuples + (Type::Paren(TypeParen { elem, .. }), _) => { + syn_resolve(elem, td, type_params, suffixes) + } + (Type::Path(TypePath { path, .. }), type_decl) => { + if let Some(generic) = generic_param(type_params, path) { + return Some(generic); + } + + match type_decl { + Slice { item } => { + let last_segment = path.segments.last()?; + let ty_name = last_segment.ident.to_string(); + let mut ty_generics: Vec<&Type> = Vec::new(); + if let PathArguments::AngleBracketed(syn_args) = &last_segment.arguments { + for arg in &syn_args.args { + if let GenericArgument::Type(t) = arg { + ty_generics.push(t) + } + } + } + if ty_name == "Vec" + && let [elem] = ty_generics.as_slice() + { + let item = syn_resolve(elem, item, type_params, suffixes)?; + return Some(Slice { + item: Box::new(item), + }); + } + if ty_name == "BTreeMap" + && let Tuple { types } = item.as_ref() + && let [key, value] = types.as_slice() + && let [ty_key, ty_value] = ty_generics.as_slice() + { + let key = syn_resolve(ty_key, key, type_params, suffixes)?; + let value = syn_resolve(ty_value, value, type_params, suffixes)?; + return Some(Slice { + item: Box::new(Tuple { + types: vec![key, value], + }), + }); + } + + None + } + Array { item: _, len: _ } => None, + Tuple { types: _ } => None, + Named { name, generics } => { + let last_segment = path.segments.last()?; + // let ty_name = last_segment.ident.to_string(); + // TODO: const generic arguments are still ignored. + // We never add anything to the `suffixes` set when we see `GenericArgument::Const`, + // which means `TypeResolver::register_user_defined` (see rs/idl-gen/src/type_resolver.rs:163-207) + // cannot differentiate two instantiations of the same type that only differ by const parameters + // unless those consts appear inside an array length. + // For types such as `struct Tag;` or wrappers that only forward the const generic to another const generic, + // every instantiation (`Tag<1>`, `Tag<2>`, …) collapses to the same IDL name, + // so the second definition silently reuses the metadata of the first and the registry becomes inconsistent. + let mut ty_generics: Vec<&Type> = Vec::new(); + if let PathArguments::AngleBracketed(syn_args) = &last_segment.arguments { + for arg in &syn_args.args { + if let GenericArgument::Type(t) = arg { + ty_generics.push(t) + } + } + } + let generics: Option> = ty_generics + .iter() + .zip(generics) + .map(|(ty, td)| syn_resolve(ty, td, type_params, suffixes)) + .collect(); + Some(Named { + name: name.clone(), + generics: generics?, + }) + } + Primitive(_) => Some(type_decl.clone()), + } + } + _ => None, + } + } + + fn generic_param( + type_params: &[sails_idl_meta::TypeParameter], + path: &syn::Path, + ) -> Option { + if let Some(ident) = path.get_ident() + && type_params.iter().any(|tp| *ident == tp.name) + { + Some(TypeDecl::Named { + name: ident.to_string(), + generics: vec![], + }) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::type_resolver::TypeResolver; + use scale_info::{MetaType, PortableRegistry, Registry, TypeInfo}; + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct GenericStruct { + f1: T, + f2: Option, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct ConstStruct { + f1: [u8; N], + f2: Option<[u8; N]>, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct ConstGenericStruct { + f1: GenericStruct, + f2: ConstStruct, + } + + #[test] + fn generic_resolver_generic_struct() { + use sails_idl_meta::{PrimitiveType::*, TypeDecl::*}; + + let meta_type = MetaType::new::>(); + let mut registry = Registry::new(); + let id = registry.register_type(&meta_type).id; + let portable_registry = PortableRegistry::from(registry); + let mut resolver = TypeResolver::from_registry(&portable_registry); + let ty = portable_registry.resolve(id).unwrap(); + let type_params = resolver.resolve_type_params(ty).unwrap(); + + let type_decl = resolver.get(id).unwrap(); + + let (generic_decl, _) = + resolve_generic_type_decl(type_decl, "GenericStruct", &type_params).unwrap(); + + assert_eq!( + &Named { + name: "GenericStruct".to_string(), + generics: vec![Primitive(U32)] + }, + type_decl + ); + + assert_eq!( + Named { + name: "GenericStruct".to_string(), + generics: vec![Named { + name: "T".to_string(), + generics: vec![] + }] + }, + generic_decl + ); + } + + #[test] + fn generic_resolver_cosnt_struct() { + use sails_idl_meta::TypeDecl::*; + + let meta_type = MetaType::new::>(); + let mut registry = Registry::new(); + let id = registry.register_type(&meta_type).id; + let portable_registry = PortableRegistry::from(registry); + let mut resolver = TypeResolver::from_registry(&portable_registry); + let ty = portable_registry.resolve(id).unwrap(); + let type_params = resolver.resolve_type_params(ty).unwrap(); + + let type_decl = resolver.get(id).unwrap(); + + let (generic_decl, _) = + resolve_generic_type_decl(type_decl, "ConstStruct", &type_params).unwrap(); + + assert_eq!( + &Named { + name: "ConstStructN32".to_string(), + generics: vec![] + }, + type_decl + ); + + assert_eq!( + Named { + name: "ConstStructN32".to_string(), + generics: vec![] + }, + generic_decl + ); + } + + #[test] + fn generic_resolver_generic_cosnt_struct() { + use sails_idl_meta::{PrimitiveType::*, TypeDecl::*}; + + let meta_type_u8_32 = MetaType::new::>(); + let meta_type_u8_64 = MetaType::new::>(); + let mut registry = Registry::new(); + let u8_32_id = registry.register_type(&meta_type_u8_32).id; + let _u8_64_id = registry.register_type(&meta_type_u8_64).id; + let portable_registry = PortableRegistry::from(registry); + let mut resolver = TypeResolver::from_registry(&portable_registry); + let ty = portable_registry.resolve(u8_32_id).unwrap(); + let type_params = resolver.resolve_type_params(ty).unwrap(); + + let type_decl = resolver.get(u8_32_id).unwrap(); + + let (generic_decl, _) = + resolve_generic_type_decl(type_decl, "ConstGenericStruct", &type_params).unwrap(); + + assert_eq!( + &Named { + name: "ConstGenericStruct".to_string(), + generics: vec![Primitive(U8)] + }, + type_decl + ); + + assert_eq!( + Named { + name: "ConstGenericStruct".to_string(), + generics: vec![Named { + name: "T".to_string(), + generics: vec![] + }] + }, + generic_decl + ); + } +} diff --git a/rs/idl-gen/src/lib.rs b/rs/idl-gen/src/lib.rs index df057eee4..0ea0ba54d 100644 --- a/rs/idl-gen/src/lib.rs +++ b/rs/idl-gen/src/lib.rs @@ -1,42 +1,60 @@ +#![no_std] + +extern crate alloc; + +#[cfg(feature = "std")] +extern crate std; + +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + format, + string::{String, ToString as _}, + vec, + vec::Vec, +}; +use askama::Template; pub use errors::*; -use handlebars::{Handlebars, handlebars_helper}; -use meta::ExpandedProgramMeta; pub use program::*; -use scale_info::{Field, PortableType, Variant, form::PortableForm}; -use serde::Serialize; -use std::{fs, io::Write, path::Path}; +use sails_idl_meta::*; +use scale_info::{Variant, form::PortableForm}; +mod builder; mod errors; -mod meta; -mod type_names; +mod generic_resolver; +mod type_resolver; -const IDL_TEMPLATE: &str = include_str!("../hbs/idl.hbs"); -const COMPOSITE_TEMPLATE: &str = include_str!("../hbs/composite.hbs"); -const VARIANT_TEMPLATE: &str = include_str!("../hbs/variant.hbs"); +const SAILS_VERSION: &str = env!("CARGO_PKG_VERSION"); pub mod program { use super::*; use sails_idl_meta::ProgramMeta; - pub fn generate_idl(idl_writer: impl Write) -> Result<()> { - render_idl( - &ExpandedProgramMeta::new(Some(P::constructors()), P::services())?, - idl_writer, - ) + pub fn generate_idl( + program_name: Option<&str>, + mut idl_writer: impl core::fmt::Write, + ) -> Result<()> { + let doc = build_program_ast::

(program_name)?; + doc.render_into(&mut idl_writer)?; + Ok(()) } - pub fn generate_idl_to_file(path: impl AsRef) -> Result<()> { - let mut idl_new_content = Vec::new(); - generate_idl::

(&mut idl_new_content)?; - if let Ok(idl_old_content) = fs::read(&path) + #[cfg(feature = "std")] + pub fn generate_idl_to_file( + program_name: Option<&str>, + path: impl AsRef, + ) -> Result<()> { + let mut idl_new_content = String::new(); + generate_idl::

(program_name, &mut idl_new_content)?; + if let Ok(idl_old_content) = std::fs::read_to_string(&path) && idl_new_content == idl_old_content { return Ok(()); } if let Some(dir_path) = path.as_ref().parent() { - fs::create_dir_all(dir_path)?; + std::fs::create_dir_all(dir_path)?; } - Ok(fs::write(&path, idl_new_content)?) + Ok(std::fs::write(&path, idl_new_content)?) } } @@ -44,77 +62,58 @@ pub mod service { use super::*; use sails_idl_meta::{AnyServiceMeta, ServiceMeta}; - pub fn generate_idl(idl_writer: impl Write) -> Result<()> { - render_idl( - &ExpandedProgramMeta::new(None, vec![("", AnyServiceMeta::new::())].into_iter())?, - idl_writer, - ) + pub fn generate_idl( + service_name: &str, + mut idl_writer: impl core::fmt::Write, + ) -> Result<()> { + let doc = build_service_ast(service_name, AnyServiceMeta::new::())?; + doc.render_into(&mut idl_writer)?; + Ok(()) } - pub fn generate_idl_to_file(path: impl AsRef) -> Result<()> { - let mut idl_new_content = Vec::new(); - generate_idl::(&mut idl_new_content)?; - if let Ok(idl_old_content) = fs::read(&path) + #[cfg(feature = "std")] + pub fn generate_idl_to_file( + service_name: &str, + path: impl AsRef, + ) -> Result<()> { + let mut idl_new_content = String::new(); + generate_idl::(service_name, &mut idl_new_content)?; + if let Ok(idl_old_content) = std::fs::read_to_string(&path) && idl_new_content == idl_old_content { return Ok(()); } - Ok(fs::write(&path, idl_new_content)?) + if let Some(dir_path) = path.as_ref().parent() { + std::fs::create_dir_all(dir_path)?; + } + Ok(std::fs::write(&path, idl_new_content)?) } } -fn render_idl(program_meta: &ExpandedProgramMeta, idl_writer: impl Write) -> Result<()> { - let program_idl_data = ProgramIdlData { - type_names: program_meta.type_names()?.collect(), - types: program_meta.types().collect(), - ctors: program_meta.ctors().collect(), - services: program_meta - .services() - .map(|s| ServiceIdlData { - name: s.name(), - commands: s.commands().collect(), - queries: s.queries().collect(), - events: s.events().collect(), - }) - .collect(), +fn build_program_ast(program_name: Option<&str>) -> Result { + let mut services = Vec::new(); + for (name, meta) in P::services() { + services.extend(builder::ServiceBuilder::new(name, &meta).build()?); + } + let program = if let Some(name) = program_name { + Some(builder::ProgramBuilder::new::

().build(name.to_string())?) + } else { + None }; - - let mut handlebars = Handlebars::new(); - handlebars - .register_template_string("idl", IDL_TEMPLATE) - .map_err(Box::new)?; - handlebars - .register_template_string("composite", COMPOSITE_TEMPLATE) - .map_err(Box::new)?; - handlebars - .register_template_string("variant", VARIANT_TEMPLATE) - .map_err(Box::new)?; - handlebars.register_helper("deref", Box::new(deref)); - - handlebars - .render_to_write("idl", &program_idl_data, idl_writer) - .map_err(Box::new)?; - - Ok(()) -} - -type CtorIdlData<'a> = (&'a str, &'a Vec>, &'a Vec); -type FuncIdlData<'a> = (&'a str, &'a Vec>, u32, &'a Vec); - -#[derive(Serialize)] -struct ProgramIdlData<'a> { - type_names: Vec, - types: Vec<&'a PortableType>, - ctors: Vec>, - services: Vec>, + let doc = IdlDoc { + globals: vec![("sails".to_string(), Some(SAILS_VERSION.to_string()))], + program, + services, + }; + Ok(doc) } -#[derive(Serialize)] -struct ServiceIdlData<'a> { - name: &'a str, - commands: Vec>, - queries: Vec>, - events: Vec<&'a Variant>, +fn build_service_ast(service_name: &str, meta: AnyServiceMeta) -> Result { + let services = builder::ServiceBuilder::new(service_name, &meta).build()?; + let doc = IdlDoc { + globals: vec![("sails".to_string(), Some(SAILS_VERSION.to_string()))], + program: None, + services, + }; + Ok(doc) } - -handlebars_helper!(deref: |v: String| { v }); diff --git a/rs/idl-gen/src/meta.rs b/rs/idl-gen/src/meta.rs deleted file mode 100644 index 2dba692ac..000000000 --- a/rs/idl-gen/src/meta.rs +++ /dev/null @@ -1,372 +0,0 @@ -// This file is part of Gear. - -// Copyright (C) 2021-2023 Gear Technologies Inc. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Struct describing the types of a service comprised of command and query handlers. - -use crate::{ - errors::{Error, Result}, - type_names, -}; -use gprimitives::*; -use sails_idl_meta::*; -use scale_info::{ - Field, MetaType, PortableRegistry, PortableType, Registry, TypeDef, Variant, form::PortableForm, -}; - -struct CtorFuncMeta(String, u32, Vec>, Vec); - -struct ServiceFuncMeta(String, u32, Vec>, u32, Vec); - -pub(crate) struct ExpandedProgramMeta { - registry: PortableRegistry, - builtin_type_ids: Vec, - ctors_type_id: Option, - ctors: Vec, - services: Vec, -} - -impl ExpandedProgramMeta { - pub fn new( - ctors: Option, - services: impl Iterator, - ) -> Result { - let mut registry = Registry::new(); - let builtin_type_ids = registry - .register_types([ - MetaType::new::(), - MetaType::new::(), - MetaType::new::(), - MetaType::new::(), - MetaType::new::(), - MetaType::new::(), - MetaType::new::(), - ]) - .iter() - .map(|t| t.id) - .collect::>(); - let ctors_type_id = ctors.map(|ctors| registry.register_type(&ctors).id); - let services_data = services - .map(|(sname, sm)| { - ( - sname, - Self::flat_meta(&sm, |sm| sm.commands()) - .into_iter() - .map(|mt| registry.register_type(mt).id) - .collect::>(), - Self::flat_meta(&sm, |sm| sm.queries()) - .into_iter() - .map(|mt| registry.register_type(mt).id) - .collect::>(), - Self::flat_meta(&sm, |sm| sm.events()) - .into_iter() - .map(|mt| registry.register_type(mt).id) - .collect::>(), - ) - }) - .collect::>(); - let registry = PortableRegistry::from(registry); - let ctors = Self::ctor_funcs(®istry, ctors_type_id)?; - let services = services_data - .into_iter() - .map(|(sname, ct_ids, qt_ids, et_ids)| { - ExpandedServiceMeta::new(®istry, sname, ct_ids, qt_ids, et_ids) - }) - .collect::>>()?; - Ok(Self { - registry, - builtin_type_ids, - ctors_type_id, - ctors, - services, - }) - } - - /// Returns complex types introduced by program only - pub fn types(&self) -> impl Iterator { - self.registry.types.iter().filter(|ty| { - !ty.ty.path.namespace().is_empty() - && self.ctors_type_id.is_none_or(|id| id != ty.id) - && !self.commands_type_ids().any(|id| id == ty.id) - && !self.queries_type_ids().any(|id| id == ty.id) - && !self.events_type_ids().any(|id| id == ty.id) - && !self.ctor_params_type_ids().any(|id| id == ty.id) - && !self.command_params_type_ids().any(|id| id == ty.id) - && !self.query_params_type_ids().any(|id| id == ty.id) - && !self.builtin_type_ids.contains(&ty.id) - }) - } - - pub fn ctors(&self) -> impl Iterator>, &Vec)> { - self.ctors.iter().map(|c| (c.0.as_str(), &c.2, &c.3)) - } - - pub fn services(&self) -> impl Iterator { - self.services.iter() - } - - /// Returns names for all types used by program including primitive, complex and "internal" ones. - /// Each type name index corresponds to id of the type - pub fn type_names(&self) -> Result> { - let names = type_names::resolve(self.registry.types.iter())?; - Ok(names.into_iter().map(|i| i.1)) - } - - fn ctor_funcs( - registry: &PortableRegistry, - func_type_id: Option, - ) -> Result> { - if func_type_id.is_none() { - return Ok(Vec::new()); - } - let func_type_id = func_type_id.unwrap(); - any_funcs(registry, func_type_id)? - .map(|c| { - if c.fields.len() != 1 { - Err(Error::FuncMetaIsInvalid(format!( - "ctor `{}` has invalid number of fields", - c.name - ))) - } else { - let params_type = registry.resolve(c.fields[0].ty.id).unwrap_or_else(|| { - panic!( - "ctor params type id {} not found while it was registered previously", - c.fields[0].ty.id - ) - }); - if let TypeDef::Composite(params_type) = ¶ms_type.type_def { - Ok(CtorFuncMeta( - c.name.to_string(), - c.fields[0].ty.id, - params_type.fields.to_vec(), - c.docs.iter().map(|s| s.to_string()).collect(), - )) - } else { - Err(Error::FuncMetaIsInvalid(format!( - "ctor `{}` params type is not a composite", - c.name - ))) - } - } - }) - .collect() - } - - fn flat_meta( - service_meta: &AnyServiceMeta, - meta: fn(&AnyServiceMeta) -> &MetaType, - ) -> Vec<&MetaType> { - let mut metas = vec![meta(service_meta)]; - for (_, base_service_meta) in service_meta.base_services() { - metas.extend(Self::flat_meta(base_service_meta, meta)); - } - metas - } - - fn commands_type_ids(&self) -> impl Iterator + '_ { - self.services - .iter() - .flat_map(|s| s.commands_type_ids.iter().copied()) - } - - fn queries_type_ids(&self) -> impl Iterator + '_ { - self.services - .iter() - .flat_map(|s| s.queries_type_ids.iter().copied()) - } - - fn events_type_ids(&self) -> impl Iterator + '_ { - self.services - .iter() - .flat_map(|s| s.events_type_ids.iter().copied()) - } - - fn ctor_params_type_ids(&self) -> impl Iterator + '_ { - self.ctors.iter().map(|v| v.1) - } - - fn command_params_type_ids(&self) -> impl Iterator + '_ { - self.services - .iter() - .flat_map(|s| s.commands.iter().chain(&s.overriden_commands).map(|v| v.1)) - } - - fn query_params_type_ids(&self) -> impl Iterator + '_ { - self.services - .iter() - .flat_map(|s| s.queries.iter().chain(&s.overriden_queries).map(|v| v.1)) - } -} - -pub(crate) struct ExpandedServiceMeta { - name: &'static str, - commands_type_ids: Vec, - commands: Vec, - overriden_commands: Vec, - queries_type_ids: Vec, - queries: Vec, - overriden_queries: Vec, - events_type_ids: Vec, - events: Vec>, -} - -impl ExpandedServiceMeta { - fn new( - registry: &PortableRegistry, - name: &'static str, - commands_type_ids: Vec, - queries_type_ids: Vec, - events_type_ids: Vec, - ) -> Result { - let (commands, overriden_commands) = - Self::service_funcs(registry, commands_type_ids.iter().copied())?; - let (queries, overriden_queries) = - Self::service_funcs(registry, queries_type_ids.iter().copied())?; - let events = Self::event_variants(registry, events_type_ids.iter().copied())?; - Ok(Self { - name, - commands_type_ids, - commands, - overriden_commands, - queries_type_ids, - queries, - overriden_queries, - events_type_ids, - events, - }) - } - - pub fn name(&self) -> &str { - self.name - } - - pub fn commands( - &self, - ) -> impl Iterator>, u32, &Vec)> { - self.commands - .iter() - .map(|c| (c.0.as_str(), &c.2, c.3, &c.4)) - } - - pub fn queries( - &self, - ) -> impl Iterator>, u32, &Vec)> { - self.queries.iter().map(|c| (c.0.as_str(), &c.2, c.3, &c.4)) - } - - pub fn events(&self) -> impl Iterator> { - self.events.iter() - } - - fn service_funcs( - registry: &PortableRegistry, - func_type_ids: impl Iterator, - ) -> Result<(Vec, Vec)> { - let mut funcs_meta = Vec::new(); - let mut overriden_funcs_meta = Vec::new(); - for func_type_id in func_type_ids { - for func_descr in any_funcs(registry, func_type_id)? { - if func_descr.fields.len() != 2 { - return Err(Error::FuncMetaIsInvalid(format!( - "func `{}` has invalid number of fields", - func_descr.name - ))); - } - let func_params_type = registry.resolve(func_descr.fields[0].ty.id).unwrap_or_else( - || { - panic!( - "func params type id {} not found while it was registered previously", - func_descr.fields[0].ty.id - ) - }, - ); - if let TypeDef::Composite(func_params_type) = &func_params_type.type_def { - let func_meta = ServiceFuncMeta( - func_descr.name.to_string(), - func_descr.fields[0].ty.id, - func_params_type.fields.to_vec(), - func_descr.fields[1].ty.id, - func_descr.docs.iter().map(|s| s.to_string()).collect(), - ); - if !funcs_meta - .iter() - .any(|fm: &ServiceFuncMeta| fm.0 == func_meta.0) - { - funcs_meta.push(func_meta); - } else { - overriden_funcs_meta.push(func_meta); - } - } else { - return Err(Error::FuncMetaIsInvalid(format!( - "func `{}` params type is not a composite", - func_descr.name - ))); - } - } - } - Ok((funcs_meta, overriden_funcs_meta)) - } - - fn event_variants( - registry: &PortableRegistry, - events_type_ids: impl Iterator, - ) -> Result>> { - let mut events_variants = Vec::new(); - for events_type_id in events_type_ids { - let events = registry.resolve(events_type_id).unwrap_or_else(|| { - panic!( - "events type id {events_type_id} not found while it was registered previously" - ) - }); - if let TypeDef::Variant(variant) = &events.type_def { - for event_variant in &variant.variants { - if events_variants - .iter() - .any(|ev: &Variant| ev.name == event_variant.name) - { - return Err(Error::EventMetaIsAmbiguous(format!( - "events type id {} contains ambiguous event variant `{}`", - events_type_id, event_variant.name - ))); - } - events_variants.push(event_variant.clone()); - } - } else { - return Err(Error::EventMetaIsInvalid(format!( - "events type id {events_type_id} references a type that is not a variant" - ))); - } - } - Ok(events_variants) - } -} - -fn any_funcs( - registry: &PortableRegistry, - func_type_id: u32, -) -> Result>> { - let funcs = registry.resolve(func_type_id).unwrap_or_else(|| { - panic!("func type id {func_type_id} not found while it was registered previously") - }); - if let TypeDef::Variant(variant) = &funcs.type_def { - Ok(variant.variants.iter()) - } else { - Err(Error::FuncMetaIsInvalid(format!( - "func type id {func_type_id} references a type that is not a variant" - ))) - } -} diff --git a/rs/idl-gen/src/type_names.rs b/rs/idl-gen/src/type_names.rs deleted file mode 100644 index 1c57a24b4..000000000 --- a/rs/idl-gen/src/type_names.rs +++ /dev/null @@ -1,1074 +0,0 @@ -// This file is part of Gear. - -// Copyright (C) 2021-2023 Gear Technologies Inc. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Type names resolution. - -use crate::errors::{Error, Result}; -use convert_case::{Case, Casing}; -use core::num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128}; -use gprimitives::*; -use scale_info::{ - PortableType, Type, TypeDef, TypeDefArray, TypeDefPrimitive, TypeDefSequence, TypeDefTuple, - TypeInfo, form::PortableForm, -}; -use std::{ - collections::{BTreeMap, HashMap}, - rc::Rc, - result::Result as StdResult, - sync::OnceLock, -}; - -pub(super) fn resolve<'a>( - types: impl Iterator, -) -> Result> { - let types = types - .map(|t| (t.id, t)) - .collect::>(); - let type_names = types.iter().try_fold( - ( - BTreeMap::::new(), - HashMap::<(String, Vec), u32>::new(), - ), - |mut type_names, ty| { - resolve_type_name(&types, *ty.0, &mut type_names.0, &mut type_names.1) - .map(|_| type_names) - }, - ); - type_names.map(|type_names| { - type_names - .0 - .iter() - .map(|(id, name)| (*id, name.as_string(false, &type_names.1))) - .collect() - }) -} - -fn resolve_type_name( - types: &BTreeMap, - type_id: u32, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, -) -> Result { - if let Some(type_name) = resolved_type_names.get(&type_id) { - return Ok(type_name.clone()); - } - - let type_info = types - .get(&type_id) - .map(|t| &t.ty) - .ok_or_else(|| Error::TypeIdIsUnknown(type_id))?; - - let type_name: RcTypeName = match &type_info.type_def { - TypeDef::Tuple(tuple_def) => Rc::new(TupleTypeName::new( - types, - tuple_def, - resolved_type_names, - by_path_type_names, - )?), - TypeDef::Sequence(vector_def) => Rc::new(VectorTypeName::new( - types, - vector_def, - resolved_type_names, - by_path_type_names, - )?), - TypeDef::Array(array_def) => Rc::new(ArrayTypeName::new( - types, - array_def, - resolved_type_names, - by_path_type_names, - )?), - TypeDef::Composite(_) => { - if BTreeMapTypeName::is_btree_map_type(type_info) { - Rc::new(BTreeMapTypeName::new( - types, - type_info, - resolved_type_names, - by_path_type_names, - )?) - } else if actor_id::TypeNameImpl::is_type(type_info) { - Rc::new(actor_id::TypeNameImpl::new()) - } else if message_id::TypeNameImpl::is_type(type_info) { - Rc::new(message_id::TypeNameImpl::new()) - } else if code_id::TypeNameImpl::is_type(type_info) { - Rc::new(code_id::TypeNameImpl::new()) - } else if h160::TypeNameImpl::is_type(type_info) { - Rc::new(h160::TypeNameImpl::new()) - } else if h256::TypeNameImpl::is_type(type_info) { - Rc::new(h256::TypeNameImpl::new()) - } else if u256::TypeNameImpl::is_type(type_info) { - Rc::new(u256::TypeNameImpl::new()) - } else if nat8::TypeNameImpl::is_type(type_info) { - Rc::new(nat8::TypeNameImpl::new()) - } else if nat16::TypeNameImpl::is_type(type_info) { - Rc::new(nat16::TypeNameImpl::new()) - } else if nat32::TypeNameImpl::is_type(type_info) { - Rc::new(nat32::TypeNameImpl::new()) - } else if nat64::TypeNameImpl::is_type(type_info) { - Rc::new(nat64::TypeNameImpl::new()) - } else if nat128::TypeNameImpl::is_type(type_info) { - Rc::new(nat128::TypeNameImpl::new()) - } else if nat256::TypeNameImpl::is_type(type_info) { - Rc::new(nat256::TypeNameImpl::new()) - } else { - Rc::new(ByPathTypeName::new( - types, - type_info, - resolved_type_names, - by_path_type_names, - )?) - } - } - TypeDef::Variant(_) => { - if ResultTypeName::is_result_type(type_info) { - Rc::new(ResultTypeName::new( - types, - type_info, - resolved_type_names, - by_path_type_names, - )?) - } else if OptionTypeName::is_option_type(type_info) { - Rc::new(OptionTypeName::new( - types, - type_info, - resolved_type_names, - by_path_type_names, - )?) - } else { - Rc::new(ByPathTypeName::new( - types, - type_info, - resolved_type_names, - by_path_type_names, - )?) - } - } - TypeDef::Primitive(primitive_def) => Rc::new(PrimitiveTypeName::new(primitive_def)?), - _ => { - return Err(Error::TypeIsUnsupported(format!("{type_info:?}"))); - } - }; - - resolved_type_names.insert(type_id, type_name.clone()); - Ok(type_name) -} - -type RcTypeName = Rc; - -trait TypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String; // Make returning &str + use OnceCell to cache the result -} - -/// By path type name resolution. -struct ByPathTypeName { - possible_names: Vec<(String, Vec)>, - type_param_type_names: Vec, -} - -impl ByPathTypeName { - pub fn new( - types: &BTreeMap, - type_info: &Type, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let type_params = type_info.type_params.iter().try_fold( - ( - Vec::with_capacity(type_info.type_params.len()), - Vec::with_capacity(type_info.type_params.len()), - ), - |(mut type_param_ids, mut type_param_type_names), type_param| { - let type_param_id = type_param - .ty - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))? - .id; - let type_param_type_name = resolve_type_name( - types, - type_param_id, - resolved_type_names, - by_path_type_names, - )?; - type_param_ids.push(type_param_id); - type_param_type_names.push(type_param_type_name); - Ok::<(Vec, Vec>), Error>(( - type_param_ids, - type_param_type_names, - )) - }, - )?; - - let mut possible_names = Self::possible_names_by_path(type_info).fold( - Vec::with_capacity(type_info.path.segments.len() + 1), - |mut possible_names, name| { - possible_names.push((name.clone(), type_params.0.clone())); - let name_ref_count = by_path_type_names - .entry((name.clone(), type_params.0.clone())) - .or_default(); - *name_ref_count += 1; - possible_names - }, - ); - if let Some(first_name) = possible_names.first() { - // add numbered type name like `TypeName1`, `TypeName2` as last name - // to solve name conflict with const generic parameters `` - let name_ref_count = by_path_type_names.get(first_name).unwrap_or(&0); - let name = format!("{}{}", first_name.0, name_ref_count); - possible_names.push((name.clone(), first_name.1.clone())); - let name_ref_count = by_path_type_names - .entry((name.clone(), type_params.0.clone())) - .or_default(); - *name_ref_count += 1; - } else { - return Err(Error::TypeIsUnsupported(format!("{type_info:?}"))); - } - - Ok(Self { - possible_names, - type_param_type_names: type_params.1, - }) - } - - fn possible_names_by_path(type_info: &Type) -> impl Iterator + '_ { - let mut name = String::default(); - type_info.path.segments.iter().rev().map(move |segment| { - name = segment.to_case(Case::Pascal) + &name; - name.clone() - }) - } -} - -impl TypeName for ByPathTypeName { - fn as_string( - &self, - _for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - let name = self - .possible_names - .iter() - .find(|possible_name| { - by_path_type_names - .get(possible_name) - .is_some_and(|ref_count| *ref_count == 1) - }) - .unwrap_or_else(|| self.possible_names.last().unwrap()); - if self.type_param_type_names.is_empty() { - name.0.clone() - } else { - let type_param_names = self - .type_param_type_names - .iter() - .map(|tn| tn.as_string(true, by_path_type_names)) - .collect::>() - .join("And"); - format!("{}For{}", name.0, type_param_names) - } - } -} - -/// BTreeMap type name resolution. -struct BTreeMapTypeName { - key_type_name: RcTypeName, - value_type_name: RcTypeName, -} - -impl BTreeMapTypeName { - pub fn new( - types: &BTreeMap, - type_info: &Type, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let key_type_id = type_info - .type_params - .iter() - .find(|param| param.name == "K") - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))? - .ty - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))?; - let value_type_id = type_info - .type_params - .iter() - .find(|param| param.name == "V") - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))? - .ty - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))?; - let key_type_name = resolve_type_name( - types, - key_type_id.id, - resolved_type_names, - by_path_type_names, - )?; - let value_type_name = resolve_type_name( - types, - value_type_id.id, - resolved_type_names, - by_path_type_names, - )?; - Ok(Self { - key_type_name, - value_type_name, - }) - } - - pub fn is_btree_map_type(type_info: &Type) -> bool { - static BTREE_MAP_TYPE_INFO: OnceLock = OnceLock::new(); - let btree_map_type_info = BTREE_MAP_TYPE_INFO.get_or_init(BTreeMap::::type_info); - btree_map_type_info.path.segments == type_info.path.segments - } -} - -impl TypeName for BTreeMapTypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - let key_type_name = self - .key_type_name - .as_string(for_generic_param, by_path_type_names); - let value_type_name = self - .value_type_name - .as_string(for_generic_param, by_path_type_names); - if for_generic_param { - format!("MapOf{key_type_name}To{value_type_name}") - } else { - format!("map ({key_type_name}, {value_type_name})") - } - } -} - -/// Result type name resolution. -struct ResultTypeName { - ok_type_name: RcTypeName, - err_type_name: RcTypeName, -} - -impl ResultTypeName { - pub fn new( - types: &BTreeMap, - type_info: &Type, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let ok_type_id = type_info - .type_params - .iter() - .find(|param| param.name == "T") - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))? - .ty - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))?; - let err_type_id = type_info - .type_params - .iter() - .find(|param| param.name == "E") - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))? - .ty - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))?; - let ok_type_name = resolve_type_name( - types, - ok_type_id.id, - resolved_type_names, - by_path_type_names, - )?; - let err_type_name = resolve_type_name( - types, - err_type_id.id, - resolved_type_names, - by_path_type_names, - )?; - Ok(Self { - ok_type_name, - err_type_name, - }) - } - - pub fn is_result_type(type_info: &Type) -> bool { - static RESULT_TYPE_INFO: OnceLock = OnceLock::new(); - let result_type_info = RESULT_TYPE_INFO.get_or_init(StdResult::<(), ()>::type_info); - result_type_info.path.segments == type_info.path.segments - } -} - -impl TypeName for ResultTypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - let ok_type_name = self - .ok_type_name - .as_string(for_generic_param, by_path_type_names); - let err_type_name = self - .err_type_name - .as_string(for_generic_param, by_path_type_names); - if for_generic_param { - format!("ResultOf{ok_type_name}Or{err_type_name}") - } else { - format!("result ({ok_type_name}, {err_type_name})") - } - } -} - -/// Option type name resolution. -struct OptionTypeName { - some_type_name: RcTypeName, -} - -impl OptionTypeName { - pub fn new( - types: &BTreeMap, - type_info: &Type, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let some_type_id = type_info - .type_params - .iter() - .find(|param| param.name == "T") - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))? - .ty - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))?; - let some_type_name = resolve_type_name( - types, - some_type_id.id, - resolved_type_names, - by_path_type_names, - )?; - Ok(Self { some_type_name }) - } - - pub fn is_option_type(type_info: &Type) -> bool { - static OPTION_TYPE_INFO: OnceLock = OnceLock::new(); - let option_type_info = OPTION_TYPE_INFO.get_or_init(Option::<()>::type_info); - option_type_info.path.segments == type_info.path.segments - } -} - -impl TypeName for OptionTypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - let some_type_name = self - .some_type_name - .as_string(for_generic_param, by_path_type_names); - if for_generic_param { - format!("OptOf{some_type_name}") - } else { - format!("opt {some_type_name}") - } - } -} - -/// Tuple type name resolution. -struct TupleTypeName { - field_type_names: Vec, -} - -impl TupleTypeName { - pub fn new( - types: &BTreeMap, - tuple_def: &TypeDefTuple, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let field_type_names = tuple_def - .fields - .iter() - .map(|field| { - resolve_type_name(types, field.id, resolved_type_names, by_path_type_names) - }) - .collect::>>()?; - Ok(Self { field_type_names }) - } -} - -impl TypeName for TupleTypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - if self.field_type_names.is_empty() { - "null".into() - } else if for_generic_param { - format!( - "StructOf{}", - self.field_type_names - .iter() - .map(|tn| tn.as_string(for_generic_param, by_path_type_names)) - .collect::>() - .join("And") - ) - } else { - format!( - "struct {{ {} }}", - self.field_type_names - .iter() - .map(|tn| tn.as_string(for_generic_param, by_path_type_names)) - .collect::>() - .join(", ") - ) - } - } -} - -/// Vector type name resolution. -struct VectorTypeName { - item_type_name: RcTypeName, -} - -impl VectorTypeName { - pub fn new( - types: &BTreeMap, - vector_def: &TypeDefSequence, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let item_type_name = resolve_type_name( - types, - vector_def.type_param.id, - resolved_type_names, - by_path_type_names, - )?; - Ok(Self { item_type_name }) - } -} - -impl TypeName for VectorTypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - let item_type_name = self - .item_type_name - .as_string(for_generic_param, by_path_type_names); - if for_generic_param { - format!("VecOf{item_type_name}") - } else { - format!("vec {item_type_name}") - } - } -} - -/// Array type name resolution. -struct ArrayTypeName { - item_type_name: RcTypeName, - len: u32, -} - -impl ArrayTypeName { - pub fn new( - types: &BTreeMap, - array_def: &TypeDefArray, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let item_type_name = resolve_type_name( - types, - array_def.type_param.id, - resolved_type_names, - by_path_type_names, - )?; - Ok(Self { - item_type_name, - len: array_def.len, - }) - } -} - -impl TypeName for ArrayTypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - let item_type_name = self - .item_type_name - .as_string(for_generic_param, by_path_type_names); - if for_generic_param { - format!("ArrOf{}{}", self.len, item_type_name) - } else { - format!("[{}, {}]", item_type_name, self.len) - } - } -} - -/// Primitive type name resolution. -struct PrimitiveTypeName { - name: &'static str, -} - -impl PrimitiveTypeName { - pub fn new(type_def: &TypeDefPrimitive) -> Result { - let name = match type_def { - TypeDefPrimitive::Bool => Ok("bool"), - TypeDefPrimitive::Char => Ok("char"), - TypeDefPrimitive::Str => Ok("str"), - TypeDefPrimitive::U8 => Ok("u8"), - TypeDefPrimitive::U16 => Ok("u16"), - TypeDefPrimitive::U32 => Ok("u32"), - TypeDefPrimitive::U64 => Ok("u64"), - TypeDefPrimitive::U128 => Ok("u128"), - TypeDefPrimitive::U256 => Err(Error::TypeIsUnsupported("u256".into())), // Rust doesn't have it - TypeDefPrimitive::I8 => Ok("i8"), - TypeDefPrimitive::I16 => Ok("i16"), - TypeDefPrimitive::I32 => Ok("i32"), - TypeDefPrimitive::I64 => Ok("i64"), - TypeDefPrimitive::I128 => Ok("i128"), - TypeDefPrimitive::I256 => Err(Error::TypeIsUnsupported("i256".into())), // Rust doesn't have it - }?; - Ok(Self { name }) - } -} - -impl TypeName for PrimitiveTypeName { - fn as_string( - &self, - for_generic_param: bool, - _by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - if for_generic_param { - self.name.to_case(Case::Pascal) - } else { - self.name.to_string() - } - } -} - -macro_rules! impl_primitive_alias_type_name { - ($primitive:ident, $alias:ident) => { - mod $alias { - use super::*; - - pub(super) struct TypeNameImpl; - - impl TypeNameImpl { - pub fn new() -> Self { - Self - } - - pub fn is_type(type_info: &Type) -> bool { - static TYPE_INFO: OnceLock = OnceLock::new(); - let info = TYPE_INFO.get_or_init($primitive::type_info); - info.path.segments == type_info.path.segments - } - } - - impl TypeName for TypeNameImpl { - fn as_string( - &self, - for_generic_param: bool, - _by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - if for_generic_param { - stringify!($primitive).into() - } else { - stringify!($alias).into() - } - } - } - } - }; -} - -impl_primitive_alias_type_name!(ActorId, actor_id); -impl_primitive_alias_type_name!(MessageId, message_id); -impl_primitive_alias_type_name!(CodeId, code_id); -impl_primitive_alias_type_name!(H160, h160); -impl_primitive_alias_type_name!(H256, h256); -impl_primitive_alias_type_name!(U256, u256); -impl_primitive_alias_type_name!(NonZeroU8, nat8); -impl_primitive_alias_type_name!(NonZeroU16, nat16); -impl_primitive_alias_type_name!(NonZeroU32, nat32); -impl_primitive_alias_type_name!(NonZeroU64, nat64); -impl_primitive_alias_type_name!(NonZeroU128, nat128); -impl_primitive_alias_type_name!(NonZeroU256, nat256); - -#[cfg(test)] -mod tests { - use std::result; - - use super::*; - use scale_info::{MetaType, PortableRegistry, Registry}; - - #[allow(dead_code)] - #[derive(TypeInfo)] - struct GenericStruct { - field: T, - } - - #[allow(dead_code)] - #[derive(TypeInfo)] - struct GenericConstStruct { - field: [T; N], - field2: [T; M], - } - - #[allow(dead_code)] - #[derive(TypeInfo)] - enum GenericEnum { - Variant1(T1), - Variant2(T2), - } - - #[allow(dead_code)] - mod mod_1 { - use super::*; - - #[derive(TypeInfo)] - pub struct T1 {} - - pub mod mod_2 { - use super::*; - - #[derive(TypeInfo)] - pub struct T2 {} - } - } - - #[allow(dead_code)] - mod mod_2 { - use super::*; - - #[derive(TypeInfo)] - pub struct T1 {} - - #[derive(TypeInfo)] - pub struct T2 {} - } - - #[test] - fn h256_u256_type_name_resolution_works() { - let mut registry = Registry::new(); - let h256_id = registry.register_type(&MetaType::new::()).id; - let h256_as_generic_param_id = registry - .register_type(&MetaType::new::>()) - .id; - let u256_id = registry.register_type(&MetaType::new::()).id; - let u256_as_generic_param_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let h256_name = type_names.get(&h256_id).unwrap(); - assert_eq!(h256_name, "h256"); - let as_generic_param_name = type_names.get(&h256_as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForH256"); - let u256_name = type_names.get(&u256_id).unwrap(); - assert_eq!(u256_name, "u256"); - let as_generic_param_name = type_names.get(&u256_as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForU256"); - } - - #[test] - fn generic_struct_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_struct_id = registry - .register_type(&MetaType::new::>()) - .id; - let string_struct_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_struct_name = type_names.get(&u32_struct_id).unwrap(); - assert_eq!(u32_struct_name, "GenericStructForU32"); - - let string_struct_name = type_names.get(&string_struct_id).unwrap(); - assert_eq!(string_struct_name, "GenericStructForStr"); - } - - #[test] - fn generic_variant_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_string_enum_id = registry - .register_type(&MetaType::new::>()) - .id; - let bool_u32_enum_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_string_enum_name = type_names.get(&u32_string_enum_id).unwrap(); - assert_eq!(u32_string_enum_name, "GenericEnumForU32AndStr"); - - let bool_u32_enum_name = type_names.get(&bool_u32_enum_id).unwrap(); - assert_eq!(bool_u32_enum_name, "GenericEnumForBoolAndU32"); - } - - #[test] - fn array_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_array_id = registry.register_type(&MetaType::new::<[u32; 10]>()).id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_array_name = type_names.get(&u32_array_id).unwrap(); - assert_eq!(u32_array_name, "[u32, 10]"); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForArrOf10U32"); - } - - #[test] - fn vector_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_vector_id = registry.register_type(&MetaType::new::>()).id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_vector_name = type_names.get(&u32_vector_id).unwrap(); - assert_eq!(u32_vector_name, "vec u32"); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForVecOfU32"); - } - - #[test] - fn result_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_result_id = registry - .register_type(&MetaType::new::>()) - .id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_result_name = type_names.get(&u32_result_id).unwrap(); - assert_eq!(u32_result_name, "result (u32, str)"); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForResultOfU32OrStr"); - } - - #[test] - fn option_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_option_id = registry.register_type(&MetaType::new::>()).id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_option_name = type_names.get(&u32_option_id).unwrap(); - assert_eq!(u32_option_name, "opt u32"); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForOptOfU32"); - } - - #[test] - fn tuple_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_str_tuple_id = registry.register_type(&MetaType::new::<(u32, String)>()).id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_str_tuple_name = type_names.get(&u32_str_tuple_id).unwrap(); - assert_eq!(u32_str_tuple_name, "struct { u32, str }"); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForStructOfU32AndStr"); - } - - #[test] - fn btree_map_type_name_resolution_works() { - let mut registry = Registry::new(); - let btree_map_id = registry - .register_type(&MetaType::new::>()) - .id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let btree_map_name = type_names.get(&btree_map_id).unwrap(); - assert_eq!(btree_map_name, "map (u32, str)"); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForMapOfU32ToStr"); - } - - #[test] - fn type_name_minification_works_for_types_with_the_same_mod_depth() { - let mut registry = Registry::new(); - let t1_id = registry.register_type(&MetaType::new::()).id; - let t2_id = registry.register_type(&MetaType::new::()).id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let t1_name = type_names.get(&t1_id).unwrap(); - assert_eq!(t1_name, "Mod1T1"); - - let t2_name = type_names.get(&t2_id).unwrap(); - assert_eq!(t2_name, "Mod2T1"); - } - - #[test] - fn type_name_minification_works_for_types_with_different_mod_depth() { - let mut registry = Registry::new(); - let t1_id = registry - .register_type(&MetaType::new::()) - .id; - let t2_id = registry.register_type(&MetaType::new::()).id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let t1_name = type_names.get(&t1_id).unwrap(); - assert_eq!(t1_name, "Mod1Mod2T2"); - - let t2_name = type_names.get(&t2_id).unwrap(); - assert_eq!(t2_name, "TestsMod2T2"); - } - - macro_rules! type_name_resolution_works { - ($primitive:ident, $alias:ident) => { - let mut registry = Registry::new(); - let id = registry.register_type(&MetaType::new::<$primitive>()).id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let name = type_names.get(&id).unwrap(); - assert_eq!(name, stringify!($alias)); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!( - as_generic_param_name, - concat!("GenericStructFor", stringify!($primitive)) - ); - }; - } - - #[test] - fn actor_id_type_name_resolution_works() { - type_name_resolution_works!(ActorId, actor_id); - } - - #[test] - fn message_id_type_name_resolution_works() { - type_name_resolution_works!(MessageId, message_id); - } - - #[test] - fn code_id_type_name_resolution_works() { - type_name_resolution_works!(CodeId, code_id); - } - - #[test] - fn h160_type_name_resolution_works() { - type_name_resolution_works!(H160, h160); - } - - #[test] - fn nonzero_u8_type_name_resolution_works() { - type_name_resolution_works!(NonZeroU8, nat8); - } - - #[test] - fn nonzero_u16_type_name_resolution_works() { - type_name_resolution_works!(NonZeroU16, nat16); - } - - #[test] - fn nonzero_u32_type_name_resolution_works() { - type_name_resolution_works!(NonZeroU32, nat32); - } - - #[test] - fn nonzero_u64_type_name_resolution_works() { - type_name_resolution_works!(NonZeroU64, nat64); - } - - #[test] - fn nonzero_u128_type_name_resolution_works() { - type_name_resolution_works!(NonZeroU128, nat128); - } - - #[test] - fn nonzero_u256_type_name_resolution_works() { - type_name_resolution_works!(NonZeroU256, nat256); - } - - #[test] - fn generic_const_struct_type_name_resolution_works() { - let mut registry = Registry::new(); - let n8_id = registry - .register_type(&MetaType::new::>()) - .id; - let n8_id_2 = registry - .register_type(&MetaType::new::>()) - .id; - let n32_id = registry - .register_type(&MetaType::new::>()) - .id; - let n256_id = registry - .register_type(&MetaType::new::>()) - .id; - let n32u256_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - assert_eq!(n8_id, n8_id_2); - assert_ne!(n8_id, n32_id); - assert_ne!(n8_id, n256_id); - assert_eq!(type_names.get(&n8_id).unwrap(), "GenericConstStruct1ForU8"); - assert_eq!(type_names.get(&n32_id).unwrap(), "GenericConstStruct2ForU8"); - assert_eq!( - type_names.get(&n256_id).unwrap(), - "GenericConstStruct3ForU8" - ); - assert_eq!( - type_names.get(&n32u256_id).unwrap(), - "GenericConstStructForU256" - ); - } -} diff --git a/rs/idl-gen/src/type_resolver.rs b/rs/idl-gen/src/type_resolver.rs new file mode 100644 index 000000000..f251d8db0 --- /dev/null +++ b/rs/idl-gen/src/type_resolver.rs @@ -0,0 +1,2183 @@ +use super::*; +use convert_case::{Case, Casing}; +use scale_info::{ + Field, PortableRegistry, StaticTypeInfo, Type, TypeDef, TypeDefComposite, TypeDefPrimitive, + TypeDefVariant, +}; + +#[derive(Debug, Clone)] +pub struct TypeResolver<'a> { + registry: &'a PortableRegistry, + map: BTreeMap, + user_defined: BTreeMap, +} + +#[derive(Debug, Clone)] +pub struct UserDefinedEntry { + pub meta_type: sails_idl_meta::Type, + pub ty: Type, +} + +impl UserDefinedEntry { + fn is_path_equals(&self, type_info: &Type) -> bool { + self.ty.path == type_info.path + } + + fn is_fields_equal(&self, type_info: &Type) -> bool { + let fs1 = Self::fields(&self.ty); + let fs2 = Self::fields(type_info); + fs1 == fs2 + } + + fn fields(type_info: &Type) -> Vec { + match &type_info.type_def { + TypeDef::Composite(type_def_composite) => type_def_composite + .fields + .iter() + .map(|f| f.ty.id) + .collect::>(), + TypeDef::Variant(type_def_variant) => { + let mut fields = Vec::new(); + type_def_variant.variants.iter().for_each(|v| { + fields.extend(v.fields.iter().map(|f| f.ty.id)); + }); + fields + } + _ => unreachable!(), + } + } + + #[cfg(test)] + fn meta_fields(&self) -> Vec { + match &self.meta_type.def { + sails_idl_meta::TypeDef::Struct(StructDef { fields }) => fields.clone(), + sails_idl_meta::TypeDef::Enum(EnumDef { variants }) => { + let mut fields = Vec::new(); + variants.iter().for_each(|v| { + fields.extend(v.def.fields.iter().cloned()); + }); + fields + } + } + } +} + +impl<'a> TypeResolver<'a> { + #[cfg(test)] + pub fn from_registry(registry: &'a PortableRegistry) -> Self { + TypeResolver::try_from(registry, BTreeSet::new()).unwrap() + } + + pub fn try_from(registry: &'a PortableRegistry, exclude: BTreeSet) -> Result { + let mut resolver = Self { + registry, + map: BTreeMap::new(), + user_defined: BTreeMap::new(), + }; + resolver.build_type_decl_map(exclude)?; + Ok(resolver) + } + + pub fn into_types(self) -> Vec { + let mut vec: Vec<_> = self + .user_defined + .into_values() + .map(|v| v.meta_type) + .collect(); + vec.sort_by(|a, b| a.name.cmp(&b.name)); + vec + } + + pub fn get(&self, key: u32) -> Option<&TypeDecl> { + self.map.get(&key) + } + + #[cfg(test)] + pub fn get_user_defined(&self, name: &str) -> Option<&UserDefinedEntry> { + self.user_defined.get(name) + } + + fn build_type_decl_map(&mut self, exclude: BTreeSet) -> Result<()> { + let filtered: Vec<_> = self + .registry + .types + .iter() + .filter(|pt| !exclude.contains(&pt.id)) + .collect(); + for pt in filtered { + let type_decl = self.resolve_type_decl(&pt.ty)?; + self.map.insert(pt.id, type_decl); + } + Ok(()) + } + + fn resolve_by_id(&mut self, id: u32) -> Result { + if let Some(decl) = self.get(id) { + return Ok(decl.clone()); + } + let ty = self + .registry + .resolve(id) + .ok_or(Error::TypeIdIsUnknown(id))?; + let type_decl = self.resolve_type_decl(ty)?; + self.map.insert(id, type_decl.clone()); + Ok(type_decl) + } + + fn resolve_type_decl(&mut self, ty: &Type) -> Result { + let decl = match &ty.type_def { + TypeDef::Composite(type_def_composite) => { + if let Some(decl) = self.resolve_known_composite(ty, type_def_composite) { + decl + } else { + let name = self.register_user_defined(ty)?; + self.resolve_user_defined(name, ty)? + } + } + TypeDef::Variant(type_def_variant) => { + if let Some(decl) = self.resolve_known_enum(ty, type_def_variant) { + decl + } else { + let name = self.register_user_defined(ty)?; + self.resolve_user_defined(name, ty)? + } + } + TypeDef::Sequence(type_def_sequence) => TypeDecl::Slice { + item: Box::new(self.resolve_by_id(type_def_sequence.type_param.id)?), + }, + TypeDef::Array(type_def_array) => TypeDecl::Array { + item: Box::new(self.resolve_by_id(type_def_array.type_param.id)?), + len: type_def_array.len, + }, + TypeDef::Tuple(type_def_tuple) => { + if type_def_tuple.fields.is_empty() { + TypeDecl::Primitive(PrimitiveType::Void) + } else { + let types = type_def_tuple + .fields + .iter() + .map(|f| self.resolve_by_id(f.id)) + .collect::>>()?; + TypeDecl::tuple(types) + } + } + TypeDef::Primitive(type_def_primitive) => { + TypeDecl::Primitive(primitive_map(type_def_primitive)?) + } + TypeDef::Compact(_) => { + return Err(Error::TypeIsUnsupported( + "TypeDef::Compact is unsupported".to_string(), + )); + } + TypeDef::BitSequence(_) => { + return Err(Error::TypeIsUnsupported( + "TypeDef::BitSequence is unsupported".to_string(), + )); + } + }; + Ok(decl) + } + + fn register_user_defined(&mut self, ty: &Type) -> Result { + let mut name = match self.unique_type_name(ty) { + Ok(name) => name, + Err(exist) => return Ok(exist), + }; + + let type_params = self.resolve_type_params(ty)?; + let mut suffixes = BTreeSet::new(); + + let def = match &ty.type_def { + TypeDef::Composite(type_def_composite) => { + let fields = type_def_composite + .fields + .iter() + .map(|f| self.resolve_field(f, &type_params, &mut suffixes)) + .collect::>>()?; + sails_idl_meta::TypeDef::Struct(StructDef { fields }) + } + TypeDef::Variant(type_def_variant) => { + let variants = type_def_variant + .variants + .iter() + .map(|v| { + let fields = v + .fields + .iter() + .map(|f| self.resolve_field(f, &type_params, &mut suffixes)) + .collect::>>()?; + Ok(EnumVariant { + name: v.name.to_string(), + def: StructDef { fields }, + docs: v.docs.iter().map(|d| d.to_string()).collect(), + annotations: vec![], // ("index".to_string(), Some(v.index.to_string())) + }) + }) + .collect::>>()?; + sails_idl_meta::TypeDef::Enum(EnumDef { variants }) + } + _ => unreachable!(), + }; + + for suffix in suffixes { + name.push_str(suffix.as_str()); + } + + if self.user_defined.contains_key(&name) { + return Ok(name); + } + + let meta_type = sails_idl_meta::Type { + name: name.clone(), + type_params, + def, + docs: ty.docs.iter().map(|d| d.to_string()).collect(), + annotations: vec![], //("rust_type".to_string(), Some(ty.path.to_string())) + }; + self.user_defined.insert( + name.clone(), + UserDefinedEntry { + meta_type, + ty: ty.clone(), + }, + ); + Ok(name) + } + + pub(crate) fn resolve_type_params( + &mut self, + ty: &Type, + ) -> Result> { + ty.type_params + .iter() + .map(|tp| { + let ty = match tp.ty { + Some(ref inner) => Some(self.resolve_by_id(inner.id)?), + None => None, + }; + let name = tp.name.to_string(); + Ok(sails_idl_meta::TypeParameter { name, ty }) + }) + .collect() + } + + fn unique_type_name(&self, ty: &Type) -> Result { + for name in possible_names_by_path(ty) { + if let Some(exists) = self.user_defined.get(&name) { + if !exists.is_path_equals(ty) { + continue; + } else if exists.is_fields_equal(ty) { + // type with exact fields already registered + return Err(name); + } else { + return Ok(name); + } + } + return Ok(name); + } + unreachable!(); + } + + fn resolve_user_defined(&mut self, name: String, ty: &Type) -> Result { + let generics = ty + .type_params + .iter() + .map(|tp| { + self.resolve_by_id( + tp.ty + .as_ref() + .ok_or(Error::TypeIsUnsupported(format!( + "Generic type parameter is unknown: {}", + tp.name + )))? + .id, + ) + }) + .collect::>>()?; + Ok(TypeDecl::Named { name, generics }) + } + + fn resolve_field( + &mut self, + field: &Field, + type_params: &[sails_idl_meta::TypeParameter], + suffixes: &mut BTreeSet, + ) -> Result { + let resolved = self.resolve_by_id(field.ty.id)?; + let type_decl = if let Some(type_name) = field.type_name.as_ref() + && &resolved.to_string() != type_name + { + let (td, suf) = crate::generic_resolver::resolve_generic_type_decl( + &resolved, + type_name, + type_params, + )?; + suffixes.extend(suf); + td + } else { + resolved + }; + Ok(StructField { + name: field.name.as_ref().map(|s| s.to_string()), + type_decl, + docs: field.docs.iter().map(|d| d.to_string()).collect(), + annotations: vec![], + }) + } + + fn resolve_known_composite( + &mut self, + ty: &Type, + _def: &TypeDefComposite, + ) -> Option { + use PrimitiveType::*; + use TypeDecl::*; + + if is_type::(ty) { + Some(Primitive(H160)) + } else if is_type::(ty) { + Some(Primitive(H256)) + } else if is_type::(ty) { + Some(Primitive(U256)) + } else if is_type::(ty) { + Some(Primitive(ActorId)) + } else if is_type::(ty) { + Some(Primitive(CodeId)) + } else if is_type::(ty) { + Some(Primitive(MessageId)) + } else if is_type::>(ty) + && let [vec_tp] = ty.type_params.as_slice() + && let Some(ty) = vec_tp.ty + && let Ok(ty) = self.resolve_by_id(ty.id) + { + Some(Slice { item: Box::new(ty) }) + } else if is_type::>(ty) + && let [key_tp, value_tp] = ty.type_params.as_slice() + && let Some(key) = key_tp.ty + && let Some(value) = value_tp.ty + && let Ok(key) = self.resolve_by_id(key.id) + && let Ok(value) = self.resolve_by_id(value.id) + { + Some(Slice { + item: Box::new(TypeDecl::tuple(vec![key, value])), + }) + } else { + None + } + } + + fn resolve_known_enum( + &mut self, + ty: &Type, + def: &TypeDefVariant, + ) -> Option { + if is_type::>(ty) + && let [ok_var, err_var] = def.variants.as_slice() + && let [ok] = ok_var.fields.as_slice() + && let [err] = err_var.fields.as_slice() + && let Ok(ok) = self.resolve_by_id(ok.ty.id) + && let Ok(err) = self.resolve_by_id(err.ty.id) + { + Some(TypeDecl::result(ok, err)) + } else if is_type::>(ty) + && let [_, some_var] = def.variants.as_slice() + && let [some] = some_var.fields.as_slice() + && let Ok(decl) = self.resolve_by_id(some.ty.id) + { + Some(TypeDecl::option(decl)) + } else { + None + } + } +} + +fn is_type(type_info: &Type) -> bool { + T::type_info().path.segments == type_info.path.segments +} + +fn primitive_map(type_def_primitive: &TypeDefPrimitive) -> Result { + use PrimitiveType::*; + + let p = match type_def_primitive { + TypeDefPrimitive::Bool => Bool, + TypeDefPrimitive::Char => Char, + TypeDefPrimitive::Str => String, + TypeDefPrimitive::U8 => U8, + TypeDefPrimitive::U16 => U16, + TypeDefPrimitive::U32 => U32, + TypeDefPrimitive::U64 => U64, + TypeDefPrimitive::U128 => U128, + TypeDefPrimitive::U256 => U256, + TypeDefPrimitive::I8 => I8, + TypeDefPrimitive::I16 => I16, + TypeDefPrimitive::I32 => I32, + TypeDefPrimitive::I64 => I64, + TypeDefPrimitive::I128 => I128, + TypeDefPrimitive::I256 => { + return Err(Error::TypeIsUnsupported( + "TypeDefPrimitive::I256 is unsupported".to_string(), + )); + } + }; + Ok(p) +} + +fn possible_names_by_path(ty: &Type) -> impl Iterator + '_ { + let mut name = String::default(); + ty.path.segments.iter().rev().map(move |segment| { + name = segment.to_case(Case::Pascal) + &name; + name.clone() + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use core::num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128}; + use gprimitives::NonZeroU256; + use sails_idl_meta::TypeDef; + use scale_info::{MetaType, Registry, TypeInfo}; + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct GenericStruct { + field: T, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct GenericConstStruct { + field: [T; N], + field2: Option<[T; O]>, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum GenericEnum { + Variant1(T1), + Variant2(T2), + Variant3(T1, Option), + Variant4(Option<(T1, GenericStruct, u32)>), + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum ManyVariants { + One, + Two(u32), + Three(Option>), + Four { a: u32, b: Option }, + Five(String, Vec), + Six((u32,)), + Seven(GenericEnum), + Eight([BTreeMap; 10]), + Nine(TupleVariantsDocs), + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum TupleVariantsDocs { + /// Docs for no tuple docs 1 + NoTupleDocs1(u32, String), + NoTupleDocs2(gprimitives::CodeId, Vec), + /// Docs for tuple docs 1 + TupleDocs1( + u32, + /// This is the second field + String, + ), + TupleDocs2( + /// This is the first field + u32, + /// This is the second field + String, + ), + /// Docs for struct docs + StructDocs { + /// This is field `a` + a: u32, + /// This is field `b` + b: String, + }, + } + + #[allow(dead_code)] + mod mod_1 { + use super::*; + + #[derive(TypeInfo)] + pub struct T1 {} + + pub mod mod_2 { + use super::*; + + #[derive(TypeInfo)] + pub struct T2 {} + } + } + + #[allow(dead_code)] + mod mod_2 { + use super::*; + + #[derive(TypeInfo)] + pub struct T1 {} + + #[derive(TypeInfo)] + pub struct T2 {} + } + + #[test] + fn type_resolver_h160_h256() { + let mut registry = Registry::new(); + let _h160_id = registry + .register_type(&MetaType::new::()) + .id; + let _h160_as_generic_param_id = registry + .register_type(&MetaType::new::>()) + .id; + + let h256_id = registry + .register_type(&MetaType::new::()) + .id; + let h256_as_generic_param_id = registry + .register_type(&MetaType::new::>()) + .id; + + let portable_registry = PortableRegistry::from(registry); + + let resolver = TypeResolver::from_registry(&portable_registry); + + let h256_decl = resolver.get(h256_id).unwrap(); + assert_eq!(*h256_decl, TypeDecl::Primitive(PrimitiveType::H256)); + + let generic_struct_decl = resolver.get(h256_as_generic_param_id).unwrap(); + assert_eq!( + *generic_struct_decl, + TypeDecl::Named { + name: "GenericStruct".to_string(), + generics: vec![TypeDecl::Primitive(PrimitiveType::H256)] + } + ); + assert_eq!(generic_struct_decl.to_string(), "GenericStruct"); + } + + #[test] + fn type_resolver_generic_struct() { + let mut registry = Registry::new(); + let u32_struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let string_struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let u32_struct = resolver.get(u32_struct_id).unwrap(); + assert_eq!(u32_struct.to_string(), "GenericStruct"); + + let string_struct = resolver.get(string_struct_id).unwrap(); + assert_eq!(string_struct.to_string(), "GenericStruct"); + } + + #[test] + fn type_resolver_generic_enum() { + let mut registry = Registry::new(); + let u32_string_enum_id = registry + .register_type(&MetaType::new::>()) + .id; + let bool_u32_enum_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let u32_string_enum = resolver.get(u32_string_enum_id).unwrap(); + assert_eq!(u32_string_enum.to_string(), "GenericEnum"); + + let bool_u32_enum = resolver.get(bool_u32_enum_id).unwrap(); + assert_eq!(bool_u32_enum.to_string(), "GenericEnum"); + } + + #[test] + fn type_resolver_array_type() { + let mut registry = Registry::new(); + let u32_array_id = registry.register_type(&MetaType::new::<[u32; 10]>()).id; + let as_generic_param_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let u32_array = resolver.get(u32_array_id).unwrap(); + assert_eq!(u32_array.to_string(), "[u32; 10]"); + let as_generic_param = resolver.get(as_generic_param_id).unwrap(); + assert_eq!(as_generic_param.to_string(), "GenericStruct<[u32; 10]>"); + } + + #[test] + fn type_resolver_vector_type() { + let mut registry = Registry::new(); + let u32_vector_id = registry.register_type(&MetaType::new::>()).id; + let as_generic_param_id = registry + .register_type(&MetaType::new::>>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let u32_vector = resolver.get(u32_vector_id).unwrap(); + assert_eq!(u32_vector.to_string(), "[u32]"); + let as_generic_param = resolver.get(as_generic_param_id).unwrap(); + assert_eq!(as_generic_param.to_string(), "GenericStruct<[u32]>"); + } + + #[test] + fn type_resolver_result_type() { + let mut registry = Registry::new(); + let u32_result_id = registry + .register_type(&MetaType::new::>()) + .id; + let as_generic_param_id = registry + .register_type(&MetaType::new::>>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let u32_result = resolver.get(u32_result_id).unwrap(); + assert_eq!(u32_result.to_string(), "Result"); + let as_generic_param = resolver.get(as_generic_param_id).unwrap(); + assert_eq!( + as_generic_param.to_string(), + "GenericStruct>" + ); + } + + #[test] + fn type_resolver_option_type() { + let mut registry = Registry::new(); + let u32_option_id = registry.register_type(&MetaType::new::>()).id; + let as_generic_param_id = registry + .register_type(&MetaType::new::>>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let u32_option = resolver.get(u32_option_id).unwrap(); + assert_eq!(u32_option.to_string(), "Option"); + let as_generic_param = resolver.get(as_generic_param_id).unwrap(); + assert_eq!(as_generic_param.to_string(), "GenericStruct>"); + } + + #[test] + fn type_resolver_tuple_type() { + let mut registry = Registry::new(); + let u32_str_tuple_id = registry.register_type(&MetaType::new::<(u32, String)>()).id; + let as_generic_param_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let u32_str_tuple = resolver.get(u32_str_tuple_id).unwrap(); + assert_eq!(u32_str_tuple.to_string(), "(u32, String)"); + let as_generic_param = resolver.get(as_generic_param_id).unwrap(); + assert_eq!(as_generic_param.to_string(), "GenericStruct<(u32, String)>"); + } + + #[test] + fn type_resolver_btree_map_type() { + let mut registry = Registry::new(); + let btree_map_id = registry + .register_type(&MetaType::new::>()) + .id; + let as_generic_param_id = registry + .register_type(&MetaType::new::>>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let btree_map = resolver.get(btree_map_id).unwrap(); + assert_eq!(btree_map.to_string(), "[(u32, String)]"); + let as_generic_param = resolver.get(as_generic_param_id).unwrap(); + assert_eq!( + as_generic_param.to_string(), + "GenericStruct<[(u32, String)]>" + ); + } + + #[test] + fn type_resolver_enum_many_variants() { + let mut registry = Registry::new(); + let id = registry.register_type(&MetaType::new::()).id; + let generic_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let ty = resolver.get(id).unwrap(); + assert_eq!(ty.to_string(), "ManyVariants"); + let as_generic_param = resolver.get(generic_id).unwrap(); + assert_eq!(as_generic_param.to_string(), "GenericStruct"); + } + + #[test] + fn non_zero_types_name_resolution_works() { + type Test = ( + NonZeroU8, + NonZeroU16, + NonZeroU32, + NonZeroU64, + NonZeroU128, + NonZeroU256, + ); + let mut registry = Registry::new(); + let id = registry.register_type(&MetaType::new::()).id; + let generic_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let ty = resolver.get(id).unwrap(); + assert_eq!( + ty.to_string(), + "(NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroU256)" + ); + + let as_generic_param = resolver.get(generic_id).unwrap(); + assert_eq!( + as_generic_param.to_string(), + "GenericStruct<(NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroU256)>" + ); + } + + macro_rules! type_name_resolution_works { + ($primitive:ty) => { + let mut registry = Registry::new(); + let id = registry.register_type(&MetaType::new::<$primitive>()).id; + let generic_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + let ty = resolver.get(id).unwrap(); + + assert_eq!(ty.to_string(), stringify!($primitive)); + let as_generic_param = resolver.get(generic_id).unwrap(); + assert_eq!( + as_generic_param.to_string(), + format!("GenericStruct<{}>", stringify!($primitive)) + ); + }; + } + + #[test] + fn actor_id_type_name_resolution_works() { + use gprimitives::ActorId; + type_name_resolution_works!(ActorId); + } + + #[test] + fn message_id_type_name_resolution_works() { + use gprimitives::MessageId; + type_name_resolution_works!(MessageId); + } + + #[test] + fn code_id_type_name_resolution_works() { + use gprimitives::CodeId; + type_name_resolution_works!(CodeId); + } + + #[test] + fn h160_type_name_resolution_works() { + use gprimitives::H160; + type_name_resolution_works!(H160); + } + + #[test] + fn h256_type_name_resolution_works() { + use gprimitives::H256; + type_name_resolution_works!(H256); + } + + #[test] + fn u256_type_name_resolution_works() { + use gprimitives::U256; + type_name_resolution_works!(U256); + } + + #[test] + fn type_name_minification_works_for_types_with_the_same_mod_depth() { + let mut registry = Registry::new(); + let t1_id = registry.register_type(&MetaType::new::()).id; + let t2_id = registry.register_type(&MetaType::new::()).id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let t1_name = resolver.get(t1_id).unwrap().to_string(); + assert_eq!(t1_name, "T1"); + + let t2_name = resolver.get(t2_id).unwrap().to_string(); + assert_eq!(t2_name, "Mod2T1"); + } + + #[test] + fn type_name_minification_works_for_types_with_different_mod_depth() { + let mut registry = Registry::new(); + let t1_id = registry + .register_type(&MetaType::new::()) + .id; + let t2_id = registry.register_type(&MetaType::new::()).id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let t1_name = resolver.get(t1_id).unwrap().to_string(); + assert_eq!(t1_name, "T2"); + + let t2_name = resolver.get(t2_id).unwrap().to_string(); + assert_eq!(t2_name, "Mod2T2"); + } + + #[test] + fn generic_const_struct_type_name_resolution_works() { + let mut registry = Registry::new(); + let n8_id = registry + .register_type(&MetaType::new::>()) + .id; + let n8_id_2 = registry + .register_type(&MetaType::new::>()) + .id; + let n32_id = registry + .register_type(&MetaType::new::>()) + .id; + let n256_id = registry + .register_type(&MetaType::new::>()) + .id; + let n32u256_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let n8_name = resolver.get(n8_id).unwrap().to_string(); + let n8_name_2 = resolver.get(n8_id_2).unwrap().to_string(); + let n32_name = resolver.get(n32_id).unwrap().to_string(); + let n256_name = resolver.get(n256_id).unwrap().to_string(); + let n32u256_name = resolver.get(n32u256_id).unwrap().to_string(); + + assert_eq!(n8_name, "GenericConstStructN8O12"); + assert_eq!(n8_name_2, "GenericConstStructN8O8"); + assert_eq!(n32_name, "GenericConstStructN32O8"); + assert_eq!(n256_name, "GenericConstStructN256O832"); + assert_eq!(n32u256_name, "GenericConstStructN32O8"); + } + + #[test] + fn simple_cases_one_generic() { + // Define helper types for the test + #[allow(dead_code)] + #[derive(TypeInfo)] + struct SimpleOneGenericStruct { + // Category 1: Simple generic usage + concrete: u32, + genericless_unit: GenericlessUnitStruct, + genericless_tuple: GenericlessTupleStruct, + genericless_named: GenericlessNamedStruct, + genericless_enum: GenericlessEnum, + genericless_variantless_enum: GenericlessVariantlessEnum, + generic_value: T, + tuple_generic: (String, T, T, u32), + option_generic: Option, + result_generic: Result, + btreemap_generic: BTreeMap, + struct_generic: GenericStruct, + enum_generic: SimpleOneGenericEnum, + + // Category 2: Two-level nested generics + option_of_option: Option>, + result_of_option: Result, String>, + btreemap_nested: BTreeMap, GenericStruct>, + struct_of_option: GenericStruct>, + enum_of_result: SimpleOneGenericEnum>, + + // Category 3: Triple-nested generics + option_triple: Option>>, + result_triple: Result>, String>, + btreemap_triple: BTreeMap>, Result>, + struct_triple: GenericStruct>>, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum SimpleOneGenericEnum { + // Category 1: Simple generic usage + NoFields, + GenericValue(T), + TupleGeneric(String, T, T, u32), + OptionGeneric(Option), + ResultGeneric(Result), + BTreeMapGeneric { + map: BTreeMap, + }, + StructGeneric { + inner: GenericStruct, + }, + NestedEnum(NestedGenericEnum), + + // Category 2: Two-level nested generics + OptionOfOption(Option>), + ResultOfOption { + res: Result, String>, + }, + DoubleNested { + btree_map_nested: BTreeMap, GenericStruct>, + struct_nested: GenericStruct>, + }, + + // Category 3: Triple-nested generics + TrippleNested { + option_triple: Option>>, + result_triple: Result>, String>, + }, + OptionTriple(Option>>), + ResultTriple { + res: Result>, String>, + }, + NoFields2, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct GenericlessUnitStruct; + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct GenericlessTupleStruct(u32, String); + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct GenericlessNamedStruct { + a: u32, + b: String, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum GenericlessEnum { + Unit, + Tuple(u32, String), + Named { a: u32, b: String }, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum GenericlessVariantlessEnum {} + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum NestedGenericEnum { + First(T), + Second(Vec), + } + + let mut registry = Registry::new(); + + let struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let enum_id = registry + .register_type(&MetaType::new::>()) + .id; + + let genericless_unit_id = registry + .register_type(&MetaType::new::()) + .id; + let genericless_tuple_id = registry + .register_type(&MetaType::new::()) + .id; + let genericless_named_id = registry + .register_type(&MetaType::new::()) + .id; + let genericless_enum_id = registry + .register_type(&MetaType::new::()) + .id; + let genericless_variantless_enum_id = registry + .register_type(&MetaType::new::()) + .id; + + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + // Check main types + assert_eq!( + resolver.get(struct_id).unwrap().to_string(), + "SimpleOneGenericStruct" + ); + let struct_generic = resolver + .get_user_defined("SimpleOneGenericStruct") + .expect("struct generic must exist"); + + assert_eq!( + resolver.get(enum_id).unwrap().to_string(), + "SimpleOneGenericEnum" + ); + let enum_generic = resolver + .get_user_defined("SimpleOneGenericEnum") + .expect("enum generic must exist"); + + // For structs: check that expected generic field strings are present + let s_fields: Vec<_> = struct_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + + let expect_struct_fields_type_names = vec![ + "u32", + "GenericlessUnitStruct", + "GenericlessTupleStruct", + "GenericlessNamedStruct", + "GenericlessEnum", + "GenericlessVariantlessEnum", + "T", + "(String, T, T, u32)", + "Option", + "Result", + "[(String, T)]", + "GenericStruct", + "SimpleOneGenericEnum", + "Option>", + "Result, String>", + "[(Option, GenericStruct)]", + "GenericStruct>", + "SimpleOneGenericEnum>", + "Option>>", + "Result>, String>", + "[(Option>, Result)]", + "GenericStruct>>", + ]; + + for expected in expect_struct_fields_type_names { + assert!( + s_fields.contains(&expected.to_string()), + "struct missing generic field {expected}, All fields: {s_fields:#?}" + ); + } + // For enums: check the collected `fields` contains expected signatures and variant names + let e_fields: Vec<_> = enum_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + + // First let's check no fields variants + let TypeDef::Enum(EnumDef { variants }) = &enum_generic.meta_type.def else { + panic!("Expected enum generic name"); + }; + + let no_fields_variant = &variants[0]; + let no_fields2_variant = &variants[variants.len() - 1]; + + assert_eq!(no_fields_variant.name, "NoFields"); + assert_eq!(no_fields2_variant.name, "NoFields2"); + assert!(no_fields_variant.def.fields.is_empty()); + assert!(no_fields2_variant.def.fields.is_empty()); + + // expected generic strings for enum fields and nested types: + let expect_enum_field_type_names = vec![ + "T", + "String", + "T", + "T", + "u32", + "Option", + "Result", + "[(String, T)]", + "GenericStruct", + "NestedGenericEnum", + "Option>", + "Result, String>", + "[(Option, GenericStruct)]", + "GenericStruct>", + "Option>>", + "Result>, String>", + ]; + + for expected in expect_enum_field_type_names { + assert!( + e_fields.contains(&expected.to_string()), + "enum missing generic field {expected}. All enum fields/entries: {e_fields:#?}" + ); + } + + // Also verify concrete_names for some representative fields to keep parity with original test spirit + // Retrieve struct type to check underlying field concrete ids + let struct_type = portable_registry + .types + .iter() + .find(|t| t.id == struct_id) + .unwrap(); + + if let scale_info::TypeDef::Composite(composite) = &struct_type.ty.type_def { + let generic_value = composite + .fields + .iter() + .find(|f| { + f.name + .as_ref() + .is_some_and(|s| s.to_string().eq("generic_value")) + }) + .unwrap(); + assert_eq!( + resolver.get(generic_value.ty.id).unwrap().to_string(), + "u32" + ); + + let tuple_generic = composite + .fields + .iter() + .find(|f| { + f.name + .as_ref() + .is_some_and(|s| s.to_string().eq("tuple_generic")) + }) + .unwrap(); + assert_eq!( + resolver.get(tuple_generic.ty.id).unwrap().to_string(), + "(String, u32, u32, u32)" + ); + + let option_generic = composite + .fields + .iter() + .find(|f| { + f.name + .as_ref() + .is_some_and(|s| s.to_string().eq("option_generic")) + }) + .unwrap(); + assert_eq!( + resolver.get(option_generic.ty.id).unwrap().to_string(), + "Option" + ); + + let btreemap_generic = composite + .fields + .iter() + .find(|f| { + f.name + .as_ref() + .is_some_and(|s| s.to_string().eq("btreemap_generic")) + }) + .unwrap(); + assert_eq!( + resolver.get(btreemap_generic.ty.id).unwrap().to_string(), + "[(String, u32)]" + ); + } else { + panic!("Expected composite type"); + } + + let genericless_unit = resolver.get(genericless_unit_id).unwrap(); + assert_eq!(genericless_unit.to_string(), "GenericlessUnitStruct"); + let genericless_unit_defined = resolver.get_user_defined("GenericlessUnitStruct").unwrap(); + assert!(genericless_unit_defined.meta_fields().is_empty()); + + let genericless_tuple = resolver.get(genericless_tuple_id).unwrap(); + assert_eq!(genericless_tuple.to_string(), "GenericlessTupleStruct"); + let genericless_tuple_def = resolver.get_user_defined("GenericlessTupleStruct").unwrap(); + let fields = genericless_tuple_def.meta_fields(); + let expected_fields_value = vec![ + StructField { + name: None, + type_decl: TypeDecl::Primitive(PrimitiveType::U32), + docs: vec![], + annotations: vec![], + }, + StructField { + name: None, + type_decl: TypeDecl::Primitive(PrimitiveType::String), + docs: vec![], + annotations: vec![], + }, + ]; + assert_eq!(fields, expected_fields_value); + + let genericless_named = resolver.get(genericless_named_id).unwrap(); + assert_eq!(genericless_named.to_string(), "GenericlessNamedStruct"); + let genericless_named_def = resolver.get_user_defined("GenericlessNamedStruct").unwrap(); + let fields = genericless_named_def.meta_fields(); + let expected_fields_value = vec![ + StructField { + name: Some("a".to_string()), + type_decl: TypeDecl::Primitive(PrimitiveType::U32), + docs: vec![], + annotations: vec![], + }, + StructField { + name: Some("b".to_string()), + type_decl: TypeDecl::Primitive(PrimitiveType::String), + docs: vec![], + annotations: vec![], + }, + ]; + assert_eq!(fields, expected_fields_value); + + let genericless_enum = resolver.get(genericless_enum_id).unwrap(); + assert_eq!(genericless_enum.to_string(), "GenericlessEnum"); + let genericless_enum_def = resolver.get_user_defined("GenericlessEnum").unwrap(); + let TypeDef::Enum(EnumDef { variants }) = &genericless_enum_def.meta_type.def else { + panic!("Expected enum"); + }; + + let expected_variants = vec![ + EnumVariant { + name: "Unit".to_string(), + def: StructDef { fields: vec![] }, + docs: vec![], + annotations: vec![], + }, + EnumVariant { + name: "Tuple".to_string(), + def: StructDef { + fields: vec![ + StructField { + name: None, + type_decl: TypeDecl::Primitive(PrimitiveType::U32), + docs: vec![], + annotations: vec![], + }, + StructField { + name: None, + type_decl: TypeDecl::Primitive(PrimitiveType::String), + docs: vec![], + annotations: vec![], + }, + ], + }, + docs: vec![], + annotations: vec![], + }, + EnumVariant { + name: "Named".to_string(), + def: StructDef { + fields: vec![ + StructField { + name: Some("a".to_string()), + type_decl: TypeDecl::Primitive(PrimitiveType::U32), + docs: vec![], + annotations: vec![], + }, + StructField { + name: Some("b".to_string()), + type_decl: TypeDecl::Primitive(PrimitiveType::String), + docs: vec![], + annotations: vec![], + }, + ], + }, + docs: vec![], + annotations: vec![], + }, + ]; + assert_eq!(variants, &expected_variants); + + let genericless_variantless_enum = resolver.get(genericless_variantless_enum_id).unwrap(); + assert_eq!( + genericless_variantless_enum.to_string(), + "GenericlessVariantlessEnum" + ); + let genericless_variantless_enum_def = resolver + .get_user_defined("GenericlessVariantlessEnum") + .unwrap(); + let TypeDef::Enum(EnumDef { variants }) = &genericless_variantless_enum_def.meta_type.def + else { + panic!("Expected enum"); + }; + assert!(variants.is_empty()); + } + + #[test] + fn complex_cases_one_generic() { + #[allow(dead_code)] + #[derive(TypeInfo)] + struct ComplexOneGenericStruct { + array_of_generic: [T; 10], + tuple_complex: (T, Vec, [T; 5]), + array_of_tuple: [(T, T); 3], + vec_of_array: Vec<[T; 8]>, + + array_of_option: [Option; 5], + tuple_of_result: (Result, Option), + vec_of_struct: Vec>, + array_of_btreemap: [BTreeMap; 2], + + array_of_vec_of_option: [Vec>; 4], + tuple_triple: (Option>, Result<[T; 3], String>), + vec_of_struct_of_option: Vec>>, + array_complex_triple: [BTreeMap, Result>; 2], + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + #[allow(clippy::type_complexity)] + enum ComplexOneGenericEnum { + ArrayOfGeneric([T; 10]), + TupleComplex(T, Vec, [T; 5]), + ArrayOfTuple([(T, T); 3]), + VecOfArray { + vec: Vec<[T; 8]>, + }, + + ArrayOfOption([Option; 5]), + TupleOfResult { + tuple: (Result, Option), + }, + VecOfStruct(Vec>), + ArrayOfBTreeMap { + array: [BTreeMap; 2], + }, + + ArrayOfVecOfOption([Vec>>; 4]), + TupleTriple { + field1: Option>>, + field2: Result, String>, + }, + VecOfStructOfOption(Vec>>), + ArrayComplexTriple([BTreeMap, String>, Result>; 2]), + } + + // Register types + let mut registry = Registry::new(); + let struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let enum_id = registry + .register_type(&MetaType::new::>()) + .id; + + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + // Check top level resolved names + let struct_complex = resolver.get(struct_id).unwrap(); + assert_eq!(struct_complex.to_string(), "ComplexOneGenericStruct"); + let struct_generic = resolver + .get_user_defined("ComplexOneGenericStruct") + .unwrap(); + // Validate Struct generics + let struct_field_types: Vec<_> = struct_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expect_struct_field_types = vec![ + "[T; 10]", + "(T, [T], [T; 5])", + "[(T, T); 3]", + "[[T; 8]]", + "[Option; 5]", + "(Result, Option)", + "[GenericStruct]", + "[[(String, T)]; 2]", + "[[Option]; 4]", + "(Option<[T]>, Result<[T; 3], String>)", + "[GenericStruct>]", + "[[(Option, Result)]; 2]", + ]; + + for expected in expect_struct_field_types { + assert!( + struct_field_types.contains(&expected.to_string()), + "Struct missing field type {expected}.\n All: {struct_field_types:#?}" + ); + } + + let enum_complex = resolver.get(enum_id).unwrap(); + assert_eq!(enum_complex.to_string(), "ComplexOneGenericEnum"); + let enum_generic = resolver.get_user_defined("ComplexOneGenericEnum").unwrap(); + + let enum_field_types: Vec<_> = enum_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expect_enum_field_types = vec![ + "[T; 10]", + "T", + "[T]", + "[T; 5]", + "[(T, T); 3]", + "[[T; 8]]", + "[Option; 5]", + "(Result, Option)", + "[GenericStruct]", + "[[(String, T)]; 2]", + "[[Option<[T]>]; 4]", + "Option>", + "Result, String>", + "[GenericStruct>]", + "[[([(Option, String)], Result)]; 2]", + ]; + + for expected in expect_enum_field_types { + assert!( + enum_field_types.contains(&expected.to_string()), + "Enum missing field type {expected}.\n All: {enum_field_types:#?}" + ); + } + } + + #[test] + fn multiple_generics() { + use gprimitives::H256; + + fn find_field_struct<'a>( + composite: &'a TypeDefComposite, + name: &str, + ) -> &'a Field { + composite + .fields + .iter() + .find(|f| f.name.as_ref().is_some_and(|s| s.to_string().eq(name))) + .unwrap_or_else(|| { + panic!("Field `{name}` not found. Fields: {:#?}", composite.fields) + }) + } + + fn find_variant<'a>( + variants: &'a [Variant], + name: &str, + ) -> &'a Variant { + variants + .iter() + .find(|v| v.name == name) + .unwrap_or_else(|| panic!("Variant `{name}` not found. Variants: {variants:#?}")) + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct MultiGenStruct { + // Category 1: Simple and complex types with single generics + just_t1: T1, + just_t2: T2, + just_t3: T3, + array_t1: [T1; 8], + tuple_t2_t3: (T2, T3), + vec_t3: Vec, + + // Category 2: Mixed generics in complex types + tuple_mixed: (T1, T2, T3), + tuple_repeated: (T1, T1, T2, T2, T3, T3), + array_of_tuple: [(T1, T2); 4], + vec_of_array: Vec<[T3; 5]>, + btreemap_t1_t2: BTreeMap, + struct_of_t3: GenericStruct, + enum_mixed: GenericEnum, + + // Category 3: Two-level nested with multiple generics + option_of_result: Option>, + array_of_option: [Option; 6], + vec_of_tuple: Vec<(T2, T3, T1)>, + tuple_of_result: (Result, Option), + btreemap_nested: BTreeMap, Result>, + struct_of_tuple: GenericStruct<(T2, T3)>, + + // Category 4: Triple-nested complex types with multiple generics + option_triple: Option, T2>>, + array_triple: [BTreeMap>; 3], + vec_of_struct_of_option: Vec>>, + array_of_vec_of_tuple: [Vec<(T1, T2)>; 2], + tuple_complex_triple: (Option>, Result<[T2; 4], T3>), + vec_complex: Vec>>, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum MultiGenEnum { + // Category 1: Simple and complex types with single generics + JustT1(T1), + JustT2(T2), + JustT3(T3), + ArrayT1([T1; 8]), + TupleT2T3((T2, T3)), + VecT3 { + vec: Vec, + }, + + // Category 2: Mixed generics in complex types + TupleMixed(T1, T2, T3), + TupleRepeated((T1, T1, T2, T2, T3, T3)), + ArrayOfTuple([(T1, T2); 4]), + VecOfArray { + vec: Vec<[T3; 5]>, + }, + BTreeMapT1T2 { + map: BTreeMap, + }, + StructOfT3(GenericStruct), + EnumMixed { + inner: GenericEnum, + }, + + // Category 3: Two-level nested with multiple generics + OptionOfResult(Option>), + ArrayOfOption([Option; 6]), + VecOfTuple(Vec<(T2, T3, T1)>), + TupleOfResult { + field1: Result, + field2: Option, + }, + BTreeMapNested { + map: BTreeMap, Result>, + }, + StructOfTuple(GenericStruct<(T2, T3)>), + + // Category 4: Triple-nested complex types with multiple generics + OptionTriple(Option, T2>>), + ArrayTriple([BTreeMap>; 3]), + VecOfStructOfOption(Vec>>), + ArrayOfVecOfTuple { + array: [Vec<(T1, T2)>; 2], + }, + TupleComplexTriple { + field1: Option>, + field2: Result<[T2; 4], T3>, + }, + VecComplex(Vec>>), + } + + // Register types and build portable registry + let mut registry = Registry::new(); + let struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let enum_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + assert_eq!( + resolver.get(struct_id).unwrap().to_string(), + "MultiGenStruct" + ); + assert_eq!( + resolver.get(enum_id).unwrap().to_string(), + "MultiGenEnum" + ); + + let struct_generic = resolver + .get_user_defined("MultiGenStruct") + .expect("MultiGenStruct generic must exist"); + let struct_field_types: Vec<_> = struct_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expect_struct_field_types = vec![ + "T1", + "T2", + "T3", + "[T1; 8]", + "(T2, T3)", + "[T3]", + "(T1, T2, T3)", + "(T1, T1, T2, T2, T3, T3)", + "[(T1, T2); 4]", + "[[T3; 5]]", + "[(T1, T2)]", + "GenericStruct", + "GenericEnum", + "Option>", + "[Option; 6]", + "[(T2, T3, T1)]", + "(Result, Option)", + "[(Option, Result)]", + "GenericStruct<(T2, T3)>", + "Option>", + "[[(T1, Option)]; 3]", + "[GenericStruct>]", + "[[(T1, T2)]; 2]", + "(Option<[T1]>, Result<[T2; 4], T3>)", + "[GenericStruct>]", + ]; + for expected in expect_struct_field_types { + assert!( + struct_field_types.contains(&expected.to_string()), + "MultiGenStruct missing field type {expected}.\n All: {struct_field_types:#?}" + ); + } + + let enum_generic = resolver + .get_user_defined("MultiGenEnum") + .expect("MultiGenEnum generic must exist"); + let enum_field_types: Vec<_> = enum_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expect_enum_field_types = vec![ + "T1", + "T2", + "T3", + "[T1; 8]", + "(T2, T3)", + "[T3]", + "T1", + "T2", + "T3", + "(T1, T1, T2, T2, T3, T3)", + "[(T1, T2); 4]", + "[[T3; 5]]", + "[(T1, T2)]", + "GenericStruct", + "GenericEnum", + "Option>", + "[Option; 6]", + "[(T2, T3, T1)]", + "Result", + "Option", + "[(Option, Result)]", + "GenericStruct<(T2, T3)>", + "Option>", + "[[(T1, Option)]; 3]", + "[GenericStruct>]", + "[[(T1, T2)]; 2]", + "Option<[T1]>", + "Result<[T2; 4], T3>", + "[GenericStruct>]", + ]; + for expected in expect_enum_field_types { + assert!( + enum_field_types.contains(&expected.to_string()), + "MultiGenEnum missing field type {expected}.\n All: {enum_field_types:#?}" + ); + } + + let struct_type = portable_registry + .types + .iter() + .find(|t| t.id == struct_id) + .unwrap(); + if let scale_info::TypeDef::Composite(composite) = &struct_type.ty.type_def { + let just_t1 = find_field_struct(composite, "just_t1"); + assert_eq!(resolver.get(just_t1.ty.id).unwrap().to_string(), "u32"); + + let tuple_t2_t3 = find_field_struct(composite, "tuple_t2_t3"); + assert_eq!( + resolver.get(tuple_t2_t3.ty.id).unwrap().to_string(), + "(String, H256)" + ); + + let vec_t3 = find_field_struct(composite, "vec_t3"); + assert_eq!(resolver.get(vec_t3.ty.id).unwrap().to_string(), "[H256]"); + + let array_triple = find_field_struct(composite, "array_triple"); + assert_eq!( + resolver.get(array_triple.ty.id).unwrap().to_string(), + "[[(u32, Option)]; 3]" + ); + } else { + panic!("Expected composite type"); + } + + let enum_type = portable_registry + .types + .iter() + .find(|t| t.id == enum_id) + .unwrap(); + if let scale_info::TypeDef::Variant(variant) = &enum_type.ty.type_def { + // check a representative tuple-like variant concrete names + let tuple_t2_t3_variant = find_variant(&variant.variants, "TupleT2T3"); + let f0 = &tuple_t2_t3_variant.fields[0]; + assert_eq!( + resolver.get(f0.ty.id).unwrap().to_string(), + "(String, H256)" + ); + + // check option/result shaped variant + let tuple_of_result_variant = find_variant(&variant.variants, "TupleOfResult"); + let field1 = tuple_of_result_variant + .fields + .iter() + .find(|f| f.name.as_ref().is_some_and(|s| s.to_string().eq("field1"))) + .unwrap(); + assert_eq!( + resolver.get(field1.ty.id).unwrap().to_string(), + "Result" + ); + } else { + panic!("Expected variant type"); + } + } + + #[test] + fn generic_const_with_generic_types() { + use gprimitives::H256; + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct ConstGenericStruct { + array: [T; N], + value: T, + vec: Vec, + option: Option, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct TwoConstGenericStruct { + array1: [T1; N], + array2: [T2; M], + tuple: (T1, T2), + nested: GenericStruct, + result: Result, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum ConstGenericEnum { + Array([T; N]), + Value(T), + Nested { inner: GenericStruct }, + } + + let mut registry = Registry::new(); + + // Register ConstGenericStruct with different N and T values + let struct_n8_u32_id = registry + .register_type(&MetaType::new::>()) + .id; + let struct_n8_string_id = registry + .register_type(&MetaType::new::>()) + .id; + + let struct_n16_u32_id = registry + .register_type(&MetaType::new::>()) + .id; + + assert_ne!(struct_n8_u32_id, struct_n8_string_id); + assert_ne!(struct_n8_u32_id, struct_n16_u32_id); + + // Register TwoConstGenericStruct + let two_const_id = registry + .register_type(&MetaType::new::>()) + .id; + + // Register ConstGenericEnum + let enum_n8_bool_id = registry + .register_type(&MetaType::new::>()) + .id; + + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + // Check ConstGenericStruct with N=8, T=u32 + let struct_n8_u32_decl = resolver.get(struct_n8_u32_id).unwrap().to_string(); + let struct_n8_string_decl = resolver.get(struct_n8_string_id).unwrap().to_string(); + let struct_n16_u32_decl = resolver.get(struct_n16_u32_id).unwrap().to_string(); + let two_const_decl = resolver.get(two_const_id).unwrap().to_string(); + let enum_n8_bool_decl = resolver.get(enum_n8_bool_id).unwrap().to_string(); + + assert_eq!(struct_n8_u32_decl, "ConstGenericStructN8"); + assert_eq!(struct_n8_string_decl, "ConstGenericStructN8"); + assert_eq!(struct_n16_u32_decl, "ConstGenericStructN16"); + assert_eq!(two_const_decl, "TwoConstGenericStructM8N4"); + assert_eq!(enum_n8_bool_decl, "ConstGenericEnumN8"); + + let TypeDecl::Named { + name: struct_n8_u32_name, + .. + } = resolver.get(struct_n8_u32_id).unwrap() + else { + panic!("Expected named type") + }; + let struct_n8_u32 = resolver.get_user_defined(struct_n8_u32_name).unwrap(); + let field_type_names: Vec<_> = struct_n8_u32 + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expected_field_type_names = vec!["[T; 8]", "T", "[T]", "Option"]; + for expected in expected_field_type_names { + assert!( + field_type_names.contains(&expected.to_string()), + "ConstGenericStruct1 missing field type name `{expected}`. All: {field_type_names:#?}", + ); + } + + let TypeDecl::Named { + name: two_const_name, + .. + } = resolver.get(two_const_id).unwrap() + else { + panic!("Expected named type") + }; + let two_const_generic = resolver.get_user_defined(two_const_name).unwrap(); + let field_type_names: Vec<_> = two_const_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expected_field_type_names = vec![ + "[T1; 4]", + "[T2; 8]", + "(T1, T2)", + "GenericStruct", + "Result", + ]; + for expected in expected_field_type_names { + assert!( + field_type_names.contains(&expected.to_string()), + "TwoConstGenericStruct missing field type name `{expected}`. All: {field_type_names:#?}", + ); + } + + let TypeDecl::Named { + name: enum_n8_bool_name, + .. + } = resolver.get(enum_n8_bool_id).unwrap() + else { + panic!("Expected named type") + }; + let enum_generic = resolver.get_user_defined(enum_n8_bool_name).unwrap(); + let field_type_names: Vec<_> = enum_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expected_field_type_names = vec!["[T; 8]", "T", "GenericStruct"]; + for expected in expected_field_type_names { + assert!( + field_type_names.contains(&expected.to_string()), + "ConstGenericEnum missing field type name `{expected}`. All: {field_type_names:#?}", + ); + } + } + + // Types for same_name_different_modules test + #[allow(dead_code)] + mod same_name_test { + use super::*; + + pub mod module_a { + use super::*; + + #[derive(TypeInfo)] + pub struct SameName { + pub value: T, + } + } + + pub mod module_b { + use super::*; + + #[derive(TypeInfo)] + pub struct SameName { + pub value: T, + } + } + + pub mod module_c { + use super::*; + + pub mod nested { + use super::*; + + #[derive(TypeInfo)] + pub struct SameName { + pub value: T, + } + } + } + } + + #[test] + fn same_name_different_mods_generic_names() { + use same_name_test::*; + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct TestStruct { + field_a: module_a::SameName, + field_b: module_b::SameName, + field_c: module_c::nested::SameName, + generic_a: GenericStruct>, + generic_b: GenericStruct>, + vec_a: Vec>, + option_b: Option>, + result_mix: Result, module_b::SameName>, + } + + let mut registry = Registry::new(); + let struct_id = registry + .register_type(&MetaType::new::>()) + .id; + + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + // Check main type + assert_eq!( + resolver.get(struct_id).unwrap().to_string(), + "TestStruct" + ); + let struct_generic = resolver + .get_user_defined("TestStruct") + .expect("TestStruct generic must exist"); + let struct_field_type_names: Vec<_> = struct_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expected_field_type_names = vec![ + "SameName", + "ModuleBSameName", + "NestedSameName", + "GenericStruct>", + "GenericStruct>", + "[NestedSameName]", + "Option>", + "Result, ModuleBSameName>", + ]; + + for expected in expected_field_type_names { + assert!( + struct_field_type_names.contains(&expected.to_string()), + "TestStruct missing field type name `{expected}`. All: {struct_field_type_names:#?}", + ); + } + } + + #[test] + fn type_names_concrete_generic_reuses() { + use gprimitives::{CodeId, H256}; + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct ReuseTestStruct { + // Same type with different generic instantiations + a1: ReusableGenericStruct, + a1r: ReusableGenericStruct, + + a2: ReusableGenericStruct>, + a2r: ReusableGenericStruct>, + + a3: ReusableGenericStruct<(T1, T2)>, + a3r: ReusableGenericStruct<(u64, H256)>, + + b1: ReusableGenericStruct, + b1r: ReusableGenericStruct, + + // Same enum with different instantiations + e1: ReusableGenericEnum, + e1r: ReusableGenericEnum, + + e2: ReusableGenericEnum, + e2r: ReusableGenericEnum, + + e3: ReusableGenericEnum, + e3r: ReusableGenericEnum<[T1; 8]>, + + // Nested reuses + n1: GenericStruct>, + n2: GenericStruct>, + n3: GenericStruct>, + + // Complex reuses + c1: Vec>, + c2: [ReusableGenericEnum; 5], + c3: Option>, + c4: Result, ReusableGenericEnum>, + c5: BTreeMap>, + c6: BTreeMap, String>, + c7: BTreeMap, ReusableGenericEnum>, + c8: BTreeMap, ReusableGenericEnum>, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum ReuseTestEnum { + // Same type with different generic instantiations + A1(ReusableGenericStruct), + A1r(ReusableGenericStruct), + + A2(ReusableGenericStruct>), + A2r(ReusableGenericStruct>), + + A3 { + field: ReusableGenericStruct<(T1, T2)>, + }, + A3r { + field: ReusableGenericStruct<(u64, H256)>, + }, + + B1(ReusableGenericStruct), + B1r(ReusableGenericStruct), + + // Same enum with different instantiations + E1(ReusableGenericEnum), + E1r(ReusableGenericEnum), + + E2(ReusableGenericEnum), + E2r(ReusableGenericEnum), + + E3 { + field: ReusableGenericEnum, + }, + E3r { + field: ReusableGenericEnum<[T1; 8]>, + }, + + // Nested reuses + N1(GenericStruct>), + N2(GenericStruct>), + N3(GenericStruct>), + + // Complex reuses + C1(Vec>), + C2 { + field: [ReusableGenericEnum; 5], + }, + C3(Option>), + C4(Result, ReusableGenericEnum>), + C5 { + field: BTreeMap>, + }, + C6(BTreeMap, String>), + C7(BTreeMap, ReusableGenericEnum>), + C8(BTreeMap, ReusableGenericEnum>), + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct ReusableGenericStruct { + data: T, + count: u32, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum ReusableGenericEnum { + Some(T), + None, + } + + let mut registry = Registry::new(); + let struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let enum_id = registry + .register_type(&MetaType::new::>()) + .id; + + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + assert_eq!( + resolver.get(struct_id).unwrap().to_string(), + "ReuseTestStruct" + ); + assert_eq!( + resolver.get(enum_id).unwrap().to_string(), + "ReuseTestEnum" + ); + + let struct_generic = resolver + .get_user_defined("ReuseTestStruct") + .expect("ReuseTestStruct generic must exist"); + let enum_generic = resolver + .get_user_defined("ReuseTestEnum") + .expect("ReuseTestEnum generic must exist"); + + let struct_field_types: Vec<_> = struct_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expect_struct_field_types = vec![ + "ReusableGenericStruct", + "ReusableGenericStruct", + "ReusableGenericStruct<[T1]>", + "ReusableGenericStruct<[bool]>", + "ReusableGenericStruct<(T1, T2)>", + "ReusableGenericStruct<(u64, H256)>", + "ReusableGenericStruct", + "ReusableGenericStruct", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum<[T1; 8]>", + "GenericStruct>", + "GenericStruct>", + "GenericStruct>", + "[ReusableGenericStruct]", + "[ReusableGenericEnum; 5]", + "Option>", + "Result, ReusableGenericEnum>", + "[(T1, ReusableGenericStruct)]", + "[(ReusableGenericEnum, String)]", + "[(ReusableGenericStruct, ReusableGenericEnum)]", + "[(ReusableGenericStruct, ReusableGenericEnum)]", + ]; + + for e in expect_struct_field_types { + assert!( + struct_field_types.contains(&e.to_string()), + "{} missing expected type signature `{}`. All entries: {:#?}", + "ReuseTestStruct", + e, + struct_field_types + ); + } + + let enum_field_types: Vec<_> = enum_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expect_enum_field_types = vec![ + "ReusableGenericStruct", + "ReusableGenericStruct", + "ReusableGenericStruct<[T1]>", + "ReusableGenericStruct<[bool]>", + "ReusableGenericStruct<(T1, T2)>", + "ReusableGenericStruct<(u64, H256)>", + "ReusableGenericStruct", + "ReusableGenericStruct", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum<[T1; 8]>", + "GenericStruct>", + "GenericStruct>", + "GenericStruct>", + "[ReusableGenericStruct]", + "[ReusableGenericEnum; 5]", + "Option>", + "Result, ReusableGenericEnum>", + "[(T1, ReusableGenericStruct)]", + "[(ReusableGenericEnum, String)]", + "[(ReusableGenericStruct, ReusableGenericEnum)]", + "[(ReusableGenericStruct, ReusableGenericEnum)]", + ]; + + for e in expect_enum_field_types { + assert!( + enum_field_types.contains(&e.to_string()), + "{} missing expected type signature `{}`. All entries: {:#?}", + "ReuseTestEnum", + e, + enum_field_types + ); + } + } +} diff --git a/rs/idl-gen/tests/generator.rs b/rs/idl-gen/tests/generator.rs index 89277741d..62cc6c9ee 100644 --- a/rs/idl-gen/tests/generator.rs +++ b/rs/idl-gen/tests/generator.rs @@ -226,7 +226,7 @@ impl ProgramMeta for TestProgramWithEmptyCtorsMeta { type ConstructorsMeta = EmptyCtorsMeta; const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = - &[("", AnyServiceMeta::new::)]; + &[("Service", AnyServiceMeta::new::)]; const ASYNC: bool = false; } @@ -247,7 +247,7 @@ impl ProgramMeta for TestProgramWithNonEmptyCtorsMeta { type ConstructorsMeta = NonEmptyCtorsMeta; const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = - &[("", AnyServiceMeta::new::)]; + &[("Test", AnyServiceMeta::new::)]; const ASYNC: bool = false; } @@ -258,7 +258,7 @@ impl ProgramMeta for TestProgramWithMultipleServicesMeta { type ConstructorsMeta = EmptyCtorsMeta; const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ - ("", AnyServiceMeta::new::), + ("Service", AnyServiceMeta::new::), ("SomeService", AnyServiceMeta::new::), ]; @@ -267,70 +267,86 @@ impl ProgramMeta for TestProgramWithMultipleServicesMeta { #[test] fn program_idl_works_with_empty_ctors() { - let mut idl = Vec::new(); - program::generate_idl::(&mut idl).unwrap(); - let generated_idl = String::from_utf8(idl).unwrap(); - let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); - - insta::assert_snapshot!(generated_idl); - let generated_idl_program = generated_idl_program.unwrap(); - assert!(generated_idl_program.ctor().is_none()); - assert_eq!(generated_idl_program.services().len(), 1); - assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); - assert_eq!(generated_idl_program.types().len(), 10); + let mut idl = String::new(); + program::generate_idl::( + Some("TestProgramWithEmptyCtorsMeta"), + &mut idl, + ) + .unwrap(); + + insta::assert_snapshot!(idl); + + let generated_idl_program = sails_idl_parser_v2::parse_idl(&idl).unwrap(); + assert!(generated_idl_program.program.is_some()); + let program = generated_idl_program.program.unwrap(); + assert!(program.ctors.is_empty()); + assert_eq!(generated_idl_program.services.len(), 1); + assert_eq!(generated_idl_program.services[0].funcs.len(), 4); + assert_eq!(generated_idl_program.services[0].types.len(), 8); } #[test] fn program_idl_works_with_non_empty_ctors() { - let mut idl = Vec::new(); - program::generate_idl::(&mut idl).unwrap(); - let generated_idl = String::from_utf8(idl).unwrap(); - let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); - - insta::assert_snapshot!(generated_idl); - let generated_idl_program = generated_idl_program.unwrap(); - assert_eq!(generated_idl_program.ctor().unwrap().funcs().len(), 2); - assert_eq!(generated_idl_program.services().len(), 1); - assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); - assert_eq!(generated_idl_program.types().len(), 10); + let mut idl = String::new(); + program::generate_idl::( + Some("TestProgramWithNonEmptyCtorsMeta"), + &mut idl, + ) + .unwrap(); + + insta::assert_snapshot!(idl); + + let generated_idl_program = sails_idl_parser_v2::parse_idl(&idl).unwrap(); + assert!(generated_idl_program.program.is_some()); + let program = generated_idl_program.program.unwrap(); + assert_eq!(program.ctors.len(), 2); + assert_eq!(generated_idl_program.services.len(), 1); + assert_eq!(generated_idl_program.services[0].funcs.len(), 4); + assert_eq!(generated_idl_program.services[0].types.len(), 8); } #[test] fn program_idl_works_with_multiple_services() { - let mut idl = Vec::new(); - program::generate_idl::(&mut idl).unwrap(); - let generated_idl = String::from_utf8(idl).unwrap(); - let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); - - insta::assert_snapshot!(generated_idl); - let generated_idl_program = generated_idl_program.unwrap(); - assert!(generated_idl_program.ctor().is_none()); - assert_eq!(generated_idl_program.services().len(), 2); - assert_eq!(generated_idl_program.services()[0].name(), ""); - assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); - assert_eq!(generated_idl_program.services()[1].name(), "SomeService"); - assert_eq!(generated_idl_program.services()[1].funcs().len(), 4); - assert_eq!(generated_idl_program.types().len(), 10); + let mut idl = String::new(); + program::generate_idl::( + Some("TestProgramWithMultipleServicesMeta"), + &mut idl, + ) + .unwrap(); + + insta::assert_snapshot!(idl); + + let generated_idl_program = sails_idl_parser_v2::parse_idl(&idl).unwrap(); + assert!(generated_idl_program.program.is_some()); + let program = generated_idl_program.program.unwrap(); + assert!(program.ctors.is_empty()); + assert_eq!(generated_idl_program.services.len(), 2); + assert_eq!(generated_idl_program.services[0].name, "Service"); + assert_eq!(generated_idl_program.services[0].funcs.len(), 4); + assert_eq!(generated_idl_program.services[0].types.len(), 8); + // TODO: dedup services by id + assert_eq!(generated_idl_program.services[1].name, "SomeService"); + assert_eq!(generated_idl_program.services[1].funcs.len(), 4); + assert_eq!(generated_idl_program.services[1].types.len(), 8); } #[test] fn service_idl_works_with_basics() { - let mut idl = Vec::new(); - service::generate_idl::(&mut idl).unwrap(); - let generated_idl = String::from_utf8(idl).unwrap(); - let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); - - insta::assert_snapshot!(generated_idl); - let generated_idl_program = generated_idl_program.unwrap(); - assert!(generated_idl_program.ctor().is_none()); - assert_eq!(generated_idl_program.services().len(), 1); - assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); - assert_eq!(generated_idl_program.types().len(), 10); + let mut idl = String::new(); + service::generate_idl::("TestServiceMeta", &mut idl).unwrap(); + + insta::assert_snapshot!(idl); + + let generated_idl_program = sails_idl_parser_v2::parse_idl(&idl).unwrap(); + assert!(generated_idl_program.program.is_none()); + assert_eq!(generated_idl_program.services.len(), 1); + assert_eq!(generated_idl_program.services[0].funcs.len(), 4); + assert_eq!(generated_idl_program.services[0].types.len(), 8); } #[test] fn service_idl_works_with_base_services() { - let mut idl = Vec::new(); + let mut idl = String::new(); service::generate_idl::< ServiceMetaWithBase< CommandsMeta, @@ -338,33 +354,18 @@ fn service_idl_works_with_base_services() { EventsMeta, ServiceMeta, >, - >(&mut idl) + >("ServiceMetaWithBase", &mut idl) .unwrap(); - let generated_idl = String::from_utf8(idl).unwrap(); - let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); - - insta::assert_snapshot!(generated_idl); - let generated_idl_program = generated_idl_program.unwrap(); - assert!(generated_idl_program.ctor().is_none()); - assert_eq!(generated_idl_program.services().len(), 1); - assert_eq!(generated_idl_program.services()[0].funcs().len(), 6); - assert_eq!(generated_idl_program.types().len(), 10); -} -#[test] -fn service_idl_fails_with_base_services_and_ambiguous_events() { - let mut idl = Vec::new(); - let result = service::generate_idl::< - ServiceMetaWithBase< - CommandsMeta, - QueriesMeta, - EventsMeta, - ServiceMeta, - >, - >(&mut idl); + insta::assert_snapshot!(idl); + + let generated_idl_program = sails_idl_parser_v2::parse_idl(&idl).unwrap(); + assert!(generated_idl_program.program.is_none()); + assert_eq!(generated_idl_program.services.len(), 2); + assert_eq!(generated_idl_program.services[0].funcs.len(), 4); + assert_eq!(generated_idl_program.services[0].types.len(), 0); + assert_eq!(generated_idl_program.services[0].events.len(), 2); - assert!(matches!( - result, - Err(sails_idl_gen::Error::EventMetaIsAmbiguous(_)) - )); + assert_eq!(generated_idl_program.services[1].funcs.len(), 4); + assert_eq!(generated_idl_program.services[1].types.len(), 8); } diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap index 040a159b1..8e3ff2ffc 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap @@ -2,106 +2,94 @@ source: rs/idl-gen/tests/generator.rs expression: generated_idl --- -/// TupleStruct docs -type TupleStruct = struct { - bool, -}; +!@sails: 0.9.2 -/// GenericStruct docs -type GenericStructForH256 = struct { - /// GenericStruct field `p1` - p1: h256, -}; +service Service { + events { + /// `This` Done + ThisDone ( + /// This is unnamed field, comments ignored + u32, + ), + ThisDoneTwice ( + /// This is the first unnamed field + u32, + /// This is the second unnamed field + u32, + ), + /// `That` Done too + ThatDone { + /// This is `p1` field + p1: String, + }, + } + functions { + /// Some description + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStructN8, p8: GenericConstStructN32) -> String; + /// Some multiline description + /// Second line + /// Third line + DoThat(par1: DoThatParam) -> (String, u32) throws (String); + /// This is a query + @query + This(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericEnum) -> (String, u32) throws String; + /// This is a second query + /// This is a second line + @query + That(pr1: ThatParam) -> String; + } + types { + struct DoThatParam { + p1: u32, + p2: String, + p3: ManyVariants, + } + /// GenericConstStruct docs + struct GenericConstStructN32 { + /// GenericStruct field `field` + field: [u8; 32], + } + /// GenericConstStruct docs + struct GenericConstStructN8 { + /// GenericStruct field `field` + field: [u8; 8], + } + /// GenericEnum docs + /// with two lines + enum GenericEnum { + /// GenericEnum `Variant1` of type 'T1' + Variant1(T1), + /// GenericEnum `Variant2` of type 'T2' + Variant2(T2), + } + /// GenericStruct docs + struct GenericStruct { + /// GenericStruct field `p1` + p1: T, + } + enum ManyVariants { + One, + Two(u32), + Three(Option<[U256]>), + Four { + a: u32, + b: Option, + }, + Five(String, [u8]), + Six((u32)), + Seven(GenericEnum), + Eight([[(u32, String)]; 10]), + } + struct ThatParam { + p1: ManyVariants, + } + /// TupleStruct docs + struct TupleStruct(bool); + } +} -/// GenericStruct docs -type GenericStructForStr = struct { - /// GenericStruct field `p1` - p1: str, -}; - -/// GenericConstStruct docs -type GenericConstStruct1 = struct { - /// GenericStruct field `field` - field: [u8, 8], -}; - -/// GenericConstStruct docs -type GenericConstStruct2 = struct { - /// GenericStruct field `field` - field: [u8, 32], -}; - -type DoThatParam = struct { - p1: u32, - p2: str, - p3: ManyVariants, -}; - -type ManyVariants = enum { - One, - Two: u32, - Three: opt vec u256, - Four: struct { - a: u32, - b: opt u16, - }, - Five: struct { - str, - vec u8, - }, - Six: struct { u32 }, - Seven: GenericEnumForU32AndStr, - Eight: [map (u32, str), 10], -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForU32AndStr = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: u32, - /// GenericEnum `Variant2` of type 'T2' - Variant2: str, -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForBoolAndU32 = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: bool, - /// GenericEnum `Variant2` of type 'T2' - Variant2: u32, -}; - -type ThatParam = struct { - p1: ManyVariants, -}; - -service { - /// Some description - DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericStructForH256, p6: GenericStructForStr, p7: GenericConstStruct1, p8: GenericConstStruct2) -> str; - /// Some multiline description - /// Second line - /// Third line - DoThat : (par1: DoThatParam) -> result (struct { str, u32 }, struct { str }); - /// This is a query - query This : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericEnumForBoolAndU32) -> result (struct { str, u32 }, str); - /// This is a second query - /// This is a second line - query That : (pr1: ThatParam) -> str; - - events { - /// `This` Done - ThisDone: u32; - ThisDoneTwice: struct { - /// This is the first unnamed field - u32, - /// This is the second unnamed field - u32, - }; - /// `That` Done too - ThatDone: struct { - /// This is `p1` field - p1: str - }; - } -}; +program TestProgramWithEmptyCtorsMeta { + services { + Service, + } +} diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap index a28c014af..a6deaf424 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap @@ -1,137 +1,180 @@ --- source: rs/idl-gen/tests/generator.rs -expression: generated_idl +expression: idl --- -/// TupleStruct docs -type TupleStruct = struct { - bool, -}; +!@sails: 0.9.2 -/// GenericStruct docs -type GenericStructForH256 = struct { - /// GenericStruct field `p1` - p1: h256, -}; - -/// GenericStruct docs -type GenericStructForStr = struct { - /// GenericStruct field `p1` - p1: str, -}; - -/// GenericConstStruct docs -type GenericConstStruct1 = struct { - /// GenericStruct field `field` - field: [u8, 8], -}; - -/// GenericConstStruct docs -type GenericConstStruct2 = struct { - /// GenericStruct field `field` - field: [u8, 32], -}; - -type DoThatParam = struct { - p1: u32, - p2: str, - p3: ManyVariants, -}; - -type ManyVariants = enum { - One, - Two: u32, - Three: opt vec u256, - Four: struct { - a: u32, - b: opt u16, - }, - Five: struct { - str, - vec u8, - }, - Six: struct { u32 }, - Seven: GenericEnumForU32AndStr, - Eight: [map (u32, str), 10], -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForU32AndStr = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: u32, - /// GenericEnum `Variant2` of type 'T2' - Variant2: str, -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForBoolAndU32 = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: bool, - /// GenericEnum `Variant2` of type 'T2' - Variant2: u32, -}; - -type ThatParam = struct { - p1: ManyVariants, -}; - -service { - /// Some description - DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericStructForH256, p6: GenericStructForStr, p7: GenericConstStruct1, p8: GenericConstStruct2) -> str; - /// Some multiline description - /// Second line - /// Third line - DoThat : (par1: DoThatParam) -> result (struct { str, u32 }, struct { str }); - /// This is a query - query This : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericEnumForBoolAndU32) -> result (struct { str, u32 }, str); - /// This is a second query - /// This is a second line - query That : (pr1: ThatParam) -> str; - - events { - /// `This` Done - ThisDone: u32; - ThisDoneTwice: struct { - /// This is the first unnamed field - u32, - /// This is the second unnamed field - u32, - }; - /// `That` Done too - ThatDone: struct { - /// This is `p1` field - p1: str - }; - } -}; +service Service { + events { + /// `This` Done + ThisDone ( + /// This is unnamed field, comments ignored + u32, + ), + ThisDoneTwice ( + /// This is the first unnamed field + u32, + /// This is the second unnamed field + u32, + ), + /// `That` Done too + ThatDone { + /// This is `p1` field + p1: String, + }, + } + functions { + /// Some description + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStructN8, p8: GenericConstStructN32) -> String; + /// Some multiline description + /// Second line + /// Third line + DoThat(par1: DoThatParam) -> (String, u32) throws (String); + /// This is a query + @query + This(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericEnum) -> (String, u32) throws String; + /// This is a second query + /// This is a second line + @query + That(pr1: ThatParam) -> String; + } + types { + struct DoThatParam { + p1: u32, + p2: String, + p3: ManyVariants, + } + /// GenericConstStruct docs + struct GenericConstStructN32 { + /// GenericStruct field `field` + field: [u8; 32], + } + /// GenericConstStruct docs + struct GenericConstStructN8 { + /// GenericStruct field `field` + field: [u8; 8], + } + /// GenericEnum docs + /// with two lines + enum GenericEnum { + /// GenericEnum `Variant1` of type 'T1' + Variant1(T1), + /// GenericEnum `Variant2` of type 'T2' + Variant2(T2), + } + /// GenericStruct docs + struct GenericStruct { + /// GenericStruct field `p1` + p1: T, + } + enum ManyVariants { + One, + Two(u32), + Three(Option<[U256]>), + Four { + a: u32, + b: Option, + }, + Five(String, [u8]), + Six((u32)), + Seven(GenericEnum), + Eight([[(u32, String)]; 10]), + } + struct ThatParam { + p1: ManyVariants, + } + /// TupleStruct docs + struct TupleStruct(bool); + } +} service SomeService { - /// Some description - DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericStructForH256, p6: GenericStructForStr, p7: GenericConstStruct1, p8: GenericConstStruct2) -> str; - /// Some multiline description - /// Second line - /// Third line - DoThat : (par1: DoThatParam) -> result (struct { str, u32 }, struct { str }); - /// This is a query - query This : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericEnumForBoolAndU32) -> result (struct { str, u32 }, str); - /// This is a second query - /// This is a second line - query That : (pr1: ThatParam) -> str; + events { + /// `This` Done + ThisDone ( + /// This is unnamed field, comments ignored + u32, + ), + ThisDoneTwice ( + /// This is the first unnamed field + u32, + /// This is the second unnamed field + u32, + ), + /// `That` Done too + ThatDone { + /// This is `p1` field + p1: String, + }, + } + functions { + /// Some description + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStructN8, p8: GenericConstStructN32) -> String; + /// Some multiline description + /// Second line + /// Third line + DoThat(par1: DoThatParam) -> (String, u32) throws (String); + /// This is a query + @query + This(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericEnum) -> (String, u32) throws String; + /// This is a second query + /// This is a second line + @query + That(pr1: ThatParam) -> String; + } + types { + struct DoThatParam { + p1: u32, + p2: String, + p3: ManyVariants, + } + /// GenericConstStruct docs + struct GenericConstStructN32 { + /// GenericStruct field `field` + field: [u8; 32], + } + /// GenericConstStruct docs + struct GenericConstStructN8 { + /// GenericStruct field `field` + field: [u8; 8], + } + /// GenericEnum docs + /// with two lines + enum GenericEnum { + /// GenericEnum `Variant1` of type 'T1' + Variant1(T1), + /// GenericEnum `Variant2` of type 'T2' + Variant2(T2), + } + /// GenericStruct docs + struct GenericStruct { + /// GenericStruct field `p1` + p1: T, + } + enum ManyVariants { + One, + Two(u32), + Three(Option<[U256]>), + Four { + a: u32, + b: Option, + }, + Five(String, [u8]), + Six((u32)), + Seven(GenericEnum), + Eight([[(u32, String)]; 10]), + } + struct ThatParam { + p1: ManyVariants, + } + /// TupleStruct docs + struct TupleStruct(bool); + } +} - events { - /// `This` Done - ThisDone: u32; - ThisDoneTwice: struct { - /// This is the first unnamed field - u32, - /// This is the second unnamed field - u32, - }; - /// `That` Done too - ThatDone: struct { - /// This is `p1` field - p1: str - }; - } -}; +program TestProgramWithMultipleServicesMeta { + services { + Service, + SomeService: Service, + } +} diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap index fc0fe672e..e19eb2ca2 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap @@ -2,114 +2,101 @@ source: rs/idl-gen/tests/generator.rs expression: generated_idl --- -/// TupleStruct docs -type TupleStruct = struct { - bool, -}; +!@sails: 0.9.2 -/// GenericStruct docs -type GenericStructForH256 = struct { - /// GenericStruct field `p1` - p1: h256, -}; +service Test { + events { + /// `This` Done + ThisDone ( + /// This is unnamed field, comments ignored + u32, + ), + ThisDoneTwice ( + /// This is the first unnamed field + u32, + /// This is the second unnamed field + u32, + ), + /// `That` Done too + ThatDone { + /// This is `p1` field + p1: String, + }, + } + functions { + /// Some description + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStructN8, p8: GenericConstStructN32) -> String; + /// Some multiline description + /// Second line + /// Third line + DoThat(par1: DoThatParam) -> (String, u32) throws (String); + /// This is a query + @query + This(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericEnum) -> (String, u32) throws String; + /// This is a second query + /// This is a second line + @query + That(pr1: ThatParam) -> String; + } + types { + struct DoThatParam { + p1: u32, + p2: String, + p3: ManyVariants, + } + /// GenericConstStruct docs + struct GenericConstStructN32 { + /// GenericStruct field `field` + field: [u8; 32], + } + /// GenericConstStruct docs + struct GenericConstStructN8 { + /// GenericStruct field `field` + field: [u8; 8], + } + /// GenericEnum docs + /// with two lines + enum GenericEnum { + /// GenericEnum `Variant1` of type 'T1' + Variant1(T1), + /// GenericEnum `Variant2` of type 'T2' + Variant2(T2), + } + /// GenericStruct docs + struct GenericStruct { + /// GenericStruct field `p1` + p1: T, + } + enum ManyVariants { + One, + Two(u32), + Three(Option<[U256]>), + Four { + a: u32, + b: Option, + }, + Five(String, [u8]), + Six((u32)), + Seven(GenericEnum), + Eight([[(u32, String)]; 10]), + } + struct ThatParam { + p1: ManyVariants, + } + /// TupleStruct docs + struct TupleStruct(bool); + } +} -/// GenericStruct docs -type GenericStructForStr = struct { - /// GenericStruct field `p1` - p1: str, -}; - -/// GenericConstStruct docs -type GenericConstStruct1 = struct { - /// GenericStruct field `field` - field: [u8, 8], -}; - -/// GenericConstStruct docs -type GenericConstStruct2 = struct { - /// GenericStruct field `field` - field: [u8, 32], -}; - -type DoThatParam = struct { - p1: u32, - p2: str, - p3: ManyVariants, -}; - -type ManyVariants = enum { - One, - Two: u32, - Three: opt vec u256, - Four: struct { - a: u32, - b: opt u16, - }, - Five: struct { - str, - vec u8, - }, - Six: struct { u32 }, - Seven: GenericEnumForU32AndStr, - Eight: [map (u32, str), 10], -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForU32AndStr = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: u32, - /// GenericEnum `Variant2` of type 'T2' - Variant2: str, -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForBoolAndU32 = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: bool, - /// GenericEnum `Variant2` of type 'T2' - Variant2: u32, -}; - -type ThatParam = struct { - p1: ManyVariants, -}; - -constructor { - /// This is New constructor - New : (); - /// This is FromStr constructor - /// with second line - FromStr : (p1: str); -}; - -service { - /// Some description - DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericStructForH256, p6: GenericStructForStr, p7: GenericConstStruct1, p8: GenericConstStruct2) -> str; - /// Some multiline description - /// Second line - /// Third line - DoThat : (par1: DoThatParam) -> result (struct { str, u32 }, struct { str }); - /// This is a query - query This : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericEnumForBoolAndU32) -> result (struct { str, u32 }, str); - /// This is a second query - /// This is a second line - query That : (pr1: ThatParam) -> str; - - events { - /// `This` Done - ThisDone: u32; - ThisDoneTwice: struct { - /// This is the first unnamed field - u32, - /// This is the second unnamed field - u32, - }; - /// `That` Done too - ThatDone: struct { - /// This is `p1` field - p1: str - }; - } -}; +program TestProgramWithNonEmptyCtorsMeta { + constructors { + /// This is New constructor + New(); + /// This is FromStr constructor + /// with second line + FromStr(p1: String); + } + services { + Test, + } +} diff --git a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap index db66155ff..6d8582cb9 100644 --- a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap +++ b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap @@ -2,112 +2,108 @@ source: rs/idl-gen/tests/generator.rs expression: generated_idl --- -/// TupleStruct docs -type TupleStruct = struct { - bool, -}; +!@sails: 0.9.2 -/// GenericStruct docs -type GenericStructForH256 = struct { - /// GenericStruct field `p1` - p1: h256, -}; +service B { + events { + ThisDoneBase(u32), + ThatDoneBase { + p1: u16, + }, + } + functions { + DoThis(p1: u32) -> u32; + DoThatBase(p1: String) -> String; + @query + ThisBase(p1: u16) -> u16; + @query + That(p1: String) -> String; + } +} -/// GenericStruct docs -type GenericStructForStr = struct { - /// GenericStruct field `p1` - p1: str, -}; - -/// GenericConstStruct docs -type GenericConstStruct1 = struct { - /// GenericStruct field `field` - field: [u8, 8], -}; - -/// GenericConstStruct docs -type GenericConstStruct2 = struct { - /// GenericStruct field `field` - field: [u8, 32], -}; - -type DoThatParam = struct { - p1: u32, - p2: str, - p3: ManyVariants, -}; - -type ManyVariants = enum { - One, - Two: u32, - Three: opt vec u256, - Four: struct { - a: u32, - b: opt u16, - }, - Five: struct { - str, - vec u8, - }, - Six: struct { u32 }, - Seven: GenericEnumForU32AndStr, - Eight: [map (u32, str), 10], -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForU32AndStr = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: u32, - /// GenericEnum `Variant2` of type 'T2' - Variant2: str, -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForBoolAndU32 = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: bool, - /// GenericEnum `Variant2` of type 'T2' - Variant2: u32, -}; - -type ThatParam = struct { - p1: ManyVariants, -}; - -service { - /// Some description - DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericStructForH256, p6: GenericStructForStr, p7: GenericConstStruct1, p8: GenericConstStruct2) -> str; - /// Some multiline description - /// Second line - /// Third line - DoThat : (par1: DoThatParam) -> result (struct { str, u32 }, struct { str }); - DoThatBase : (p1: str) -> str; - /// This is a query - query This : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericEnumForBoolAndU32) -> result (struct { str, u32 }, str); - /// This is a second query - /// This is a second line - query That : (pr1: ThatParam) -> str; - query ThisBase : (p1: u16) -> u16; - - events { - /// `This` Done - ThisDone: u32; - ThisDoneTwice: struct { - /// This is the first unnamed field - u32, - /// This is the second unnamed field - u32, - }; - /// `That` Done too - ThatDone: struct { - /// This is `p1` field - p1: str - }; - ThisDoneBase: u32; - ThatDoneBase: struct { - p1: u16 - }; - } -}; +service ServiceMetaWithBase { + extends { + B, + } + events { + /// `This` Done + ThisDone ( + /// This is unnamed field, comments ignored + u32, + ), + ThisDoneTwice ( + /// This is the first unnamed field + u32, + /// This is the second unnamed field + u32, + ), + /// `That` Done too + ThatDone { + /// This is `p1` field + p1: String, + }, + } + functions { + /// Some description + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStructN8, p8: GenericConstStructN32) -> String; + /// Some multiline description + /// Second line + /// Third line + DoThat(par1: DoThatParam) -> (String, u32) throws (String); + /// This is a query + @query + This(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericEnum) -> (String, u32) throws String; + /// This is a second query + /// This is a second line + @query + That(pr1: ThatParam) -> String; + } + types { + struct DoThatParam { + p1: u32, + p2: String, + p3: ManyVariants, + } + /// GenericConstStruct docs + struct GenericConstStructN32 { + /// GenericStruct field `field` + field: [u8; 32], + } + /// GenericConstStruct docs + struct GenericConstStructN8 { + /// GenericStruct field `field` + field: [u8; 8], + } + /// GenericEnum docs + /// with two lines + enum GenericEnum { + /// GenericEnum `Variant1` of type 'T1' + Variant1(T1), + /// GenericEnum `Variant2` of type 'T2' + Variant2(T2), + } + /// GenericStruct docs + struct GenericStruct { + /// GenericStruct field `p1` + p1: T, + } + enum ManyVariants { + One, + Two(u32), + Three(Option<[U256]>), + Four { + a: u32, + b: Option, + }, + Five(String, [u8]), + Six((u32)), + Seven(GenericEnum), + Eight([[(u32, String)]; 10]), + } + struct ThatParam { + p1: ManyVariants, + } + /// TupleStruct docs + struct TupleStruct(bool); + } +} diff --git a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap index 040a159b1..122912663 100644 --- a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap +++ b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap @@ -2,106 +2,88 @@ source: rs/idl-gen/tests/generator.rs expression: generated_idl --- -/// TupleStruct docs -type TupleStruct = struct { - bool, -}; +!@sails: 0.9.2 -/// GenericStruct docs -type GenericStructForH256 = struct { - /// GenericStruct field `p1` - p1: h256, -}; - -/// GenericStruct docs -type GenericStructForStr = struct { - /// GenericStruct field `p1` - p1: str, -}; - -/// GenericConstStruct docs -type GenericConstStruct1 = struct { - /// GenericStruct field `field` - field: [u8, 8], -}; - -/// GenericConstStruct docs -type GenericConstStruct2 = struct { - /// GenericStruct field `field` - field: [u8, 32], -}; - -type DoThatParam = struct { - p1: u32, - p2: str, - p3: ManyVariants, -}; - -type ManyVariants = enum { - One, - Two: u32, - Three: opt vec u256, - Four: struct { - a: u32, - b: opt u16, - }, - Five: struct { - str, - vec u8, - }, - Six: struct { u32 }, - Seven: GenericEnumForU32AndStr, - Eight: [map (u32, str), 10], -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForU32AndStr = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: u32, - /// GenericEnum `Variant2` of type 'T2' - Variant2: str, -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForBoolAndU32 = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: bool, - /// GenericEnum `Variant2` of type 'T2' - Variant2: u32, -}; - -type ThatParam = struct { - p1: ManyVariants, -}; - -service { - /// Some description - DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericStructForH256, p6: GenericStructForStr, p7: GenericConstStruct1, p8: GenericConstStruct2) -> str; - /// Some multiline description - /// Second line - /// Third line - DoThat : (par1: DoThatParam) -> result (struct { str, u32 }, struct { str }); - /// This is a query - query This : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericEnumForBoolAndU32) -> result (struct { str, u32 }, str); - /// This is a second query - /// This is a second line - query That : (pr1: ThatParam) -> str; - - events { - /// `This` Done - ThisDone: u32; - ThisDoneTwice: struct { - /// This is the first unnamed field - u32, - /// This is the second unnamed field - u32, - }; - /// `That` Done too - ThatDone: struct { - /// This is `p1` field - p1: str - }; - } -}; +service TestServiceMeta { + events { + /// `This` Done + ThisDone ( + /// This is unnamed field, comments ignored + u32, + ), + ThisDoneTwice ( + /// This is the first unnamed field + u32, + /// This is the second unnamed field + u32, + ), + /// `That` Done too + ThatDone { + /// This is `p1` field + p1: String, + }, + } + functions { + /// Some description + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStructN8, p8: GenericConstStructN32) -> String; + /// Some multiline description + /// Second line + /// Third line + DoThat(par1: DoThatParam) -> (String, u32) throws (String); + /// This is a query + @query + This(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericEnum) -> (String, u32) throws String; + /// This is a second query + /// This is a second line + @query + That(pr1: ThatParam) -> String; + } + types { + struct DoThatParam { + p1: u32, + p2: String, + p3: ManyVariants, + } + /// GenericConstStruct docs + struct GenericConstStructN32 { + /// GenericStruct field `field` + field: [u8; 32], + } + /// GenericConstStruct docs + struct GenericConstStructN8 { + /// GenericStruct field `field` + field: [u8; 8], + } + /// GenericEnum docs + /// with two lines + enum GenericEnum { + /// GenericEnum `Variant1` of type 'T1' + Variant1(T1), + /// GenericEnum `Variant2` of type 'T2' + Variant2(T2), + } + /// GenericStruct docs + struct GenericStruct { + /// GenericStruct field `p1` + p1: T, + } + enum ManyVariants { + One, + Two(u32), + Three(Option<[U256]>), + Four { + a: u32, + b: Option, + }, + Five(String, [u8]), + Six((u32)), + Seven(GenericEnum), + Eight([[(u32, String)]; 10]), + } + struct ThatParam { + p1: ManyVariants, + } + /// TupleStruct docs + struct TupleStruct(bool); + } +} diff --git a/rs/idl-meta/Cargo.toml b/rs/idl-meta/Cargo.toml index 7f66230e3..1f4ccc76d 100644 --- a/rs/idl-meta/Cargo.toml +++ b/rs/idl-meta/Cargo.toml @@ -10,7 +10,7 @@ repository.workspace = true rust-version.workspace = true [dependencies] -askama = { workspace = true, optional = true } +askama = { workspace = true, optional = true, features = ["alloc", "derive"] } scale-info.workspace = true parity-scale-codec = { workspace = true, features = ["derive"] } diff --git a/rs/idl-meta/src/ast.rs b/rs/idl-meta/src/ast.rs index 85f695d8f..ffb17c6bc 100644 --- a/rs/idl-meta/src/ast.rs +++ b/rs/idl-meta/src/ast.rs @@ -59,6 +59,7 @@ pub struct ProgramUnit { pub struct ServiceExpo { pub name: String, pub route: Option, + // TODO: interface_id: [u8; 8], pub docs: Vec, pub annotations: Vec<(String, Option)>, } diff --git a/rs/idl-meta/src/lib.rs b/rs/idl-meta/src/lib.rs index 3a7877518..8df637c05 100644 --- a/rs/idl-meta/src/lib.rs +++ b/rs/idl-meta/src/lib.rs @@ -33,6 +33,16 @@ impl InterfaceId { Self(inner) } + /// Create interface ID from bytes. + pub const fn from_bytes_8(bytes: [u8; 8]) -> Self { + Self(bytes) + } + + /// Create interface ID from u64. + pub const fn from_u64(int: u64) -> Self { + Self(int.to_le_bytes()) + } + /// Get interface ID as a byte slice pub fn as_bytes(&self) -> &[u8] { &self.0 @@ -102,6 +112,7 @@ pub struct AnyServiceMeta { queries: MetaType, events: MetaType, base_services: Vec<(&'static str, AnyServiceMeta)>, + interface_id: InterfaceId, } impl AnyServiceMeta { @@ -111,6 +122,7 @@ impl AnyServiceMeta { queries: S::queries(), events: S::events(), base_services: S::base_services().collect(), + interface_id: S::INTERFACE_ID, } } @@ -131,6 +143,10 @@ impl AnyServiceMeta { .iter() .map(|&(name, ref meta)| (name, meta)) } + + pub fn interface_id(&self) -> InterfaceId { + self.interface_id + } } pub trait ProgramMeta { diff --git a/rs/src/builder.rs b/rs/src/builder.rs index fcedfa605..b67d29919 100644 --- a/rs/src/builder.rs +++ b/rs/src/builder.rs @@ -7,7 +7,6 @@ use std::{ ffi::OsStr, path::{Path, PathBuf}, string::{String, ToString}, - vec::Vec, }; /// Shorthand function to be used in `build.rs`. @@ -15,7 +14,7 @@ use std::{ /// See [Builder::build()]. /// /// Code -/// ```rust +/// ```rust,ignore /// use std::{env, path::PathBuf}; /// /// let out_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -146,7 +145,7 @@ impl ClientBuilder

{ pub fn with_program_name(self, program_name: &str) -> Self { Self { - program_name: program_name.to_string(), + program_name: program_name.to_case(Case::Snake), ..self } } @@ -165,10 +164,11 @@ impl ClientBuilder

{ /// /// Returns client code generator. pub fn build_idl<'a>(&'a self) -> ClientGenerator<'a, IdlPath<'a>> { + let program_name = self.program_name.to_case(Case::Pascal); let idl_path = self.idl_path.as_ref().expect("idl path not set"); let client_path = self.client_path.as_ref().expect("client path not set"); // Generate IDL file for the program - sails_idl_gen::generate_idl_to_file::

(idl_path.as_path()) + sails_idl_gen::generate_idl_to_file::

(Some(program_name.as_str()), idl_path.as_path()) .expect("Error generating IDL from program"); ClientGenerator::from_idl_path(idl_path.as_path()).with_client_path(client_path.as_path()) @@ -180,10 +180,14 @@ impl ClientBuilder

{ /// - first `Option` is path to the IDL file if generated. /// - second `Option` is path to the client file if generated. pub fn build(self) -> (Option, Option) { + let program_name = self.program_name.to_case(Case::Pascal); if let Some(idl_path) = self.idl_path.as_ref() { // Generate IDL file for the program - sails_idl_gen::generate_idl_to_file::

(idl_path.as_path()) - .expect("Error generating IDL from program"); + sails_idl_gen::generate_idl_to_file::

( + Some(program_name.as_str()), + idl_path.as_path(), + ) + .expect("Error generating IDL from program"); if let Some(client_path) = self.client_path.as_ref() { // Generate client code from IDL file @@ -194,17 +198,14 @@ impl ClientBuilder

{ } } else if let Some(client_path) = self.client_path.as_ref() { // Generate IDL string for the program - let mut idl = Vec::new(); - sails_idl_gen::generate_idl::

(&mut idl).expect("Error generating IDL from program"); - let idl = String::from_utf8(idl).unwrap(); + let mut idl = String::new(); + sails_idl_gen::generate_idl::

(Some(program_name.as_str()), &mut idl) + .expect("Error generating IDL from program"); // Generate client code from IDL string ClientGenerator::from_idl(&idl) .with_no_std(self.no_std) - .generate_to( - self.program_name.to_case(Case::Pascal).as_str(), - client_path.as_path(), - ) + .generate_to(client_path.as_path()) .expect("Error generating client from IDL"); }