use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, GenericArgument, ItemImpl, PathArguments, Type};
pub fn handler_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
let item_impl = parse_macro_input!(item as ItemImpl);
let handler_ty = &item_impl.self_ty;
let use_inject = !attr.is_empty();
let (req_ty_opt, rsp_ty_opt) = extract_handler_types(&item_impl);
let handler_ty_name = extract_type_name(handler_ty);
let default_type = syn::parse_str::<Type>("()").unwrap();
let req_ty = req_ty_opt.unwrap_or(&default_type);
let rsp_ty = rsp_ty_opt.unwrap_or(&default_type);
let req_ty_name = type_to_string(req_ty);
let factory_fn = format_ident!("__lrwf_factory_{}", handler_ty_name.replace("::", "_"));
let call_fn = format_ident!("__lrwf_call_{}", handler_ty_name.replace("::", "_"));
let factory_body = if use_inject {
let constructor_fn =
format_ident!("__rdi_construct_{}", handler_ty_name.replace("::", "_"));
quote! {
let arc: ::std::sync::Arc<#handler_ty> = #constructor_fn(resolver);
match ::std::sync::Arc::try_unwrap(arc) {
Ok(owned) => Ok(::std::boxed::Box::new(owned) as ::std::boxed::Box<dyn ::std::any::Any + Send>),
Err(_) => Err(::rust_webx::Error::Internal(
format!("handler Arc must be uniquely owned after fresh construction: {}", #handler_ty_name)
)),
}
}
} else {
quote! {
Ok(::std::boxed::Box::new(<#handler_ty as ::std::default::Default>::default())
as ::std::boxed::Box<dyn ::std::any::Any + Send>)
}
};
let expanded = quote! {
#item_impl
#[doc(hidden)]
fn #factory_fn(
resolver: &dyn ::rust_webx::rust_dix::IServiceResolver,
) -> ::rust_webx::Result<::std::boxed::Box<dyn ::std::any::Any + Send>> {
#factory_body
}
#[doc(hidden)]
fn #call_fn(
handler: ::std::boxed::Box<dyn ::std::any::Any + Send>,
request: ::std::boxed::Box<dyn ::std::any::Any + Send>,
) -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ::rust_webx::Result<::std::boxed::Box<dyn ::std::any::Any + Send>>> + Send>> {
Box::pin(async move {
let mut h = *handler
.downcast::<#handler_ty>()
.map_err(|_| ::rust_webx::Error::Internal(
format!("Handler downcast failed for: {}", #handler_ty_name)
))?;
let req = *request
.downcast::<#req_ty>()
.map_err(|_| ::rust_webx::Error::Internal(
format!("Request downcast failed for: {}", #req_ty_name)
))?;
let result: #rsp_ty = h.handle(req).await?;
Ok(::std::boxed::Box::new(result) as ::std::boxed::Box<dyn ::std::any::Any + Send>)
})
}
::inventory::submit! {
::rust_webx::HandlerRegistration {
req_type_id: ::std::any::TypeId::of::<#req_ty>(),
req_type_name: #req_ty_name,
factory: #factory_fn,
call: #call_fn,
}
}
};
TokenStream::from(expanded)
}
fn extract_handler_types(item_impl: &ItemImpl) -> (Option<&Type>, Option<&Type>) {
let (_, path, _) = match &item_impl.trait_ {
Some(t) => t,
None => return (None, None),
};
let last_seg = match path.segments.last() {
Some(s) => s,
None => return (None, None),
};
match &last_seg.arguments {
PathArguments::AngleBracketed(args) => {
let req = match args.args.first() {
Some(GenericArgument::Type(t)) => Some(t),
_ => None,
};
let rsp = match args.args.get(1) {
Some(GenericArgument::Type(t)) => Some(t),
_ => None,
};
(req, rsp)
}
_ => (None, None),
}
}
fn extract_type_name(ty: &Type) -> String {
match ty {
Type::Path(type_path) => type_path
.path
.segments
.iter()
.map(|seg| seg.ident.to_string())
.collect::<Vec<_>>()
.join("_"),
_ => "UnknownType".to_string(),
}
}
fn type_to_string(ty: &Type) -> String {
match ty {
Type::Path(tp) => tp
.path
.segments
.iter()
.map(|s| s.ident.to_string())
.collect::<Vec<_>>()
.join("::"),
_ => format!("{}", quote! { #ty }),
}
}