Skip to content

Commit 189ae08

Browse files
authored
Merge pull request #132 from Tuntii/docs-maintenance-2026-02-23-12444186712278693427
docs: cookbook expansion and learning path improvements
2 parents 1e235e7 + 567a28e commit 189ae08

8 files changed

Lines changed: 139 additions & 10 deletions

File tree

docs/.agent/docs_coverage.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@
3939
| File Uploads | `recipes/file_uploads.md` | `rustapi-core` | Updated (Buffered) |
4040
| Deployment | `recipes/deployment.md` | `cargo-rustapi` | OK |
4141
| Testing | `recipes/testing.md` | `rustapi-testing` | OK |
42+
| Graceful Shutdown | `recipes/graceful_shutdown.md` | `rustapi-core/src/server.rs` (`run_with_shutdown`) | OK |

docs/.agent/last_run.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"last_processed_ref": "v0.1.335",
3-
"date": "2026-02-19",
4-
"notes": "Deleted incorrect recipes (tuning, action pattern). Updated File Uploads and WebSockets recipes for accuracy. Expanded Learning Path with mini projects."
3+
"date": "2026-02-23",
4+
"notes": "Fixed MockServer examples in testing recipe. Added Graceful Shutdown recipe. Enhanced Learning Path with 'The Email Worker' mini-project."
55
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Documentation Run Report: 2026-02-23
2+
3+
**Status**: Success
4+
**Detected Version**: v0.1.335 (No change)
5+
**Focus**: Cookbook Expansion & Learning Path Improvement
6+
7+
## Changes
8+
9+
### 1. Fixes
10+
- **`docs/cookbook/src/recipes/testing.md`**: Fixed incorrect `MockServer` usage in examples. The previous code used `.await` on non-async builder methods and an invalid `url()` method.
11+
12+
### 2. New Recipes
13+
- **`docs/cookbook/src/recipes/graceful_shutdown.md`**: Added a comprehensive guide on implementing graceful shutdown using `RustApi::run_with_shutdown` and handling Unix signals (`SIGTERM`).
14+
15+
### 3. Learning Path Improvements
16+
- **`docs/cookbook/src/learning/curriculum.md`**: Enhanced "Module 11: Background Jobs & Testing" with a concrete mini-project ("The Email Worker") and improved knowledge check questions.
17+
18+
### 4. Index Updates
19+
- Added "Graceful Shutdown" to `docs/cookbook/src/recipes/README.md` and `docs/cookbook/src/SUMMARY.md`.
20+
21+
## Next Steps
22+
- Continue expanding the Cookbook with recipes for "Configuration Management" and "Error Handling Patterns".
23+
- Review "Module 12: Observability" for potential mini-project additions.

docs/cookbook/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
- [Production Tuning](recipes/high_performance.md)
4747
- [Response Compression](recipes/compression.md)
4848
- [Resilience Patterns](recipes/resilience.md)
49+
- [Graceful Shutdown](recipes/graceful_shutdown.md)
4950
- [Audit Logging](recipes/audit_logging.md)
5051
- [Time-Travel Debugging (Replay)](recipes/replay.md)
5152
- [Deployment](recipes/deployment.md)

