Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions docs/.agent/docs_coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,23 @@
| Sync Validation | `docs/cookbook/src/crates/rustapi_validate.md` | `rustapi-validate/src/lib.rs` (`Validate`) | OK |
| Async Validation | `docs/cookbook/src/crates/rustapi_validate.md` | `rustapi-validate/src/v2/mod.rs` (`AsyncValidate`) | OK |
| **Extras** | | | |
| JWT Auth | `docs/cookbook/src/recipes/jwt_auth.md` | `rustapi-extras/src/jwt.rs` (`JwtLayer`) | OK |
| OAuth2 | `docs/cookbook/src/recipes/oauth2_client.md` | `rustapi-extras/src/oauth2.rs` (`OAuth2Client`) | OK |
| Database | `docs/cookbook/src/recipes/db_integration.md` | N/A (Integration pattern) | Needs Update |
| **Ecosystem** | | | |
| WebSockets | `docs/cookbook/src/recipes/websockets.md` | `rustapi-ws/src/lib.rs` (`WebSocketUpgrade`) | OK |
| SSR (View) | `docs/cookbook/src/recipes/server_side_rendering.md` | `rustapi-view/src/lib.rs` (`View`) | OK |
| gRPC | `docs/cookbook/src/recipes/grpc_integration.md` | `rustapi-grpc/src/lib.rs` (`TonicServer`) | OK |
| Jobs | `docs/cookbook/src/recipes/background_jobs.md` | `rustapi-jobs/src/lib.rs` (`Job`) | OK |
| TOON (AI) | `docs/cookbook/src/recipes/ai_integration.md` | `rustapi-toon/src/lib.rs` (`LlmResponse`) | OK |
| Auth (JWT) | `recipes/jwt_auth.md` | `rustapi-extras/src/jwt` | OK |
| Auth (OAuth2) | `recipes/oauth2_client.md` | `rustapi-extras/src/oauth2` | OK |
| Security | `recipes/csrf_protection.md` | `rustapi-extras/src/security` | OK |
| Observability | `crates/rustapi_extras.md` | `rustapi-extras/src/telemetry` | OK |
| Audit Logging | `recipes/audit_logging.md` | `rustapi-extras/src/audit` | OK |
| Middleware (Advanced) | `recipes/advanced_middleware.md` | `rustapi-extras/src/{rate_limit, dedup, cache}` | OK |
| **Jobs** | | | |
| Job Queue (Crate) | `crates/rustapi_jobs.md` | `rustapi-jobs` | OK |
| Background Jobs (Recipe) | `recipes/background_jobs.md` | `rustapi-jobs` | OK |
| **Integrations** | | | |
| gRPC | `recipes/grpc_integration.md` | `rustapi-grpc` | OK |
| SSR | `recipes/server_side_rendering.md` | `rustapi-view` | OK |
| AI / TOON | `recipes/ai_integration.md` | `rustapi-toon` | OK |
| WebSockets | `recipes/websockets.md` | `rustapi-ws` | Updated |
| **Learning** | | | |
| Structured Path | `learning/curriculum.md` | N/A | Updated (Mini Projects) |
| **Recipes** | | | |
| File Uploads | `recipes/file_uploads.md` | `rustapi-core` | Updated (Buffered) |
| Deployment | `recipes/deployment.md` | `cargo-rustapi` | OK |
| Testing | `recipes/testing.md` | `rustapi-testing` | OK |
37 changes: 14 additions & 23 deletions docs/.agent/docs_inventory.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
# Documentation Inventory

