From c6883bd5a2f0585d3fef23f1ed5c500a39564a97 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 19 Dec 2025 19:00:36 +0100 Subject: [PATCH 1/4] wip: service id in ast --- rs/idl-gen/src/builder.rs | 51 ++++++++++++----- rs/idl-gen/tests/generator.rs | 4 +- ...r__program_idl_works_with_empty_ctors.snap | 6 +- ...gram_idl_works_with_multiple_services.snap | 8 +-- ...rogram_idl_works_with_non_empty_ctors.snap | 6 +- ..._service_idl_works_with_base_services.snap | 8 +-- ...erator__service_idl_works_with_basics.snap | 4 +- rs/idl-meta/src/ast.rs | 55 +++++++++++++++++-- rs/idl-meta/src/lib.rs | 39 +++++++++++++ rs/idl-meta/templates/program.askama | 2 +- rs/idl-meta/tests/fixture/mod.rs | 15 +++-- rs/idl-parser-v2/src/ffi/ast/mod.rs | 9 +-- rs/idl-parser-v2/src/idl.pest | 13 +++-- rs/idl-parser-v2/src/lib.rs | 31 +++++++---- 14 files changed, 186 insertions(+), 65 deletions(-) diff --git a/rs/idl-gen/src/builder.rs b/rs/idl-gen/src/builder.rs index a0332bc0b..99e3f3122 100644 --- a/rs/idl-gen/src/builder.rs +++ b/rs/idl-gen/src/builder.rs @@ -25,8 +25,12 @@ impl ProgramBuilder { service_ids.insert(key, service_name); (service_name.to_string(), None) }; + let interface_id = meta.interface_id(); ServiceExpo { - name, + name: ServiceIdent { + name, + interface_id: Some(interface_id), + }, route, docs: vec![], annotations: vec![], @@ -173,7 +177,10 @@ impl<'a> ServiceBuilder<'a> { let mut services = Vec::new(); let mut extends = Vec::new(); for (name, meta) in self.meta.base_services() { - extends.push(name.to_string()); + extends.push(ServiceIdent { + name: name.to_string(), + interface_id: Some(meta.interface_id()), + }); // TODO: dedup base services based on `interface_id` services.extend(ServiceBuilder::new(name, meta).build()?); } @@ -186,7 +193,10 @@ impl<'a> ServiceBuilder<'a> { let types = resolver.into_types(); services.push(ServiceUnit { - name: self.name.to_string(), + name: ServiceIdent { + name: self.name.to_string(), + interface_id: Some(self.meta.interface_id()), + }, extends, funcs: [commands, queries].concat(), events, @@ -597,7 +607,10 @@ mod tests { assert_eq!( meta.services[0], ServiceExpo { - name: "TestService1".to_string(), + name: ServiceIdent { + name: "TestService1".to_string(), + interface_id: Some(InterfaceId::zero()) + }, route: None, docs: vec![], annotations: vec![] @@ -606,7 +619,11 @@ mod tests { assert_eq!( meta.services[1], ServiceExpo { - name: "TestService1".to_string(), + name: ServiceIdent { + name: "TestService1".to_string(), + interface_id: Some(InterfaceId::zero()) + }, + route: Some("TestService2".to_string()), docs: vec![], annotations: vec![] @@ -615,7 +632,10 @@ mod tests { assert_eq!( meta.services[2], ServiceExpo { - name: "TestService1".to_string(), + name: ServiceIdent { + name: "TestService1".to_string(), + interface_id: Some(InterfaceId::zero()) + }, route: Some("TestService3".to_string()), docs: vec![], annotations: vec![] @@ -772,12 +792,15 @@ mod tests { 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!(base_service.name.name, "BaseServiceMeta"); + assert_eq!(extended_service.name.name, "ExtendedService"); assert_eq!( extended_service.extends, - vec!["BaseServiceMeta".to_string()] + vec![ServiceIdent { + name: "BaseServiceMeta".to_string(), + interface_id: Some(BaseServiceMeta::INTERFACE_ID) + }] ); assert_eq!(base_service.funcs.len(), 2); @@ -873,8 +896,8 @@ mod tests { 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"); + assert_eq!(base_service.name.name, "BaseService"); + assert_eq!(extended_service.name.name, "ExtendedService"); let base_cmd = base_service .funcs @@ -953,8 +976,8 @@ mod tests { 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"); + assert_eq!(base_service.name.name, "BaseService"); + assert_eq!(extended_service.name.name, "ExtendedService"); let base_event = base_service .events @@ -1107,7 +1130,7 @@ mod tests { 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(); + let names: Vec<_> = services.iter().map(|s| s.name.name.as_str()).collect(); assert_eq!( names, vec![ diff --git a/rs/idl-gen/tests/generator.rs b/rs/idl-gen/tests/generator.rs index 62cc6c9ee..ed200f447 100644 --- a/rs/idl-gen/tests/generator.rs +++ b/rs/idl-gen/tests/generator.rs @@ -321,11 +321,11 @@ fn program_idl_works_with_multiple_services() { 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].name.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].name.name, "SomeService"); 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 8e3ff2ffc..7bbedf5fd 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 @@ -1,10 +1,10 @@ --- source: rs/idl-gen/tests/generator.rs -expression: generated_idl +expression: idl --- !@sails: 0.9.2 -service Service { +service Service@0x0000000000000000 { events { /// `This` Done ThisDone ( @@ -90,6 +90,6 @@ service Service { program TestProgramWithEmptyCtorsMeta { services { - Service, + Service@0x0000000000000000, } } 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 a6deaf424..6774698c5 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 @@ -4,7 +4,7 @@ expression: idl --- !@sails: 0.9.2 -service Service { +service Service@0x0000000000000000 { events { /// `This` Done ThisDone ( @@ -88,7 +88,7 @@ service Service { } } -service SomeService { +service SomeService@0x0000000000000000 { events { /// `This` Done ThisDone ( @@ -174,7 +174,7 @@ service SomeService { program TestProgramWithMultipleServicesMeta { services { - Service, - SomeService: Service, + Service@0x0000000000000000, + Service@0x0000000000000000: SomeService, } } 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 e19eb2ca2..612abb65e 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 @@ -1,10 +1,10 @@ --- source: rs/idl-gen/tests/generator.rs -expression: generated_idl +expression: idl --- !@sails: 0.9.2 -service Test { +service Test@0x0000000000000000 { events { /// `This` Done ThisDone ( @@ -97,6 +97,6 @@ program TestProgramWithNonEmptyCtorsMeta { FromStr(p1: String); } services { - Test, + Test@0x0000000000000000, } } 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 6d8582cb9..46755f80d 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 @@ -1,10 +1,10 @@ --- source: rs/idl-gen/tests/generator.rs -expression: generated_idl +expression: idl --- !@sails: 0.9.2 -service B { +service B@0x0000000000000000 { events { ThisDoneBase(u32), ThatDoneBase { @@ -21,9 +21,9 @@ service B { } } -service ServiceMetaWithBase { +service ServiceMetaWithBase@0x0000000000000000 { extends { - B, + B@0x0000000000000000, } events { /// `This` Done 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 122912663..b85d8586d 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 @@ -1,10 +1,10 @@ --- source: rs/idl-gen/tests/generator.rs -expression: generated_idl +expression: idl --- !@sails: 0.9.2 -service TestServiceMeta { +service TestServiceMeta@0x0000000000000000 { events { /// `This` Done ThisDone ( diff --git a/rs/idl-meta/src/ast.rs b/rs/idl-meta/src/ast.rs index ffb17c6bc..f4662d00d 100644 --- a/rs/idl-meta/src/ast.rs +++ b/rs/idl-meta/src/ast.rs @@ -1,3 +1,4 @@ +use crate::InterfaceId; use alloc::{ boxed::Box, format, @@ -5,7 +6,10 @@ use alloc::{ vec, vec::Vec, }; -use core::fmt::{Display, Write}; +use core::{ + fmt::{Display, Write}, + str::FromStr, +}; // -------------------------------- IDL model --------------------------------- @@ -49,17 +53,56 @@ pub struct ProgramUnit { pub annotations: Vec<(String, Option)>, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ServiceIdent { + pub name: String, + pub interface_id: Option, +} + +impl Display for ServiceIdent { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(&self.name)?; + if let Some(id) = self.interface_id { + f.write_str("@")?; + id.fmt(f)?; + } + Ok(()) + } +} + +impl FromStr for ServiceIdent { + type Err = String; + + fn from_str(s: &str) -> Result { + let (name, interface_id) = match s.split_once('@') { + None => (s.trim().to_string(), None), + Some((name, id_str)) => { + if name.is_empty() { + return Err("name is empty".to_string()); + } + if id_str.is_empty() { + return Err("interface_id is empty".to_string()); + } + + // Delegate parsing to InterfaceId's FromStr + let id = id_str.trim().parse::()?; + (name.trim().to_string(), Some(id)) + } + }; + Ok(ServiceIdent { name, interface_id }) + } +} + /// Single service export entry inside a `program { services { ... } }` section. /// /// Represents a link between: /// - the exported service name visible to the client, /// - an optional low-level `route` (transport / path) used by the runtime, /// - may contain documentation comments and annotations. -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct ServiceExpo { - pub name: String, + pub name: ServiceIdent, pub route: Option, - // TODO: interface_id: [u8; 8], pub docs: Vec, pub annotations: Vec<(String, Option)>, } @@ -93,8 +136,8 @@ pub struct CtorFunc { template(path = "service.askama", escape = "none") )] pub struct ServiceUnit { - pub name: String, - pub extends: Vec, + pub name: ServiceIdent, + pub extends: Vec, pub funcs: Vec, pub events: Vec, pub types: Vec, diff --git a/rs/idl-meta/src/lib.rs b/rs/idl-meta/src/lib.rs index 8df637c05..47f5f7142 100644 --- a/rs/idl-meta/src/lib.rs +++ b/rs/idl-meta/src/lib.rs @@ -5,6 +5,10 @@ extern crate alloc; #[cfg(feature = "ast")] mod ast; +use alloc::{ + format, + string::{String, ToString as _}, +}; #[cfg(feature = "ast")] pub use ast::*; use parity_scale_codec::{Decode, Encode, Error}; @@ -67,6 +71,41 @@ impl InterfaceId { } } +impl core::fmt::Display for InterfaceId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("0x")?; + for byte in self.as_bytes() { + write!(f, "{byte:02x}")?; + } + Ok(()) + } +} + +impl core::str::FromStr for InterfaceId { + type Err = String; + + fn from_str(mut s: &str) -> Result { + // Strip optional 0x / 0X prefix + if let Some(rest) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { + s = rest; + } + + if s.len() != 16 { + return Err(format!("expected 16 hex digits (8 bytes), got {}", s.len())); + } + + let mut bytes = [0u8; 8]; + for (i, chunk) in s.as_bytes().chunks_exact(2).enumerate() { + let hex = core::str::from_utf8(chunk).map_err(|_| "invalid UTF-8".to_string())?; + + bytes[i] = + u8::from_str_radix(hex, 16).map_err(|_| format!("invalid hex byte: {}", hex))?; + } + + Ok(InterfaceId(bytes)) + } +} + impl Encode for InterfaceId { fn encode_to(&self, dest: &mut O) { dest.write(self.as_bytes()); diff --git a/rs/idl-meta/templates/program.askama b/rs/idl-meta/templates/program.askama index 384acb404..a8bba1751 100644 --- a/rs/idl-meta/templates/program.askama +++ b/rs/idl-meta/templates/program.askama @@ -35,7 +35,7 @@ program {{ name }} { {%- for (k, v) in s.annotations %} @{{ k }} {%- if v.is_some() -%}: {{ v.as_ref().unwrap() }}{% endif %} {%- endfor %} - {% if s.route.is_some() %}{{ s.route.as_ref().unwrap() }}: {% endif %}{{ s.name }}, + {{ s.name }}{% if s.route.is_some() %}: {{ s.route.as_ref().unwrap() }}{% endif %}, {%- endfor %} } diff --git a/rs/idl-meta/tests/fixture/mod.rs b/rs/idl-meta/tests/fixture/mod.rs index d30e92cee..763eb57cb 100644 --- a/rs/idl-meta/tests/fixture/mod.rs +++ b/rs/idl-meta/tests/fixture/mod.rs @@ -124,8 +124,11 @@ pub fn enum_variants_type() -> Type { pub fn counter_service() -> ServiceUnit { ServiceUnit { - name: "Counter".to_string(), - extends: vec!["BaseService".to_string(), "AnotherBaseService".to_string()], + name: "Counter".parse().unwrap(), + extends: vec![ + "BaseService".parse().unwrap(), + "AnotherBaseService".parse().unwrap(), + ], funcs: vec![ ServiceFunc { name: "Add".to_string(), @@ -232,7 +235,7 @@ pub fn service_func() -> ServiceFunc { pub fn this_that_service() -> ServiceUnit { ServiceUnit { - name: "ThisThat".to_string(), + name: "ThisThat".parse().unwrap(), extends: vec![], funcs: vec![service_func()], events: vec![], @@ -409,19 +412,19 @@ pub fn program_unit() -> ProgramUnit { ], services: vec![ ServiceExpo { - name: "Ping".to_string(), + name: "Ping".parse().unwrap(), route: None, docs: vec![], annotations: vec![], }, ServiceExpo { - name: "Counter".to_string(), + name: "Counter".parse().unwrap(), route: None, docs: vec![], annotations: vec![], }, ServiceExpo { - name: "Counter".to_string(), + name: "Counter".parse().unwrap(), route: Some("Counter2".to_string()), docs: vec!["Another Counter service".to_owned()], annotations: vec![], diff --git a/rs/idl-parser-v2/src/ffi/ast/mod.rs b/rs/idl-parser-v2/src/ffi/ast/mod.rs index 07ab22ebf..3f107d82c 100644 --- a/rs/idl-parser-v2/src/ffi/ast/mod.rs +++ b/rs/idl-parser-v2/src/ffi/ast/mod.rs @@ -1,3 +1,4 @@ +use crate::alloc::string::ToString; use crate::ast; use alloc::{boxed::Box, ffi::CString, format, string::String, vec::Vec}; use core::ffi::{CStr, c_char}; @@ -321,9 +322,9 @@ pub struct ServiceUnit { impl ServiceUnit { pub fn from_ast(service_unit: &ast::ServiceUnit, allocations: &mut Allocations) -> Self { - let name_ffi = allocate_string(&service_unit.name, allocations); - let (extends_ptr, extends_len) = - allocate_ffi_string_vec(&service_unit.extends, allocations); + let name_ffi = allocate_string(&service_unit.name.to_string(), allocations); + let extends: Vec<_> = service_unit.extends.iter().map(|s| s.to_string()).collect(); + let (extends_ptr, extends_len) = allocate_ffi_string_vec(&extends, allocations); let (docs_ptr, docs_len) = allocate_ffi_string_vec(&service_unit.docs, allocations); let (annotations_ptr, annotations_len) = allocate_annotation_vec(&service_unit.annotations, allocations); @@ -623,7 +624,7 @@ pub struct ServiceExpo { impl ServiceExpo { pub fn from_ast(service_expo: &ast::ServiceExpo, allocations: &mut Allocations) -> Self { - let name_ffi = allocate_string(&service_expo.name, allocations); + let name_ffi = allocate_string(&service_expo.name.to_string(), allocations); let (route_ptr, route_len) = if let Some(route) = &service_expo.route { let route_ffi = allocate_string(route, allocations); (route_ffi.ptr, route_ffi.len) diff --git a/rs/idl-parser-v2/src/idl.pest b/rs/idl-parser-v2/src/idl.pest index 605e763c3..f1e952f2a 100644 --- a/rs/idl-parser-v2/src/idl.pest +++ b/rs/idl-parser-v2/src/idl.pest @@ -2,9 +2,10 @@ WHITESPACE = _{ WHITE_SPACE | NEWLINE } COMMENT = _{ "//" ~ !"/" ~ (!NEWLINE ~ ANY)* } // ---------- Lexemes ---------- -Ident = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* } -Number = @{ ASCII_DIGIT+ } -StrToEol = @{ (!NEWLINE ~ ANY)* } +Ident = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* } +Number = @{ ASCII_DIGIT+ } +StrToEol = @{ (!NEWLINE ~ ANY)* } +ServiceIdent = @{ Ident ~ ("@0x" ~ HEX_DIGIT+)? } // ---------- Docs & Annotations ---------- GlobalAnn = { WHITESPACE* ~ "!@" ~ Ident ~ (":" ~ StrToEol)? } @@ -45,9 +46,9 @@ Variant = { DocsAndAnnotations? ~ Ident ~ (StructDef | TupleDef)? } AliasDecl = { "alias" ~ Ident ~ TypeParams? ~ "=" ~ TypeDecl ~ ";" } // ---------- Service ---------- -ServiceDecl = { DocsAndAnnotations* ~ "service" ~ Ident ~ "{" ~ (ExtendsBlock | EventsBlock | FunctionsBlock | TypesBlock)* ~ "}" } +ServiceDecl = { DocsAndAnnotations* ~ "service" ~ ServiceIdent ~ "{" ~ (ExtendsBlock | EventsBlock | FunctionsBlock | TypesBlock)* ~ "}" } -ExtendsBlock = { "extends" ~ "{" ~ Ident ~ ("," ~ Ident)* ~ ","? ~ "}" } +ExtendsBlock = { "extends" ~ "{" ~ ServiceIdent ~ ("," ~ ServiceIdent)* ~ ","? ~ "}" } EventsBlock = { "events" ~ "{" ~ Variant ~ ("," ~ Variant)* ~ ","? ~ "}" } FunctionsBlock = { "functions" ~ "{" ~ FuncDecl* ~ "}" } TypesBlock = { "types" ~ "{" ~ (StructDecl | EnumDecl | AliasDecl)* ~ "}" } @@ -64,7 +65,7 @@ ProgramDecl = { DocsAndAnnotations? ~ "program" ~ Ident ~ "{" ~ (Construct ConstructorsBlock = { "constructors" ~ "{" ~ CtorDecl* ~ "}" } CtorDecl = { DocsAndAnnotations? ~ Ident ~ "(" ~ Params? ~ ")" ~ ";"? } ServicesBlock = { "services" ~ "{" ~ (ServiceExpo ~ ","?)* ~ "}" } -ServiceExpo = { DocsAndAnnotations? ~ Ident ~ (":" ~ Ident)? } +ServiceExpo = { DocsAndAnnotations? ~ ServiceIdent ~ (":" ~ Ident)? } // ---------- Top level ---------- Top = { SOI ~ (GlobalAnn | ServiceDecl | ProgramDecl)* ~ EOI } diff --git a/rs/idl-parser-v2/src/lib.rs b/rs/idl-parser-v2/src/lib.rs index 862e4516f..a0f50c016 100644 --- a/rs/idl-parser-v2/src/lib.rs +++ b/rs/idl-parser-v2/src/lib.rs @@ -81,6 +81,16 @@ fn parse_ident(p: Pair) -> Result { Err(Error::Rule("expected Ident".to_string())) } +fn parse_service_ident(p: Pair) -> Result { + if p.as_rule() == Rule::ServiceIdent { + p.as_str() + .parse::() + .map_err(|e| Error::Parse(e.to_string())) + } else { + Err(Error::Rule("expected ServiceIdent".to_string())) + } +} + fn parse_annotation(p: Pair) -> Result { let mut key = None; let mut val = None; @@ -352,7 +362,7 @@ fn parse_func(p: Pair) -> Result { fn parse_service(p: Pair) -> Result { let mut it = p.into_inner(); let (docs, annotations) = parse_docs_and_annotations(&mut it)?; - let name = expect_rule(&mut it, Rule::Ident)?.as_str().to_string(); + let name = expect_next(&mut it, parse_service_ident)?; let mut extends = Vec::new(); let mut events = Vec::new(); @@ -361,8 +371,9 @@ fn parse_service(p: Pair) -> Result { for item in it { match item.as_rule() { Rule::ExtendsBlock => { - for i in item.into_inner().filter(|x| x.as_rule() == Rule::Ident) { - extends.push(i.as_str().to_string()); + for i in item.into_inner() { + let ident = parse_service_ident(i)?; + extends.push(ident); } } Rule::EventsBlock => { @@ -436,14 +447,14 @@ fn parse_program(p: Pair) -> Result { { let mut sit = s.into_inner(); let (docs, annotations) = parse_docs_and_annotations(&mut sit)?; - let mut name = expect_next(&mut sit, parse_ident)?; - let mut route = None; - if let Some(p) = sit.next() + let name = expect_next(&mut sit, parse_service_ident)?; + let route = if let Some(p) = sit.next() && p.as_rule() == Rule::Ident { - route = Some(name); - name = p.as_str().to_string(); - } + Some(p.as_str().to_string()) + } else { + None + }; services.push(ServiceExpo { name, route, @@ -656,7 +667,7 @@ mod tests { "#; let mut pairs = IdlParser::parse(Rule::ServiceDecl, SRC).expect("parse idl"); let svc = expect_next(&mut pairs, parse_service).expect("parse"); - assert_eq!(svc.name, "X"); + assert_eq!(svc.name.to_string(), "X"); assert!(svc.funcs.iter().any(|f| f.name == "Ping")); } From 433b71cf6fdeda701df2927b09b575ec3fb5e8ef Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 22 Dec 2025 14:25:49 +0100 Subject: [PATCH 2/4] wip: client-gen, route-idx --- Cargo.lock | 1 + benchmarks/ping-pong/client/ping_pong.idl | 4 +-- examples/demo/client/demo_client.idl | 36 +++++++++---------- examples/redirect/client/redirect_client.idl | 4 +-- .../proxy-client/redirect_proxy_client.idl | 4 +-- rs/client-gen/Cargo.toml | 1 + rs/client-gen/src/mock_generator.rs | 17 ++++++--- rs/client-gen/src/root_generator.rs | 24 ++++++++----- rs/client-gen/src/service_generators.rs | 13 ++++--- rs/idl-gen/src/builder.rs | 4 ++- rs/idl-meta/src/ast.rs | 1 + rs/idl-meta/src/lib.rs | 2 +- rs/idl-meta/tests/fixture/mod.rs | 3 ++ .../snapshots/templates__idl_program.snap | 2 +- rs/idl-parser-v2/src/lib.rs | 9 ++++- 15 files changed, 79 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c7e46e4c..5e512c3ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7208,6 +7208,7 @@ dependencies = [ "genco", "insta", "parity-scale-codec", + "sails-idl-meta", "sails-idl-parser-v2", ] diff --git a/benchmarks/ping-pong/client/ping_pong.idl b/benchmarks/ping-pong/client/ping_pong.idl index da5eb8d78..a51dcce91 100644 --- a/benchmarks/ping-pong/client/ping_pong.idl +++ b/benchmarks/ping-pong/client/ping_pong.idl @@ -1,7 +1,7 @@ !@sails: 0.9.2 -service PingPongService { +service PingPongService@0x229cc617d36f5be2 { functions { Ping(payload: PingPongPayload) -> PingPongPayload; } @@ -20,6 +20,6 @@ program PingPong { NewForBench(); } services { - PingPongService, + PingPongService@0x229cc617d36f5be2, } } diff --git a/examples/demo/client/demo_client.idl b/examples/demo/client/demo_client.idl index fdd7e0923..5e72e51a1 100644 --- a/examples/demo/client/demo_client.idl +++ b/examples/demo/client/demo_client.idl @@ -1,13 +1,13 @@ !@sails: 0.9.2 -service PingPong { +service PingPong@0x21bd9a9aa51da264 { functions { Ping(input: String) -> String throws String; } } -service Counter { +service Counter@0x579d6daba41b7d82 { events { /// Emitted when a new value is added to the counter Added(u32), @@ -25,7 +25,7 @@ service Counter { } } -service WalkerService { +service WalkerService@0xee1536b55170bf0a { events { Walked { from: (i32, i32), @@ -39,7 +39,7 @@ service WalkerService { } } -service MammalService { +service MammalService@0xff6b93e1961026fe { functions { MakeSound() -> String; @query @@ -47,10 +47,10 @@ service MammalService { } } -service Dog { +service Dog@0x18666e67a21917a1 { extends { - WalkerService, - MammalService, + WalkerService@0xee1536b55170bf0a, + MammalService@0xff6b93e1961026fe, } events { Barked, @@ -60,7 +60,7 @@ service Dog { } } -service References { +service References@0xb7a968809a79420a { functions { Add(v: u32) -> u32; AddByte(byte: u8) -> [u8]; @@ -79,7 +79,7 @@ service References { } } -service ThisThat { +service ThisThat@0x18ff5d765e12ddad { functions { DoThat(param: DoThatParam) -> (ActorId, NonZeroU32, ManyVariantsReply) throws (String); DoThis(p1: u32, p2: String, p3: (Option, NonZeroU8), p4: TupleStruct) -> (String, u32); @@ -120,7 +120,7 @@ service ThisThat { } } -service ValueFee { +service ValueFee@0x41c1080b4e1e8dc5 { events { Withheld(u128), } @@ -131,7 +131,7 @@ service ValueFee { } } -service Chaos { +service Chaos@0xf0c8c80dfabf72d5 { functions { @query PanicAfterWait(); @@ -150,12 +150,12 @@ program DemoClient { New(counter: Option, dog_position: Option<(i32, i32)>); } services { - PingPong, - Counter, - Dog, - References, - ThisThat, - ValueFee, - Chaos, + PingPong@0x21bd9a9aa51da264, + Counter@0x579d6daba41b7d82, + Dog@0x18666e67a21917a1, + References@0xb7a968809a79420a, + ThisThat@0x18ff5d765e12ddad, + ValueFee@0x41c1080b4e1e8dc5, + Chaos@0xf0c8c80dfabf72d5, } } diff --git a/examples/redirect/client/redirect_client.idl b/examples/redirect/client/redirect_client.idl index 475104d88..c3073074d 100644 --- a/examples/redirect/client/redirect_client.idl +++ b/examples/redirect/client/redirect_client.idl @@ -1,7 +1,7 @@ !@sails: 0.9.2 -service Redirect { +service Redirect@0x731817d29b2c518b { functions { /// Exit from program with inheritor ID Exit(inheritor_id: ActorId); @@ -16,6 +16,6 @@ program RedirectClient { New(); } services { - Redirect, + Redirect@0x731817d29b2c518b, } } diff --git a/examples/redirect/proxy-client/redirect_proxy_client.idl b/examples/redirect/proxy-client/redirect_proxy_client.idl index 4b2634273..5934f5de5 100644 --- a/examples/redirect/proxy-client/redirect_proxy_client.idl +++ b/examples/redirect/proxy-client/redirect_proxy_client.idl @@ -1,7 +1,7 @@ !@sails: 0.9.2 -service Proxy { +service Proxy@0xda3d188945af8090 { functions { /// Get program ID of the target program via client @query @@ -15,6 +15,6 @@ program RedirectProxyClient { New(target: ActorId); } services { - Proxy, + Proxy@0xda3d188945af8090, } } diff --git a/rs/client-gen/Cargo.toml b/rs/client-gen/Cargo.toml index 218e12c3f..71fd39dc0 100644 --- a/rs/client-gen/Cargo.toml +++ b/rs/client-gen/Cargo.toml @@ -14,6 +14,7 @@ anyhow.workspace = true convert_case.workspace = true genco.workspace = true parity-scale-codec.workspace = true +sails-idl-meta = { workspace = true, features = ["ast"] } sails-idl-parser-v2 = { workspace = true, features = ["std"] } [dev-dependencies] diff --git a/rs/client-gen/src/mock_generator.rs b/rs/client-gen/src/mock_generator.rs index 367336c3b..37fcbc05a 100644 --- a/rs/client-gen/src/mock_generator.rs +++ b/rs/client-gen/src/mock_generator.rs @@ -2,7 +2,10 @@ use crate::helpers::fn_args_with_types_path; use convert_case::{Case, Casing}; use genco::prelude::*; use rust::Tokens; -use sails_idl_parser_v2::{ast, visitor, visitor::Visitor}; +use sails_idl_parser_v2::{ + ast::{self, ServiceIdent}, + visitor::{self, Visitor}, +}; pub(crate) struct MockGenerator<'ast> { service_name: &'ast str, @@ -40,10 +43,14 @@ impl<'ast> Visitor<'ast> for MockGenerator<'ast> { fn visit_service_unit(&mut self, service: &'ast ast::ServiceUnit) { visitor::accept_service_unit(service, self); - for extended_service_name in &service.extends { - let method_name = extended_service_name.to_case(Case::Snake); - let impl_name = extended_service_name.to_case(Case::Pascal); - let mod_name = extended_service_name.to_case(Case::Snake); + for ServiceIdent { + name, + interface_id: _, + } in &service.extends + { + let method_name = name.to_case(Case::Snake); + let impl_name = name.to_case(Case::Pascal); + let mod_name = name.to_case(Case::Snake); quote_in! { self.tokens => fn $(&method_name) (&self, ) -> $(self.sails_path)::client::Service; diff --git a/rs/client-gen/src/root_generator.rs b/rs/client-gen/src/root_generator.rs index d7987b7af..8a8dedec9 100644 --- a/rs/client-gen/src/root_generator.rs +++ b/rs/client-gen/src/root_generator.rs @@ -110,7 +110,10 @@ impl<'ast> Visitor<'ast> for RootGenerator<'ast> { self.tokens.extend(ctor_gen.finalize()); for service_item in &program.services { - let service_name = service_item.route.as_ref().unwrap_or(&service_item.name); + let service_name = service_item + .route + .as_ref() + .unwrap_or(&service_item.name.name); self.program_exported_services.push(service_name); } @@ -119,7 +122,7 @@ impl<'ast> Visitor<'ast> for RootGenerator<'ast> { fn visit_service_unit(&mut self, service: &'ast ast::ServiceUnit) { let mut client_gen = ServiceGenerator::new( - &service.name, + &service.name.name, self.sails_path, &self.external_types, self.mocks_feature_name, @@ -141,20 +144,23 @@ impl<'ast> Visitor<'ast> for RootGenerator<'ast> { } fn visit_service_expo(&mut self, service_item: &'ast ast::ServiceExpo) { - let service_name = service_item.route.as_ref().unwrap_or(&service_item.name); - let method_name = service_item.name.to_case(Case::Snake); - let route_pascal_case = service_name.to_case(Case::Pascal); - let route_snake_case = service_name.to_case(Case::Snake); + let service_route = service_item + .route + .as_ref() + .unwrap_or(&service_item.name.name); + let method_name = service_route.to_case(Case::Snake); + let name_pascal_case = service_item.name.name.to_case(Case::Pascal); + let name_snake_case = service_item.name.name.to_case(Case::Snake); generate_doc_comments(&mut self.service_trait_tokens, &service_item.docs); quote_in!(self.service_trait_tokens => - $['\r'] fn $(&method_name)(&self) -> $(self.sails_path)::client::Service<$(route_snake_case.clone())::$(route_pascal_case.clone())Impl, Self::Env>; + $['\r'] fn $(&method_name)(&self) -> $(self.sails_path)::client::Service<$(&name_snake_case)::$(&name_pascal_case)Impl, Self::Env>; ); quote_in!(self.service_impl_tokens => - $['\r'] fn $(&method_name)(&self) -> $(self.sails_path)::client::Service<$(route_snake_case)::$(route_pascal_case)Impl, Self::Env> { - self.service(stringify!($(service_item.name.clone()))) + $['\r'] fn $(&method_name)(&self) -> $(self.sails_path)::client::Service<$(&name_snake_case)::$(&name_pascal_case)Impl, Self::Env> { + self.service(stringify!($(&name_pascal_case))) } ); } diff --git a/rs/client-gen/src/service_generators.rs b/rs/client-gen/src/service_generators.rs index 32dd67c10..2f5131de6 100644 --- a/rs/client-gen/src/service_generators.rs +++ b/rs/client-gen/src/service_generators.rs @@ -5,6 +5,7 @@ use crate::type_generators::{TopLevelTypeGenerator, generate_type_decl_with_path use convert_case::{Case, Casing}; use genco::prelude::*; use rust::Tokens; +use sails_idl_meta::ServiceIdent; use sails_idl_parser_v2::{ast, visitor, visitor::Visitor}; use std::collections::HashMap; @@ -100,10 +101,14 @@ impl<'ast> Visitor<'ast> for ServiceGenerator<'ast> { fn visit_service_unit(&mut self, service: &'ast ast::ServiceUnit) { visitor::accept_service_unit(service, self); - for extended_service_name in &service.extends { - let method_name = extended_service_name.to_case(Case::Snake); - let impl_name = extended_service_name.to_case(Case::Pascal); - let mod_name = extended_service_name.to_case(Case::Snake); + for ServiceIdent { + name, + interface_id: _, + } in &service.extends + { + let method_name = name.to_case(Case::Snake); + let impl_name = name.to_case(Case::Pascal); + let mod_name = name.to_case(Case::Snake); quote_in! { self.trait_tokens => $['\r'] fn $(&method_name)(&self) -> $(self.sails_path)::client::Service; diff --git a/rs/idl-gen/src/builder.rs b/rs/idl-gen/src/builder.rs index 99e3f3122..063f948c3 100644 --- a/rs/idl-gen/src/builder.rs +++ b/rs/idl-gen/src/builder.rs @@ -15,7 +15,8 @@ impl ProgramBuilder { let ctors = P::constructors(); let ctors_type_id = registry.register_type(&ctors).id; let services_expo = P::services() - .map(|(service_name, meta)| { + .enumerate() + .map(|(idx, (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); @@ -32,6 +33,7 @@ impl ProgramBuilder { interface_id: Some(interface_id), }, route, + route_idx: (idx as u8) + 1, docs: vec![], annotations: vec![], } diff --git a/rs/idl-meta/src/ast.rs b/rs/idl-meta/src/ast.rs index f4662d00d..60168d05a 100644 --- a/rs/idl-meta/src/ast.rs +++ b/rs/idl-meta/src/ast.rs @@ -103,6 +103,7 @@ impl FromStr for ServiceIdent { pub struct ServiceExpo { pub name: ServiceIdent, pub route: Option, + pub route_idx: u8, 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 47f5f7142..30fbf9ae1 100644 --- a/rs/idl-meta/src/lib.rs +++ b/rs/idl-meta/src/lib.rs @@ -99,7 +99,7 @@ impl core::str::FromStr for InterfaceId { let hex = core::str::from_utf8(chunk).map_err(|_| "invalid UTF-8".to_string())?; bytes[i] = - u8::from_str_radix(hex, 16).map_err(|_| format!("invalid hex byte: {}", hex))?; + u8::from_str_radix(hex, 16).map_err(|_| format!("invalid hex byte: {hex}"))?; } Ok(InterfaceId(bytes)) diff --git a/rs/idl-meta/tests/fixture/mod.rs b/rs/idl-meta/tests/fixture/mod.rs index 763eb57cb..3eed9b6ca 100644 --- a/rs/idl-meta/tests/fixture/mod.rs +++ b/rs/idl-meta/tests/fixture/mod.rs @@ -414,18 +414,21 @@ pub fn program_unit() -> ProgramUnit { ServiceExpo { name: "Ping".parse().unwrap(), route: None, + route_idx: 1, docs: vec![], annotations: vec![], }, ServiceExpo { name: "Counter".parse().unwrap(), route: None, + route_idx: 2, docs: vec![], annotations: vec![], }, ServiceExpo { name: "Counter".parse().unwrap(), route: Some("Counter2".to_string()), + route_idx: 3, docs: vec!["Another Counter service".to_owned()], annotations: vec![], }, diff --git a/rs/idl-meta/tests/snapshots/templates__idl_program.snap b/rs/idl-meta/tests/snapshots/templates__idl_program.snap index e83953aaa..ac0edb503 100644 --- a/rs/idl-meta/tests/snapshots/templates__idl_program.snap +++ b/rs/idl-meta/tests/snapshots/templates__idl_program.snap @@ -19,7 +19,7 @@ program Demo { Ping, Counter, /// Another Counter service - Counter2: Counter, + Counter: Counter2, } types { struct DoThatParam { diff --git a/rs/idl-parser-v2/src/lib.rs b/rs/idl-parser-v2/src/lib.rs index a0f50c016..b2cd75bc4 100644 --- a/rs/idl-parser-v2/src/lib.rs +++ b/rs/idl-parser-v2/src/lib.rs @@ -441,10 +441,16 @@ fn parse_program(p: Pair) -> Result { } } Rule::ServicesBlock => { - for s in item + for (idx, s) in item .into_inner() .filter(|x| x.as_rule() == Rule::ServiceExpo) + .enumerate() { + if idx > u8::MAX.into() { + return Err(Error::Validation( + "Too many services in program. Max: 255".to_string(), + )); + } let mut sit = s.into_inner(); let (docs, annotations) = parse_docs_and_annotations(&mut sit)?; let name = expect_next(&mut sit, parse_service_ident)?; @@ -458,6 +464,7 @@ fn parse_program(p: Pair) -> Result { services.push(ServiceExpo { name, route, + route_idx: (idx as u8) + 1, docs, annotations, }); From ac06dcd09c2f25117496f2a4bb02a7fd06f5b574 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 22 Dec 2025 18:45:53 +0100 Subject: [PATCH 3/4] feat: idl-gen interface_id checks, dedup --- ...nsta__ethapp_with_events_generate_idl.snap | 8 +- rs/idl-gen/src/builder.rs | 493 ++++++++++-------- rs/idl-gen/src/errors.rs | 8 +- rs/idl-gen/src/lib.rs | 7 +- rs/idl-gen/tests/generator.rs | 32 +- ...r__program_idl_works_with_empty_ctors.snap | 4 +- ...gram_idl_works_with_multiple_services.snap | 90 +--- ...rogram_idl_works_with_non_empty_ctors.snap | 4 +- ..._service_idl_works_with_base_services.snap | 6 +- ...erator__service_idl_works_with_basics.snap | 2 +- rs/idl-meta/src/lib.rs | 10 +- 11 files changed, 322 insertions(+), 342 deletions(-) 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 7c02a8bd6..dfee38404 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 @@ -4,7 +4,7 @@ expression: idl --- !@sails: 0.9.2 -service Svc1 { +service Svc1@0x349b842b767fb2ab { events { DoThisEvent { /// Some u32 value @@ -20,7 +20,7 @@ service Svc1 { } } -service Svc2 { +service Svc2@0xc7f3ccbc102da928 { functions { DoThis(p1: u32, p2: String) -> u32; } @@ -31,7 +31,7 @@ program MyProgram { Create(); } services { - Svc1, - Svc2, + Svc1@0x349b842b767fb2ab, + Svc2@0xc7f3ccbc102da928, } } diff --git a/rs/idl-gen/src/builder.rs b/rs/idl-gen/src/builder.rs index 063f948c3..2f5924921 100644 --- a/rs/idl-gen/src/builder.rs +++ b/rs/idl-gen/src/builder.rs @@ -5,45 +5,21 @@ use scale_info::*; pub struct ProgramBuilder { registry: PortableRegistry, ctors_type_id: u32, - services_expo: Vec, + service_expos: Vec<(&'static str, AnyServiceMeta)>, } 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() - .enumerate() - .map(|(idx, (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) - }; - let interface_id = meta.interface_id(); - ServiceExpo { - name: ServiceIdent { - name, - interface_id: Some(interface_id), - }, - route, - route_idx: (idx as u8) + 1, - docs: vec![], - annotations: vec![], - } - }) - .collect::>(); + let service_expos = P::services().collect(); + let registry = PortableRegistry::from(registry); Self { registry, ctors_type_id, - services_expo, + service_expos, } } @@ -51,7 +27,7 @@ impl ProgramBuilder { any_funcs(&self.registry, self.ctors_type_id)? .map(|c| { if c.fields.len() != 1 { - Err(Error::FuncMetaIsInvalid(format!( + Err(Error::MetaIsInvalid(format!( "ctor `{}` has invalid number of fields", c.name ))) @@ -67,7 +43,7 @@ impl ProgramBuilder { .iter() .map(|f| -> Result<_> { let name = f.name.as_ref().ok_or_else(|| { - Error::FuncMetaIsInvalid(format!( + Error::MetaIsInvalid(format!( "ctor `{}` param is missing a name", c.name )) @@ -89,7 +65,7 @@ impl ProgramBuilder { annotations: vec![], }) } else { - Err(Error::FuncMetaIsInvalid(format!( + Err(Error::MetaIsInvalid(format!( "ctor `{}` params type is not a composite", c.name ))) @@ -99,19 +75,54 @@ impl ProgramBuilder { .collect() } - pub fn build(self, name: String) -> Result { + pub fn build(self, name: String, services: &[ServiceUnit]) -> 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(); + if self.service_expos.len() > u8::MAX as usize { + return Err(Error::MetaIsInvalid( + "Too many services in program. Max: 255".to_string(), + )); + } + + let expos: Result> = self + .service_expos + .into_iter() + .enumerate() + .map(|(idx, (route, meta))| { + let interface_id = meta.interface_id(); + let Some(service) = services + .iter() + .find(|s| s.name.interface_id == Some(interface_id)) + else { + return Err(Error::MetaIsInvalid(format!( + "service `{route}@{interface_id}` not defined" + ))); + }; + let route = if service.name.name == route { + None + } else { + Some(route.to_string()) + }; + + Ok(ServiceExpo { + name: service.name.clone(), + route, + route_idx: (idx as u8) + 1, + docs: vec![], + annotations: vec![], + }) + }) + .collect(); + Ok(ProgramUnit { name, ctors, - services, + services: expos?, types, docs: vec![], annotations: vec![], @@ -129,7 +140,7 @@ fn any_funcs( if let scale_info::TypeDef::Variant(variant) = &funcs.type_def { Ok(variant.variants.iter()) } else { - Err(Error::FuncMetaIsInvalid(format!( + Err(Error::MetaIsInvalid(format!( "func type id {func_type_id} references a type that is not a variant" ))) } @@ -143,7 +154,7 @@ fn any_funcs_ids(registry: &PortableRegistry, func_type_id: u32) -> Result>>() @@ -175,16 +186,41 @@ impl<'a> ServiceBuilder<'a> { } } - pub fn build(self) -> Result> { - let mut services = Vec::new(); + pub fn build(self, services: &mut Vec) -> Result { + let mut visited = BTreeSet::new(); + self.build_inner(services, &mut visited) + } + + fn build_inner( + self, + services: &mut Vec, + visited: &mut BTreeSet, + ) -> core::result::Result { + let interface_id = self.meta.interface_id(); + if let Some(service) = services + .iter() + .find(|s| s.name.interface_id == Some(interface_id)) + { + // if service already built, return ident + return Ok(service.name.clone()); + } + + // cycle detection for base service recursion + let key = u64::from_le_bytes(interface_id.0); + if !visited.insert(key) { + return Err(Error::MetaIsInvalid(format!( + "service `{}` has cyclic base services", + ServiceIdent { + name: self.name.to_string(), + interface_id: Some(interface_id), + } + ))); + } + let mut extends = Vec::new(); for (name, meta) in self.meta.base_services() { - extends.push(ServiceIdent { - name: name.to_string(), - interface_id: Some(meta.interface_id()), - }); - // TODO: dedup base services based on `interface_id` - services.extend(ServiceBuilder::new(name, meta).build()?); + let ident = ServiceBuilder::new(name, &meta).build_inner(services, visited)?; + extends.push(ident); } let exclude = BTreeSet::from_iter(self.exclude_type_ids()?); @@ -194,11 +230,12 @@ impl<'a> ServiceBuilder<'a> { let events = self.events(&resolver)?; let types = resolver.into_types(); + let ident = ServiceIdent { + name: self.name.to_string(), + interface_id: Some(interface_id), + }; services.push(ServiceUnit { - name: ServiceIdent { - name: self.name.to_string(), - interface_id: Some(self.meta.interface_id()), - }, + name: ident.clone(), extends, funcs: [commands, queries].concat(), events, @@ -206,7 +243,8 @@ impl<'a> ServiceBuilder<'a> { docs: vec![], annotations: vec![], }); - Ok(services) + visited.remove(&key); + Ok(ident) } fn exclude_type_ids(&self) -> Result> { @@ -225,7 +263,7 @@ impl<'a> ServiceBuilder<'a> { any_funcs(&self.registry, self.commands_type_id)? .map(|c| { if c.fields.len() != 2 { - Err(Error::FuncMetaIsInvalid(format!( + Err(Error::MetaIsInvalid(format!( "command `{}` has invalid number of fields", c.name ))) @@ -252,7 +290,7 @@ impl<'a> ServiceBuilder<'a> { .iter() .map(|f| -> Result<_> { let name = f.name.as_ref().ok_or_else(|| { - Error::FuncMetaIsInvalid(format!( + Error::MetaIsInvalid(format!( "command `{}` param is missing a name", c.name )) @@ -277,7 +315,7 @@ impl<'a> ServiceBuilder<'a> { annotations: vec![], }) } else { - Err(Error::FuncMetaIsInvalid(format!( + Err(Error::MetaIsInvalid(format!( "command `{}` params type is not a composite", c.name ))) @@ -291,7 +329,7 @@ impl<'a> ServiceBuilder<'a> { any_funcs(&self.registry, self.queries_type_id)? .map(|c| { if c.fields.len() != 2 { - Err(Error::FuncMetaIsInvalid(format!( + Err(Error::MetaIsInvalid(format!( "query `{}` has invalid number of fields", c.name ))) @@ -318,7 +356,7 @@ impl<'a> ServiceBuilder<'a> { .iter() .map(|f| -> Result<_> { let name = f.name.as_ref().ok_or_else(|| { - Error::FuncMetaIsInvalid(format!( + Error::MetaIsInvalid(format!( "query `{}` param is missing a name", c.name )) @@ -344,7 +382,7 @@ impl<'a> ServiceBuilder<'a> { annotations: vec![("query".to_string(), None)], }) } else { - Err(Error::FuncMetaIsInvalid(format!( + Err(Error::MetaIsInvalid(format!( "query `{}` params type is not a composite", c.name ))) @@ -435,12 +473,15 @@ mod tests { const ASYNC: bool = false; } - ProgramBuilder::new::>().build("TestProgram".to_string()) + let services = Vec::new(); + ProgramBuilder::new::>().build("TestProgram".to_string(), &services) } fn test_service_units(service_name: &'static str) -> Result> { let meta = AnyServiceMeta::new::(); - ServiceBuilder::new(service_name, &meta).build() + let mut services = Vec::new(); + ServiceBuilder::new(service_name, &meta).build(&mut services)?; + Ok(services) } // ------------------------------------------------------------------------------------ @@ -490,8 +531,8 @@ mod tests { let result = test_program_unit::(); assert!(result.is_err()); - let Err(Error::FuncMetaIsInvalid(msg)) = result else { - panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + let Err(Error::MetaIsInvalid(msg)) = result else { + panic!("Expected MetaIsInvalid error, got {result:?}"); }; assert_eq!(msg.as_str(), expected_error_msg); } @@ -587,7 +628,7 @@ mod tests { type EventsMeta = utils::NoEvents; const BASE_SERVICES: &'static [(&'static str, sails_idl_meta::AnyServiceMetaFn)] = &[]; const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::zero(); + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(1); } struct TestProgram; @@ -601,8 +642,11 @@ mod tests { const ASYNC: bool = false; } + let services = + test_service_units::("TestService1").expect("ServiceBuilder error"); + let meta = ProgramBuilder::new::() - .build("TestProgram".to_string()) + .build("TestProgram".to_string(), &services) .expect("ProgramBuilder error"); assert_eq!(meta.services.len(), 3); @@ -611,9 +655,10 @@ mod tests { ServiceExpo { name: ServiceIdent { name: "TestService1".to_string(), - interface_id: Some(InterfaceId::zero()) + interface_id: Some(InterfaceId::from_u64(1)) }, route: None, + route_idx: 1, docs: vec![], annotations: vec![] } @@ -623,10 +668,10 @@ mod tests { ServiceExpo { name: ServiceIdent { name: "TestService1".to_string(), - interface_id: Some(InterfaceId::zero()) + interface_id: Some(InterfaceId::from_u64(1)) }, - route: Some("TestService2".to_string()), + route_idx: 2, docs: vec![], annotations: vec![] } @@ -636,9 +681,10 @@ mod tests { ServiceExpo { name: ServiceIdent { name: "TestService1".to_string(), - interface_id: Some(InterfaceId::zero()) + interface_id: Some(InterfaceId::from_u64(1)) }, route: Some("TestService3".to_string()), + route_idx: 3, docs: vec![], annotations: vec![] } @@ -1145,103 +1191,133 @@ mod tests { ); } - // #[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); - // } + #[test] + 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 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 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; - // } + 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); - // } + 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); - // } + #[test] + 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 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 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; - // } + 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); - // } + let doc = build_program_ast::(None).unwrap(); + assert_eq!(doc.services.len(), 3); + } + + #[test] + fn base_services_cycle_detection() { + 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)] = + &[("ServiceB", AnyServiceMeta::new::)]; + 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::)]; + const ASYNC: bool = false; + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(71u64); + } + + let res = test_service_units::("ServiceA"); + assert!(res.is_err()); + let Err(Error::MetaIsInvalid(msg)) = res else { + panic!("Expected MetaIsInvalid error, got {res:?}"); + }; + assert!(msg.contains("cyclic base services")); + } // #[test] // #[ignore = "TODO [future]: Must be error when Sails binary protocol is implemented"] @@ -1312,8 +1388,8 @@ mod tests { let res = test_service_units::("InvalidEventsService"); assert!(res.is_err()); - let Err(Error::FuncMetaIsInvalid(msg)) = res else { - panic!("Expected FuncMetaIsInvalid error, got {res:?}"); + let Err(Error::MetaIsInvalid(msg)) = res else { + panic!("Expected MetaIsInvalid error, got {res:?}"); }; assert!(msg.contains("references a type that is not a variant")); } @@ -1459,8 +1535,8 @@ mod tests { let internal_check = |result: Result>| { assert!(result.is_err()); - let Err(Error::FuncMetaIsInvalid(msg)) = result else { - panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + let Err(Error::MetaIsInvalid(msg)) = result else { + panic!("Expected MetaIsInvalid error, got {result:?}"); }; assert!(msg.contains("references a type that is not a variant")); }; @@ -1543,8 +1619,8 @@ mod tests { 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:?}"); + let Err(Error::MetaIsInvalid(msg)) = result else { + panic!("Expected MetaIsInvalid error, got {result:?}"); }; assert_eq!(msg.as_str(), expected_msg); }; @@ -1590,8 +1666,8 @@ mod tests { let result = test_service_units::("TestService"); assert!(result.is_err()); - let Err(Error::FuncMetaIsInvalid(msg)) = result else { - panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + let Err(Error::MetaIsInvalid(msg)) = result else { + panic!("Expected MetaIsInvalid error, got {result:?}"); }; assert_eq!( msg.as_str(), @@ -1626,8 +1702,8 @@ mod tests { let result = test_service_units::("TestService"); assert!(result.is_err()); - let Err(Error::FuncMetaIsInvalid(msg)) = result else { - panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + let Err(Error::MetaIsInvalid(msg)) = result else { + panic!("Expected MetaIsInvalid error, got {result:?}"); }; assert_eq!(msg.as_str(), "command `BadCmd` param is missing a name"); } @@ -2217,54 +2293,53 @@ mod tests { 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); - // } + #[test] + 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 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 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; - // } + 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); - // } + 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 3c4056005..e1d95c0af 100644 --- a/rs/idl-gen/src/errors.rs +++ b/rs/idl-gen/src/errors.rs @@ -4,12 +4,8 @@ pub type Result = core::result::Result; #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("funcion meta is invalid: {0}")] - FuncMetaIsInvalid(String), - #[error("event meta is invalid: {0}")] - EventMetaIsInvalid(String), - #[error("event meta is ambiguous: {0}")] - EventMetaIsAmbiguous(String), + #[error("meta is invalid: {0}")] + MetaIsInvalid(String), #[error("type id `{0}` is not found in the type registry")] TypeIdIsUnknown(u32), #[error("type `{0}` is not supported")] diff --git a/rs/idl-gen/src/lib.rs b/rs/idl-gen/src/lib.rs index 0ea0ba54d..4b84dcf26 100644 --- a/rs/idl-gen/src/lib.rs +++ b/rs/idl-gen/src/lib.rs @@ -93,10 +93,10 @@ pub mod service { 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()?); + builder::ServiceBuilder::new(name, &meta).build(&mut services)?; } let program = if let Some(name) = program_name { - Some(builder::ProgramBuilder::new::

().build(name.to_string())?) + Some(builder::ProgramBuilder::new::

().build(name.to_string(), &services)?) } else { None }; @@ -109,7 +109,8 @@ fn build_program_ast(program_name: Option<&str>) -> Result Result { - let services = builder::ServiceBuilder::new(service_name, &meta).build()?; + let mut services = Vec::new(); + builder::ServiceBuilder::new(service_name, &meta).build(&mut services)?; let doc = IdlDoc { globals: vec![("sails".to_string(), Some(SAILS_VERSION.to_string()))], program: None, diff --git a/rs/idl-gen/tests/generator.rs b/rs/idl-gen/tests/generator.rs index ed200f447..91cb740de 100644 --- a/rs/idl-gen/tests/generator.rs +++ b/rs/idl-gen/tests/generator.rs @@ -1,9 +1,7 @@ use gprimitives::*; use meta_params::*; use sails_idl_gen::{program, service}; -use sails_idl_meta::{ - AnyServiceMeta, AnyServiceMetaFn, InterfaceId, ProgramMeta, ServiceMeta as RtlServiceMeta, -}; +use sails_idl_meta::{AnyServiceMeta, AnyServiceMetaFn, InterfaceId, ProgramMeta, ServiceMeta}; use scale_info::{StaticTypeInfo, TypeInfo}; use std::{collections::BTreeMap, result::Result as StdResult}; @@ -178,32 +176,32 @@ enum AmbiguousBaseEventsMeta { ThatDoneBase { p1: u16 }, } -struct ServiceMeta { +struct GenericService { _commands: std::marker::PhantomData, _queries: std::marker::PhantomData, _events: std::marker::PhantomData, } -impl RtlServiceMeta - for ServiceMeta +impl ServiceMeta + for GenericService { type CommandsMeta = C; type QueriesMeta = Q; type EventsMeta = E; const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId([0u8; 8]); + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(1); } -struct ServiceMetaWithBase { +struct GenericServiceWithBase { _commands: std::marker::PhantomData, _queries: std::marker::PhantomData, _events: std::marker::PhantomData, _base: std::marker::PhantomData, } -impl RtlServiceMeta - for ServiceMetaWithBase +impl ServiceMeta + for GenericServiceWithBase { type CommandsMeta = C; type QueriesMeta = Q; @@ -211,10 +209,10 @@ impl const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[("B", AnyServiceMeta::new::)]; const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId([0u8; 8]); + const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(2); } -type TestServiceMeta = ServiceMeta; +type TestServiceMeta = GenericService; #[allow(dead_code)] #[derive(TypeInfo)] @@ -320,14 +318,10 @@ fn program_idl_works_with_multiple_services() { 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.len(), 1); assert_eq!(generated_idl_program.services[0].name.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.name, "SomeService"); - assert_eq!(generated_idl_program.services[1].funcs.len(), 4); - assert_eq!(generated_idl_program.services[1].types.len(), 8); } #[test] @@ -348,11 +342,11 @@ fn service_idl_works_with_basics() { fn service_idl_works_with_base_services() { let mut idl = String::new(); service::generate_idl::< - ServiceMetaWithBase< + GenericServiceWithBase< CommandsMeta, QueriesMeta, EventsMeta, - ServiceMeta, + GenericService, >, >("ServiceMetaWithBase", &mut idl) .unwrap(); 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 7bbedf5fd..7e13d470e 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 @@ -4,7 +4,7 @@ expression: idl --- !@sails: 0.9.2 -service Service@0x0000000000000000 { +service Service@0x0100000000000000 { events { /// `This` Done ThisDone ( @@ -90,6 +90,6 @@ service Service@0x0000000000000000 { program TestProgramWithEmptyCtorsMeta { services { - Service@0x0000000000000000, + Service@0x0100000000000000, } } 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 6774698c5..d3e753eda 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 @@ -4,91 +4,7 @@ expression: idl --- !@sails: 0.9.2 -service Service@0x0000000000000000 { - 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@0x0000000000000000 { +service Service@0x0100000000000000 { events { /// `This` Done ThisDone ( @@ -174,7 +90,7 @@ service SomeService@0x0000000000000000 { program TestProgramWithMultipleServicesMeta { services { - Service@0x0000000000000000, - Service@0x0000000000000000: SomeService, + Service@0x0100000000000000, + Service@0x0100000000000000: SomeService, } } 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 612abb65e..e87731508 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 @@ -4,7 +4,7 @@ expression: idl --- !@sails: 0.9.2 -service Test@0x0000000000000000 { +service Test@0x0100000000000000 { events { /// `This` Done ThisDone ( @@ -97,6 +97,6 @@ program TestProgramWithNonEmptyCtorsMeta { FromStr(p1: String); } services { - Test@0x0000000000000000, + Test@0x0100000000000000, } } 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 46755f80d..20bfc2196 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 @@ -4,7 +4,7 @@ expression: idl --- !@sails: 0.9.2 -service B@0x0000000000000000 { +service B@0x0100000000000000 { events { ThisDoneBase(u32), ThatDoneBase { @@ -21,9 +21,9 @@ service B@0x0000000000000000 { } } -service ServiceMetaWithBase@0x0000000000000000 { +service ServiceMetaWithBase@0x0200000000000000 { extends { - B@0x0000000000000000, + B@0x0100000000000000, } events { /// `This` Done 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 b85d8586d..4b10fe92a 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 @@ -4,7 +4,7 @@ expression: idl --- !@sails: 0.9.2 -service TestServiceMeta@0x0000000000000000 { +service TestServiceMeta@0x0100000000000000 { events { /// `This` Done ThisDone ( diff --git a/rs/idl-meta/src/lib.rs b/rs/idl-meta/src/lib.rs index 30fbf9ae1..fa1547a42 100644 --- a/rs/idl-meta/src/lib.rs +++ b/rs/idl-meta/src/lib.rs @@ -150,7 +150,7 @@ pub struct AnyServiceMeta { commands: MetaType, queries: MetaType, events: MetaType, - base_services: Vec<(&'static str, AnyServiceMeta)>, + base_services: Vec<(&'static str, AnyServiceMetaFn)>, interface_id: InterfaceId, } @@ -160,7 +160,7 @@ impl AnyServiceMeta { commands: S::commands(), queries: S::queries(), events: S::events(), - base_services: S::base_services().collect(), + base_services: S::BASE_SERVICES.to_vec(), interface_id: S::INTERFACE_ID, } } @@ -177,10 +177,8 @@ impl AnyServiceMeta { &self.events } - pub fn base_services(&self) -> impl Iterator { - self.base_services - .iter() - .map(|&(name, ref meta)| (name, meta)) + pub fn base_services(&self) -> impl Iterator { + self.base_services.iter().map(|&(name, f)| (name, f())) } pub fn interface_id(&self) -> InterfaceId { From de07671aef1a3edf39881bd1d412eafdfa6a0342 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 23 Dec 2025 13:08:41 +0100 Subject: [PATCH 4/4] fix: service idx check --- rs/idl-parser-v2/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rs/idl-parser-v2/src/lib.rs b/rs/idl-parser-v2/src/lib.rs index b2cd75bc4..ea56097f9 100644 --- a/rs/idl-parser-v2/src/lib.rs +++ b/rs/idl-parser-v2/src/lib.rs @@ -441,12 +441,12 @@ fn parse_program(p: Pair) -> Result { } } Rule::ServicesBlock => { - for (idx, s) in item + for s in item .into_inner() .filter(|x| x.as_rule() == Rule::ServiceExpo) - .enumerate() { - if idx > u8::MAX.into() { + let len = services.len(); + if len >= u8::MAX as usize { return Err(Error::Validation( "Too many services in program. Max: 255".to_string(), )); @@ -464,7 +464,7 @@ fn parse_program(p: Pair) -> Result { services.push(ServiceExpo { name, route, - route_idx: (idx as u8) + 1, + route_idx: (len as u8) + 1, docs, annotations, });