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