Skip to main content

App

Struct App 

Source
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

Source

pub fn builder() -> AppBuilder

Creates a new application builder.

Examples found in repository?
examples/hello_world.rs (line 62)
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
Hide additional examples
examples/getting_started.rs (line 29)
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}
examples/crud_api.rs (line 262)
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}
examples/auth_example.rs (line 253)
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}
Source

pub fn config(&self) -> &AppConfig

Returns the application configuration.

Examples found in repository?
examples/getting_started.rs (line 78)
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}
Source

pub fn route_count(&self) -> usize

Returns the number of registered routes.

Examples found in repository?
examples/hello_world.rs (line 74)
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
Hide additional examples
examples/getting_started.rs (line 34)
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}
examples/crud_api.rs (line 270)
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}
examples/auth_example.rs (line 262)
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}
Source

pub fn state(&self) -> &Arc<RwLock<RawRwLock, StateContainer>>

Returns the shared state container (protected by RwLock for lifespan mutation).

Source

pub fn get_state<T>(&self) -> Option<Arc<T>>
where T: Send + Sync + 'static,

Gets a reference to shared state of type T.

Source

pub fn dependency_overrides(&self) -> &Arc<DependencyOverrides>

Returns the dependency overrides registry.

Source

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).

Source

pub fn override_dependency_value<T>(&self, value: T)
where T: FromDependency,

Registers a fixed dependency override value.

Source

pub fn clear_dependency_overrides(&self)

Clears all registered dependency overrides.

Source

pub fn exception_handlers(&self) -> &Arc<ExceptionHandlers>

Returns the exception handlers registry.

Source

pub fn handle_error<E>(&self, ctx: &RequestContext, err: E) -> Option<Response>
where E: Error + Send + Sync + 'static,

Handles an error using registered exception handlers.

If a handler is registered for the error type, it will be invoked. Otherwise, returns None.

Source

pub fn handle_error_or_default<E>( &self, ctx: &RequestContext, err: E, ) -> Response
where E: Error + Send + Sync + 'static,

Handles an error, returning a 500 response if no handler is registered.

Source

pub fn mounted_apps(&self) -> &[MountedApp]

Returns the mounted sub-applications.

Source

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 using take_background_tasks().
Source

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;
}
Source

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::Success if all hooks succeeded
  • StartupOutcome::PartialSuccess if some hooks had non-fatal errors
  • StartupOutcome::Aborted if a fatal hook error occurred
Source

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.

Source

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.

Source

pub fn pending_startup_hooks(&self) -> usize

Returns the number of pending startup hooks.

Source

pub fn pending_shutdown_hooks(&self) -> usize

Returns the number of pending shutdown hooks.

Source

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!");
Source

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

Trait Implementations§

Source§

impl AppServeExt for App

Source§

fn serve( self, addr: impl Into<String>, ) -> impl Future<Output = Result<(), ServeError>> + Send

Starts the HTTP server and begins accepting connections. Read more
Source§

fn serve_with_config( self, config: ServerConfig, ) -> impl Future<Output = Result<(), ServeError>> + Send

Starts the HTTP server with custom configuration. Read more
Source§

impl Debug for App

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Handler for App

Source§

fn call<'a>( &'a self, ctx: &'a RequestContext, req: &'a mut Request, ) -> Pin<Box<dyn Future<Output = Response> + Send + 'a>>

Process a request and return a response.
Source§

fn dependency_overrides(&self) -> Option<Arc<DependencyOverrides>>

Optional dependency overrides to apply when building request contexts. Read more

Auto Trait Implementations§

§

impl !Freeze for App

§

impl !RefUnwindSafe for App

§

impl Send for App

§

impl Sync for App

§

impl Unpin for App

§

impl !UnwindSafe for App

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, _span: NoopSpan) -> Self

Instruments this future with a span (no-op when disabled).
Source§

fn in_current_span(self) -> Self

Instruments this future with the current span (no-op when disabled).
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> ResponseProduces<T> for T