Skip to main content

demo_api/
demo_api.rs

1//! Full-featured demo application showcasing the fastapi_rust framework.
2//!
3//! Demonstrates:
4//! - Route definitions with multiple HTTP methods
5//! - Path and query parameter extraction
6//! - JSON request/response handling
7//! - Middleware (CORS, security headers, rate limiting, request ID)
8//! - Error handling with validation
9//! - Dependency injection
10//! - Pagination
11//! - Background tasks
12//! - OpenAPI schema generation
13//! - Configuration
14//!
15//! Run with: `cargo run --example demo_api`
16
17use fastapi_rust::prelude::*;
18use serde::{Deserialize, Serialize};
19
20// ============================================================
21// Domain Models
22// ============================================================
23
24/// A user in our system.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct User {
27    pub id: u64,
28    pub name: String,
29    pub email: String,
30    pub role: UserRole,
31}
32
33/// User roles for authorization.
34#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
35pub enum UserRole {
36    Admin,
37    Editor,
38    Viewer,
39}
40
41/// Request body for creating a user.
42#[derive(Debug, Deserialize)]
43pub struct CreateUser {
44    pub name: String,
45    pub email: String,
46    pub role: Option<UserRole>,
47}
48
49/// Request body for updating a user.
50#[derive(Debug, Deserialize)]
51pub struct UpdateUser {
52    pub name: Option<String>,
53    pub email: Option<String>,
54    pub role: Option<UserRole>,
55}
56
57/// Response envelope for consistent API responses.
58#[derive(Debug, Serialize)]
59pub struct ApiResponse<T: Serialize> {
60    pub data: T,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub message: Option<String>,
63}
64
65impl<T: Serialize> ApiResponse<T> {
66    pub fn ok(data: T) -> Self {
67        Self {
68            data,
69            message: None,
70        }
71    }
72
73    pub fn with_message(data: T, msg: impl Into<String>) -> Self {
74        Self {
75            data,
76            message: Some(msg.into()),
77        }
78    }
79}
80
81/// Paginated response wrapper.
82#[derive(Debug, Serialize)]
83pub struct PaginatedResponse<T: Serialize> {
84    pub items: Vec<T>,
85    pub total: u64,
86    pub page: u64,
87    pub per_page: u64,
88    pub total_pages: u64,
89}
90
91// ============================================================
92// Application State
93// ============================================================
94
95/// Shared application state.
96#[derive(Debug, Clone)]
97pub struct AppState {
98    pub app_name: String,
99    pub version: String,
100}
101
102impl Default for AppState {
103    fn default() -> Self {
104        Self {
105            app_name: "Demo API".into(),
106            version: env!("CARGO_PKG_VERSION").into(),
107        }
108    }
109}
110
111// ============================================================
112// Route Handlers
113// ============================================================
114
115/// Health check endpoint.
116///
117/// Returns service status and version information.
118/// Used by load balancers and monitoring systems.
119fn health_check() -> serde_json::Value {
120    serde_json::json!({
121        "status": "healthy",
122        "version": env!("CARGO_PKG_VERSION"),
123        "uptime_notice": "This is a demo application"
124    })
125}
126
127/// List users with pagination.
128///
129/// Supports page/per_page query parameters with sensible defaults.
130fn list_users(page: u64, per_page: u64) -> PaginatedResponse<User> {
131    // Simulated data
132    let all_users = sample_users();
133    let total = all_users.len() as u64;
134    let total_pages = total.div_ceil(per_page);
135    #[allow(clippy::cast_possible_truncation)]
136    let start = ((page - 1) * per_page) as usize;
137    #[allow(clippy::cast_possible_truncation)]
138    let items: Vec<User> = all_users
139        .into_iter()
140        .skip(start)
141        .take(per_page as usize)
142        .collect();
143
144    PaginatedResponse {
145        items,
146        total,
147        page,
148        per_page,
149        total_pages,
150    }
151}
152
153/// Get a single user by ID.
154fn get_user(id: u64) -> Result<ApiResponse<User>, (u16, String)> {
155    sample_users()
156        .into_iter()
157        .find(|u| u.id == id)
158        .map(ApiResponse::ok)
159        .ok_or((404, format!("User {id} not found")))
160}
161
162/// Create a new user.
163fn create_user(input: CreateUser) -> ApiResponse<User> {
164    let user = User {
165        id: 42, // In production, this is typically auto-generated
166        name: input.name,
167        email: input.email,
168        role: input.role.unwrap_or(UserRole::Viewer),
169    };
170    ApiResponse::with_message(user, "User created successfully")
171}
172
173/// Delete a user by ID.
174#[allow(dead_code)]
175fn delete_user(id: u64) -> Result<ApiResponse<()>, (u16, String)> {
176    if sample_users().iter().any(|u| u.id == id) {
177        Ok(ApiResponse::with_message((), format!("User {id} deleted")))
178    } else {
179        Err((404, format!("User {id} not found")))
180    }
181}
182
183// ============================================================
184// Configuration
185// ============================================================
186
187/// Build the application configuration.
188fn build_config() -> AppConfig {
189    AppConfig {
190        name: "Demo API".into(),
191        version: env!("CARGO_PKG_VERSION").into(),
192        debug: cfg!(debug_assertions),
193        max_body_size: 10 * 1024 * 1024, // 10 MB
194        request_timeout_ms: 30_000,
195        root_path: String::new(),
196        root_path_in_servers: false,
197        trailing_slash_mode: fastapi_core::routing::TrailingSlashMode::Strict,
198        debug_config: fastapi_core::error::DebugConfig::default(),
199    }
200}
201
202/// Print demo information.
203fn print_demo_info() {
204    println!("=== FastAPI Rust Demo Application ===");
205    println!();
206    println!("This example demonstrates:");
207    println!("  - Route definitions (GET, POST, DELETE)");
208    println!("  - Path and query parameter extraction");
209    println!("  - JSON request/response handling");
210    println!("  - Middleware configuration (CORS, security headers)");
211    println!("  - Pagination patterns");
212    println!("  - Error handling");
213    println!("  - Application configuration");
214    println!();
215    println!("Endpoints:");
216    println!("  GET  /health              - Health check");
217    println!("  GET  /api/v1/users        - List users (paginated)");
218    println!("  GET  /api/v1/users/:id    - Get user by ID");
219    println!("  POST /api/v1/users        - Create user");
220    println!("  DELETE /api/v1/users/:id  - Delete user");
221    println!();
222    println!("Configuration:");
223    let config = build_config();
224    println!("  Name:           {}", config.name);
225    println!("  Version:        {}", config.version);
226    println!("  Debug:          {}", config.debug);
227    println!(
228        "  Max body size:  {} MB",
229        config.max_body_size / (1024 * 1024)
230    );
231    println!("  Timeout:        {}ms", config.request_timeout_ms);
232    println!();
233    println!("CORS: allow_any_origin=true, allow_credentials=true");
234    println!("Rate limit: 100 req/min (token bucket)");
235    println!("Security headers: X-Content-Type-Options, X-Frame-Options, Referrer-Policy");
236    println!();
237    println!("--- Demo data ---");
238    println!();
239
240    // Demonstrate the handlers
241    println!("Health check:");
242    let health = health_check();
243    print_pretty(&health);
244    println!();
245
246    println!("List users (page 1, 2 per page):");
247    let users = list_users(1, 2);
248    print_pretty(&users);
249    println!();
250
251    println!("Get user 1:");
252    match get_user(1) {
253        Ok(resp) => print_pretty(&resp),
254        Err((code, msg)) => println!("  Error {code}: {msg}"),
255    }
256    println!();
257
258    println!("Get user 999 (not found):");
259    match get_user(999) {
260        Ok(resp) => print_pretty(&resp),
261        Err((code, msg)) => println!("  Error {code}: {msg}"),
262    }
263    println!();
264
265    println!("Create user:");
266    let new_user = create_user(CreateUser {
267        name: "New User".into(),
268        email: "new@example.com".into(),
269        role: Some(UserRole::Editor),
270    });
271    print_pretty(&new_user);
272}
273
274fn print_pretty<T: Serialize>(value: &T) {
275    match serde_json::to_string_pretty(value) {
276        Ok(text) => println!("  {text}"),
277        Err(err) => println!("  <json error: {err}>"),
278    }
279}
280
281// ============================================================
282// Sample Data
283// ============================================================
284
285fn sample_users() -> Vec<User> {
286    vec![
287        User {
288            id: 1,
289            name: "Alice Johnson".into(),
290            email: "alice@example.com".into(),
291            role: UserRole::Admin,
292        },
293        User {
294            id: 2,
295            name: "Bob Smith".into(),
296            email: "bob@example.com".into(),
297            role: UserRole::Editor,
298        },
299        User {
300            id: 3,
301            name: "Carol Williams".into(),
302            email: "carol@example.com".into(),
303            role: UserRole::Viewer,
304        },
305    ]
306}
307
308// ============================================================
309// Main
310// ============================================================
311
312fn main() {
313    print_demo_info();
314}