Skip to main content

camel_bean_macros/
lib.rs

1mod handler;
2
3use handler::find_handler_methods;
4use proc_macro::TokenStream;
5use quote::quote;
6use syn::{DeriveInput, ItemImpl, parse_macro_input};
7
8/// Derive macro for BeanProcessor trait implementation
9/// This is a placeholder - Task 2.2 will complete the implementation
10#[proc_macro_derive(Bean)]
11pub fn derive_bean(input: TokenStream) -> TokenStream {
12    let input = parse_macro_input!(input as DeriveInput);
13
14    // TODO: Implementation in next task
15    let name = &input.ident;
16
17    let expanded = quote! {
18        // Placeholder implementation
19        impl camel_bean::BeanProcessor for #name {
20            async fn call(
21                &self,
22                method: &str,
23                exchange: &mut camel_api::Exchange,
24            ) -> Result<(), camel_api::CamelError> {
25                unimplemented!("Bean derive macro requires impl block with #[handler] methods")
26            }
27
28            fn methods(&self) -> Vec<&'static str> {
29                vec![]
30            }
31        }
32    };
33
34    TokenStream::from(expanded)
35}
36
37/// Marker attribute for handler methods
38/// This attribute does not transform the method - it's detected by the Bean derive macro
39/// to identify which methods should be exposed as bean handlers
40#[proc_macro_attribute]
41pub fn handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
42    // Pass through the method unchanged
43    // The Bean derive macro (or bean_impl attribute in Task 2.2) will detect this attribute
44    item
45}
46
47/// Attribute macro for generating BeanProcessor implementation from an impl block
48///
49/// # Example
50///
51/// ```ignore
52/// use camel_bean::bean_impl;
53/// use camel_bean::handler;
54///
55/// struct OrderService;
56///
57/// #[bean_impl]
58/// impl OrderService {
59///     #[handler]
60///     pub async fn process(&self, body: Order) -> Result<ProcessedOrder, String> {
61///         // Process order
62///         Ok(ProcessedOrder { ... })
63///     }
64/// }
65/// ```
66#[proc_macro_attribute]
67pub fn bean_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
68    let input = parse_macro_input!(item as ItemImpl);
69
70    match bean_impl_gen(input) {
71        Ok(tokens) => tokens,
72        Err(err) => err.to_compile_error().into(),
73    }
74}
75
76/// Generate BeanProcessor implementation from an impl block
77fn bean_impl_gen(item: ItemImpl) -> Result<TokenStream, syn::Error> {
78    let self_ty = &item.self_ty;
79
80    // Find handler methods
81    let handlers = find_handler_methods(&item.items)?;
82
83    if handlers.is_empty() {
84        return Err(syn::Error::new_spanned(
85            self_ty,
86            "No #[handler] methods found in impl block",
87        ));
88    }
89
90    // Generate match arms for each handler
91    let match_arms: Vec<_> = handlers
92        .iter()
93        .map(|handler| {
94            let method_name = &handler.name;
95
96            // Generate parameter extraction and method call
97            let (param_extraction, method_call) = generate_handler_invocation(handler)?;
98
99            // Generate result handling
100            let result_handling = generate_result_handling(handler)?;
101
102            Ok(quote! {
103                #method_name => {
104                    #param_extraction
105                    let result = #method_call;
106                    #result_handling
107                }
108            })
109        })
110        .collect::<Result<Vec<_>, syn::Error>>()?;
111
112    // Generate method names list
113    let method_names: Vec<_> = handlers.iter().map(|h| h.name.as_str()).collect();
114
115    let expanded = quote! {
116        #item
117
118        #[::camel_bean::async_trait]
119        impl ::camel_bean::BeanProcessor for #self_ty {
120            async fn call(
121                &self,
122                method: &str,
123                exchange: &mut ::camel_api::Exchange,
124            ) -> Result<(), ::camel_api::CamelError> {
125                match method {
126                    #(#match_arms)*
127                    _ => Err(::camel_api::CamelError::ProcessorError(
128                        format!("Method '{}' not found", method)
129                    ))
130                }
131            }
132
133            fn methods(&self) -> Vec<&'static str> {
134                vec![#(#method_names),*]
135            }
136        }
137    };
138
139    Ok(TokenStream::from(expanded))
140}
141
142/// Generate parameter extraction code for a handler method
143fn generate_handler_invocation(
144    handler: &handler::HandlerMethod,
145) -> Result<(proc_macro2::TokenStream, proc_macro2::TokenStream), syn::Error> {
146    let method_ident = &handler.ident;
147
148    // Build parameter list
149    let mut params = Vec::new();
150    let mut extraction = Vec::new();
151
152    // Body parameter
153    if let Some(body_type) = &handler.body_type {
154        extraction.push(quote! {
155            let body_json = match &exchange.input.body {
156                ::camel_api::Body::Json(value) => value.clone(),
157                _ => return Err(::camel_api::CamelError::TypeConversionFailed(
158                    "Expected JSON body".to_string()
159                )),
160            };
161            let body: #body_type = serde_json::from_value(body_json)
162                .map_err(|e| ::camel_api::CamelError::TypeConversionFailed(
163                    format!("Failed to deserialize body: {}", e)
164                ))?;
165        });
166        params.push(quote! { body });
167    }
168
169    // Headers parameter
170    if handler.has_headers {
171        extraction.push(quote! {
172            let headers = exchange.input.headers.clone();
173        });
174        params.push(quote! { headers });
175    }
176
177    // Exchange parameter
178    if handler.has_exchange {
179        params.push(quote! { exchange });
180    }
181
182    let extraction_code = quote! { #(#extraction)* };
183    let method_call = quote! { self.#method_ident(#(#params),*).await };
184
185    Ok((extraction_code, method_call))
186}
187
188/// Generate result handling code based on return type
189fn generate_result_handling(
190    handler: &handler::HandlerMethod,
191) -> Result<proc_macro2::TokenStream, syn::Error> {
192    // If no return type or exchange-only handler, don't set body
193    if handler.return_type.is_none() {
194        return Ok(quote! {
195            result.map_err(|e| ::camel_api::CamelError::ProcessorError(e.to_string()))?;
196            Ok(())
197        });
198    }
199
200    // Check if this is an exchange-only handler (has exchange but no body type)
201    if handler.has_exchange && handler.body_type.is_none() {
202        // Exchange handlers manage the exchange themselves, just propagate errors
203        return Ok(quote! {
204            result.map_err(|e| ::camel_api::CamelError::ProcessorError(e.to_string()))?;
205            Ok(())
206        });
207    }
208
209    // For handlers with return values, extract the value and set it as body
210    // We need to handle Result<T> return types
211    Ok(quote! {
212        let value = result.map_err(|e| ::camel_api::CamelError::ProcessorError(e.to_string()))?;
213        exchange.input.body = ::camel_api::Body::Json(serde_json::to_value(value)
214            .map_err(|e| ::camel_api::CamelError::TypeConversionFailed(
215                format!("Failed to serialize result: {}", e)
216            ))?);
217        Ok(())
218    })
219}