Skip to content

Commit 3eb5c23

Browse files
committed
Add comprehensive RustAPI cookbook documentation
Introduces a new, structured documentation set for RustAPI, including sections on getting started, core concepts, crate deep dives, and practical recipes. Updates SUMMARY.md to reflect the new organization and adds detailed guides on handlers, performance, testing, crate internals, and common development tasks such as CRUD resources, JWT authentication, database integration, file uploads, middleware, and production tuning.
1 parent 0e6a850 commit 3eb5c23

29 files changed

+1555
-15
lines changed

docs/cookbook/src/SUMMARY.md

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,35 @@
22

33
[Introduction](introduction.md)
44

5-
- [Part I: The Architecture](architecture/README.md)
6-
- [The Action Pattern](architecture/action_pattern.md)
7-
- [Performance Philosophy](architecture/performance.md)
8-
- [Testing Pyramid](architecture/testing_strategy.md)
5+
- [Part I: Getting Started](getting_started/README.md)
6+
- [Installation](getting_started/installation.md)
7+
- [Quickstart](getting_started/quickstart.md)
8+
- [Project Structure](getting_started/structure.md)
99

10-
- [Part II: Recipes](recipes/README.md)
11-
- [Adding a New Feature](recipes/new_feature.md)
12-
- [Quality & Maintenance](recipes/maintenance.md)
13-
- [Performance Tuning](recipes/tuning.md)
14-
- [Simulating CI](recipes/ci_simulation.md)
10+
- [Part II: Core Concepts](concepts/README.md)
11+
- [Handlers & Extractors](concepts/handlers.md)
12+
- [Performance Philosophy](concepts/performance.md)
13+
- [Testing Strategy](concepts/testing.md)
14+
15+
- [Part III: Crate Deep Dives](crates/README.md)
16+
- [rustapi-core: The Engine](crates/rustapi_core.md)
17+
- [rustapi-macros: The Magic](crates/rustapi_macros.md)
18+
- [rustapi-validate: The Gatekeeper](crates/rustapi_validation.md)
19+
- [rustapi-openapi: The Cartographer](crates/rustapi_openapi.md)
20+
- [rustapi-extras: The Toolbox](crates/rustapi_extras.md)
21+
- [rustapi-toon: The Diplomat](crates/rustapi_toon.md)
22+
- [rustapi-ws: The Live Wire](crates/rustapi_ws.md)
23+
- [rustapi-view: The Artist](crates/rustapi_view.md)
24+
- [rustapi-jobs: The Workhorse](crates/rustapi_jobs.md)
25+
- [rustapi-testing: The Auditor](crates/rustapi_testing.md)
26+
- [cargo-rustapi: The Architect](crates/cargo_rustapi.md)
27+
28+
- [Part IV: Recipes](recipes/README.md)
29+
- [Creating Resources](recipes/crud_resource.md)
30+
- [JWT Authentication](recipes/jwt_auth.md)
31+
- [Database Integration](recipes/db_integration.md)
32+
- [File Uploads](recipes/file_uploads.md)
33+
- [Custom Middleware](recipes/custom_middleware.md)
34+
- [Real-time Chat](recipes/websockets.md)
35+
- [Production Tuning](recipes/high_performance.md)
1536

