diff --git a/docs/cookbook/src/crates/rustapi_extras.md b/docs/cookbook/src/crates/rustapi_extras.md
index 4d27b8b6..67a332a6 100644
--- a/docs/cookbook/src/crates/rustapi_extras.md
+++ b/docs/cookbook/src/crates/rustapi_extras.md
@@ -15,6 +15,10 @@ This crate is a collection of production-ready middleware. Everything is behind
| `audit` | `AuditStore`, `AuditLogger` |
| `insight` | `InsightLayer`, `InsightStore` |
| `rate-limit` | `RateLimitLayer` |
+| `replay` | `ReplayLayer` (Time-Travel Debugging) |
+| `timeout` | `TimeoutLayer` |
+| `guard` | `PermissionGuard` |
+| `sanitization` | Input sanitization utilities |
## Middleware Usage
@@ -135,7 +139,7 @@ let app = RustApi::new()
### Structured Logging
-Emit logs as JSON for aggregators like Datadog or Splunk.
+Emit logs as JSON for aggregators like Datadog or Splunk. This is different from request logging; it formats your application logs.
```rust
use rustapi_extras::structured_logging::{StructuredLoggingLayer, JsonFormatter};
@@ -184,6 +188,33 @@ let app = RustApi::new()
.layer(ApiKeyLayer::new("my-secret-key"));
```
+### Permission Guards
+
+The `guard` feature provides role-based access control (RBAC) helpers.
+
+```rust
+use rustapi_extras::guard::PermissionGuard;
+
+// Only allows users with "admin" role
+#[rustapi_rs::get("/admin")]
+async fn admin_panel(
+ _guard: PermissionGuard
+) -> &'static str {
+ "Welcome Admin"
+}
+```
+
+### Input Sanitization
+
+The `sanitization` feature helps prevent XSS by cleaning user input.
+
+```rust
+use rustapi_extras::sanitization::sanitize_html;
+
+let safe_html = sanitize_html("Hello");
+// Result: "<script>alert(1)</script>Hello"
+```
+
## Resilience
### Circuit Breaker
@@ -208,6 +239,18 @@ let app = RustApi::new()
.layer(RetryLayer::default());
```
+### Timeout
+
+Ensure requests don't hang indefinitely.
+
+```rust
+use rustapi_extras::timeout::TimeoutLayer;
+use std::time::Duration;
+
+let app = RustApi::new()
+ .layer(TimeoutLayer::new(Duration::from_secs(30)));
+```
+
## Optimization
### Caching
@@ -231,3 +274,21 @@ use rustapi_extras::dedup::DedupLayer;
let app = RustApi::new()
.layer(DedupLayer::new());
```
+
+## Debugging
+
+### Time-Travel Debugging (Replay)
+
+The `replay` feature allows you to record production traffic and replay it locally for debugging.
+
+See the [Time-Travel Debugging Recipe](../recipes/replay.md) for full details.
+
+```rust
+use rustapi_extras::replay::{ReplayLayer, ReplayConfig, InMemoryReplayStore};
+
+let replay_config = ReplayConfig::default();
+let store = InMemoryReplayStore::new(1_000);
+
+let app = RustApi::new()
+ .layer(ReplayLayer::new(replay_config).with_store(store));
+```
diff --git a/docs/cookbook/src/crates/rustapi_jobs.md b/docs/cookbook/src/crates/rustapi_jobs.md
index 49f15057..21be0721 100644
--- a/docs/cookbook/src/crates/rustapi_jobs.md
+++ b/docs/cookbook/src/crates/rustapi_jobs.md
@@ -11,29 +11,36 @@ Long-running tasks shouldn't block HTTP requests. `rustapi-jobs` provides a robu
Here is how to set up a simple background job queue using the in-memory backend.
-### 1. Define the Job
+### 1. Define the Job and Data
-Jobs are simple structs that implement `Serialize` and `Deserialize`.
+Jobs are separated into two parts:
+1. The **Data** struct (the payload), which must be serializable.
+2. The **Job** struct (the handler), which contains the logic.
```rust
use serde::{Deserialize, Serialize};
use rustapi_jobs::{Job, JobContext, Result};
-use std::sync::Arc;
+use async_trait::async_trait;
+// 1. The payload data
#[derive(Serialize, Deserialize, Debug, Clone)]
-struct EmailJob {
+struct EmailJobData {
to: String,
subject: String,
body: String,
}
-// Implement the Job trait to define how to process it
-#[async_trait::async_trait]
+// 2. The handler struct (usually stateless)
+#[derive(Clone)]
+struct EmailJob;
+
+#[async_trait]
impl Job for EmailJob {
const NAME: &'static str = "email_job";
+ type Data = EmailJobData;
- async fn run(&self, _ctx: JobContext) -> Result<()> {
- println!("Sending email to {} with subject: {}", self.to, self.subject);
+ async fn execute(&self, _ctx: JobContext, data: Self::Data) -> Result<()> {
+ println!("Sending email to {} with subject: {}", data.to, data.subject);
// Simulate work
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
Ok(())
@@ -46,7 +53,7 @@ impl Job for EmailJob {
In your `main` function, initialize the queue and start the worker.
```rust
-use rustapi_jobs::{JobQueue, InMemoryBackend, EnqueueOptions};
+use rustapi_jobs::{JobQueue, InMemoryBackend};
#[tokio::main]
async fn main() -> Result<(), Box> {
@@ -56,17 +63,19 @@ async fn main() -> Result<(), Box> {
// 2. Create the queue
let queue = JobQueue::new(backend);
- // 3. Register the job type
- queue.register_job::();
+ // 3. Register the job handler
+ queue.register_job(EmailJob).await;
// 4. Start the worker in the background
let worker_queue = queue.clone();
tokio::spawn(async move {
- worker_queue.start_workers().await;
+ if let Err(e) = worker_queue.start_worker().await {
+ eprintln!("Worker failed: {:?}", e);
+ }
});
- // 5. Enqueue a job
- queue.enqueue(EmailJob {
+ // 5. Enqueue a job (pass the DATA, not the handler)
+ queue.enqueue::(EmailJobData {
to: "user@example.com".into(),
subject: "Welcome!".into(),
body: "Thanks for joining.".into(),
diff --git a/docs/cookbook/src/crates/rustapi_testing.md b/docs/cookbook/src/crates/rustapi_testing.md
index b500dbd2..df410e64 100644
--- a/docs/cookbook/src/crates/rustapi_testing.md
+++ b/docs/cookbook/src/crates/rustapi_testing.md
@@ -7,6 +7,15 @@
1. **In-process API testing**: Testing your endpoints without binding to a real TCP port.
2. **External service mocking**: Mocking downstream services (like payment gateways or auth providers) that your API calls.
+## Installation
+
+Add the crate to your `dev-dependencies`:
+
+```toml
+[dev-dependencies]
+rustapi-testing = { version = "0.1.300" }
+```
+
## The `TestClient`
Integration testing is often slow and painful because it involves spinning up a server, waiting for ports, and managing child processes. `TestClient` solves this by wrapping your `RustApi` application and executing requests directly against the service layer.