docs/cookbook/src/learning/curriculum.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -212,15 +212,23 @@ Create a `POST /register` endpoint that accepts a JSON body `{"username": "...",
212212
- **Prerequisites:** Phase 3.
213213
- **Reading:** [Background Jobs Recipe](../recipes/background_jobs.md), [Testing Strategy](../concepts/testing.md).
214214
- **Task:**
215-
1. Implement a job that sends a "Welcome" email (simulated) when a user registers.
216-
2. Write an integration test using `TestClient` to verify the registration endpoint.
217-
- **Expected Output:** Registration returns 200 immediately; console logs show "Sending welcome email to ..." shortly after. Tests pass.
218-
- **Pitfalls:** Forgetting to start the job worker loop.
215+
1. Implement a job `WelcomeEmailJob` that sends a "Welcome" email (simulated with `tokio::time::sleep`).
216+
2. Enqueue this job inside your `POST /register` handler.
217+
3. Write an integration test using `TestClient` to verify the registration endpoint.
218+
- **Expected Output:** Registration returns 200 immediately (low latency); console logs show "Sending welcome email to ..." shortly after (asynchronous). Tests pass.
219+
- **Pitfalls:** Forgetting to start the job worker loop (`JobWorker::new(queue).run().await`).
220+
221+
#### 🛠️ Mini Project: "The Email Worker"
222+
Create a system where users can request a "Report".
223+
1. `POST /reports`: Enqueues a `GenerateReportJob`. Returns `{"job_id": "..."}` immediately.
224+
2. The job simulates 5 seconds of work and then writes "Report Generated" to a file or log.
225+
3. (Bonus) Use Redis backend for persistence.
219226

220227
#### 🧠 Knowledge Check
221-
1. Why use background jobs for email sending?
222-
2. Which backend is suitable for local development?
228+
1. Why should you offload email sending to a background job?
229+
2. Which backend is suitable for local development vs production?
223230
3. How do you enqueue a job from a handler?
231+
4. How can you test that a job was enqueued without actually running it?
224232

225233
### 🏆 Phase 3 Capstone: "The Real-Time Collaboration Tool"
226234
**Objective:** Build a real-time collaborative note-taking app.

docs/cookbook/src/recipes/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Each recipe follows a simple structure:
2727
- [Production Tuning](high_performance.md)
2828
- [Response Compression](compression.md)
2929
- [Resilience Patterns](resilience.md)
30+
- [Graceful Shutdown](graceful_shutdown.md)
3031
- [Time-Travel Debugging (Replay)](replay.md)
3132
- [Deployment](deployment.md)
3233
- [HTTP/3 (QUIC)](http3_quic.md)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Graceful Shutdown
2+
3+
Graceful shutdown allows your API to stop accepting new connections and finish processing active requests before terminating. This is crucial for avoiding data loss and ensuring a smooth deployment process.
4+
5+
## Problem
6+
7+
When you stop a server (e.g., via `CTRL+C` or `SIGTERM`), you want to ensure that:
8+
1. The server stops listening on the port.
9+
2. Ongoing requests are allowed to complete.
10+
3. Resources (database connections, background jobs) are cleaned up properly.
11+
12+
## Solution
13+
14+
RustAPI provides the `run_with_shutdown` method, which accepts a `Future`. When this future completes, the server initiates the shutdown process.
15+
16+
### Basic Example (CTRL+C)
17+
18+
```rust
19+
use rustapi_rs::prelude::*;
20+
use tokio::signal;
21+
22+
#[tokio::main]
23+
async fn main() -> Result<()> {
24+
// 1. Define your application
25+
let app = RustApi::new().route("/", get(hello));
26+
27+
// 2. Define the shutdown signal
28+
let shutdown_signal = async {
29+
signal::ctrl_c()
30+
.await
31+
.expect("failed to install CTRL+C handler");
32+
};
33+
34+
// 3. Run with shutdown
35+
println!("Server running... Press CTRL+C to stop.");
36+
app.run_with_shutdown("127.0.0.1:3000", shutdown_signal).await?;
37+
38+
println!("Server stopped gracefully.");
39+
Ok(())
40+
}
41+
42+
async fn hello() -> &'static str {
43+
// Simulate some work
44+
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
45+
"Hello, World!"
46+
}
47+
```
48+
49+
### Production Example (Unix Signals)
50+
51+
In a production environment (like Kubernetes or Docker), you need to handle `SIGTERM` as well as `SIGINT`.
52+
53+
```rust
54+
use rustapi_rs::prelude::*;
55+
use tokio::signal;
56+
57+
#[tokio::main]
58+
async fn main() -> Result<()> {
59+
let app = RustApi::new().route("/", get(hello));
60+
61+
app.run_with_shutdown("0.0.0.0:3000", shutdown_signal()).await?;
62+
63+
Ok(())
64+
}
65+
66+
async fn shutdown_signal() {
67+
let ctrl_c = async {
68+
signal::ctrl_c()
69+
.await
70+
.expect("failed to install Ctrl+C handler");
71+
};
72+
73+
#[cfg(unix)]
74+
let terminate = async {
75+
signal::unix::signal(signal::unix::SignalKind::terminate())
76+
.expect("failed to install signal handler")
77+
.recv()
78+
.await;
79+
};
80+
81+
#[cfg(not(unix))]
82+
let terminate = std::future::pending::<()>();
83+
84+
tokio::select! {
85+
_ = ctrl_c => println!("Received Ctrl+C"),
86+
_ = terminate => println!("Received SIGTERM"),
87+
}
88+
}
89+
```
90+
91+
## Discussion
92+
93+
- **Active Requests**: RustAPI (via Hyper) will wait for active requests to complete.
94+
- **Timeout**: You might want to wrap the server execution in a timeout if you want to force shutdown after a certain period (though Hyper usually handles connection draining well).
95+
- **Background Tasks**: If you have spawned background tasks using `tokio::spawn`, they are detached and will be aborted when the runtime shuts down. For critical background work, consider using a dedicated job queue (like `rustapi-jobs`) or a `CancellationToken` to coordinate shutdown.

docs/cookbook/src/recipes/testing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,10 @@ async fn test_external_integration() {
100100
MockResponse::new()
101101
.status(200)
102102
.body(r#"{"data": "mocked"}"#)
103-
).await;
103+
);
104104

105105
// 3. Use the mock server's URL in your app configuration
106-
let mock_url = mock_server.url("/external-data");
106+
let mock_url = format!("{}{}", mock_server.base_url(), "/external-data");
107107

108108
// Simulating your app logic calling the external service
109109
let client = reqwest::Client::new();

0 commit comments

Comments
 (0)