16-
- [Part III: Reference](reference/README.md)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Core Concepts
2+
3+
Documentation of the fundamental architectural decisions and patterns in RustAPI.
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Handlers & Extractors
2+
3+
The **Handler** is the fundamental unit of work in RustAPI. It transforms an incoming HTTP request into an outgoing HTTP response.
4+
5+
Unlike many web frameworks that enforce a strict method signature (e.g., `fn(req: Request, res: Response)`), RustAPI embraces a flexible, type-safe approach powered by Rust's trait system.
6+
7+
## The Philosophy: "Ask for what you need"
8+
9+
In RustAPI, you don't manually parse the request object inside your business logic. Instead, you declare the data you need as function arguments, and the framework's **Extractors** handle the plumbing for you.
10+
11+
If the data cannot be extracted (e.g., missing header, invalid JSON), the request is rejected *before* your handler is ever called. This means your handler logic is guaranteed to operate on valid, type-safe data.
12+
13+
## Anatomy of a Handler
14+
15+
A handler is simply an asynchronous function that takes zero or more **Extractors** as arguments and returns something that implements `IntoResponse`.
16+
17+
```rust
18+
use rustapi::prelude::*;
19+
20+
async fn create_user(
21+
State(db): State<DbPool>, // 1. Dependency Injection
22+
Path(user_id): Path<Uuid>, // 2. URL Path Parameter
23+
Json(payload): Json<CreateUser>, // 3. JSON Request Body
24+
) -> Result<impl IntoResponse, ApiError> {
25+
26+
let user = db.create_user(user_id, payload).await?;
27+
28+
Ok((StatusCode::CREATED, Json(user)))
29+
}
30+
```
31+
32+
### Key Rules
33+
1. **Order Matters (Slightly)**: Extractors that consume the request body (like `Json<T>` or `Multipart`) must be the *last* argument. This is because the request body is a stream that can only be read once.
34+
2. **Async by Default**: Handlers are `async fn`. This allows non-blocking I/O operations (DB calls, external API requests).
35+
3. **Debuggable**: Handlers are just functions. You can unit test them easily.
36+
37+
## Extractors: The `FromRequest` Trait
38+
39+
Extractors are types that implement `FromRequest` (or `FromRequestParts` for headers/query params). They isolate the "HTTP parsing" logic from your "Business" logic.
40+
41+
### Common Build-in Extractors
42+
43+
| Extractor | Source | Example Usage |
44+
|-----------|--------|---------------|
45+
| `Path<T>` | URL Path Segments | `fn get_user(Path(id): Path<u32>)` |
46+
| `Query<T>` | Query String | `fn search(Query(params): Query<SearchFn>)` |
47+
| `Json<T>` | Request Body | `fn update(Json(data): Json<UpdateDto>)` |
48+
| `HeaderMap` | HTTP Headers | `fn headers(headers: HeaderMap)` |
49+
| `State<T>` | Application State | `fn db_op(State(pool): State<PgPool>)` |
50+
| `Extension<T>` | Request-local extensions | `fn logic(Extension(user): Extension<User>)` |
51+
52+
### Custom Extractors
53+
54+
You can create your own extractors to encapsulate repetitive validation or parsing logic. For example, extracting a user ID from a verified JWT:
55+
56+
```rust
57+
pub struct AuthenticatedUser(pub Uuid);
58+
59+
#[async_trait]
60+
impl<S> FromRequestParts<S> for AuthenticatedUser
61+
where
62+
S: Send + Sync,
63+
{
64+
type Rejection = ApiError;
65+
66+
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
67+
let auth_header = parts.headers.get("Authorization")
68+
.ok_or(ApiError::Unauthorized("Missing token"))?;
69+
70+
let token = auth_header.to_str().map_err(|_| ApiError::Unauthorized("Invalid token"))?;
71+
let user_id = verify_jwt(token)?; // Your verification logic
72+
73+
Ok(AuthenticatedUser(user_id))
74+
}
75+
}
76+
77+
// Usage in handler: cleaner and reusable!
78+
async fn profile(AuthenticatedUser(uid): AuthenticatedUser) -> impl IntoResponse {
79+
format!("User ID: {}", uid)
80+
}
81+
```
82+
83+
## Responses: The `IntoResponse` Trait
84+
85+
A handler can return any type that implements `IntoResponse`. RustAPI provides implementations for many common types:
86+
87+
- `StatusCode` (e.g., return `200 OK` or `404 Not Found`)
88+
- `Json<T>` (serializes struct to JSON)
89+
- `String` / `&str` (plain text response)
90+
- `Vec<u8>` / `Bytes` (binary data)
91+
- `HeaderMap` (response headers)
92+
- `Html<String>` (HTML content)
93+
94+
### Tuple Responses
95+
You can combine types using tuples to set status codes and headers along with the body:
96+
97+
```rust
98+
// Returns 201 Created + JSON Body
99+
async fn create() -> (StatusCode, Json<User>) {
100+
(StatusCode::CREATED, Json(user))
101+
}
102+
103+
// Returns Custom Header + Plain Text
104+
async fn custom() -> (HeaderMap, &'static str) {
105+
let mut headers = HeaderMap::new();
106+
headers.insert("X-Custom", "Value".parse().unwrap());
107+
(headers, "Response with headers")
108+
}
109+
```
110+
111+
### Error Handling
112+
113+
Handlers often return `Result<T, E>`. If the handler returns `Ok(T)`, the `T` is converted to a response. If it returns `Err(E)`, the `E` is converted to a response.
114+
115+
This effectively means your `Error` type must implement `IntoResponse`.
116+
117+
```rust
118+
// Recommended pattern: Centralized API Error enum
119+
pub enum ApiError {
120+
NotFound(String),
121+
InternalServerError,
122+
}
123+
124+
impl IntoResponse for ApiError {
125+
fn into_response(self) -> Response {
126+
let (status, message) = match self {
127+
ApiError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
128+
ApiError::InternalServerError => (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong".to_string()),
129+
};
130+
131+
(status, Json(json!({ "error": message }))).into_response()
132+
}
133+
}
134+
```
135+
136+
## Best Practices
137+
138+
1. **Keep Handlers Thin**: Move complex business logic to "Service" structs or domain modules. Handlers should focus on HTTP translation (decoding request -> calling service -> encoding response).
139+
2. **Use `State` for Dependencies**: Avoid global variables. Pass DB pools and config via `State`.
140+
3. **Parse Early**: Use specific types in `Json<T>` structs rather than `serde_json::Value` to leverage the type system for validation.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Performance Philosophy
2+
3+
RustAPI is built on a simple premise: **Abstractions shouldn't cost you runtime performance.**
4+
5+
We leverage Rust's unique ownership system and modern async ecosystem (Tokio, Hyper) to deliver performance that rivals C++ servers, while maintaining developer safe-guards.
6+
7+
## The Pillars of Speed
8+
9+
### 1. Zero-Copy Networking
10+
Where possible, RustAPI avoids copying memory. When you receive a large JSON payload or file upload, we aim to pass pointers to the underlying memory buffer rather than cloning the data.
11+
12+
- **`Bytes` over `Vec<u8>`**: We use the `bytes` crate extensively. Passing a `Bytes` object around is `O(1)` (it's just a reference-counted pointer and length), whereas cloning a `Vec<u8>` is `O(n)`.
13+
- **String View**: Extractors like `Path` and `Query` often leverage `Cow<'str, str>` (Clone on Write) to avoid allocations if the data doesn't need to be modified.
14+
15+
### 2. Multi-Core Async Runtime
16+
RustAPI runs on **Tokio**, a work-stealing, multi-threaded runtime.
17+
18+
- **Non-blocking I/O**: A single thread can handle thousands of concurrent idle connections (e.g., WebSockets waiting for messages) with minimal memory overhead.
19+
- **Work Stealing**: If one CPU core is overloaded with tasks, other idle cores will "steal" work from its queue, ensuring balanced utilization of your hardware.
20+
21+
### 3. Compile-Time Router
22+
Our router (`matchit`) is based on a **Radix Trie** structure.
23+
24+
- **O(log n) Lookup**: Route matching speed depends on the length of the URL, *not* the number of routes defined. Having 10 routes or 10,000 routes has negligible impact on routing latency.
25+
- **Allocation-Free Matching**: For standard paths, routing decisions happen without heap allocations.
26+
27+
## Memory Management
28+
29+
### Stack vs. Heap
30+
RustAPI encourages stack allocation for small, short-lived data.
31+
- **Extractors** are often allocated on the stack.
32+
- **Response bodies** are streamed, meaning a 1GB file download doesn't require 1GB of RAM. It flows through a small, constant-sized buffer.
33+
34+
### Connection Pooling
35+
For database performance, we strongly recommend using connection pooling (e.g., `sqlx::Pool`).
36+
- **Reuse**: Establishing a TCP connection and performing a simplified SSL handshake for every request is slow. Pooling keeps connections open and ready.
37+
- **Multiplexing**: Some drivers allow multiple queries to be in-flight on a single connection simultaneously.
38+
39+
## Optimizing Your App
40+
41+
To get the most out of RustAPI, follow these guidelines:
42+
43+
1. **Avoid Blocking the Async Executor**: Never run CPU-intensive tasks (cryptography, image processing) or blocking I/O (std::fs::read) directly in an async handler.
44+
- *Solution*: Use `tokio::task::spawn_blocking` to offload these to a dedicated thread pool.
45+
46+
```rust
47+
// BAD: Blocks the thread, potentially stalling other requests
48+
fn handler() {
49+
let digest = tough_crypto_hash(data);
50+
}
51+
52+
// GOOD: Runs on a thread meant for blocking work
53+
async fn handler() {
54+
let digest = tokio::task::spawn_blocking(move || {
55+
tough_crypto_hash(data)
56+
}).await.unwrap();
57+
}
58+
```
59+
60+
2. **JSON Serialization**: While `serde` is fast, JSON text processing is CPU heavy.
61+
- For extremely high-throughput endpoints, consider binary formats like **Protobuf** or **MessagePack** if the client supports it.
62+
63+
3. **Keep `State` Light**: Your `State` struct is cloned for every request. Wrap large shared data in `Arc<T>` so only the pointer is cloned, not the data itself.
64+
65+
```rust
66+
// Fast
67+
#[derive(Clone)]
68+
struct AppState {
69+
db: PgPool, // Internally uses Arc
70+
config: Arc<Config>, // Wrapped in Arc manually
71+
}
72+
```
73+
74+
## Benchmarking
75+
76+
Performance is not a guessing game. Use tools like `wrk`, `k6`, or `drill` to stress-test your specific endpoints.
77+
78+
```bash
79+
# Example using drill
80+
drill --benchmark benchmark.yml --stats
81+
```
82+
83+
Remember: RustAPI provides the *capability* for high performance, but your application logic ultimately dictates the speed. Use the tools wisely.

0 commit comments

Comments
 (0)