mod handler;
use handler::find_handler_methods;
use proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, ItemImpl, parse_macro_input};
#[proc_macro_derive(Bean)]
pub fn derive_bean(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let expanded = quote! {
impl camel_bean::BeanProcessor for #name {
async fn call(
&self,
method: &str,
exchange: &mut camel_api::Exchange,
) -> Result<(), camel_api::CamelError> {
unimplemented!("Bean derive macro requires impl block with #[handler] methods")
}
fn methods(&self) -> Vec<&'static str> {
vec![]
}
}
};
TokenStream::from(expanded)
}
#[proc_macro_attribute]
pub fn handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[proc_macro_attribute]
pub fn bean_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemImpl);
match bean_impl_gen(input) {
Ok(tokens) => tokens,
Err(err) => err.to_compile_error().into(),
}
}
fn bean_impl_gen(item: ItemImpl) -> Result<TokenStream, syn::Error> {
let self_ty = &item.self_ty;
let handlers = find_handler_methods(&item.items)?;
if handlers.is_empty() {
return Err(syn::Error::new_spanned(
self_ty,
"No #[handler] methods found in impl block",
));
}
let match_arms: Vec<_> = handlers
.iter()
.map(|handler| {
let method_name = &handler.name;
let (param_extraction, method_call) = generate_handler_invocation(handler)?;
let result_handling = generate_result_handling(handler)?;
Ok(quote! {
#method_name => {
#param_extraction
let result = #method_call;
#result_handling
}
})
})
.collect::<Result<Vec<_>, syn::Error>>()?;
let method_names: Vec<_> = handlers.iter().map(|h| h.name.as_str()).collect();
let expanded = quote! {
#item
#[::camel_bean::async_trait]
impl ::camel_bean::BeanProcessor for #self_ty {
async fn call(
&self,
method: &str,
exchange: &mut ::camel_api::Exchange,
) -> Result<(), ::camel_api::CamelError> {
match method {
#(#match_arms)*
_ => Err(::camel_api::CamelError::ProcessorError(
format!("Method '{}' not found", method)
))
}
}
fn methods(&self) -> Vec<&'static str> {
vec![#(#method_names),*]
}
}
};
Ok(TokenStream::from(expanded))
}
fn generate_handler_invocation(
handler: &handler::HandlerMethod,
) -> Result<(proc_macro2::TokenStream, proc_macro2::TokenStream), syn::Error> {
let method_ident = &handler.ident;
let mut params = Vec::new();
let mut extraction = Vec::new();
if let Some(body_type) = &handler.body_type {
extraction.push(quote! {
let body_json = match &exchange.input.body {
::camel_api::Body::Json(value) => value.clone(),
_ => return Err(::camel_api::CamelError::TypeConversionFailed(
"Expected JSON body".to_string()
)),
};
let body: #body_type = serde_json::from_value(body_json)
.map_err(|e| ::camel_api::CamelError::TypeConversionFailed(
format!("Failed to deserialize body: {}", e)
))?;
});
params.push(quote! { body });
}
if handler.has_headers {
extraction.push(quote! {
let headers = exchange.input.headers.clone();
});
params.push(quote! { headers });
}
if handler.has_exchange {
params.push(quote! { exchange });
}
let extraction_code = quote! { #(#extraction)* };
let method_call = quote! { self.#method_ident(#(#params),*).await };
Ok((extraction_code, method_call))
}
fn generate_result_handling(
handler: &handler::HandlerMethod,
) -> Result<proc_macro2::TokenStream, syn::Error> {
if handler.return_type.is_none() {
return Ok(quote! {
result.map_err(|e| ::camel_api::CamelError::ProcessorError(e.to_string()))?;
Ok(())
});
}
if handler.has_exchange && handler.body_type.is_none() {
return Ok(quote! {
result.map_err(|e| ::camel_api::CamelError::ProcessorError(e.to_string()))?;
Ok(())
});
}
Ok(quote! {
let value = result.map_err(|e| ::camel_api::CamelError::ProcessorError(e.to_string()))?;
exchange.input.body = ::camel_api::Body::Json(serde_json::to_value(value)
.map_err(|e| ::camel_api::CamelError::TypeConversionFailed(
format!("Failed to serialize result: {}", e)
))?);
Ok(())
})
}