use super::*;
use proc_macro2::TokenStream;
use quote::quote;
use std::collections::BTreeSet;
impl ServiceBuilder<'_> {
pub(super) fn meta_trait_impl(&self) -> TokenStream {
let sails_path = self.sails_path;
let generics = &self.generics;
let service_type_path = self.type_path;
let service_type_constraints = self.type_constraints();
let mut base_names = BTreeSet::new();
let base_services_meta = self.base_types.iter().map(|base_type| {
let path_wo_lifetimes = shared::remove_lifetimes(base_type);
let base_type_pathless_name = path_wo_lifetimes
.segments
.last()
.expect("Base service path should have at least one segment")
.ident
.to_string();
if !base_names.insert(base_type_pathless_name.clone()) {
abort!(
base_type,
"Base service with the same name was defined - `{}`",
base_type_pathless_name
);
}
quote! {
#sails_path::meta::BaseServiceMeta::new::< super:: #path_wo_lifetimes >( #base_type_pathless_name )
}
});
let has_async_handler = self
.service_handlers
.iter()
.any(|fn_builder| fn_builder.is_async());
let service_meta_asyncness = if has_async_handler {
quote!(true)
} else if self.base_types.is_empty() {
quote!(false)
} else {
let base_asyncness = self.base_types.iter().map(|base_type| {
let path_wo_lifetimes = shared::remove_lifetimes(base_type);
quote! {
<super:: #path_wo_lifetimes as #sails_path::meta::ServiceMeta>::ASYNC
}
});
quote!(#( #base_asyncness )||*)
};
let interface_id_computation = self.generate_interface_id();
let methods_meta = self.generate_methods_meta();
let override_validations = self.generate_override_validations();
quote! {
const __INTERFACE_ID: #sails_path::meta::InterfaceId = #interface_id_computation;
impl #generics #sails_path::meta::Identifiable for super:: #service_type_path #service_type_constraints {
const INTERFACE_ID: #sails_path::meta::InterfaceId = __INTERFACE_ID;
}
impl #generics #sails_path::meta::ServiceMeta for super:: #service_type_path #service_type_constraints {
type CommandsMeta = CommandsMeta;
type QueriesMeta = QueriesMeta;
type EventsMeta = EventsMeta;
const BASE_SERVICES: &'static [#sails_path::meta::BaseServiceMeta] = &[
#( #base_services_meta ),*
];
const METHODS: &'static [#sails_path::meta::MethodMetadata] = &[
#( #methods_meta ),*
];
const ASYNC: bool = #service_meta_asyncness ;
}
#override_validations
}
}
pub(super) fn meta_module(&self) -> TokenStream {
let sails_path = self.sails_path;
let type_info_path = &sails_paths::type_info_path(sails_path);
let meta_module_ident = &self.meta_module_ident;
let no_events_type = Path::from(Ident::new("NoEvents", Span::call_site()));
let events_type = self.events_type.unwrap_or(&no_events_type);
let interface_id_ident = quote!(__INTERFACE_ID);
let invocation_params_structs = self
.service_handlers
.iter()
.map(|fn_builder| fn_builder.params_struct(&interface_id_ident));
let commands_meta_variants = self
.service_handlers
.iter()
.filter(|h| h.overrides.is_none())
.filter_map(|fn_builder| {
(!fn_builder.is_query()).then_some(fn_builder.handler_meta_variant())
});
let queries_meta_variants = self
.service_handlers
.iter()
.filter(|h| h.overrides.is_none())
.filter_map(|fn_builder| {
(fn_builder.is_query()).then_some(fn_builder.handler_meta_variant())
});
let meta_trait_impl = self.meta_trait_impl();
quote! {
mod #meta_module_ident {
use super::*;
#meta_trait_impl
#( #invocation_params_structs )*
#[derive(#sails_path::TypeInfo)]
#[type_info(crate = #type_info_path)]
pub enum CommandsMeta {
#(#commands_meta_variants),*
}
#[derive(#sails_path::TypeInfo)]
#[type_info(crate = #type_info_path)]
pub enum QueriesMeta {
#(#queries_meta_variants),*
}
#[derive(#sails_path::TypeInfo)]
#[type_info(crate = #type_info_path )]
pub enum #no_events_type {}
pub type EventsMeta = #events_type;
}
}
}
fn generate_interface_id(&self) -> TokenStream {
let sails_path = self.sails_path;
let fn_hash_computations: Vec<_> = self
.service_handlers
.iter()
.filter(|h| h.overrides.is_none())
.map(|handler| {
let fn_hash = FnHashBuilder::from_handler(handler, sails_path).build();
quote! {
final_hash = final_hash.update(& #fn_hash);
}
})
.collect();
let events_hash = if let Some(events_type) = self.events_type {
quote!(final_hash = final_hash.update(&<#events_type as #sails_path::sails_reflect_hash::ReflectHash>::HASH);)
} else {
quote!()
};
let base_services_hash = if !self.base_types.is_empty() {
let base_service_ids = self.base_types.iter().map(|base_type| {
let path_wo_lifetimes = shared::remove_lifetimes(base_type);
quote!(final_hash = final_hash.update(&<super:: #path_wo_lifetimes as #sails_path::meta::Identifiable>::INTERFACE_ID.0);)
});
quote!(#(#base_service_ids)*)
} else {
Default::default()
};
quote! {
{
let mut final_hash = #sails_path::keccak_const::Keccak256::new();
#(#fn_hash_computations)*
#events_hash
#base_services_hash
let hash = final_hash.finalize();
#sails_path::meta::InterfaceId::from_bytes_32(hash)
}
}
}
fn generate_methods_meta(&self) -> Vec<TokenStream> {
let sails_path = self.sails_path;
self.service_handlers
.iter()
.filter(|h| h.overrides.is_none())
.map(|handler| {
let name = &handler.route;
let entry_id = handler.entry_id;
let fn_hash = FnHashBuilder::from_handler(handler, sails_path).build();
let is_async = handler.is_async();
quote! {
#sails_path::meta::MethodMetadata {
name: #name,
entry_id: #entry_id,
hash: #fn_hash,
is_async: #is_async,
}
}
})
.collect()
}
fn generate_override_validations(&self) -> TokenStream {
let sails_path = self.sails_path;
let validations = self.service_handlers.iter().filter_map(|handler| {
handler.overrides.as_ref().map(|base_path| {
let name = &handler.route;
let entry_id_arg = if let Some(id) = handler.override_entry_id {
quote! { Some(#id) }
} else {
quote! { None }
};
let base_path_wo_lifetimes = shared::remove_lifetimes(base_path);
let current_fn_hash = FnHashBuilder::from_handler(handler, sails_path)
.with_override_name(quote!(base_name))
.build();
quote! {
const _: () = {
let base_methods = <super::#base_path_wo_lifetimes as #sails_path::meta::ServiceMeta>::METHODS;
if let Some(method) = #sails_path::meta::find_method_data(base_methods, #name, #entry_id_arg) {
let base_name = method.name;
if !#sails_path::meta::bytes32_eq(&method.hash, &#current_fn_hash) {
core::panic!(concat!("Override signature mismatch for method `", #name, "`"));
}
} else {
core::panic!(concat!("Method `", #name, "` not found in base service"));
}
};
}
})
});
quote! { #( #validations )* }
}
}
struct FnHashBuilder<'a> {
sails_path: &'a Path,
is_query: bool,
route: &'a str,
arg_types: &'a [&'a Type],
result_type: Type,
error_type: Option<Type>,
override_name: Option<TokenStream>,
}
impl<'a> FnHashBuilder<'a> {
fn from_handler(handler: &'a FnBuilder<'a>, sails_path: &'a Path) -> Self {
let (result_type, _) = handler.result_type_with_value();
let result_type = shared::replace_any_lifetime_with_static(result_type.clone());
let error_type = handler
.error_type
.as_ref()
.map(|et| shared::replace_any_lifetime_with_static(et.clone()));
Self {
sails_path,
is_query: handler.is_query(),
route: &handler.route,
arg_types: handler.params_types(),
result_type,
error_type,
override_name: None,
}
}
fn with_override_name(mut self, name: TokenStream) -> Self {
self.override_name = Some(name);
self
}
fn build(self) -> TokenStream {
let sails_path = self.sails_path;
let arg_types = self.arg_types;
let result_type = &self.result_type;
let result_tokens = if let Some(error_type) = &self.error_type {
quote!( -> #result_type | #error_type )
} else {
quote!( -> #result_type )
};
if let Some(name_expr) = self.override_name {
let kind = if self.is_query { "query" } else { "command" };
quote! {
#sails_path::hash_fn!( #kind #name_expr, ( #( #arg_types ),* ) #result_tokens )
}
} else {
let kind_ident = if self.is_query {
quote!(query)
} else {
quote!(command)
};
let name_ident = Ident::new(self.route, Span::call_site());
quote! {
#sails_path::hash_fn!( #kind_ident #name_ident ( #( #arg_types ),* ) #result_tokens )
}
}
}
}