| File Path | Purpose | Last Updated Version | Owner Crate | Status |
|-----------|---------|----------------------|-------------|--------|
| `docs/README.md` | Main entry point | 0.1.335 | rustapi-rs | OK |
| `docs/GETTING_STARTED.md` | Quick start guide | 0.1.335 | rustapi-rs | OK |
| `docs/ARCHITECTURE.md` | High-level architecture | 0.1.335 | rustapi-core | OK |
| `docs/FEATURES.md` | Feature list | 0.1.335 | rustapi-rs | OK |
| `docs/PHILOSOPHY.md` | Design philosophy | 0.1.335 | rustapi-rs | OK |
| `docs/native_openapi.md` | Native OpenAPI details | 0.1.335 | rustapi-openapi | OK |
| `docs/cookbook/src/SUMMARY.md` | Cookbook ToC | 0.1.335 | rustapi-rs | Needs Update |
| `docs/cookbook/src/introduction.md` | Cookbook Intro | 0.1.335 | rustapi-rs | OK |
| `docs/cookbook/src/troubleshooting.md` | Common issues | 0.1.335 | rustapi-rs | OK |
| `docs/cookbook/src/learning/curriculum.md` | Learning Path | 0.1.335 | rustapi-rs | Needs Update |
| `docs/cookbook/src/recipes/db_integration.md` | Database recipe | 0.1.335 | rustapi-rs | Needs Update |
| `docs/cookbook/src/recipes/file_uploads.md` | File upload recipe | 0.1.335 | rustapi-core | OK |
| `docs/cookbook/src/recipes/compression.md` | Compression recipe | 0.1.335 | rustapi-core | OK |
| `docs/cookbook/src/recipes/openapi_refs.md` | OpenAPI Refs recipe | 0.1.335 | rustapi-openapi | OK |
| `docs/cookbook/src/recipes/http3_quic.md` | HTTP/3 recipe | 0.1.335 | rustapi-core | OK |
| `docs/cookbook/src/recipes/jwt_auth.md` | JWT Auth recipe | 0.1.335 | rustapi-extras | OK |
| `docs/cookbook/src/recipes/websockets.md` | WebSocket recipe | 0.1.335 | rustapi-ws | OK |
| `docs/cookbook/src/recipes/server_side_rendering.md` | SSR recipe | 0.1.335 | rustapi-view | OK |
| `docs/cookbook/src/recipes/grpc_integration.md` | gRPC recipe | 0.1.335 | rustapi-grpc | OK |
| `docs/cookbook/src/recipes/background_jobs.md` | Jobs recipe | 0.1.335 | rustapi-jobs | OK |
| `docs/cookbook/src/recipes/ai_integration.md` | AI/TOON recipe | 0.1.335 | rustapi-toon | OK |
| File | Purpose | Owner Crate | Status |
|------|---------|-------------|--------|
| `README.md` | Project overview, key features, quick start | Root | OK |
| `docs/README.md` | Documentation landing page | Docs | OK |
| `docs/cookbook/src/SUMMARY.md` | Cookbook navigation structure | Docs | OK |
| `docs/cookbook/src/learning/curriculum.md` | Structured learning path | Docs | Updated (Mini Projects) |
| `docs/cookbook/src/recipes/file_uploads.md` | Recipe for File Uploads | Docs | Updated (Buffered) |
| `docs/cookbook/src/recipes/websockets.md` | Recipe for Real-time Chat | Docs | Updated (Extractors) |
| `docs/cookbook/src/recipes/background_jobs.md` | Recipe for Background Jobs | Docs | OK |
| `docs/cookbook/src/recipes/tuning.md` | Performance Tuning | Docs | DELETED |
| `docs/cookbook/src/recipes/new_feature.md` | New Feature Guide | Docs | DELETED |
| `docs/cookbook/src/architecture/action_pattern.md` | Action Pattern Guide | Docs | DELETED |
| `crates/rustapi-core/src/hateoas.rs` | API Reference for HATEOAS | rustapi-core | OK |
| `crates/rustapi-core/src/extract.rs` | API Reference for Extractors | rustapi-core | OK |
4 changes: 2 additions & 2 deletions docs/.agent/last_run.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"last_processed_ref": "v0.1.335",
"date": "2026-02-17",
"notes": "Added recipes for Compression, OpenAPI Refs, File Uploads, and Database Integration. Updated Learning Path with new modules."
"date": "2026-02-19",
"notes": "Deleted incorrect recipes (tuning, action pattern). Updated File Uploads and WebSockets recipes for accuracy. Expanded Learning Path with mini projects."
}
32 changes: 32 additions & 0 deletions docs/.agent/run_report_2026-02-19.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Run Report: 2026-02-19

## Summary
Performed a continuous improvement pass on the documentation, focusing on accuracy in recipes and expanding the learning path.

## Version Detection
- **Version**: 0.1.335
- **Status**: No new version detected. Maintenance mode.

## Changes
### 🗑️ Deleted Orphaned/Incorrect Files
- `docs/cookbook/src/recipes/tuning.md`: Referenced non-existent benchmark scripts.
- `docs/cookbook/src/recipes/new_feature.md`: Described non-existent "Action Pattern".
- `docs/cookbook/src/architecture/action_pattern.md`: Described non-existent "Action Pattern".

