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.