event_base 0.4.0

An Event Driven Framework
Documentation
# Middleware

Middleware lets you intercept, inspect, or modify messages **before** they reach the handler. Common use cases include logging, metrics, validation, authentication, and rate limiting.

---

## How it works

Middleware is a chain of components that wraps the final handler. Each middleware can:

- Inspect the message before processing
- Modify the message (e.g., add metadata)
- Short-circuit the chain by returning an `Ack` directly
- Measure and log processing time

The chain follows the **onion model**:

```
Message → Middleware1 → Middleware2 → ... → Handler → Ack → Middleware2 → Middleware1
```

Each middleware calls `next.run(msg)` to pass control to the next component. Code after `next.run()` runs on the way back (post-processing).

---

## The `Middleware` trait

```rust
#[async_trait]
pub trait Middleware: Send + Sync {
    async fn handle(&self, msg: &mut EMessage, next: Next<'_>) -> Ack;
}
```

### `Next`

```rust
pub struct Next<'a> {
    next: &'a [Box<dyn Middleware>],
    index: usize,
    handler: &'a dyn EHandler,
}

impl<'a> Next<'a> {
    /// Proceed to the next middleware or the final handler.
    pub async fn run(&self, msg: &mut EMessage) -> Ack;
}
```

---

## Writing a middleware

### Example: Logger middleware

```rust
use async_trait::async_trait;
use event_base::prelude::*;
use std::time::Instant;

pub struct LoggerMiddleware;

#[async_trait]
impl Middleware for LoggerMiddleware {
    async fn handle(&self, msg: &mut EMessage, next: Next<'_>) -> Ack {
        let start = Instant::now();
        tracing::info!("[BEFORE] Processing message: {}", msg.id);

        let ack = next.run(msg).await;

        tracing::info!("[AFTER] {} completed in {:?}: {:?}", msg.id, start.elapsed(), ack);
        ack
    }
}
```

### Example: Metrics middleware

```rust
pub struct MetricsMiddleware {
    counter: std::sync::atomic::AtomicU64,
}

#[async_trait]
impl Middleware for MetricsMiddleware {
    async fn handle(&self, msg: &mut EMessage, next: Next<'_>) -> Ack {
        self.counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
        let ack = next.run(msg).await;
        let count = self.counter.load(std::sync::atomic::Ordering::Relaxed);
        tracing::info!("Messages processed so far: {}", count);
        ack
    }
}
```

### Example: Auth middleware (short-circuit)

```rust
pub struct AuthMiddleware {
    valid_token: String,
}

#[async_trait]
impl Middleware for AuthMiddleware {
    async fn handle(&self, msg: &mut EMessage, next: Next<'_>) -> Ack {
        // Check for an auth token in the message metadata
        let has_auth = msg.metadata.source.as_deref() == Some(&self.valid_token);
        if !has_auth {
            return Ack::Dead {
                dead_reason: DeadReason::Other("Unauthorized".into()),
            };
        }
        next.run(msg).await
    }
}
```

---

## Configuring middleware on a handler

Use the `middleware` parameter in the `#[handler]` attribute:

```rust
#[handler(
    topic = "order.created",
    workers = 2,
    middleware = [LoggerMiddleware, MetricsMiddleware]
)]
async fn handle_order(msg: &EMessage) -> Ack {
    // Business logic here
    Ack::Ack
}
```

Middleware types must be importable at the call site. They are instantiated automatically by the generated code.

### Built-in middleware

When the `middleware` feature is enabled, you get:

```rust
use event_base::middleware::logger::LoggerMiddleware;
```

`LoggerMiddleware` logs every message before and after processing with duration.

---

## The Pipeline

Internally, middleware and handler are composed into a `Pipeline`:

```rust
pub struct Pipeline {
    middlewares: Vec<Box<dyn Middleware>>,
    handler: Arc<dyn EHandler>,
}

impl Pipeline {
    pub fn new(handler: Box<dyn EHandler>) -> Self;
    pub fn with(mut self, middleware: impl Middleware + 'static) -> Self;
    pub async fn run(&self, msg: &mut EMessage) -> Ack;
}
```

You can construct pipelines manually:

```rust
use event_base::prelude::*;

let pipeline = Pipeline::new(Box::new(MyHandler))
    .with(LoggerMiddleware)
    .with(MetricsMiddleware);

let mut msg = EMessage::new("test", b"data".to_vec(), DeliveryMode::Standard, None);
let ack = pipeline.run(&mut msg).await;
```

---

## Order of execution

Middleware run in the order they are declared:

```rust
#[handler(middleware = [A, B, C])]
```

Execution order: `A.handle → B.handle → C.handle → handler → C → B → A`

If middleware `A` returns an `Ack` without calling `next.run()`, neither `B`, `C`, nor the handler will execute.

---

## Best practices

1. **Keep middleware stateless** where possible — use atomic counters or external storage for state.
2. **Don't panic** in middleware — always return an `Ack`.
3. **Use short-circuiting sparingly** — it can make debugging harder.
4. **Order matters** — place auth/validation middleware first, logging/metrics last (to capture full processing time).

---

## Next steps

- [Sending Messages]sending.md — Delivery modes and API
- [Persistence & WAL]persistence.md — Durable message storage