### 📝 Updated Recipes
- **File Uploads** (`docs/cookbook/src/recipes/file_uploads.md`):
- Removed incorrect claims about streaming support in `Multipart`.
- Updated example to correctly use buffered `field.bytes()` and `field.save_to()`.
- Added warning about memory usage and `DefaultBodyLimit`.
- **WebSockets** (`docs/cookbook/src/recipes/websockets.md`):
- Corrected `ws_handler` signature to use `WebSocket` extractor instead of `WebSocketUpgrade`.
- Corrected `handle_socket` signature to accept `WebSocketStream`.
- Fixed imports and usage of `StreamExt`.

### 📚 Learning Path
- **Curriculum** (`docs/cookbook/src/learning/curriculum.md`):
- Added "Mini Projects" to Module 1 (Echo Server), Module 2 (Calculator), and Module 3 (User Registry) to encourage hands-on practice.

## TODOs
- Verify if `rustapi-core` plans to support streaming multipart in the future.
- Review other recipes for similar inaccuracies.
39 changes: 0 additions & 39 deletions docs/cookbook/src/architecture/action_pattern.md

This file was deleted.

9 changes: 9 additions & 0 deletions docs/cookbook/src/learning/curriculum.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ This curriculum is designed to take you from a RustAPI beginner to an advanced u
- **Expected Output:** A running server that responds to `GET /` with "Hello World".
- **Pitfalls:** Not enabling `tokio` features if setting up manually.

#### 🛠️ Mini Project: "The Echo Server"
Create a new endpoint `POST /echo` that accepts any text body and returns it back to the client. This verifies your setup handles basic I/O correctly.

#### 🧠 Knowledge Check
1. What command scaffolds a new RustAPI project?
2. Which feature flag is required for the async runtime?
Expand All @@ -25,6 +28,9 @@ This curriculum is designed to take you from a RustAPI beginner to an advanced u
- **Expected Output:** Endpoints that return static JSON data.
- **Pitfalls:** Forgetting to register routes in `main.rs` if not using auto-discovery.

#### 🛠️ Mini Project: "The Calculator"
Create an endpoint `GET /add?a=5&b=10` that returns `{"result": 15}`. This practices query parameter extraction and JSON responses.

#### 🧠 Knowledge Check
1. Which macro is used to define a GET handler?
2. How do you return a JSON response from a handler?
Expand All @@ -37,6 +43,9 @@ This curriculum is designed to take you from a RustAPI beginner to an advanced u
- **Expected Output:** `GET /users/{id}` returns the ID. `POST /users` echoes the JSON body.
- **Pitfalls:** Consuming the body twice (e.g., using `Json` and `Body` in the same handler).

#### 🛠️ Mini Project: "The User Registry"
Create a `POST /register` endpoint that accepts a JSON body `{"username": "...", "age": ...}` and returns a welcome message using the username. Use the `Json` extractor.

#### 🧠 Knowledge Check
1. Which extractor is used for URL parameters like `/users/:id`?
2. Which extractor parses the request body as JSON?
Expand Down
49 changes: 28 additions & 21 deletions docs/cookbook/src/recipes/file_uploads.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# File Uploads

Handling file uploads efficiently is crucial for modern applications. RustAPI provides a `Multipart` extractor that allows you to stream uploads, enabling you to handle large files (e.g., 1GB+) without consuming proportional RAM.
Handling file uploads is a common requirement. RustAPI provides a `Multipart` extractor to parse `multipart/form-data` requests.

## Dependencies

Expand All @@ -13,15 +13,13 @@ tokio = { version = "1", features = ["fs", "io-util"] }
uuid = { version = "1", features = ["v4"] }
```

## Streaming Upload Example
## Buffered Upload Example

Here is a complete, runnable example of a file upload server that streams files to a `./uploads` directory.
RustAPI's `Multipart` extractor currently buffers the entire request body into memory before parsing. This means it is suitable for small to medium file uploads (e.g., images, documents) but care must be taken with very large files to avoid running out of RAM.

```rust
use rustapi_rs::prelude::*;
use rustapi_core::multipart::Multipart;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use rustapi_rs::extract::{Multipart, DefaultBodyLimit};
use std::path::Path;

