camel-bean-macros 0.5.0

Proc-macros for camel-bean crate
Documentation

camel-bean-macros

Proc-macros for the camel-bean crate that enable ergonomic bean definition and automatic BeanProcessor trait implementation.

Overview

This crate provides three proc-macros:

  1. #[bean_impl] - Main macro for generating BeanProcessor impl from impl blocks
  2. #[handler] - Marker attribute for bean handler methods
  3. #[derive(Bean)] - Placeholder derive macro (not recommended)

Macros

#[bean_impl] - Generate BeanProcessor Implementation

The primary macro that transforms an impl block into a BeanProcessor implementation with automatic parameter binding.

use camel_bean::{bean_impl, handler};

pub struct OrderService;

#[bean_impl]
impl OrderService {
    #[handler]
    pub async fn process(&self, body: Order) -> Result<ProcessedOrder, String> {
        // Business logic
        Ok(ProcessedOrder { /* ... */ })
    }

    #[handler]
    pub async fn validate(&self, body: Order, headers: Headers) -> Result<bool, String> {
        // Validation logic
        Ok(true)
    }
}

What it generates:

impl OrderService {
    // ... your methods unchanged ...
}

#[async_trait]
impl BeanProcessor for OrderService {
    async fn call(&self, method: &str, exchange: &mut Exchange) -> Result<(), CamelError> {
        match method {
            "process" => {
                // Extract body parameter
                let body: Order = exchange.input.body.try_into()?;
                // Call method
                let result = self.process(body).await;
                // Handle result (set response body or error)
                // ...
            }
            "validate" => {
                // Extract body and headers
                let body: Order = exchange.input.body.try_into()?;
                let headers = exchange.input.headers.clone();
                // Call and handle result
                // ...
            }
            _ => return Err(CamelError::from(format!("Unknown method: {}", method)))
        }
        Ok(())
    }

    fn methods(&self) -> Vec<&'static str> {
        vec!["process", "validate"]
    }
}

#[handler] - Mark Handler Methods

Marker attribute that identifies which methods should be exposed as bean handlers.

#[bean_impl]
impl MyService {
    #[handler]  // This method is exposed
    pub async fn process(&self, body: Data) -> Result<Response, Error> {
        // ...
    }

    // This method is NOT exposed (no #[handler])
    fn helper(&self) -> String {
        "internal".to_string()
    }
}

Rules:

  • Must be async
  • First parameter must be &self
  • Supported parameter types:
    • body: T - Extracted from exchange body (requires TryFrom<Body>)
    • headers: Headers - Cloned from exchange headers
    • exchange: &mut Exchange - Full exchange access
  • Return type must be Result<T, E> where E: Display

#[derive(Bean)] - Placeholder (Not Recommended)

Derive macro that generates a placeholder BeanProcessor implementation.

⚠️ Warning: This macro is incomplete and requires manual impl block with #[handler] methods. Use #[bean_impl] instead.

// NOT RECOMMENDED
#[derive(Bean)]
struct MyService;

// You still need to write impl block manually
impl MyService {
    #[handler]
    pub async fn process(&self, body: Data) -> Result<Response, Error> {
        // ...
    }
}

Parameter Binding

The #[bean_impl] macro automatically extracts parameters by name:

#[bean_impl]
impl UserService {
    // Extract only body
    #[handler]
    pub async fn create(&self, body: CreateUser) -> Result<User, Error> {
        // ...
    }

    // Extract body + headers
    #[handler]
    pub async fn update(&self, body: UpdateUser, headers: Headers) -> Result<User, Error> {
        let user_id = headers.get("user-id").unwrap();
        // ...
    }

    // Full exchange access
    #[handler]
    pub async fn complex(&self, exchange: &mut Exchange) -> Result<(), Error> {
        // Read body, headers, properties, set response, etc.
        // ...
    }
}

Parameter name inference:

  • body → Extracted from exchange.input.body via TryFrom<Body>
  • headers → Cloned from exchange.input.headers
  • exchange → Mutable reference to the full exchange

Return Type Handling

Return types are automatically handled:

#[bean_impl]
impl MyService {
    // Success: body set to Result<T>
    // Error: exchange.set_error()
    #[handler]
    pub async fn process(&self, body: Input) -> Result<Output, String> {
        Ok(Output { /* ... */ })
    }
}

Result handling:

  • Ok(T) → Body set to JSON-serialized T (if T is not ())
  • Err(E)exchange.set_error() called with error message

Error Handling

The macro generates proper error handling:

#[bean_impl]
impl MyService {
    #[handler]
    pub async fn process(&self, body: Data) -> Result<Response, String> {
        if body.invalid() {
            return Err("Invalid data".to_string());
        }
        Ok(Response { /* ... */ })
    }
}

Generated error handling:

  • Parameter extraction failures → CamelError::ProcessorError
  • Method not found → BeanError::MethodNotFound
  • Handler errors → Set on exchange via set_error()

Integration with camel-bean

This crate is a dependency of camel-bean and is re-exported for convenience:

// Usually you don't depend on camel-bean-macros directly
use camel_bean::{bean_impl, handler, BeanRegistry};

// camel-bean re-exports the macros

Examples

See the bean-demo example for a complete working example.

Implementation Details

Code Generation

The #[bean_impl] macro:

  1. Parses the impl block to find #[handler] methods
  2. Validates method signatures (async, &self, valid parameters)
  3. Generates parameter extraction code for each handler
  4. Generates method dispatch via match on method name
  5. Generates result handling (set body or error)
  6. Preserves original impl block unchanged

Supported Types

Parameter types:

  • body: T where T: TryFrom<Body>
  • headers: Headers (cloned)
  • exchange: &mut Exchange

Return types:

  • Result<T, E> where E: Display
  • T is serialized to JSON on success
  • () is allowed (no body set on success)

See Also

License

Apache-2.0