pub struct App { /* private fields */ }Expand description
A configured web application.
The App holds all routes, middleware, state, and lifecycle hooks,
and provides methods to handle incoming requests.
Implementations§
Source§impl App
impl App
Sourcepub fn builder() -> AppBuilder
pub fn builder() -> AppBuilder
Creates a new application builder.
Examples found in repository?
51fn main() {
52 println!("fastapi_rust Hello World Example");
53 println!("================================\n");
54
55 // Build the application
56 //
57 // App::builder() creates a new application builder that lets you:
58 // - Add routes for different HTTP methods
59 // - Configure middleware
60 // - Set application state
61 // - Define exception handlers
62 let app = App::builder()
63 // Register a GET handler for the root path "/"
64 //
65 // This is equivalent to:
66 // @app.get("/")
67 // def hello():
68 // return "Hello, World!"
69 // in Python FastAPI
70 .get("/", hello_handler)
71 // Build the final immutable App
72 .build();
73
74 println!("App created with {} route(s)\n", app.route_count());
75
76 // Create a test client to make requests to our app
77 //
78 // TestClient wraps any Handler (including App) and provides
79 // a convenient API for making HTTP requests in tests.
80 let client = TestClient::new(app);
81
82 // Make a GET request to "/"
83 println!("Making request: GET /");
84 let response = client.get("/").send();
85
86 // Check the response
87 println!(
88 "GET / -> {} {}",
89 response.status().as_u16(),
90 response.status().canonical_reason()
91 );
92 println!("Response: {}\n", response.text());
93
94 // Verify success
95 assert_eq!(response.status().as_u16(), 200);
96 assert_eq!(response.text(), "Hello, World!");
97
98 // Try a path that doesn't exist - should get 404
99 println!("Making request: GET /not-found");
100 let response = client.get("/not-found").send();
101 println!(
102 "GET /not-found -> {} {}",
103 response.status().as_u16(),
104 response.status().canonical_reason()
105 );
106 assert_eq!(response.status().as_u16(), 404);
107
108 println!("\nAll assertions passed!");
109}More examples
24fn main() {
25 println!("Getting Started Guide - Code Validation\n");
26
27 // === Basic App Example ===
28 println!("1. Basic app with two routes:");
29 let app = App::builder()
30 .get("/", hello)
31 .get("/health", health)
32 .build();
33
34 println!(" Routes: {}", app.route_count());
35 let client = TestClient::new(app);
36
37 let response = client.get("/").send();
38 println!(
39 " GET / -> {} ({})",
40 response.status().as_u16(),
41 response.text()
42 );
43 assert_eq!(response.status().as_u16(), 200);
44 assert_eq!(response.text(), "Hello, World!");
45
46 let response = client.get("/health").send();
47 println!(
48 " GET /health -> {} ({})",
49 response.status().as_u16(),
50 response.text()
51 );
52 assert_eq!(response.status().as_u16(), 200);
53
54 // === App with Middleware ===
55 println!("\n2. App with middleware:");
56 let app = App::builder()
57 .middleware(RequestIdMiddleware::new())
58 .middleware(SecurityHeaders::new())
59 .get("/", hello)
60 .build();
61
62 let client = TestClient::new(app);
63 let response = client.get("/").send();
64 println!(" GET / -> {}", response.status().as_u16());
65 assert_eq!(response.status().as_u16(), 200);
66
67 // === App with Configuration ===
68 println!("\n3. App with configuration:");
69 let config = AppConfig::new()
70 .name("My API")
71 .version("1.0.0")
72 .debug(true)
73 .max_body_size(10 * 1024 * 1024)
74 .request_timeout_ms(30_000);
75
76 let app = App::builder().config(config).get("/", hello).build();
77
78 println!(" App name: {}", app.config().name);
79 println!(" Version: {}", app.config().version);
80 assert_eq!(app.config().name, "My API");
81 assert_eq!(app.config().version, "1.0.0");
82
83 // === 404 for unknown routes ===
84 println!("\n4. 404 for unknown routes:");
85 let app = App::builder().get("/", hello).build();
86
87 let client = TestClient::new(app);
88 let response = client.get("/nonexistent").send();
89 println!(" GET /nonexistent -> {}", response.status().as_u16());
90 assert_eq!(response.status().as_u16(), 404);
91
92 println!("\nAll getting started examples validated successfully!");
93}252fn main() {
253 // Initialize the global store
254 *STORE.lock().unwrap() = Some(UserDb {
255 users: HashMap::new(),
256 next_id: 1,
257 });
258
259 println!("fastapi_rust CRUD API Example");
260 println!("=============================\n");
261
262 let app = App::builder()
263 .post("/users", create_user)
264 .get("/users", list_users)
265 .get("/users/{id}", get_user)
266 .put("/users/{id}", update_user)
267 .delete("/users/{id}", delete_user)
268 .build();
269
270 println!("App created with {} route(s)\n", app.route_count());
271 let client = TestClient::new(app);
272
273 // 1. Create users
274 println!("1. Create users");
275 let resp = client
276 .post("/users")
277 .header("content-type", "application/json")
278 .body(r#"{"name": "Alice", "email": "alice@example.com"}"#)
279 .send();
280 println!(
281 " POST /users -> {} {}",
282 resp.status().as_u16(),
283 resp.text()
284 );
285 assert_eq!(resp.status().as_u16(), 201);
286
287 let resp = client
288 .post("/users")
289 .header("content-type", "application/json")
290 .body(r#"{"name": "Bob", "email": "bob@example.com"}"#)
291 .send();
292 println!(
293 " POST /users -> {} {}",
294 resp.status().as_u16(),
295 resp.text()
296 );
297 assert_eq!(resp.status().as_u16(), 201);
298
299 // 2. List users
300 println!("\n2. List all users");
301 let resp = client.get("/users").send();
302 println!(
303 " GET /users -> {} {}",
304 resp.status().as_u16(),
305 resp.text()
306 );
307 assert_eq!(resp.status().as_u16(), 200);
308
309 // 3. Get user by ID
310 println!("\n3. Get user by ID");
311 let resp = client.get("/users/1").send();
312 println!(
313 " GET /users/1 -> {} {}",
314 resp.status().as_u16(),
315 resp.text()
316 );
317 assert_eq!(resp.status().as_u16(), 200);
318
319 // 4. Get nonexistent user
320 println!("\n4. Get nonexistent user");
321 let resp = client.get("/users/999").send();
322 println!(
323 " GET /users/999 -> {} {}",
324 resp.status().as_u16(),
325 resp.text()
326 );
327 assert_eq!(resp.status().as_u16(), 404);
328
329 // 5. Update user
330 println!("\n5. Update user");
331 let resp = client
332 .put("/users/1")
333 .header("content-type", "application/json")
334 .body(r#"{"name": "Alice Smith", "email": "alice.smith@example.com"}"#)
335 .send();
336 println!(
337 " PUT /users/1 -> {} {}",
338 resp.status().as_u16(),
339 resp.text()
340 );
341 assert_eq!(resp.status().as_u16(), 200);
342
343 // 6. Validation: empty name
344 println!("\n6. Validation error (empty name)");
345 let resp = client
346 .post("/users")
347 .header("content-type", "application/json")
348 .body(r#"{"name": "", "email": "bad@example.com"}"#)
349 .send();
350 println!(
351 " POST /users -> {} {}",
352 resp.status().as_u16(),
353 resp.text()
354 );
355 assert_eq!(resp.status().as_u16(), 400);
356
357 // 7. Validation: invalid email
358 println!("\n7. Validation error (invalid email)");
359 let resp = client
360 .post("/users")
361 .header("content-type", "application/json")
362 .body(r#"{"name": "Charlie", "email": "not-an-email"}"#)
363 .send();
364 println!(
365 " POST /users -> {} {}",
366 resp.status().as_u16(),
367 resp.text()
368 );
369 assert_eq!(resp.status().as_u16(), 400);
370
371 // 8. Wrong Content-Type
372 println!("\n8. Wrong Content-Type");
373 let resp = client
374 .post("/users")
375 .header("content-type", "text/plain")
376 .body(r#"{"name": "Dan", "email": "dan@example.com"}"#)
377 .send();
378 println!(
379 " POST /users -> {} {}",
380 resp.status().as_u16(),
381 resp.text()
382 );
383 assert_eq!(resp.status().as_u16(), 415);
384
385 // 9. Delete user
386 println!("\n9. Delete user");
387 let resp = client.delete("/users/2").send();
388 println!(" DELETE /users/2 -> {}", resp.status().as_u16());
389 assert_eq!(resp.status().as_u16(), 204);
390
391 // 10. Verify deletion
392 println!("\n10. Verify deletion");
393 let resp = client.get("/users/2").send();
394 println!(
395 " GET /users/2 -> {} {}",
396 resp.status().as_u16(),
397 resp.text()
398 );
399 assert_eq!(resp.status().as_u16(), 404);
400
401 let resp = client.get("/users").send();
402 println!(
403 " GET /users -> {} {}",
404 resp.status().as_u16(),
405 resp.text()
406 );
407 assert_eq!(resp.status().as_u16(), 200);
408
409 println!("\nAll CRUD operations passed!");
410}248fn main() {
249 println!("fastapi_rust Authentication Example");
250 println!("====================================\n");
251
252 // Build the application with public and protected routes
253 let app = App::builder()
254 // Public endpoints - accessible to everyone
255 .get("/public", public_handler)
256 // Login endpoint - returns a token
257 .post("/login", login_handler)
258 // Protected endpoint - requires valid bearer token
259 .get("/protected", protected_handler)
260 .build();
261
262 println!("App created with {} route(s)\n", app.route_count());
263
264 // Create a test client
265 let client = TestClient::new(app);
266
267 // =========================================================================
268 // Test 1: Public endpoint - no auth required
269 // =========================================================================
270 println!("1. Public endpoint - no auth required");
271 let response = client.get("/public").send();
272 println!(
273 " GET /public -> {} {}",
274 response.status().as_u16(),
275 response.status().canonical_reason()
276 );
277 assert_eq!(response.status().as_u16(), 200);
278 assert!(response.text().contains("public endpoint"));
279
280 // =========================================================================
281 // Test 2: Protected endpoint - without token (should get 401)
282 // =========================================================================
283 println!("\n2. Protected endpoint - without token");
284 let response = client.get("/protected").send();
285 println!(
286 " GET /protected -> {} {}",
287 response.status().as_u16(),
288 response.status().canonical_reason()
289 );
290 assert_eq!(
291 response.status().as_u16(),
292 401,
293 "Protected endpoint should return 401 without token"
294 );
295
296 // Check for WWW-Authenticate header
297 let has_www_auth = response
298 .headers()
299 .iter()
300 .any(|(name, value)| name == "www-authenticate" && value == b"Bearer");
301 assert!(
302 has_www_auth,
303 "401 response should include WWW-Authenticate: Bearer header"
304 );
305
306 // =========================================================================
307 // Test 3: Login endpoint - get a token
308 // =========================================================================
309 println!("\n3. Login endpoint - get a token");
310 let response = client
311 .post("/login")
312 .header("content-type", "application/json")
313 .body(r#"{"username":"test","password":"test123"}"#)
314 .send();
315 println!(
316 " POST /login -> {} {}",
317 response.status().as_u16(),
318 response.status().canonical_reason()
319 );
320 assert_eq!(response.status().as_u16(), 200);
321
322 // Parse the response to get the token
323 let body: serde_json::Value = serde_json::from_str(response.text()).unwrap();
324 let token = body["access_token"].as_str().unwrap();
325 println!(" Token: {token}");
326 assert_eq!(token, SECRET_TOKEN);
327
328 // =========================================================================
329 // Test 4: Protected endpoint - with valid token (should get 200)
330 // =========================================================================
331 println!("\n4. Protected endpoint - with valid token");
332 let response = client
333 .get("/protected")
334 .header("authorization", format!("Bearer {SECRET_TOKEN}"))
335 .send();
336 println!(
337 " GET /protected (Authorization: Bearer {}) -> {} {}",
338 SECRET_TOKEN,
339 response.status().as_u16(),
340 response.status().canonical_reason()
341 );
342 assert_eq!(
343 response.status().as_u16(),
344 200,
345 "Protected endpoint should return 200 with valid token"
346 );
347 assert!(response.text().contains("protected resource"));
348
349 // =========================================================================
350 // Test 5: Protected endpoint - with invalid token (should get 403)
351 // =========================================================================
352 println!("\n5. Protected endpoint - with invalid token");
353 let response = client
354 .get("/protected")
355 .header("authorization", "Bearer wrong_token")
356 .send();
357 println!(
358 " GET /protected (Authorization: Bearer wrong_token) -> {} {}",
359 response.status().as_u16(),
360 response.status().canonical_reason()
361 );
362 assert_eq!(
363 response.status().as_u16(),
364 403,
365 "Protected endpoint should return 403 with invalid token"
366 );
367
368 // =========================================================================
369 // Test 6: Protected endpoint - with wrong auth scheme (should get 401)
370 // =========================================================================
371 println!("\n6. Protected endpoint - with wrong auth scheme");
372 let response = client
373 .get("/protected")
374 .header("authorization", "Basic dXNlcjpwYXNz")
375 .send();
376 println!(
377 " GET /protected (Authorization: Basic ...) -> {} {}",
378 response.status().as_u16(),
379 response.status().canonical_reason()
380 );
381 assert_eq!(
382 response.status().as_u16(),
383 401,
384 "Protected endpoint should return 401 with wrong auth scheme"
385 );
386
387 // =========================================================================
388 // Test 7: Login with wrong Content-Type (should get 415)
389 // =========================================================================
390 println!("\n7. Login with wrong Content-Type");
391 let response = client
392 .post("/login")
393 .header("content-type", "text/plain")
394 .body("username=test&password=test123")
395 .send();
396 println!(
397 " POST /login (Content-Type: text/plain) -> {} {}",
398 response.status().as_u16(),
399 response.status().canonical_reason()
400 );
401 assert_eq!(
402 response.status().as_u16(),
403 415,
404 "Login should return 415 with wrong Content-Type"
405 );
406
407 // =========================================================================
408 // Test 8: Token case sensitivity (lowercase 'bearer')
409 // =========================================================================
410 println!("\n8. Token case sensitivity (lowercase 'bearer')");
411 let response = client
412 .get("/protected")
413 .header("authorization", format!("bearer {SECRET_TOKEN}"))
414 .send();
415 println!(
416 " GET /protected (Authorization: bearer {}) -> {} {}",
417 SECRET_TOKEN,
418 response.status().as_u16(),
419 response.status().canonical_reason()
420 );
421 assert_eq!(
422 response.status().as_u16(),
423 200,
424 "Bearer scheme should be case-insensitive (lowercase accepted)"
425 );
426
427 println!("\nAll authentication tests passed!");
428}Sourcepub fn config(&self) -> &AppConfig
pub fn config(&self) -> &AppConfig
Returns the application configuration.
Examples found in repository?
24fn main() {
25 println!("Getting Started Guide - Code Validation\n");
26
27 // === Basic App Example ===
28 println!("1. Basic app with two routes:");
29 let app = App::builder()
30 .get("/", hello)
31 .get("/health", health)
32 .build();
33
34 println!(" Routes: {}", app.route_count());
35 let client = TestClient::new(app);
36
37 let response = client.get("/").send();
38 println!(
39 " GET / -> {} ({})",
40 response.status().as_u16(),
41 response.text()
42 );
43 assert_eq!(response.status().as_u16(), 200);
44 assert_eq!(response.text(), "Hello, World!");
45
46 let response = client.get("/health").send();
47 println!(
48 " GET /health -> {} ({})",
49 response.status().as_u16(),
50 response.text()
51 );
52 assert_eq!(response.status().as_u16(), 200);
53
54 // === App with Middleware ===
55 println!("\n2. App with middleware:");
56 let app = App::builder()
57 .middleware(RequestIdMiddleware::new())
58 .middleware(SecurityHeaders::new())
59 .get("/", hello)
60 .build();
61
62 let client = TestClient::new(app);
63 let response = client.get("/").send();
64 println!(" GET / -> {}", response.status().as_u16());
65 assert_eq!(response.status().as_u16(), 200);
66
67 // === App with Configuration ===
68 println!("\n3. App with configuration:");
69 let config = AppConfig::new()
70 .name("My API")
71 .version("1.0.0")
72 .debug(true)
73 .max_body_size(10 * 1024 * 1024)
74 .request_timeout_ms(30_000);
75
76 let app = App::builder().config(config).get("/", hello).build();
77
78 println!(" App name: {}", app.config().name);
79 println!(" Version: {}", app.config().version);
80 assert_eq!(app.config().name, "My API");
81 assert_eq!(app.config().version, "1.0.0");
82
83 // === 404 for unknown routes ===
84 println!("\n4. 404 for unknown routes:");
85 let app = App::builder().get("/", hello).build();
86
87 let client = TestClient::new(app);
88 let response = client.get("/nonexistent").send();
89 println!(" GET /nonexistent -> {}", response.status().as_u16());
90 assert_eq!(response.status().as_u16(), 404);
91
92 println!("\nAll getting started examples validated successfully!");
93}Sourcepub fn route_count(&self) -> usize
pub fn route_count(&self) -> usize
Returns the number of registered routes.
Examples found in repository?
51fn main() {
52 println!("fastapi_rust Hello World Example");
53 println!("================================\n");
54
55 // Build the application
56 //
57 // App::builder() creates a new application builder that lets you:
58 // - Add routes for different HTTP methods
59 // - Configure middleware
60 // - Set application state
61 // - Define exception handlers
62 let app = App::builder()
63 // Register a GET handler for the root path "/"
64 //
65 // This is equivalent to:
66 // @app.get("/")
67 // def hello():
68 // return "Hello, World!"
69 // in Python FastAPI
70 .get("/", hello_handler)
71 // Build the final immutable App
72 .build();
73
74 println!("App created with {} route(s)\n", app.route_count());
75
76 // Create a test client to make requests to our app
77 //
78 // TestClient wraps any Handler (including App) and provides
79 // a convenient API for making HTTP requests in tests.
80 let client = TestClient::new(app);
81
82 // Make a GET request to "/"
83 println!("Making request: GET /");
84 let response = client.get("/").send();
85
86 // Check the response
87 println!(
88 "GET / -> {} {}",
89 response.status().as_u16(),
90 response.status().canonical_reason()
91 );
92 println!("Response: {}\n", response.text());
93
94 // Verify success
95 assert_eq!(response.status().as_u16(), 200);
96 assert_eq!(response.text(), "Hello, World!");
97
98 // Try a path that doesn't exist - should get 404
99 println!("Making request: GET /not-found");
100 let response = client.get("/not-found").send();
101 println!(
102 "GET /not-found -> {} {}",
103 response.status().as_u16(),
104 response.status().canonical_reason()
105 );
106 assert_eq!(response.status().as_u16(), 404);
107
108 println!("\nAll assertions passed!");
109}More examples
24fn main() {
25 println!("Getting Started Guide - Code Validation\n");
26
27 // === Basic App Example ===
28 println!("1. Basic app with two routes:");
29 let app = App::builder()
30 .get("/", hello)
31 .get("/health", health)
32 .build();
33
34 println!(" Routes: {}", app.route_count());
35 let client = TestClient::new(app);
36
37 let response = client.get("/").send();
38 println!(
39 " GET / -> {} ({})",
40 response.status().as_u16(),
41 response.text()
42 );
43 assert_eq!(response.status().as_u16(), 200);
44 assert_eq!(response.text(), "Hello, World!");
45
46 let response = client.get("/health").send();
47 println!(
48 " GET /health -> {} ({})",
49 response.status().as_u16(),
50 response.text()
51 );
52 assert_eq!(response.status().as_u16(), 200);
53
54 // === App with Middleware ===
55 println!("\n2. App with middleware:");
56 let app = App::builder()
57 .middleware(RequestIdMiddleware::new())
58 .middleware(SecurityHeaders::new())
59 .get("/", hello)
60 .build();
61
62 let client = TestClient::new(app);
63 let response = client.get("/").send();
64 println!(" GET / -> {}", response.status().as_u16());
65 assert_eq!(response.status().as_u16(), 200);
66
67 // === App with Configuration ===
68 println!("\n3. App with configuration:");
69 let config = AppConfig::new()
70 .name("My API")
71 .version("1.0.0")
72 .debug(true)
73 .max_body_size(10 * 1024 * 1024)
74 .request_timeout_ms(30_000);
75
76 let app = App::builder().config(config).get("/", hello).build();
77
78 println!(" App name: {}", app.config().name);
79 println!(" Version: {}", app.config().version);
80 assert_eq!(app.config().name, "My API");
81 assert_eq!(app.config().version, "1.0.0");
82
83 // === 404 for unknown routes ===
84 println!("\n4. 404 for unknown routes:");
85 let app = App::builder().get("/", hello).build();
86
87 let client = TestClient::new(app);
88 let response = client.get("/nonexistent").send();
89 println!(" GET /nonexistent -> {}", response.status().as_u16());
90 assert_eq!(response.status().as_u16(), 404);
91
92 println!("\nAll getting started examples validated successfully!");
93}252fn main() {
253 // Initialize the global store
254 *STORE.lock().unwrap() = Some(UserDb {
255 users: HashMap::new(),
256 next_id: 1,
257 });
258
259 println!("fastapi_rust CRUD API Example");
260 println!("=============================\n");
261
262 let app = App::builder()
263 .post("/users", create_user)
264 .get("/users", list_users)
265 .get("/users/{id}", get_user)
266 .put("/users/{id}", update_user)
267 .delete("/users/{id}", delete_user)
268 .build();
269
270 println!("App created with {} route(s)\n", app.route_count());
271 let client = TestClient::new(app);
272
273 // 1. Create users
274 println!("1. Create users");
275 let resp = client
276 .post("/users")
277 .header("content-type", "application/json")
278 .body(r#"{"name": "Alice", "email": "alice@example.com"}"#)
279 .send();
280 println!(
281 " POST /users -> {} {}",
282 resp.status().as_u16(),
283 resp.text()
284 );
285 assert_eq!(resp.status().as_u16(), 201);
286
287 let resp = client
288 .post("/users")
289 .header("content-type", "application/json")
290 .body(r#"{"name": "Bob", "email": "bob@example.com"}"#)
291 .send();
292 println!(
293 " POST /users -> {} {}",
294 resp.status().as_u16(),
295 resp.text()
296 );
297 assert_eq!(resp.status().as_u16(), 201);
298
299 // 2. List users
300 println!("\n2. List all users");
301 let resp = client.get("/users").send();
302 println!(
303 " GET /users -> {} {}",
304 resp.status().as_u16(),
305 resp.text()
306 );
307 assert_eq!(resp.status().as_u16(), 200);
308
309 // 3. Get user by ID
310 println!("\n3. Get user by ID");
311 let resp = client.get("/users/1").send();
312 println!(
313 " GET /users/1 -> {} {}",
314 resp.status().as_u16(),
315 resp.text()
316 );
317 assert_eq!(resp.status().as_u16(), 200);
318
319 // 4. Get nonexistent user
320 println!("\n4. Get nonexistent user");
321 let resp = client.get("/users/999").send();
322 println!(
323 " GET /users/999 -> {} {}",
324 resp.status().as_u16(),
325 resp.text()
326 );
327 assert_eq!(resp.status().as_u16(), 404);
328
329 // 5. Update user
330 println!("\n5. Update user");
331 let resp = client
332 .put("/users/1")
333 .header("content-type", "application/json")
334 .body(r#"{"name": "Alice Smith", "email": "alice.smith@example.com"}"#)
335 .send();
336 println!(
337 " PUT /users/1 -> {} {}",
338 resp.status().as_u16(),
339 resp.text()
340 );
341 assert_eq!(resp.status().as_u16(), 200);
342
343 // 6. Validation: empty name
344 println!("\n6. Validation error (empty name)");
345 let resp = client
346 .post("/users")
347 .header("content-type", "application/json")
348 .body(r#"{"name": "", "email": "bad@example.com"}"#)
349 .send();
350 println!(
351 " POST /users -> {} {}",
352 resp.status().as_u16(),
353 resp.text()
354 );
355 assert_eq!(resp.status().as_u16(), 400);
356
357 // 7. Validation: invalid email
358 println!("\n7. Validation error (invalid email)");
359 let resp = client
360 .post("/users")
361 .header("content-type", "application/json")
362 .body(r#"{"name": "Charlie", "email": "not-an-email"}"#)
363 .send();
364 println!(
365 " POST /users -> {} {}",
366 resp.status().as_u16(),
367 resp.text()
368 );
369 assert_eq!(resp.status().as_u16(), 400);
370
371 // 8. Wrong Content-Type
372 println!("\n8. Wrong Content-Type");
373 let resp = client
374 .post("/users")
375 .header("content-type", "text/plain")
376 .body(r#"{"name": "Dan", "email": "dan@example.com"}"#)
377 .send();
378 println!(
379 " POST /users -> {} {}",
380 resp.status().as_u16(),
381 resp.text()
382 );
383 assert_eq!(resp.status().as_u16(), 415);
384
385 // 9. Delete user
386 println!("\n9. Delete user");
387 let resp = client.delete("/users/2").send();
388 println!(" DELETE /users/2 -> {}", resp.status().as_u16());
389 assert_eq!(resp.status().as_u16(), 204);
390
391 // 10. Verify deletion
392 println!("\n10. Verify deletion");
393 let resp = client.get("/users/2").send();
394 println!(
395 " GET /users/2 -> {} {}",
396 resp.status().as_u16(),
397 resp.text()
398 );
399 assert_eq!(resp.status().as_u16(), 404);
400
401 let resp = client.get("/users").send();
402 println!(
403 " GET /users -> {} {}",
404 resp.status().as_u16(),
405 resp.text()
406 );
407 assert_eq!(resp.status().as_u16(), 200);
408
409 println!("\nAll CRUD operations passed!");
410}248fn main() {
249 println!("fastapi_rust Authentication Example");
250 println!("====================================\n");
251
252 // Build the application with public and protected routes
253 let app = App::builder()
254 // Public endpoints - accessible to everyone
255 .get("/public", public_handler)
256 // Login endpoint - returns a token
257 .post("/login", login_handler)
258 // Protected endpoint - requires valid bearer token
259 .get("/protected", protected_handler)
260 .build();
261
262 println!("App created with {} route(s)\n", app.route_count());
263
264 // Create a test client
265 let client = TestClient::new(app);
266
267 // =========================================================================
268 // Test 1: Public endpoint - no auth required
269 // =========================================================================
270 println!("1. Public endpoint - no auth required");
271 let response = client.get("/public").send();
272 println!(
273 " GET /public -> {} {}",
274 response.status().as_u16(),
275 response.status().canonical_reason()
276 );
277 assert_eq!(response.status().as_u16(), 200);
278 assert!(response.text().contains("public endpoint"));
279
280 // =========================================================================
281 // Test 2: Protected endpoint - without token (should get 401)
282 // =========================================================================
283 println!("\n2. Protected endpoint - without token");
284 let response = client.get("/protected").send();
285 println!(
286 " GET /protected -> {} {}",
287 response.status().as_u16(),
288 response.status().canonical_reason()
289 );
290 assert_eq!(
291 response.status().as_u16(),
292 401,
293 "Protected endpoint should return 401 without token"
294 );
295
296 // Check for WWW-Authenticate header
297 let has_www_auth = response
298 .headers()
299 .iter()
300 .any(|(name, value)| name == "www-authenticate" && value == b"Bearer");
301 assert!(
302 has_www_auth,
303 "401 response should include WWW-Authenticate: Bearer header"
304 );
305
306 // =========================================================================
307 // Test 3: Login endpoint - get a token
308 // =========================================================================
309 println!("\n3. Login endpoint - get a token");
310 let response = client
311 .post("/login")
312 .header("content-type", "application/json")
313 .body(r#"{"username":"test","password":"test123"}"#)
314 .send();
315 println!(
316 " POST /login -> {} {}",
317 response.status().as_u16(),
318 response.status().canonical_reason()
319 );
320 assert_eq!(response.status().as_u16(), 200);
321
322 // Parse the response to get the token
323 let body: serde_json::Value = serde_json::from_str(response.text()).unwrap();
324 let token = body["access_token"].as_str().unwrap();
325 println!(" Token: {token}");
326 assert_eq!(token, SECRET_TOKEN);
327
328 // =========================================================================
329 // Test 4: Protected endpoint - with valid token (should get 200)
330 // =========================================================================
331 println!("\n4. Protected endpoint - with valid token");
332 let response = client
333 .get("/protected")
334 .header("authorization", format!("Bearer {SECRET_TOKEN}"))
335 .send();
336 println!(
337 " GET /protected (Authorization: Bearer {}) -> {} {}",
338 SECRET_TOKEN,
339 response.status().as_u16(),
340 response.status().canonical_reason()
341 );
342 assert_eq!(
343 response.status().as_u16(),
344 200,
345 "Protected endpoint should return 200 with valid token"
346 );
347 assert!(response.text().contains("protected resource"));
348
349 // =========================================================================
350 // Test 5: Protected endpoint - with invalid token (should get 403)
351 // =========================================================================
352 println!("\n5. Protected endpoint - with invalid token");
353 let response = client
354 .get("/protected")
355 .header("authorization", "Bearer wrong_token")
356 .send();
357 println!(
358 " GET /protected (Authorization: Bearer wrong_token) -> {} {}",
359 response.status().as_u16(),
360 response.status().canonical_reason()
361 );
362 assert_eq!(
363 response.status().as_u16(),
364 403,
365 "Protected endpoint should return 403 with invalid token"
366 );
367
368 // =========================================================================
369 // Test 6: Protected endpoint - with wrong auth scheme (should get 401)
370 // =========================================================================
371 println!("\n6. Protected endpoint - with wrong auth scheme");
372 let response = client
373 .get("/protected")
374 .header("authorization", "Basic dXNlcjpwYXNz")
375 .send();
376 println!(
377 " GET /protected (Authorization: Basic ...) -> {} {}",
378 response.status().as_u16(),
379 response.status().canonical_reason()
380 );
381 assert_eq!(
382 response.status().as_u16(),
383 401,
384 "Protected endpoint should return 401 with wrong auth scheme"
385 );
386
387 // =========================================================================
388 // Test 7: Login with wrong Content-Type (should get 415)
389 // =========================================================================
390 println!("\n7. Login with wrong Content-Type");
391 let response = client
392 .post("/login")
393 .header("content-type", "text/plain")
394 .body("username=test&password=test123")
395 .send();
396 println!(
397 " POST /login (Content-Type: text/plain) -> {} {}",
398 response.status().as_u16(),
399 response.status().canonical_reason()
400 );
401 assert_eq!(
402 response.status().as_u16(),
403 415,
404 "Login should return 415 with wrong Content-Type"
405 );
406
407 // =========================================================================
408 // Test 8: Token case sensitivity (lowercase 'bearer')
409 // =========================================================================
410 println!("\n8. Token case sensitivity (lowercase 'bearer')");
411 let response = client
412 .get("/protected")
413 .header("authorization", format!("bearer {SECRET_TOKEN}"))
414 .send();
415 println!(
416 " GET /protected (Authorization: bearer {}) -> {} {}",
417 SECRET_TOKEN,
418 response.status().as_u16(),
419 response.status().canonical_reason()
420 );
421 assert_eq!(
422 response.status().as_u16(),
423 200,
424 "Bearer scheme should be case-insensitive (lowercase accepted)"
425 );
426
427 println!("\nAll authentication tests passed!");
428}Sourcepub fn state(&self) -> &Arc<RwLock<RawRwLock, StateContainer>>
pub fn state(&self) -> &Arc<RwLock<RawRwLock, StateContainer>>
Returns the shared state container (protected by RwLock for lifespan mutation).
Sourcepub fn dependency_overrides(&self) -> &Arc<DependencyOverrides>
pub fn dependency_overrides(&self) -> &Arc<DependencyOverrides>
Returns the dependency overrides registry.
Sourcepub fn override_dependency<T, F, Fut>(&self, f: F)where
T: FromDependency,
F: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<T, <T as FromDependency>::Error>> + Send + 'static,
pub fn override_dependency<T, F, Fut>(&self, f: F)where
T: FromDependency,
F: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<T, <T as FromDependency>::Error>> + Send + 'static,
Registers a dependency override for this application (useful in tests).
Sourcepub fn override_dependency_value<T>(&self, value: T)where
T: FromDependency,
pub fn override_dependency_value<T>(&self, value: T)where
T: FromDependency,
Registers a fixed dependency override value.
Sourcepub fn clear_dependency_overrides(&self)
pub fn clear_dependency_overrides(&self)
Clears all registered dependency overrides.
Sourcepub fn exception_handlers(&self) -> &Arc<ExceptionHandlers>
pub fn exception_handlers(&self) -> &Arc<ExceptionHandlers>
Returns the exception handlers registry.
Sourcepub fn handle_error<E>(&self, ctx: &RequestContext, err: E) -> Option<Response>
pub fn handle_error<E>(&self, ctx: &RequestContext, err: E) -> Option<Response>
Handles an error using registered exception handlers.
If a handler is registered for the error type, it will be invoked.
Otherwise, returns None.
Sourcepub fn handle_error_or_default<E>(
&self,
ctx: &RequestContext,
err: E,
) -> Response
pub fn handle_error_or_default<E>( &self, ctx: &RequestContext, err: E, ) -> Response
Handles an error, returning a 500 response if no handler is registered.
Sourcepub fn mounted_apps(&self) -> &[MountedApp]
pub fn mounted_apps(&self) -> &[MountedApp]
Returns the mounted sub-applications.
Sourcepub async fn handle(&self, ctx: &RequestContext, req: &mut Request) -> Response
pub async fn handle(&self, ctx: &RequestContext, req: &mut Request) -> Response
Handles an incoming request.
This first checks mounted sub-applications (in registration order),
then matches against registered routes, runs middleware, and returns
the response. Path parameters are extracted and stored in request
extensions for use by the Path extractor.
§Mounted App Handling
When a request path matches a mounted app’s prefix, the request is forwarded to that app with the prefix stripped from the path. The mounted app’s middleware runs instead of the parent’s middleware.
§Special Parameter Injection
- ResponseMutations: Headers and cookies set by handlers are automatically applied to the final response.
- BackgroundTasks: Tasks are stored in request extensions via
BackgroundTasksInner. The HTTP server should retrieve and execute these after sending the response usingtake_background_tasks().
Sourcepub fn take_background_tasks(req: &mut Request) -> Option<BackgroundTasks>
pub fn take_background_tasks(req: &mut Request) -> Option<BackgroundTasks>
Take background tasks from a request after handling.
This should be called by the HTTP server after handle() returns
and the response has been sent to the client. The returned tasks
should be executed asynchronously.
§Example
let response = app.handle(&ctx, &mut request).await;
// Send response to client...
if let Some(tasks) = App::take_background_tasks(&mut request) {
tasks.execute_all().await;
}Sourcepub async fn run_startup_hooks(&self) -> StartupOutcome
pub async fn run_startup_hooks(&self) -> StartupOutcome
Runs all startup hooks.
Hooks run in registration order (FIFO). If a hook returns an error
with abort: true, execution stops and returns StartupOutcome::Aborted.
This consumes the startup hooks - they can only be run once.
§Returns
StartupOutcome::Successif all hooks succeededStartupOutcome::PartialSuccessif some hooks had non-fatal errorsStartupOutcome::Abortedif a fatal hook error occurred
Sourcepub async fn run_shutdown_hooks(&self)
pub async fn run_shutdown_hooks(&self)
Runs all shutdown hooks.
Hooks run in reverse registration order (LIFO). Errors are logged but do not stop other hooks from running.
This consumes the shutdown hooks - they can only be run once.
Sourcepub fn transfer_shutdown_hooks(&self, controller: &ShutdownController)
pub fn transfer_shutdown_hooks(&self, controller: &ShutdownController)
Transfers shutdown hooks to a ShutdownController.
This moves all registered shutdown hooks to the controller, which will run them during the appropriate shutdown phase.
Call this when integrating with the server’s shutdown mechanism.
Sourcepub fn pending_startup_hooks(&self) -> usize
pub fn pending_startup_hooks(&self) -> usize
Returns the number of pending startup hooks.
Sourcepub fn pending_shutdown_hooks(&self) -> usize
pub fn pending_shutdown_hooks(&self) -> usize
Returns the number of pending shutdown hooks.
Sourcepub fn test_client(self: Arc<App>) -> TestClient<Arc<App>>
pub fn test_client(self: Arc<App>) -> TestClient<Arc<App>>
Creates a crate::TestClient for this application.
The test client provides in-process testing without network overhead, simulating HTTP requests against the full application stack including middleware, routing, and handlers.
§Example
use fastapi_core::{App, Request, Response, RequestContext, Method};
async fn hello(ctx: &RequestContext, req: &mut Request) -> Response {
Response::ok().body_text("Hello!")
}
let app = App::builder()
.route("/hello", Method::Get, hello)
.build();
// Create test client from app
let client = app.test_client();
// Make requests
let response = client.get("/hello").send();
assert_eq!(response.status().as_u16(), 200);
assert_eq!(response.text(), "Hello!");Sourcepub fn test_client_with_seed(self: Arc<App>, seed: u64) -> TestClient<Arc<App>>
pub fn test_client_with_seed(self: Arc<App>, seed: u64) -> TestClient<Arc<App>>
Creates a crate::TestClient with a specific seed for deterministic testing.
Using the same seed produces reproducible behavior for tests involving concurrent operations or random elements.
§Example
let app = Arc::new(App::builder().build());
let client = app.test_client_with_seed(42);
// Tests will be deterministic with this seed