#[tokio::main]
Expand All @@ -35,6 +33,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Increase body limit to 1GB (default is usually 1MB)
.body_limit(1024 * 1024 * 1024)
.route("/upload", post(upload_handler))
// Increase body limit to 50MB (default is usually 2MB)
// ⚠️ IMPORTANT: Since Multipart buffers the whole body,
// setting this too high can exhaust server memory.
.layer(DefaultBodyLimit::max(50 * 1024 * 1024))
.run("127.0.0.1:8080")
.await
}
Expand All @@ -56,27 +58,31 @@ async fn upload_handler(mut multipart: Multipart) -> Result<Json<UploadResponse>
let mut uploaded_files = Vec::new();

// Iterate over the fields in the multipart form
while let Some(mut field) = multipart.next_field().await.map_err(|_| ApiError::bad_request("Invalid multipart"))? {
while let Some(field) = multipart.next_field().await.map_err(|_| ApiError::bad_request("Invalid multipart"))? {

// Skip fields that are not files
if !field.is_file() {
continue;
}

let file_name = field.file_name().unwrap_or("unknown.bin").to_string();
let content_type = field.content_type().unwrap_or("application/octet-stream").to_string();

// ⚠️ Security: Never trust the user-provided filename directly!
// It could contain paths like "../../../etc/passwd".
// Always generate a safe filename or sanitize inputs.
let safe_filename = format!("{}-{}", uuid::Uuid::new_v4(), file_name);
let path = Path::new("./uploads").join(&safe_filename);

println!("Streaming file: {} -> {:?}", file_name, path);
// Option 1: Use the helper method (sanitizes filename automatically)
// field.save_to("./uploads", Some(&safe_filename)).await.map_err(|e| ApiError::internal(e.to_string()))?;

// Open destination file
let mut file = File::create(&path).await.map_err(|e| ApiError::internal(e.to_string()))?;
// Option 2: Manual write (gives you full control)
let data = field.bytes().await.map_err(|e| ApiError::internal(e.to_string()))?;
let path = Path::new("./uploads").join(&safe_filename);

// Stream the field content chunk-by-chunk
// This is memory efficient even for large files.
while let Some(chunk) = field.chunk().await.map_err(|_| ApiError::bad_request("Stream error"))? {
file.write_all(&chunk).await.map_err(|e| ApiError::internal(e.to_string()))?;
}
tokio::fs::write(&path, &data).await.map_err(|e| ApiError::internal(e.to_string()))?;

println!("Saved file: {} -> {:?}", file_name, path);

uploaded_files.push(FileResult {
original_name: file_name,
Expand All @@ -94,13 +100,14 @@ async fn upload_handler(mut multipart: Multipart) -> Result<Json<UploadResponse>

## Key Concepts

### 1. Streaming vs Buffering
By default, some frameworks load the entire file into RAM. RustAPI's `Multipart` allows you to process the stream incrementally using `field.chunk()`.
- **Buffering**: `field.bytes().await` (Load all into RAM - simple but dangerous for large files)
- **Streaming**: `field.chunk().await` (Load small chunks - scalable)
### 1. Buffering
RustAPI loads the entire `multipart/form-data` body into memory.
- **Pros**: Simple API, easy to work with.
- **Cons**: High memory usage for concurrent large uploads.
- **Mitigation**: Set a reasonable `DefaultBodyLimit` (e.g., 10MB - 100MB) to prevent DoS attacks.

### 2. Body Limits
The default request body limit is often small (e.g., 1MB) to prevent DoS attacks. You must explicitly increase this limit for file upload routes using `RustApi::new().body_limit(size)`. This applies globally to the application instance. If you need different limits for different routes, consider creating separate router instances or using a custom layer.
The default request body limit is small (2MB) to prevent attacks. You **must** explicitly increase this limit for file upload routes using `.layer(DefaultBodyLimit::max(size_in_bytes))`.

### 3. Security
- **Path Traversal**: Malicious users can send filenames like `../../system32/cmd.exe`. Always rename files or sanitize filenames strictly.
Expand Down
73 changes: 0 additions & 73 deletions docs/cookbook/src/recipes/new_feature.md

This file was deleted.

31 changes: 0 additions & 31 deletions docs/cookbook/src/recipes/tuning.md

This file was deleted.

Loading
Loading