diff --git a/Makefile b/Makefile index 9613a32e..bced59d2 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ test: @cargo test --workspace --all-targets clippy: - @cargo clippy --workspace --all-targets -- -D warnings -A unused-imports + @cargo clippy --workspace --all-targets -- -D warnings build-parser: @echo "Building idlparser" diff --git a/idl-gen/src/errors.rs b/idl-gen/src/errors.rs index f7041c53..c84638a0 100644 --- a/idl-gen/src/errors.rs +++ b/idl-gen/src/errors.rs @@ -26,6 +26,8 @@ pub enum Error { FuncMetaIsInvalid(String), #[error("event meta is invalid: {0}")] EventMetaIsInvalid(String), + #[error("event meta is ambiguous: {0}")] + EventMetaIsAmbiguous(String), #[error("type id `{0}` is not found in the type registry")] TypeIdIsUnknown(u32), #[error("type `{0}` is not supported")] diff --git a/idl-gen/src/lib.rs b/idl-gen/src/lib.rs index 1a9158ab..553b9226 100644 --- a/idl-gen/src/lib.rs +++ b/idl-gen/src/lib.rs @@ -18,7 +18,7 @@ //! Functionality for generating IDL files describing some service based on its Rust code. -use errors::Result; +pub use errors::*; use handlebars::{handlebars_helper, Handlebars}; use meta::ExpandedProgramMeta; use scale_info::{form::PortableForm, Field, PortableType, Variant}; diff --git a/idl-gen/src/meta.rs b/idl-gen/src/meta.rs index 6b849fcf..6eea0bba 100644 --- a/idl-gen/src/meta.rs +++ b/idl-gen/src/meta.rs @@ -58,12 +58,21 @@ impl ExpandedProgramMeta { .collect::>(); let ctors_type_id = ctors.map(|ctors| registry.register_type(&ctors).id); let services_data = services - .map(|(sn, sm)| { + .map(|(sname, sm)| { ( - sn, - registry.register_type(sm.commands()).id, - registry.register_type(sm.queries()).id, - registry.register_type(sm.events()).id, + 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::>(); @@ -71,7 +80,9 @@ impl ExpandedProgramMeta { let ctors = Self::ctor_funcs(®istry, ctors_type_id)?; let services = services_data .into_iter() - .map(|(sn, ctid, qtid, etid)| ExpandedServiceMeta::new(®istry, sn, ctid, qtid, etid)) + .map(|(sname, ct_ids, qt_ids, et_ids)| { + ExpandedServiceMeta::new(®istry, sname, ct_ids, qt_ids, et_ids) + }) .collect::>>()?; Ok(Self { registry, @@ -151,16 +162,33 @@ impl ExpandedProgramMeta { .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().map(|s| s.commands_type_id) + self.services + .iter() + .flat_map(|s| s.commands_type_ids.iter().copied()) } fn queries_type_ids(&self) -> impl Iterator + '_ { - self.services.iter().map(|s| s.queries_type_id) + self.services + .iter() + .flat_map(|s| s.queries_type_ids.iter().copied()) } fn events_type_ids(&self) -> impl Iterator + '_ { - self.services.iter().map(|s| s.events_type_id) + self.services + .iter() + .flat_map(|s| s.events_type_ids.iter().copied()) } fn ctor_params_type_ids(&self) -> impl Iterator + '_ { @@ -170,23 +198,25 @@ impl ExpandedProgramMeta { fn command_params_type_ids(&self) -> impl Iterator + '_ { self.services .iter() - .flat_map(|s| s.commands.iter().map(|v| v.1)) + .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().map(|v| v.1)) + .flat_map(|s| s.queries.iter().chain(&s.overriden_queries).map(|v| v.1)) } } pub(crate) struct ExpandedServiceMeta { name: &'static str, - commands_type_id: u32, + commands_type_ids: Vec, commands: Vec, - queries_type_id: u32, + overriden_commands: Vec, + queries_type_ids: Vec, queries: Vec, - events_type_id: u32, + overriden_queries: Vec, + events_type_ids: Vec, events: Vec>, } @@ -194,20 +224,24 @@ impl ExpandedServiceMeta { fn new( registry: &PortableRegistry, name: &'static str, - commands_type_id: u32, - queries_type_id: u32, - events_type_id: u32, + commands_type_ids: Vec, + queries_type_ids: Vec, + events_type_ids: Vec, ) -> Result { - let commands = Self::service_funcs(registry, commands_type_id)?; - let queries = Self::service_funcs(registry, queries_type_id)?; - let events = Self::event_variants(registry, events_type_id)?; + 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_id, + commands_type_ids, commands, - queries_type_id, + overriden_commands, + queries_type_ids, queries, - events_type_id, + overriden_queries, + events_type_ids, events, }) } @@ -230,58 +264,86 @@ impl ExpandedServiceMeta { fn service_funcs( registry: &PortableRegistry, - func_type_id: u32, - ) -> Result> { - any_funcs(registry, func_type_id)? - .map(|f| { - if f.fields.len() != 2 { - Err(Error::FuncMetaIsInvalid(format!( + 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", - f.name - ))) - } else { - let params_type = registry.resolve(f.fields[0].ty.id).unwrap_or_else(|| { - panic!( + 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", - f.fields[0].ty.id + func_descr.fields[0].ty.id ) - }); - if let TypeDef::Composite(params_type) = ¶ms_type.type_def { - Ok(ServiceFuncMeta( - f.name.to_string(), - f.fields[0].ty.id, - params_type.fields.to_vec(), - f.fields[1].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, + ); + if !funcs_meta + .iter() + .any(|fm: &ServiceFuncMeta| fm.0 == func_meta.0) + { + funcs_meta.push(func_meta); } else { - Err(Error::FuncMetaIsInvalid(format!( - "func `{}` params type is not a composite", - f.name - ))) + overriden_funcs_meta.push(func_meta); } + } else { + return Err(Error::FuncMetaIsInvalid(format!( + "func `{}` params type is not a composite", + func_descr.name + ))); } - }) - .collect() + } + } + Ok((funcs_meta, overriden_funcs_meta)) } fn event_variants( registry: &PortableRegistry, - events_type_id: u32, + events_type_ids: impl Iterator, ) -> Result>> { - let events = registry.resolve(events_type_id).unwrap_or_else(|| { - panic!( - "events type id {} not found while it was registered previously", - events_type_id - ) - }); - if let TypeDef::Variant(variant) = &events.type_def { - Ok(variant.variants.to_vec()) - } else { - Err(Error::EventMetaIsInvalid(format!( - "events type id {} references a type that is not a variant", - events_type_id - ))) + 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 {} not found while it was registered previously", + events_type_id + ) + }); + 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 {} references a type that is not a variant", + events_type_id + ))); + } } + Ok(events_variants) } } diff --git a/idl-gen/tests/generator.rs b/idl-gen/tests/generator.rs index 78b59d60..80324995 100644 --- a/idl-gen/tests/generator.rs +++ b/idl-gen/tests/generator.rs @@ -1,125 +1,201 @@ +use meta_params::*; use sails_idl_gen::{program, service}; use sails_rtl::{ - meta::{AnyServiceMeta, ProgramMeta, ServiceMeta}, + meta::{AnyServiceMeta, ProgramMeta, ServiceMeta as RtlServiceMeta}, H256, U256, }; -use scale_info::{MetaType, TypeInfo}; +use scale_info::{MetaType, StaticTypeInfo, TypeInfo}; use std::{collections::BTreeMap, result::Result as StdResult}; #[allow(dead_code)] -#[derive(TypeInfo)] -pub struct GenericStruct { - pub p1: T, -} +mod types { + use super::*; -#[allow(dead_code)] -#[derive(TypeInfo)] -pub enum GenericEnum { - Variant1(T1), - Variant2(T2), -} + #[derive(TypeInfo)] + pub struct GenericStruct { + pub p1: T, + } -#[allow(dead_code)] -#[derive(TypeInfo)] -pub struct DoThatParam { - pub p1: u32, - pub p2: String, - pub p3: ManyVariants, + #[derive(TypeInfo)] + pub enum GenericEnum { + Variant1(T1), + Variant2(T2), + } + + #[derive(TypeInfo)] + pub struct TupleStruct(bool); + + #[derive(TypeInfo)] + pub enum ManyVariants { + One, + Two(u32), + Three(Option>), + Four { a: u32, b: Option }, + Five(String, Vec), + Six((u32,)), + Seven(GenericEnum), + Eight([BTreeMap; 10]), + } + + #[derive(TypeInfo)] + pub struct DoThatParam { + pub p1: u32, + pub p2: String, + pub p3: ManyVariants, + } + + #[derive(TypeInfo)] + pub struct ThatParam { + pub p1: ManyVariants, + } } #[allow(dead_code)] -#[derive(TypeInfo)] -pub struct ThatParam { - pub p1: ManyVariants, +mod meta_params { + use super::{types::*, *}; + + #[derive(TypeInfo)] + pub struct DoThisParams { + p1: u32, + p2: String, + p3: (Option, u8), + p4: TupleStruct, + p5: GenericStruct, + p6: GenericStruct, + } + + #[derive(TypeInfo)] + pub struct DoThatParams { + par1: DoThatParam, + } + + #[derive(TypeInfo)] + pub struct ThisParams { + p1: u32, + p2: String, + p3: (Option, u8), + p4: TupleStruct, + p5: GenericEnum, + } + + #[derive(TypeInfo)] + pub struct ThatParams { + pr1: ThatParam, + } + + #[derive(TypeInfo)] + pub struct SingleParams { + pub p1: T, + } + + #[derive(TypeInfo)] + pub struct NoParams; } #[allow(dead_code)] #[derive(TypeInfo)] -pub struct TupleStruct(bool); +enum CommandsMeta { + DoThis(DoThisParams, String), + DoThat(DoThatParams, StdResult<(String, u32), (String,)>), +} #[allow(dead_code)] #[derive(TypeInfo)] -pub enum ManyVariants { - One, - Two(u32), - Three(Option>), - Four { a: u32, b: Option }, - Five(String, Vec), - Six((u32,)), - Seven(GenericEnum), - Eight([BTreeMap; 10]), +enum BaseCommandsMeta { + DoThis(SingleParams, u32), + DoThatBase(SingleParams, String), } #[allow(dead_code)] #[derive(TypeInfo)] -struct DoThisParams { - p1: u32, - p2: String, - p3: (Option, u8), - p4: TupleStruct, - p5: GenericStruct, - p6: GenericStruct, +enum QueriesMeta { + This(ThisParams, StdResult<(String, u32), String>), + That(ThatParams, String), } #[allow(dead_code)] #[derive(TypeInfo)] -struct DoThatParams { - par1: DoThatParam, +enum BaseQueriesMeta { + ThisBase(SingleParams, u16), + That(SingleParams, String), } #[allow(dead_code)] #[derive(TypeInfo)] -enum CommandsMeta { - DoThis(DoThisParams, String), - DoThat(DoThatParams, StdResult<(String, u32), (String,)>), +enum EventsMeta { + ThisDone(u32), + ThatDone { p1: String }, } #[allow(dead_code)] #[derive(TypeInfo)] -struct ThisParams { - p1: u32, - p2: String, - p3: (Option, u8), - p4: TupleStruct, - p5: GenericEnum, +enum BaseEventsMeta { + ThisDoneBase(u32), + ThatDoneBase { p1: u16 }, } #[allow(dead_code)] #[derive(TypeInfo)] -struct ThatParams { - pr1: ThatParam, +enum AmbiguousBaseEventsMeta { + ThisDone(u32), // Conflicts with `EventsMeta::ThisDone` even it has the same signatur + ThatDoneBase { p1: u16 }, } -#[allow(dead_code)] -#[derive(TypeInfo)] -enum QueriesMeta { - This(ThisParams, StdResult<(String, u32), String>), - That(ThatParams, String), +struct ServiceMeta { + _commands: std::marker::PhantomData, + _queries: std::marker::PhantomData, + _events: std::marker::PhantomData, } -#[allow(dead_code)] -#[derive(TypeInfo)] -enum EventsMeta { - ThisDone(u32), - ThatDone { p1: String }, +impl RtlServiceMeta + for ServiceMeta +{ + fn commands() -> MetaType { + scale_info::meta_type::() + } + + fn queries() -> MetaType { + scale_info::meta_type::() + } + + fn events() -> MetaType { + scale_info::meta_type::() + } + + fn base_services() -> impl Iterator { + [].into_iter() + } } -struct TestServiceMeta; +struct ServiceMetaWithBase { + _commands: std::marker::PhantomData, + _queries: std::marker::PhantomData, + _events: std::marker::PhantomData, + _base: std::marker::PhantomData, +} -impl ServiceMeta for TestServiceMeta { +impl RtlServiceMeta + for ServiceMetaWithBase +{ fn commands() -> MetaType { - scale_info::meta_type::() + scale_info::meta_type::() } fn queries() -> MetaType { - scale_info::meta_type::() + scale_info::meta_type::() } fn events() -> MetaType { - scale_info::meta_type::() + scale_info::meta_type::() + } + + fn base_services() -> impl Iterator { + [AnyServiceMeta::new::()].into_iter() } } +type TestServiceMeta = ServiceMeta; + #[allow(dead_code)] #[derive(TypeInfo)] enum EmptyCtorsMeta {} @@ -132,25 +208,15 @@ impl ProgramMeta for TestProgramWithEmptyCtorsMeta { } fn services() -> impl Iterator { - vec![("", AnyServiceMeta::new::())].into_iter() + [("", AnyServiceMeta::new::())].into_iter() } } -#[allow(dead_code)] -#[derive(TypeInfo)] -struct NewParams; - -#[allow(dead_code)] -#[derive(TypeInfo)] -struct FromStrParams { - s: String, -} - #[allow(dead_code)] #[derive(TypeInfo)] enum NonEmptyCtorsMeta { - New(NewParams), - FromStr(FromStrParams), + New(NoParams), + FromStr(SingleParams), } struct TestProgramWithNonEmptyCtorsMeta; @@ -161,7 +227,7 @@ impl ProgramMeta for TestProgramWithNonEmptyCtorsMeta { } fn services() -> impl Iterator { - vec![("", AnyServiceMeta::new::())].into_iter() + [("", AnyServiceMeta::new::())].into_iter() } } @@ -173,7 +239,7 @@ impl ProgramMeta for TestProgramWithMultipleServicesMeta { } fn services() -> impl Iterator { - vec![ + [ ("", AnyServiceMeta::new::()), ("SomeService", AnyServiceMeta::new::()), ] @@ -182,7 +248,7 @@ impl ProgramMeta for TestProgramWithMultipleServicesMeta { } #[test] -fn generare_program_idl_works_with_empty_ctors() { +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(); @@ -197,7 +263,7 @@ fn generare_program_idl_works_with_empty_ctors() { } #[test] -fn generare_program_idl_works_with_non_empty_ctors() { +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(); @@ -212,7 +278,7 @@ fn generare_program_idl_works_with_non_empty_ctors() { } #[test] -fn generate_program_idl_works_with_multiple_services() { +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(); @@ -230,7 +296,7 @@ fn generate_program_idl_works_with_multiple_services() { } #[test] -fn generate_service_idl_works() { +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(); @@ -243,3 +309,44 @@ fn generate_service_idl_works() { assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); assert_eq!(generated_idl_program.types().len(), 8); } + +#[test] +fn service_idl_works_with_base_services() { + let mut idl = Vec::new(); + service::generate_idl::< + ServiceMetaWithBase< + CommandsMeta, + QueriesMeta, + EventsMeta, + ServiceMeta, + >, + >(&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(), 8); +} + +#[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); + + assert!(matches!( + result, + Err(sails_idl_gen::Error::EventMetaIsAmbiguous(_)) + )); +} diff --git a/idl-gen/tests/snapshots/generator__generare_program_idl_works_with_empty_ctors.snap b/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap similarity index 100% rename from idl-gen/tests/snapshots/generator__generare_program_idl_works_with_empty_ctors.snap rename to idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap diff --git a/idl-gen/tests/snapshots/generator__generate_program_idl_works_with_multiple_services.snap b/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap similarity index 100% rename from idl-gen/tests/snapshots/generator__generate_program_idl_works_with_multiple_services.snap rename to idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap diff --git a/idl-gen/tests/snapshots/generator__generare_program_idl_works_with_non_empty_ctors.snap b/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap similarity index 95% rename from idl-gen/tests/snapshots/generator__generare_program_idl_works_with_non_empty_ctors.snap rename to idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap index 329f66a6..76bba35d 100644 --- a/idl-gen/tests/snapshots/generator__generare_program_idl_works_with_non_empty_ctors.snap +++ b/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap @@ -1,5 +1,5 @@ --- -source: idlgen/tests/generator.rs +source: idl-gen/tests/generator.rs expression: generated_idl --- type TupleStruct = struct { @@ -47,7 +47,7 @@ type ThatParam = struct { constructor { New : (); - FromStr : (s: str); + FromStr : (p1: str); }; service { @@ -61,5 +61,3 @@ service { ThatDone: struct { p1: str }; } }; - - diff --git a/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap b/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap new file mode 100644 index 00000000..cfc8b76b --- /dev/null +++ b/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap @@ -0,0 +1,62 @@ +--- +source: idl-gen/tests/generator.rs +expression: generated_idl +--- +type TupleStruct = struct { + bool, +}; + +type GenericStructForH256 = struct { + p1: h256, +}; + +type GenericStructForStr = struct { + p1: str, +}; + +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], +}; + +type GenericEnumForU32AndStr = enum { + Variant1: u32, + Variant2: str, +}; + +type GenericEnumForBoolAndU32 = enum { + Variant1: bool, + Variant2: u32, +}; + +type ThatParam = struct { + p1: ManyVariants, +}; + +service { + DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericStructForH256, p6: GenericStructForStr) -> str; + DoThat : (par1: DoThatParam) -> result (struct { str, u32 }, struct { str }); + DoThatBase : (p1: str) -> str; + query This : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericEnumForBoolAndU32) -> result (struct { str, u32 }, str); + query That : (pr1: ThatParam) -> str; + query ThisBase : (p1: u16) -> u16; + + events { + ThisDone: u32; + ThatDone: struct { p1: str }; + ThisDoneBase: u32; + ThatDoneBase: struct { p1: u16 }; + } +}; diff --git a/idl-gen/tests/snapshots/generator__generate_service_idl_works.snap b/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap similarity index 100% rename from idl-gen/tests/snapshots/generator__generate_service_idl_works.snap rename to idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap diff --git a/macros/core/src/service.rs b/macros/core/src/service.rs index ecf24454..6a8df420 100644 --- a/macros/core/src/service.rs +++ b/macros/core/src/service.rs @@ -1,413 +1,558 @@ -// 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 . - -//! Supporting functions and structures for the `gservice` macro. - -use crate::{ - sails_paths, - shared::{self, Func, ImplType}, -}; -use convert_case::{Case, Casing}; -use parity_scale_codec::Encode; -use proc_macro2::{Span, TokenStream as TokenStream2}; -use proc_macro_error::abort; -use quote::quote; -use std::collections::BTreeMap; -use syn::{ - spanned::Spanned, GenericArgument, Ident, ImplItemFn, ItemImpl, Path, PathArguments, Type, - TypeParamBound, Visibility, WhereClause, WherePredicate, -}; - -static mut SERVICE_SPANS: BTreeMap = BTreeMap::new(); - -pub fn gservice(service_impl_tokens: TokenStream2) -> TokenStream2 { - let service_impl = parse_gservice_impl(service_impl_tokens); - ensure_single_gservice_on_impl(&service_impl); - ensure_single_gservice_by_name(&service_impl); - gen_gservice_impl(service_impl) -} - -#[doc(hidden)] -pub fn __gservice_internal(service_impl_tokens: TokenStream2) -> TokenStream2 { - let service_impl = parse_gservice_impl(service_impl_tokens); - gen_gservice_impl(service_impl) -} - -fn parse_gservice_impl(service_impl_tokens: TokenStream2) -> ItemImpl { - syn::parse2(service_impl_tokens).unwrap_or_else(|err| { - abort!( - err.span(), - "`gservice` attribute can be applied to impls only: {}", - err - ) - }) -} - -fn ensure_single_gservice_on_impl(service_impl: &ItemImpl) { - let attr_gservice = service_impl.attrs.iter().find(|attr| { - attr.meta - .path() - .segments - .last() - .map(|s| s.ident == "gservice") - .unwrap_or(false) - }); - if attr_gservice.is_some() { - abort!( - service_impl, - "multiple `gservice` attributes on the same impl are not allowed", - ) - } -} - -fn ensure_single_gservice_by_name(service_impl: &ItemImpl) { - let path = shared::impl_type_path(service_impl); - let type_ident = path.path.segments.last().unwrap().ident.to_string(); - if unsafe { SERVICE_SPANS.get(&type_ident) }.is_some() { - abort!( - service_impl, - "multiple `gservice` attributes on a type with the same name are not allowed" - ) - } - unsafe { SERVICE_SPANS.insert(type_ident, service_impl.span()) }; -} - -fn gen_gservice_impl(service_impl: ItemImpl) -> TokenStream2 { - let (service_type_path, service_type_args, service_type_constraints) = { - let service_type = ImplType::new(&service_impl); - ( - service_type.path().clone(), - service_type.args().clone(), - service_type.constraints().cloned(), - ) - }; - - let service_handlers = discover_service_handlers(&service_impl); - - if service_handlers.is_empty() { - abort!( - service_impl, - "`gservice` attribute requires impl to define at least one public method" - ); - } - - let no_events_type = Path::from(Ident::new("NoEvents", Span::call_site())); - let events_type = service_type_constraints - .as_ref() - .and_then(discover_service_events_type) - .unwrap_or(&no_events_type); - - let inner_ident = Ident::new("inner", Span::call_site()); - let input_ident = Ident::new("input", Span::call_site()); - - let mut exposure_funcs = Vec::with_capacity(service_handlers.len()); - let mut invocation_params_structs = Vec::with_capacity(service_handlers.len()); - let mut invocation_funcs = Vec::with_capacity(service_handlers.len()); - let mut invocation_dispatches = Vec::with_capacity(service_handlers.len()); - let mut commands_meta_variants = Vec::with_capacity(service_handlers.len()); - let mut queries_meta_variants = Vec::with_capacity(service_handlers.len()); - - for (handler_route, (handler_fn, ..)) in &service_handlers { - let handler_fn = &handler_fn.sig; - let handler_func = Func::from(handler_fn); - let handler_generator = HandlerGenerator::from(handler_func.clone()); - let invocation_func_ident = handler_generator.invocation_func_ident(); - - exposure_funcs.push({ - let handler_ident = handler_func.ident(); - let handler_params = handler_func.params().iter().map(|item| item.0); - let handler_await_token = handler_func.is_async().then(|| quote!(.await)); - quote!( - pub #handler_fn { - let exposure_scope = sails_rtl::gstd::services::ExposureCallScope::new(self); - self. #inner_ident . #handler_ident (#(#handler_params),*) #handler_await_token - } - ) - }); - invocation_params_structs.push(handler_generator.params_struct()); - invocation_funcs.push(handler_generator.invocation_func()); - invocation_dispatches.push({ - let handler_route_bytes = handler_route.encode(); - let handler_route_len = handler_route_bytes.len(); - quote!( - if #input_ident.starts_with(& [ #(#handler_route_bytes),* ]) { - let output = self.#invocation_func_ident(&#input_ident[#handler_route_len..]).await; - static INVOCATION_ROUTE: [u8; #handler_route_len] = [ #(#handler_route_bytes),* ]; - return [INVOCATION_ROUTE.as_ref(), &output].concat(); - } - ) - }); - - let handler_meta_variant = { - let params_struct_ident = handler_generator.params_struct_ident(); - let result_type = handler_generator.result_type(); - let handler_route_ident = Ident::new(handler_route, Span::call_site()); - - quote!(#handler_route_ident(#params_struct_ident, #result_type)) - }; - if handler_generator.is_query() { - queries_meta_variants.push(handler_meta_variant); - } else { - commands_meta_variants.push(handler_meta_variant); - } - } - - let message_id_ident = Ident::new("message_id", Span::call_site()); - let route_ident = Ident::new("route", Span::call_site()); - - let scale_types_path = sails_paths::scale_types_path(); - let scale_codec_path = sails_paths::scale_codec_path(); - let scale_info_path = sails_paths::scale_info_path(); - - let unexpected_route_panic = - shared::generate_unexpected_input_panic(&input_ident, "Unknown request"); - - quote!( - #service_impl - - pub struct Exposure { - #message_id_ident : sails_rtl::MessageId, - #route_ident : &'static [u8], - #inner_ident : T, - } - - impl #service_type_args Exposure<#service_type_path> #service_type_constraints { - #(#exposure_funcs)* - - pub async fn handle(&mut self, mut #input_ident: &[u8]) -> Vec { - #(#invocation_dispatches)* - #unexpected_route_panic - } - - #(#invocation_funcs)* - } - - impl #service_type_args sails_rtl::gstd::services::Exposure for Exposure<#service_type_path> #service_type_constraints { - fn message_id(&self) -> sails_rtl::MessageId { - self. #message_id_ident - } - - fn route(&self) -> &'static [u8] { - self. #route_ident - } - } - - impl #service_type_args sails_rtl::gstd::services::Service for #service_type_path #service_type_constraints { - type Exposure = Exposure< #service_type_path >; - - fn expose(self, #message_id_ident : sails_rtl::MessageId, #route_ident : &'static [u8]) -> Self::Exposure { - Self::Exposure { #message_id_ident , #route_ident , inner: self } - } - } - - impl #service_type_args sails_rtl::meta::ServiceMeta for #service_type_path #service_type_constraints { - fn commands() -> #scale_info_path ::MetaType { - #scale_info_path ::MetaType::new::() - } - - fn queries() -> #scale_info_path ::MetaType { - #scale_info_path ::MetaType::new::() - } - - fn events() -> #scale_info_path ::MetaType { - #scale_info_path ::MetaType::new::() - } - } - - use #scale_types_path ::Decode as __ServiceDecode; - use #scale_types_path ::Encode as __ServiceEncode; - use #scale_types_path ::TypeInfo as __ServiceTypeInfo; - - #( - #[derive(__ServiceDecode, __ServiceTypeInfo)] - #[codec(crate = #scale_codec_path )] - #[scale_info(crate = #scale_info_path )] - #invocation_params_structs - )* - - mod meta { - use super::*; - - #[derive(__ServiceTypeInfo)] - #[scale_info(crate = #scale_info_path )] - pub enum CommandsMeta { - #(#commands_meta_variants),* - } - - #[derive(__ServiceTypeInfo)] - #[scale_info(crate = #scale_info_path )] - pub enum QueriesMeta { - #(#queries_meta_variants),* - } - - #[derive(__ServiceTypeInfo)] - #[scale_info(crate = #scale_info_path )] - pub enum #no_events_type {} - - pub type EventsMeta = #events_type; - } - ) -} - -fn discover_service_handlers(service_impl: &ItemImpl) -> BTreeMap { - shared::discover_invocation_targets(service_impl, |fn_item| { - matches!(fn_item.vis, Visibility::Public(_)) && fn_item.sig.receiver().is_some() - }) -} - -fn discover_service_events_type(where_clause: &WhereClause) -> Option<&Path> { - let event_types = where_clause - .predicates - .iter() - .filter_map(|predicate| { - if let WherePredicate::Type(predicate) = predicate { - return Some(&predicate.bounds); - } - None - }) - .flatten() - .filter_map(|bound| { - if let TypeParamBound::Trait(trait_bound) = bound { - let last_segment = trait_bound.path.segments.last()?; - if last_segment.ident == "EventTrigger" { - if let PathArguments::AngleBracketed(args) = &last_segment.arguments { - if args.args.len() == 1 { - if let GenericArgument::Type(Type::Path(type_path)) = - args.args.first().unwrap() - { - return Some(&type_path.path); - } - } - } - } - } - None - }) - .collect::>(); - if event_types.len() > 1 { - abort!( - where_clause, - "Multiple event types found. Please specify only one event type" - ); - } - event_types.first().copied() -} - -struct HandlerGenerator<'a> { - handler: Func<'a>, -} - -impl<'a> HandlerGenerator<'a> { - fn from(handler: Func<'a>) -> Self { - Self { handler } - } - - fn params_struct_ident(&self) -> Ident { - Ident::new( - &format!( - "__{}Params", - self.handler.ident().to_string().to_case(Case::Pascal) - ), - Span::call_site(), - ) - } - - fn result_type(&self) -> Type { - self.handler.result().clone() - } - - fn handler_func_ident(&self) -> Ident { - self.handler.ident().clone() - } - - fn invocation_func_ident(&self) -> Ident { - Ident::new( - &format!("__{}", self.handler_func_ident()), - Span::call_site(), - ) - } - - fn is_query(&self) -> bool { - self.handler - .receiver() - .map_or(true, |r| r.mutability.is_none()) - } - - fn params_struct(&self) -> TokenStream2 { - let params_struct_ident = self.params_struct_ident(); - let params_struct_members = self.handler.params().iter().map(|item| { - let arg_ident = item.0; - let arg_type = item.1; - quote!(#arg_ident: #arg_type) - }); - - quote!( - pub struct #params_struct_ident { - #(#params_struct_members),* - } - ) - } - - fn invocation_func(&self) -> TokenStream2 { - let invocation_func_ident = self.invocation_func_ident(); - let receiver = self.handler.receiver(); - let params_struct_ident = self.params_struct_ident(); - let handler_func_ident = self.handler_func_ident(); - let handler_func_params = self.handler.params().iter().map(|item| { - let param_ident = item.0; - quote!(request.#param_ident) - }); - - let await_token = self.handler.is_async().then(|| quote!(.await)); - - quote!( - async fn #invocation_func_ident(#receiver, mut input: &[u8]) -> Vec - { - let request = #params_struct_ident::decode(&mut input).expect("Failed to decode request"); - let result = self.#handler_func_ident(#(#handler_func_params),*)#await_token; - return result.encode(); - } - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use quote::ToTokens; - - #[test] - fn events_type_is_discovered_via_where_clause() { - let input = quote! { - impl SomeService where T: EventTrigger { - pub async fn do_this(&mut self) -> u32 { - 42 - } - } - }; - let service_impl = syn::parse2(input).unwrap(); - let item_type = ImplType::new(&service_impl); - - let result = discover_service_events_type(item_type.constraints().unwrap()); - - assert_eq!( - result.unwrap().to_token_stream().to_string(), - quote!(events::SomeEvents).to_string() - ); - } -} +// 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 . + +//! Supporting functions and structures for the `gservice` macro. + +use crate::{ + sails_paths, + shared::{self, Func, ImplType}, +}; +use convert_case::{Case, Casing}; +use parity_scale_codec::Encode; +use proc_macro2::{Span, TokenStream}; +use proc_macro_error::abort; +use quote::{quote, ToTokens}; +use std::collections::BTreeMap; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + spanned::Spanned, + Expr, GenericArgument, Ident, ImplItemFn, ItemImpl, Path, PathArguments, Result as SynResult, + Token, Type, TypeParamBound, Visibility, WhereClause, WherePredicate, +}; + +static mut SERVICE_SPANS: BTreeMap = BTreeMap::new(); + +pub fn gservice(args: TokenStream, service_impl: TokenStream) -> TokenStream { + let service_impl = parse_gservice_impl(service_impl); + ensure_single_gservice_on_impl(&service_impl); + ensure_single_gservice_by_name(&service_impl); + gen_gservice_impl(args, service_impl) +} + +#[doc(hidden)] +pub fn __gservice_internal(args: TokenStream, service_impl: TokenStream) -> TokenStream { + let service_impl = parse_gservice_impl(service_impl); + gen_gservice_impl(args, service_impl) +} + +fn parse_gservice_impl(service_impl_tokens: TokenStream) -> ItemImpl { + syn::parse2(service_impl_tokens).unwrap_or_else(|err| { + abort!( + err.span(), + "`gservice` attribute can be applied to impls only: {}", + err + ) + }) +} + +fn ensure_single_gservice_on_impl(service_impl: &ItemImpl) { + let attr_gservice = service_impl.attrs.iter().find(|attr| { + attr.meta + .path() + .segments + .last() + .map(|s| s.ident == "gservice") + .unwrap_or(false) + }); + if attr_gservice.is_some() { + abort!( + service_impl, + "multiple `gservice` attributes on the same impl are not allowed", + ) + } +} + +fn ensure_single_gservice_by_name(service_impl: &ItemImpl) { + let path = shared::impl_type_path(service_impl); + let type_ident = path.path.segments.last().unwrap().ident.to_string(); + if unsafe { SERVICE_SPANS.get(&type_ident) }.is_some() { + abort!( + service_impl, + "multiple `gservice` attributes on a type with the same name are not allowed" + ) + } + unsafe { SERVICE_SPANS.insert(type_ident, service_impl.span()) }; +} + +fn gen_gservice_impl(args: TokenStream, service_impl: ItemImpl) -> TokenStream { + let service_args = syn::parse2::(args).unwrap_or_else(|err| { + abort!( + err.span(), + "failed to parse `gservice` attribute arguments: {}", + err + ) + }); + + let (service_type, service_type_args, service_type_constraints) = { + let service_type = ImplType::new(&service_impl); + ( + service_type.path().clone(), + service_type.args().clone(), + service_type.constraints().cloned(), + ) + }; + + let service_base_types = service_args.items.iter().flat_map(|item| match item { + ServiceArg::Extends(paths) => paths, + }); + + let service_handlers = discover_service_handlers(&service_impl); + + if service_handlers.is_empty() && !service_base_types.clone().any(|_| true) { + abort!( + service_impl, + "`gservice` attribute requires impl to define at least one public method or extend another service" + ); + } + + let no_events_type = Path::from(Ident::new("NoEvents", Span::call_site())); + let events_type = service_type_constraints + .as_ref() + .and_then(discover_service_events_type) + .unwrap_or(&no_events_type); + + let inner_ident = Ident::new("inner", Span::call_site()); + let input_ident = Ident::new("input", Span::call_site()); + + let mut exposure_funcs = Vec::with_capacity(service_handlers.len()); + let mut invocation_params_structs = Vec::with_capacity(service_handlers.len()); + let mut invocation_funcs = Vec::with_capacity(service_handlers.len()); + let mut invocation_dispatches = Vec::with_capacity(service_handlers.len()); + let mut commands_meta_variants = Vec::with_capacity(service_handlers.len()); + let mut queries_meta_variants = Vec::with_capacity(service_handlers.len()); + + for (handler_route, (handler_fn, ..)) in &service_handlers { + let handler_fn = &handler_fn.sig; + let handler_func = Func::from(handler_fn); + let handler_generator = HandlerGenerator::from(handler_func.clone()); + let invocation_func_ident = handler_generator.invocation_func_ident(); + + exposure_funcs.push({ + let handler_ident = handler_func.ident(); + let handler_params = handler_func.params().iter().map(|item| item.0); + let handler_await_token = handler_func.is_async().then(|| quote!(.await)); + quote!( + pub #handler_fn { + let exposure_scope = sails_rtl::gstd::services::ExposureCallScope::new(self); + self. #inner_ident . #handler_ident (#(#handler_params),*) #handler_await_token + } + ) + }); + invocation_params_structs.push(handler_generator.params_struct()); + invocation_funcs.push(handler_generator.invocation_func()); + invocation_dispatches.push({ + let handler_route_bytes = handler_route.encode(); + let handler_route_len = handler_route_bytes.len(); + quote!( + if #input_ident.starts_with(& [ #(#handler_route_bytes),* ]) { + let output = self.#invocation_func_ident(&#input_ident[#handler_route_len..]).await; + static INVOCATION_ROUTE: [u8; #handler_route_len] = [ #(#handler_route_bytes),* ]; + return Some([INVOCATION_ROUTE.as_ref(), &output].concat()); + } + ) + }); + + let handler_meta_variant = { + let params_struct_ident = handler_generator.params_struct_ident(); + let result_type = handler_generator.result_type(); + let handler_route_ident = Ident::new(handler_route, Span::call_site()); + + quote!(#handler_route_ident(#params_struct_ident, #result_type)) + }; + if handler_generator.is_query() { + queries_meta_variants.push(handler_meta_variant); + } else { + commands_meta_variants.push(handler_meta_variant); + } + } + + let message_id_ident = Ident::new("message_id", Span::call_site()); + let route_ident = Ident::new("route", Span::call_site()); + + let service_base_types = service_args.items.iter().flat_map(|item| match item { + ServiceArg::Extends(paths) => paths, + }); + + let code_for_base_types = service_base_types + .enumerate() + .map(|(idx, base_type)| { + let base_ident = Ident::new(&format!("base_{}", idx), Span::call_site()); + + let exposure_as_ref_impl = quote!( + impl AsRef<< #base_type as sails_rtl::gstd::services::Service>::Exposure> for Exposure< #service_type > { + fn as_ref(&self) -> &< #base_type as sails_rtl::gstd::services::Service>::Exposure { + &self. #base_ident + } + } + ); + + let base_exposure_member = quote!( + #base_ident : < #base_type as sails_rtl::gstd::services::Service>::Exposure, + ); + + let base_exposure_instantiation = quote!( + #base_ident : < #base_type as Clone>::clone(AsRef::< #base_type >::as_ref(&self)) + .expose( #message_id_ident , #route_ident ), + ); + + let base_exposure_invocation = quote!( + if let Some(output) = self. #base_ident .try_handle(#input_ident).await { + return Some(output); + } + ); + + let base_service_meta = quote!(sails_rtl::meta::AnyServiceMeta::new::< #base_type >()); + + (exposure_as_ref_impl, base_exposure_member, base_exposure_instantiation, base_exposure_invocation, base_service_meta) + }); + + let exposure_as_ref_impls = code_for_base_types + .clone() + .map(|(exposure_as_ref_impl, ..)| exposure_as_ref_impl); + + let base_exposures_members = code_for_base_types + .clone() + .map(|(_, base_exposure_member, ..)| base_exposure_member); + + let base_exposures_instantiations = code_for_base_types + .clone() + .map(|(_, _, base_exposure_instantiation, ..)| base_exposure_instantiation); + + let base_exposures_invocations = code_for_base_types + .clone() + .map(|(_, _, _, base_exposure_invocation, ..)| base_exposure_invocation); + + let base_services_meta = + code_for_base_types.map(|(_, _, _, _, base_service_meta)| base_service_meta); + + let scale_types_path = sails_paths::scale_types_path(); + let scale_codec_path = sails_paths::scale_codec_path(); + let scale_info_path = sails_paths::scale_info_path(); + + let unexpected_route_panic = + shared::generate_unexpected_input_panic(&input_ident, "Unknown request"); + + quote!( + #service_impl + + pub struct Exposure { + #message_id_ident : sails_rtl::MessageId, + #route_ident : &'static [u8], + #inner_ident : T, + #( #base_exposures_members )* + } + + impl #service_type_args Exposure<#service_type> #service_type_constraints { + #( #exposure_funcs )* + + pub async fn handle(&mut self, mut #input_ident: &[u8]) -> Vec { + self.try_handle( #input_ident ).await.unwrap_or_else(|| { + #unexpected_route_panic + }) + } + + pub async fn try_handle(&mut self, #input_ident : &[u8]) -> Option> { + #( #invocation_dispatches )* + #( #base_exposures_invocations )* + None + } + + #(#invocation_funcs)* + } + + impl #service_type_args sails_rtl::gstd::services::Exposure for Exposure< #service_type > #service_type_constraints { + fn message_id(&self) -> sails_rtl::MessageId { + self. #message_id_ident + } + + fn route(&self) -> &'static [u8] { + self. #route_ident + } + } + + #( #exposure_as_ref_impls )* + + impl #service_type_args sails_rtl::gstd::services::Service for #service_type #service_type_constraints { + type Exposure = Exposure< #service_type >; + + fn expose(self, #message_id_ident : sails_rtl::MessageId, #route_ident : &'static [u8]) -> Self::Exposure { + Self::Exposure { + #message_id_ident , + #route_ident , + #( #base_exposures_instantiations )* + #inner_ident : self, + } + } + } + + impl #service_type_args sails_rtl::meta::ServiceMeta for #service_type #service_type_constraints { + fn commands() -> #scale_info_path ::MetaType { + #scale_info_path ::MetaType::new::() + } + + fn queries() -> #scale_info_path ::MetaType { + #scale_info_path ::MetaType::new::() + } + + fn events() -> #scale_info_path ::MetaType { + #scale_info_path ::MetaType::new::() + } + + fn base_services() -> impl Iterator { + [ + #( #base_services_meta ),* + ].into_iter() + } + } + + use #scale_types_path ::Decode as __ServiceDecode; + use #scale_types_path ::Encode as __ServiceEncode; + use #scale_types_path ::TypeInfo as __ServiceTypeInfo; + + #( + #[derive(__ServiceDecode, __ServiceTypeInfo)] + #[codec(crate = #scale_codec_path )] + #[scale_info(crate = #scale_info_path )] + #invocation_params_structs + )* + + mod meta { + use super::*; + + #[derive(__ServiceTypeInfo)] + #[scale_info(crate = #scale_info_path )] + pub enum CommandsMeta { + #(#commands_meta_variants),* + } + + #[derive(__ServiceTypeInfo)] + #[scale_info(crate = #scale_info_path )] + pub enum QueriesMeta { + #(#queries_meta_variants),* + } + + #[derive(__ServiceTypeInfo)] + #[scale_info(crate = #scale_info_path )] + pub enum #no_events_type {} + + pub type EventsMeta = #events_type; + } + ) +} + +struct ServiceArgs { + items: Punctuated, +} + +impl Parse for ServiceArgs { + fn parse(input: ParseStream) -> SynResult { + Ok(Self { + items: input.parse_terminated(ServiceArg::parse, Token![,])?, + }) + } +} + +#[derive(Debug)] +enum ServiceArg { + Extends(Vec), +} + +impl Parse for ServiceArg { + fn parse(input: ParseStream) -> SynResult { + let ident = input.parse::()?; + input.parse::()?; + let values = input.parse::()?; + match ident.to_string().as_str() { + "extends" => { + if let Expr::Path(path_expr) = values { + // Check path_expr.attrs is empty and qself is none + return Ok(Self::Extends(vec![path_expr.path])); + } else if let Expr::Array(array_expr) = values { + let mut paths = Vec::new(); + for item_expr in array_expr.elems { + if let Expr::Path(path_expr) = item_expr { + paths.push(path_expr.path); + } else { + abort!( + item_expr, + "unexpected value for `extends` argument: {}", + item_expr.to_token_stream() + ) + } + } + return Ok(Self::Extends(paths)); + } + abort!( + ident, + "unexpected value for `extends` argument: {}", + values.to_token_stream() + ) + } + _ => abort!(ident, "unknown argument: {}", ident), + } + } +} + +fn discover_service_handlers(service_impl: &ItemImpl) -> BTreeMap { + shared::discover_invocation_targets(service_impl, |fn_item| { + matches!(fn_item.vis, Visibility::Public(_)) && fn_item.sig.receiver().is_some() + }) +} + +fn discover_service_events_type(where_clause: &WhereClause) -> Option<&Path> { + let event_types = where_clause + .predicates + .iter() + .filter_map(|predicate| { + if let WherePredicate::Type(predicate) = predicate { + return Some(&predicate.bounds); + } + None + }) + .flatten() + .filter_map(|bound| { + if let TypeParamBound::Trait(trait_bound) = bound { + let last_segment = trait_bound.path.segments.last()?; + if last_segment.ident == "EventTrigger" { + if let PathArguments::AngleBracketed(args) = &last_segment.arguments { + if args.args.len() == 1 { + if let GenericArgument::Type(Type::Path(type_path)) = + args.args.first().unwrap() + { + return Some(&type_path.path); + } + } + } + } + } + None + }) + .collect::>(); + if event_types.len() > 1 { + abort!( + where_clause, + "Multiple event types found. Please specify only one event type" + ); + } + event_types.first().copied() +} + +struct HandlerGenerator<'a> { + handler: Func<'a>, +} + +impl<'a> HandlerGenerator<'a> { + fn from(handler: Func<'a>) -> Self { + Self { handler } + } + + fn params_struct_ident(&self) -> Ident { + Ident::new( + &format!( + "__{}Params", + self.handler.ident().to_string().to_case(Case::Pascal) + ), + Span::call_site(), + ) + } + + fn result_type(&self) -> Type { + self.handler.result().clone() + } + + fn handler_func_ident(&self) -> Ident { + self.handler.ident().clone() + } + + fn invocation_func_ident(&self) -> Ident { + Ident::new( + &format!("__{}", self.handler_func_ident()), + Span::call_site(), + ) + } + + fn is_query(&self) -> bool { + self.handler + .receiver() + .map_or(true, |r| r.mutability.is_none()) + } + + fn params_struct(&self) -> TokenStream { + let params_struct_ident = self.params_struct_ident(); + let params_struct_members = self.handler.params().iter().map(|item| { + let arg_ident = item.0; + let arg_type = item.1; + quote!(#arg_ident: #arg_type) + }); + + quote!( + pub struct #params_struct_ident { + #(#params_struct_members),* + } + ) + } + + fn invocation_func(&self) -> TokenStream { + let invocation_func_ident = self.invocation_func_ident(); + let receiver = self.handler.receiver(); + let params_struct_ident = self.params_struct_ident(); + let handler_func_ident = self.handler_func_ident(); + let handler_func_params = self.handler.params().iter().map(|item| { + let param_ident = item.0; + quote!(request.#param_ident) + }); + + let await_token = self.handler.is_async().then(|| quote!(.await)); + + quote!( + async fn #invocation_func_ident(#receiver, mut input: &[u8]) -> Vec + { + let request = #params_struct_ident::decode(&mut input).expect("Failed to decode request"); + let result = self.#handler_func_ident(#(#handler_func_params),*)#await_token; + return result.encode(); + } + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use quote::ToTokens; + + #[test] + fn events_type_is_discovered_via_where_clause() { + let input = quote! { + impl SomeService where T: EventTrigger { + pub async fn do_this(&mut self) -> u32 { + 42 + } + } + }; + let service_impl = syn::parse2(input).unwrap(); + let item_type = ImplType::new(&service_impl); + + let result = discover_service_events_type(item_type.constraints().unwrap()); + + assert_eq!( + result.unwrap().to_token_stream().to_string(), + quote!(events::SomeEvents).to_string() + ); + } +} diff --git a/macros/core/tests/program.rs b/macros/core/tests/gprogram.rs similarity index 86% rename from macros/core/tests/program.rs rename to macros/core/tests/gprogram.rs index e1eb5531..b93ef1ad 100644 --- a/macros/core/tests/program.rs +++ b/macros/core/tests/gprogram.rs @@ -2,7 +2,7 @@ use quote::quote; use sails_macros_core::__gprogram_internal as gprogram; #[test] -fn gprogram_generates_init_for_single_ctor() { +fn generates_init_for_single_ctor() { let input = quote! { impl MyProgram { pub async fn new(p1: u32, p2: String) -> Self { @@ -18,7 +18,7 @@ fn gprogram_generates_init_for_single_ctor() { } #[test] -fn gprogram_generates_init_for_multiple_ctors() { +fn generates_init_for_multiple_ctors() { let input = quote! { impl MyProgram { pub async fn new(p1: u32, p2: String) -> Self { @@ -38,7 +38,7 @@ fn gprogram_generates_init_for_multiple_ctors() { } #[test] -fn gprogram_generates_init_for_no_ctor() { +fn generates_init_for_no_ctor() { let input = quote! { impl MyProgram { } @@ -51,7 +51,7 @@ fn gprogram_generates_init_for_no_ctor() { } #[test] -fn gprogram_generates_handle_for_single_service_with_non_empty_route() { +fn generates_handle_for_single_service_with_non_empty_route() { let input = quote! { impl MyProgram { pub fn service(&self) -> MyService { @@ -67,7 +67,7 @@ fn gprogram_generates_handle_for_single_service_with_non_empty_route() { } #[test] -fn gprogram_generates_handle_for_multiple_services_with_non_empty_routes() { +fn generates_handle_for_multiple_services_with_non_empty_routes() { let input = quote! { impl MyProgram { #[groute("svc1")] diff --git a/macros/core/tests/service.rs b/macros/core/tests/gservice.rs similarity index 55% rename from macros/core/tests/service.rs rename to macros/core/tests/gservice.rs index 311ef9e7..7f478161 100644 --- a/macros/core/tests/service.rs +++ b/macros/core/tests/gservice.rs @@ -1,8 +1,9 @@ +use proc_macro2::TokenStream; use quote::quote; use sails_macros_core::__gservice_internal as gservice; #[test] -fn gservice_works() { +fn works_with_basics() { let input = quote! { impl SomeService { pub async fn do_this(&mut self, p1: u32, p2: String) -> u32 { @@ -15,14 +16,14 @@ fn gservice_works() { } }; - let result = gservice(input).to_string(); + let result = gservice(TokenStream::new(), input).to_string(); let result = prettyplease::unparse(&syn::parse_str(&result).unwrap()); insta::assert_snapshot!(result); } #[test] -fn gservice_works_for_lifetimes_and_generics() { +fn works_with_lifetimes_and_generics() { let input = quote! { impl<'a, 'b, T, TEventTrigger> SomeService<'a, 'b, T, TEventTrigger> where T : Clone, @@ -33,7 +34,27 @@ fn gservice_works_for_lifetimes_and_generics() { } }; - let result = gservice(input).to_string(); + let result = gservice(TokenStream::new(), input).to_string(); + let result = prettyplease::unparse(&syn::parse_str(&result).unwrap()); + + insta::assert_snapshot!(result); +} + +#[test] +fn works_with_extends() { + let args = quote! { + extends = [ExtendedService1, ExtendedService2], + //arg42 = "Hello, World!" + }; + let input = quote! { + impl SomeService { + pub fn do_this(&mut self) -> u32 { + 42 + } + } + }; + + let result = gservice(args, input).to_string(); let result = prettyplease::unparse(&syn::parse_str(&result).unwrap()); insta::assert_snapshot!(result); diff --git a/macros/core/tests/snapshots/program__gprogram_generates_handle_for_multiple_services_with_empty_and_non_empty_routes.snap b/macros/core/tests/snapshots/gprogram__generates_handle_for_multiple_services_with_empty_and_non_empty_routes.snap similarity index 98% rename from macros/core/tests/snapshots/program__gprogram_generates_handle_for_multiple_services_with_empty_and_non_empty_routes.snap rename to macros/core/tests/snapshots/gprogram__generates_handle_for_multiple_services_with_empty_and_non_empty_routes.snap index b213e6e7..df9d880d 100644 --- a/macros/core/tests/snapshots/program__gprogram_generates_handle_for_multiple_services_with_empty_and_non_empty_routes.snap +++ b/macros/core/tests/snapshots/gprogram__generates_handle_for_multiple_services_with_empty_and_non_empty_routes.snap @@ -1,5 +1,5 @@ --- -source: macros/core/tests/program.rs +source: macros/core/tests/gprogram.rs expression: result --- impl MyProgram { diff --git a/macros/core/tests/snapshots/program__gprogram_generates_handle_for_multiple_services_with_non_empty_routes.snap b/macros/core/tests/snapshots/gprogram__generates_handle_for_multiple_services_with_non_empty_routes.snap similarity index 99% rename from macros/core/tests/snapshots/program__gprogram_generates_handle_for_multiple_services_with_non_empty_routes.snap rename to macros/core/tests/snapshots/gprogram__generates_handle_for_multiple_services_with_non_empty_routes.snap index 774e6dc5..904fc416 100644 --- a/macros/core/tests/snapshots/program__gprogram_generates_handle_for_multiple_services_with_non_empty_routes.snap +++ b/macros/core/tests/snapshots/gprogram__generates_handle_for_multiple_services_with_non_empty_routes.snap @@ -1,5 +1,5 @@ --- -source: macros/core/tests/program.rs +source: macros/core/tests/gprogram.rs expression: result --- static __ROUTE_SERVICE2: [u8; 9usize] = [ diff --git a/macros/core/tests/snapshots/program__gprogram_generates_handle_for_single_service_with_empty_route.snap b/macros/core/tests/snapshots/gprogram__generates_handle_for_single_service_with_empty_route.snap similarity index 98% rename from macros/core/tests/snapshots/program__gprogram_generates_handle_for_single_service_with_empty_route.snap rename to macros/core/tests/snapshots/gprogram__generates_handle_for_single_service_with_empty_route.snap index 34f92c16..3acf1c10 100644 --- a/macros/core/tests/snapshots/program__gprogram_generates_handle_for_single_service_with_empty_route.snap +++ b/macros/core/tests/snapshots/gprogram__generates_handle_for_single_service_with_empty_route.snap @@ -1,5 +1,5 @@ --- -source: macros/core/tests/program.rs +source: macros/core/tests/gprogram.rs expression: result --- impl MyProgram { diff --git a/macros/core/tests/snapshots/program__gprogram_generates_handle_for_single_service_with_non_empty_route.snap b/macros/core/tests/snapshots/gprogram__generates_handle_for_single_service_with_non_empty_route.snap similarity index 98% rename from macros/core/tests/snapshots/program__gprogram_generates_handle_for_single_service_with_non_empty_route.snap rename to macros/core/tests/snapshots/gprogram__generates_handle_for_single_service_with_non_empty_route.snap index d325d30a..eac2600b 100644 --- a/macros/core/tests/snapshots/program__gprogram_generates_handle_for_single_service_with_non_empty_route.snap +++ b/macros/core/tests/snapshots/gprogram__generates_handle_for_single_service_with_non_empty_route.snap @@ -1,5 +1,5 @@ --- -source: macros/core/tests/program.rs +source: macros/core/tests/gprogram.rs expression: result --- static __ROUTE_SERVICE: [u8; 8usize] = [ diff --git a/macros/core/tests/snapshots/program__gprogram_generates_init_for_multiple_ctors.snap b/macros/core/tests/snapshots/gprogram__generates_init_for_multiple_ctors.snap similarity index 99% rename from macros/core/tests/snapshots/program__gprogram_generates_init_for_multiple_ctors.snap rename to macros/core/tests/snapshots/gprogram__generates_init_for_multiple_ctors.snap index 63a95613..f4520394 100644 --- a/macros/core/tests/snapshots/program__gprogram_generates_init_for_multiple_ctors.snap +++ b/macros/core/tests/snapshots/gprogram__generates_init_for_multiple_ctors.snap @@ -1,5 +1,5 @@ --- -source: macros/core/tests/program.rs +source: macros/core/tests/gprogram.rs expression: result --- impl MyProgram { diff --git a/macros/core/tests/snapshots/program__gprogram_generates_init_for_no_ctor.snap b/macros/core/tests/snapshots/gprogram__generates_init_for_no_ctor.snap similarity index 98% rename from macros/core/tests/snapshots/program__gprogram_generates_init_for_no_ctor.snap rename to macros/core/tests/snapshots/gprogram__generates_init_for_no_ctor.snap index e327814d..ba133e65 100644 --- a/macros/core/tests/snapshots/program__gprogram_generates_init_for_no_ctor.snap +++ b/macros/core/tests/snapshots/gprogram__generates_init_for_no_ctor.snap @@ -1,5 +1,5 @@ --- -source: macros/core/tests/program.rs +source: macros/core/tests/gprogram.rs expression: result --- impl MyProgram {} diff --git a/macros/core/tests/snapshots/program__gprogram_generates_init_for_single_ctor.snap b/macros/core/tests/snapshots/gprogram__generates_init_for_single_ctor.snap similarity index 98% rename from macros/core/tests/snapshots/program__gprogram_generates_init_for_single_ctor.snap rename to macros/core/tests/snapshots/gprogram__generates_init_for_single_ctor.snap index 90bf5923..99fbeb78 100644 --- a/macros/core/tests/snapshots/program__gprogram_generates_init_for_single_ctor.snap +++ b/macros/core/tests/snapshots/gprogram__generates_init_for_single_ctor.snap @@ -1,5 +1,5 @@ --- -source: macros/core/tests/program.rs +source: macros/core/tests/gprogram.rs expression: result --- impl MyProgram { diff --git a/macros/core/tests/snapshots/service__gservice_works.snap b/macros/core/tests/snapshots/gservice__works_with_basics.snap similarity index 76% rename from macros/core/tests/snapshots/service__gservice_works.snap rename to macros/core/tests/snapshots/gservice__works_with_basics.snap index 00581cc6..eedd5e62 100644 --- a/macros/core/tests/snapshots/service__gservice_works.snap +++ b/macros/core/tests/snapshots/gservice__works_with_basics.snap @@ -1,5 +1,5 @@ --- -source: macros/core/tests/service.rs +source: macros/core/tests/gservice.rs expression: result --- impl SomeService { @@ -25,6 +25,27 @@ impl Exposure { self.inner.this(p1) } pub async fn handle(&mut self, mut input: &[u8]) -> Vec { + self.try_handle(input) + .await + .unwrap_or_else(|| { + { + let mut __input = input; + let input = String::decode(&mut __input) + .unwrap_or_else(|_| { + if input.len() <= 8 { + format!("0x{}", sails_rtl::hex::encode(input)) + } else { + format!( + "0x{}..{}", sails_rtl::hex::encode(& input[..4]), + sails_rtl::hex::encode(& input[input.len() - 4..]) + ) + } + }); + panic!("Unknown request: {}", input) + } + }) + } + pub async fn try_handle(&mut self, input: &[u8]) -> Option> { if input.starts_with(&[24u8, 68u8, 111u8, 84u8, 104u8, 105u8, 115u8]) { let output = self.__do_this(&input[7usize..]).await; static INVOCATION_ROUTE: [u8; 7usize] = [ @@ -36,28 +57,14 @@ impl Exposure { 105u8, 115u8, ]; - return [INVOCATION_ROUTE.as_ref(), &output].concat(); + return Some([INVOCATION_ROUTE.as_ref(), &output].concat()); } if input.starts_with(&[16u8, 84u8, 104u8, 105u8, 115u8]) { let output = self.__this(&input[5usize..]).await; static INVOCATION_ROUTE: [u8; 5usize] = [16u8, 84u8, 104u8, 105u8, 115u8]; - return [INVOCATION_ROUTE.as_ref(), &output].concat(); - } - { - let mut __input = input; - let input = String::decode(&mut __input) - .unwrap_or_else(|_| { - if input.len() <= 8 { - format!("0x{}", sails_rtl::hex::encode(input)) - } else { - format!( - "0x{}..{}", sails_rtl::hex::encode(& input[..4]), - sails_rtl::hex::encode(& input[input.len() - 4..]) - ) - } - }); - panic!("Unknown request: {}", input) + return Some([INVOCATION_ROUTE.as_ref(), &output].concat()); } + None } async fn __do_this(&mut self, mut input: &[u8]) -> Vec { let request = __DoThisParams::decode(&mut input) @@ -104,6 +111,9 @@ impl sails_rtl::meta::ServiceMeta for SomeService { fn events() -> sails_rtl::scale_info::MetaType { sails_rtl::scale_info::MetaType::new::() } + fn base_services() -> impl Iterator { + [].into_iter() + } } use sails_rtl::Decode as __ServiceDecode; use sails_rtl::Encode as __ServiceEncode; diff --git a/macros/core/tests/snapshots/gservice__works_with_extends.snap b/macros/core/tests/snapshots/gservice__works_with_extends.snap new file mode 100644 index 00000000..d6a96d21 --- /dev/null +++ b/macros/core/tests/snapshots/gservice__works_with_extends.snap @@ -0,0 +1,157 @@ +--- +source: macros/core/tests/gservice.rs +expression: result +--- +impl SomeService { + pub fn do_this(&mut self) -> u32 { + 42 + } +} +pub struct Exposure { + message_id: sails_rtl::MessageId, + route: &'static [u8], + inner: T, + base_0: ::Exposure, + base_1: ::Exposure, +} +impl Exposure { + pub fn do_this(&mut self) -> u32 { + let exposure_scope = sails_rtl::gstd::services::ExposureCallScope::new(self); + self.inner.do_this() + } + pub async fn handle(&mut self, mut input: &[u8]) -> Vec { + self.try_handle(input) + .await + .unwrap_or_else(|| { + { + let mut __input = input; + let input = String::decode(&mut __input) + .unwrap_or_else(|_| { + if input.len() <= 8 { + format!("0x{}", sails_rtl::hex::encode(input)) + } else { + format!( + "0x{}..{}", sails_rtl::hex::encode(& input[..4]), + sails_rtl::hex::encode(& input[input.len() - 4..]) + ) + } + }); + panic!("Unknown request: {}", input) + } + }) + } + pub async fn try_handle(&mut self, input: &[u8]) -> Option> { + if input.starts_with(&[24u8, 68u8, 111u8, 84u8, 104u8, 105u8, 115u8]) { + let output = self.__do_this(&input[7usize..]).await; + static INVOCATION_ROUTE: [u8; 7usize] = [ + 24u8, + 68u8, + 111u8, + 84u8, + 104u8, + 105u8, + 115u8, + ]; + return Some([INVOCATION_ROUTE.as_ref(), &output].concat()); + } + if let Some(output) = self.base_0.try_handle(input).await { + return Some(output); + } + if let Some(output) = self.base_1.try_handle(input).await { + return Some(output); + } + None + } + async fn __do_this(&mut self, mut input: &[u8]) -> Vec { + let request = __DoThisParams::decode(&mut input) + .expect("Failed to decode request"); + let result = self.do_this(); + return result.encode(); + } +} +impl sails_rtl::gstd::services::Exposure for Exposure { + fn message_id(&self) -> sails_rtl::MessageId { + self.message_id + } + fn route(&self) -> &'static [u8] { + self.route + } +} +impl AsRef<::Exposure> +for Exposure { + fn as_ref( + &self, + ) -> &::Exposure { + &self.base_0 + } +} +impl AsRef<::Exposure> +for Exposure { + fn as_ref( + &self, + ) -> &::Exposure { + &self.base_1 + } +} +impl sails_rtl::gstd::services::Service for SomeService { + type Exposure = Exposure; + fn expose( + self, + message_id: sails_rtl::MessageId, + route: &'static [u8], + ) -> Self::Exposure { + Self::Exposure { + message_id, + route, + base_0: ::clone( + AsRef::::as_ref(&self), + ) + .expose(message_id, route), + base_1: ::clone( + AsRef::::as_ref(&self), + ) + .expose(message_id, route), + inner: self, + } + } +} +impl sails_rtl::meta::ServiceMeta for SomeService { + fn commands() -> sails_rtl::scale_info::MetaType { + sails_rtl::scale_info::MetaType::new::() + } + fn queries() -> sails_rtl::scale_info::MetaType { + sails_rtl::scale_info::MetaType::new::() + } + fn events() -> sails_rtl::scale_info::MetaType { + sails_rtl::scale_info::MetaType::new::() + } + fn base_services() -> impl Iterator { + [ + sails_rtl::meta::AnyServiceMeta::new::(), + sails_rtl::meta::AnyServiceMeta::new::(), + ] + .into_iter() + } +} +use sails_rtl::Decode as __ServiceDecode; +use sails_rtl::Encode as __ServiceEncode; +use sails_rtl::TypeInfo as __ServiceTypeInfo; +#[derive(__ServiceDecode, __ServiceTypeInfo)] +#[codec(crate = sails_rtl::scale_codec)] +#[scale_info(crate = sails_rtl::scale_info)] +pub struct __DoThisParams {} +mod meta { + use super::*; + #[derive(__ServiceTypeInfo)] + #[scale_info(crate = sails_rtl::scale_info)] + pub enum CommandsMeta { + DoThis(__DoThisParams, u32), + } + #[derive(__ServiceTypeInfo)] + #[scale_info(crate = sails_rtl::scale_info)] + pub enum QueriesMeta {} + #[derive(__ServiceTypeInfo)] + #[scale_info(crate = sails_rtl::scale_info)] + pub enum NoEvents {} + pub type EventsMeta = NoEvents; +} diff --git a/macros/core/tests/snapshots/service__gservice_works_for_lifetimes_and_generics.snap b/macros/core/tests/snapshots/gservice__works_with_lifetimes_and_generics.snap similarity index 75% rename from macros/core/tests/snapshots/service__gservice_works_for_lifetimes_and_generics.snap rename to macros/core/tests/snapshots/gservice__works_with_lifetimes_and_generics.snap index ea4ec331..6af1104b 100644 --- a/macros/core/tests/snapshots/service__gservice_works_for_lifetimes_and_generics.snap +++ b/macros/core/tests/snapshots/gservice__works_with_lifetimes_and_generics.snap @@ -1,5 +1,5 @@ --- -source: macros/core/tests/service.rs +source: macros/core/tests/gservice.rs expression: result --- impl<'a, 'b, T, TEventTrigger> SomeService<'a, 'b, T, TEventTrigger> @@ -26,6 +26,27 @@ where self.inner.do_this() } pub async fn handle(&mut self, mut input: &[u8]) -> Vec { + self.try_handle(input) + .await + .unwrap_or_else(|| { + { + let mut __input = input; + let input = String::decode(&mut __input) + .unwrap_or_else(|_| { + if input.len() <= 8 { + format!("0x{}", sails_rtl::hex::encode(input)) + } else { + format!( + "0x{}..{}", sails_rtl::hex::encode(& input[..4]), + sails_rtl::hex::encode(& input[input.len() - 4..]) + ) + } + }); + panic!("Unknown request: {}", input) + } + }) + } + pub async fn try_handle(&mut self, input: &[u8]) -> Option> { if input.starts_with(&[24u8, 68u8, 111u8, 84u8, 104u8, 105u8, 115u8]) { let output = self.__do_this(&input[7usize..]).await; static INVOCATION_ROUTE: [u8; 7usize] = [ @@ -37,23 +58,9 @@ where 105u8, 115u8, ]; - return [INVOCATION_ROUTE.as_ref(), &output].concat(); - } - { - let mut __input = input; - let input = String::decode(&mut __input) - .unwrap_or_else(|_| { - if input.len() <= 8 { - format!("0x{}", sails_rtl::hex::encode(input)) - } else { - format!( - "0x{}..{}", sails_rtl::hex::encode(& input[..4]), - sails_rtl::hex::encode(& input[input.len() - 4..]) - ) - } - }); - panic!("Unknown request: {}", input) + return Some([INVOCATION_ROUTE.as_ref(), &output].concat()); } + None } async fn __do_this(&mut self, mut input: &[u8]) -> Vec { let request = __DoThisParams::decode(&mut input) @@ -109,6 +116,9 @@ where fn events() -> sails_rtl::scale_info::MetaType { sails_rtl::scale_info::MetaType::new::() } + fn base_services() -> impl Iterator { + [].into_iter() + } } use sails_rtl::Decode as __ServiceDecode; use sails_rtl::Encode as __ServiceEncode; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 234c8db8..f4082d1b 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -23,18 +23,18 @@ use proc_macro_error::proc_macro_error; #[proc_macro_error] #[proc_macro_attribute] -pub fn gservice(_attrs: TokenStream, impl_tokens: TokenStream) -> TokenStream { - sails_macros_core::gservice(impl_tokens.into()).into() +pub fn gservice(args: TokenStream, impl_tokens: TokenStream) -> TokenStream { + sails_macros_core::gservice(args.into(), impl_tokens.into()).into() } #[proc_macro_error] #[proc_macro_attribute] -pub fn gprogram(_attrs: TokenStream, impl_tokens: TokenStream) -> TokenStream { +pub fn gprogram(_args: TokenStream, impl_tokens: TokenStream) -> TokenStream { sails_macros_core::gprogram(impl_tokens.into()).into() } #[proc_macro_error] #[proc_macro_attribute] -pub fn groute(attrs: TokenStream, impl_item_fn_tokens: TokenStream) -> TokenStream { - sails_macros_core::groute(attrs.into(), impl_item_fn_tokens.into()).into() +pub fn groute(args: TokenStream, impl_item_fn_tokens: TokenStream) -> TokenStream { + sails_macros_core::groute(args.into(), impl_item_fn_tokens.into()).into() } diff --git a/macros/tests/ui/gservice_fails_no_handlers_found.rs b/macros/tests/ui/gservice_fails_no_handlers_or_extends_found.rs similarity index 100% rename from macros/tests/ui/gservice_fails_no_handlers_found.rs rename to macros/tests/ui/gservice_fails_no_handlers_or_extends_found.rs diff --git a/macros/tests/ui/gservice_fails_no_handlers_found.stderr b/macros/tests/ui/gservice_fails_no_handlers_or_extends_found.stderr similarity index 51% rename from macros/tests/ui/gservice_fails_no_handlers_found.stderr rename to macros/tests/ui/gservice_fails_no_handlers_or_extends_found.stderr index 39751bc1..5c39bc6e 100644 --- a/macros/tests/ui/gservice_fails_no_handlers_found.stderr +++ b/macros/tests/ui/gservice_fails_no_handlers_or_extends_found.stderr @@ -1,5 +1,5 @@ -error: `gservice` attribute requires impl to define at least one public method - --> tests/ui/gservice_fails_no_handlers_found.rs:6:1 +error: `gservice` attribute requires impl to define at least one public method or extend another service + --> tests/ui/gservice_fails_no_handlers_or_extends_found.rs:6:1 | 6 | impl MyService {} | ^^^^^^^^^^^^^^^^^ diff --git a/macros/tests/ui/gservice_works.rs b/macros/tests/ui/gservice_works_with_basics.rs similarity index 100% rename from macros/tests/ui/gservice_works.rs rename to macros/tests/ui/gservice_works_with_basics.rs diff --git a/macros/tests/ui/gservice_works_with_extends.rs b/macros/tests/ui/gservice_works_with_extends.rs new file mode 100644 index 00000000..c654a0e6 --- /dev/null +++ b/macros/tests/ui/gservice_works_with_extends.rs @@ -0,0 +1,105 @@ +use sails_rtl::{ + gstd::{gservice, services::Service}, + Decode, Encode, +}; + +mod base { + use super::*; + + pub const BASE_NAME_RESULT: &str = "base-name"; + pub const NAME_RESULT: &str = "base"; + + #[derive(Clone)] + pub struct Base; + + #[gservice] + impl Base { + pub fn base_name(&self) -> String { + "base-name".to_string() + } + + pub fn name(&self) -> String { + "base".to_string() + } + } +} + +mod extended { + use super::*; + + pub const EXTENDED_NAME_RESULT: &str = "extended-name"; + pub const NAME_RESULT: &str = "extended"; + + pub struct Extended { + base: base::Base, + } + + impl Extended { + pub fn new(base: base::Base) -> Self { + Self { base } + } + } + + #[gservice(extends = base::Base)] + impl Extended { + pub fn extended_name(&self) -> String { + "extended-name".to_string() + } + + pub fn name(&self) -> String { + "extended".to_string() + } + } + + impl AsRef for Extended { + fn as_ref(&self) -> &base::Base { + &self.base + } + } +} + +#[tokio::main] +async fn main() { + const NAME_METHOD: &str = "Name"; + const BASE_NAME_METHOD: &str = "BaseName"; + const EXTENDED_NAME_METHOD: &str = "ExtendedName"; + + let mut extended_svc = extended::Extended::new(base::Base).expose(123.into(), &[1, 2, 3]); + + let output = extended_svc.handle(&EXTENDED_NAME_METHOD.encode()).await; + + assert_eq!( + output, + [ + EXTENDED_NAME_METHOD.encode(), + extended::EXTENDED_NAME_RESULT.encode() + ] + .concat() + ); + + let _base: &::Exposure = extended_svc.as_ref(); + + let output = extended_svc.handle(&BASE_NAME_METHOD.encode()).await; + let mut output = output.as_slice(); + let func_name = String::decode(&mut output).unwrap(); + assert_eq!(func_name, BASE_NAME_METHOD); + + let result = String::decode(&mut output).unwrap(); + assert_eq!(result, base::BASE_NAME_RESULT); + + let output = extended_svc.handle(&EXTENDED_NAME_METHOD.encode()).await; + let mut output = output.as_slice(); + let func_name = String::decode(&mut output).unwrap(); + assert_eq!(func_name, EXTENDED_NAME_METHOD); + + let result = String::decode(&mut output).unwrap(); + assert_eq!(result, extended::EXTENDED_NAME_RESULT); + + let output = extended_svc.handle(&NAME_METHOD.encode()).await; + let mut output = output.as_slice(); + let func_name = String::decode(&mut output).unwrap(); + assert_eq!(func_name, NAME_METHOD); + + let result = String::decode(&mut output).unwrap(); + assert_eq!(result, extended::NAME_RESULT); +} diff --git a/macros/tests/ui/gservice_works_for_lifecycles_and_generics.rs b/macros/tests/ui/gservice_works_with_lifecycles_and_generics.rs similarity index 100% rename from macros/tests/ui/gservice_works_for_lifecycles_and_generics.rs rename to macros/tests/ui/gservice_works_with_lifecycles_and_generics.rs diff --git a/rtl/src/lib.rs b/rtl/src/lib.rs index 7b020f04..b71a7462 100644 --- a/rtl/src/lib.rs +++ b/rtl/src/lib.rs @@ -12,18 +12,21 @@ pub mod prelude; mod types; pub mod meta { + use crate::Vec; use scale_info::MetaType; pub trait ServiceMeta { fn commands() -> MetaType; fn queries() -> MetaType; fn events() -> MetaType; + fn base_services() -> impl Iterator; } pub struct AnyServiceMeta { commands: MetaType, queries: MetaType, events: MetaType, + base_services: Vec, } impl AnyServiceMeta { @@ -32,6 +35,7 @@ pub mod meta { commands: S::commands(), queries: S::queries(), events: S::events(), + base_services: S::base_services().collect(), } } @@ -46,6 +50,10 @@ pub mod meta { pub fn events(&self) -> &MetaType { &self.events } + + pub fn base_services(&self) -> impl Iterator { + self.base_services.iter() + } } pub trait ProgramMeta {