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 72)
61fn main() {
62    println!("fastapi_rust Hello World Example");
63    println!("================================\n");
64
65    // Build the application
66    //
67    // App::builder() creates a new application builder that lets you:
68    // - Add routes for different HTTP methods
69    // - Configure middleware
70    // - Set application state
71    // - Define exception handlers
72    let app = App::builder()
73        // Register a GET handler for the root path "/"
74        //
75        // This is equivalent to:
76        //   @app.get("/")
77        //   def hello():
78        //       return "Hello, World!"
79        // in Python FastAPI
80        .get("/", hello_handler)
81        // Build the final immutable App
82        .build();
83
84    println!("App created with {} route(s)\n", app.route_count());
85
86    // Create a test client to make requests to our app
87    //
88    // TestClient wraps any Handler (including App) and provides
89    // a convenient API for making HTTP requests in tests.
90    let client = TestClient::new(app);
91
92    // Make a GET request to "/"
93    println!("Making request: GET /");
94    let response = client.get("/").send();
95
96    // Check the response
97    println!(
98        "GET / -> {} {}",
99        response.status().as_u16(),
100        response.status().canonical_reason()
101    );
102    println!("Response: {}\n", response.text());
103
104    // Verify success
105    if !check_eq(response.status().as_u16(), 200, "GET / should return 200") {
106        return;
107    }
108    if !check_eq(response.text(), "Hello, World!", "GET / should return body") {
109        return;
110    }
111
112    // Try a path that doesn't exist - should get 404
113    println!("Making request: GET /not-found");
114    let response = client.get("/not-found").send();
115    println!(
116        "GET /not-found -> {} {}",
117        response.status().as_u16(),
118        response.status().canonical_reason()
119    );
120    if !check_eq(
121        response.status().as_u16(),
122        404,
123        "Unknown routes should return 404",
124    ) {
125        return;
126    }
127
128    println!("\nAll assertions passed!");
129}
More examples
Hide additional examples
examples/getting_started.rs (line 39)
34fn main() {
35    println!("Getting Started Guide - Code Validation\n");
36
37    // === Basic App Example ===
38    println!("1. Basic app with two routes:");
39    let app = App::builder()
40        .get("/", hello)
41        .get("/health", health)
42        .build();
43
44    println!("   Routes: {}", app.route_count());
45    let client = TestClient::new(app);
46
47    let response = client.get("/").send();
48    println!(
49        "   GET / -> {} ({})",
50        response.status().as_u16(),
51        response.text()
52    );
53    if !check_eq(response.status().as_u16(), 200, "GET / should return 200") {
54        return;
55    }
56    if !check_eq(response.text(), "Hello, World!", "GET / should return body") {
57        return;
58    }
59
60    let response = client.get("/health").send();
61    println!(
62        "   GET /health -> {} ({})",
63        response.status().as_u16(),
64        response.text()
65    );
66    if !check_eq(
67        response.status().as_u16(),
68        200,
69        "GET /health should return 200",
70    ) {
71        return;
72    }
73
74    // === App with Middleware ===
75    println!("\n2. App with middleware:");
76    let app = App::builder()
77        .middleware(RequestIdMiddleware::new())
78        .middleware(SecurityHeaders::new())
79        .get("/", hello)
80        .build();
81
82    let client = TestClient::new(app);
83    let response = client.get("/").send();
84    println!("   GET / -> {}", response.status().as_u16());
85    if !check_eq(
86        response.status().as_u16(),
87        200,
88        "GET / with middleware should return 200",
89    ) {
90        return;
91    }
92
93    // === App with Configuration ===
94    println!("\n3. App with configuration:");
95    let config = AppConfig::new()
96        .name("My API")
97        .version("1.0.0")
98        .debug(true)
99        .max_body_size(10 * 1024 * 1024)
100        .request_timeout_ms(30_000);
101
102    let app = App::builder().config(config).get("/", hello).build();
103
104    println!("   App name: {}", app.config().name);
105    println!("   Version: {}", app.config().version);
106    if !check_eq(
107        app.config().name.as_str(),
108        "My API",
109        "Config name should match",
110    ) {
111        return;
112    }
113    if !check_eq(
114        app.config().version.as_str(),
115        "1.0.0",
116        "Config version should match",
117    ) {
118        return;
119    }
120
121    // === 404 for unknown routes ===
122    println!("\n4. 404 for unknown routes:");
123    let app = App::builder().get("/", hello).build();
124
125    let client = TestClient::new(app);
126    let response = client.get("/nonexistent").send();
127    println!("   GET /nonexistent -> {}", response.status().as_u16());
128    if !check_eq(
129        response.status().as_u16(),
130        404,
131        "Unknown routes should return 404",
132    ) {
133        return;
134    }
135
136    println!("\nAll getting started examples validated successfully!");
137}
examples/crud_api.rs (line 284)
271fn main() {
272    // Initialize the global store
273    let mut guard = STORE
274        .lock()
275        .unwrap_or_else(std::sync::PoisonError::into_inner);
276    *guard = Some(UserDb {
277        users: HashMap::new(),
278        next_id: 1,
279    });
280
281    println!("fastapi_rust CRUD API Example");
282    println!("=============================\n");
283
284    let app = App::builder()
285        .post("/users", create_user)
286        .get("/users", list_users)
287        .get("/users/{id}", get_user)
288        .put("/users/{id}", update_user)
289        .delete("/users/{id}", delete_user)
290        .build();
291
292    println!("App created with {} route(s)\n", app.route_count());
293    let client = TestClient::new(app);
294
295    // 1. Create users
296    println!("1. Create users");
297    let resp = client
298        .post("/users")
299        .header("content-type", "application/json")
300        .body(r#"{"name": "Alice", "email": "alice@example.com"}"#)
301        .send();
302    println!(
303        "   POST /users -> {} {}",
304        resp.status().as_u16(),
305        resp.text()
306    );
307    if !check_eq(resp.status().as_u16(), 201, "Create user should return 201") {
308        return;
309    }
310
311    let resp = client
312        .post("/users")
313        .header("content-type", "application/json")
314        .body(r#"{"name": "Bob", "email": "bob@example.com"}"#)
315        .send();
316    println!(
317        "   POST /users -> {} {}",
318        resp.status().as_u16(),
319        resp.text()
320    );
321    if !check_eq(resp.status().as_u16(), 201, "Create user should return 201") {
322        return;
323    }
324
325    // 2. List users
326    println!("\n2. List all users");
327    let resp = client.get("/users").send();
328    println!(
329        "   GET /users -> {} {}",
330        resp.status().as_u16(),
331        resp.text()
332    );
333    if !check_eq(resp.status().as_u16(), 200, "List users should return 200") {
334        return;
335    }
336
337    // 3. Get user by ID
338    println!("\n3. Get user by ID");
339    let resp = client.get("/users/1").send();
340    println!(
341        "   GET /users/1 -> {} {}",
342        resp.status().as_u16(),
343        resp.text()
344    );
345    if !check_eq(resp.status().as_u16(), 200, "Get user should return 200") {
346        return;
347    }
348
349    // 4. Get nonexistent user
350    println!("\n4. Get nonexistent user");
351    let resp = client.get("/users/999").send();
352    println!(
353        "   GET /users/999 -> {} {}",
354        resp.status().as_u16(),
355        resp.text()
356    );
357    if !check_eq(
358        resp.status().as_u16(),
359        404,
360        "Missing user should return 404",
361    ) {
362        return;
363    }
364
365    // 5. Update user
366    println!("\n5. Update user");
367    let resp = client
368        .put("/users/1")
369        .header("content-type", "application/json")
370        .body(r#"{"name": "Alice Smith", "email": "alice.smith@example.com"}"#)
371        .send();
372    println!(
373        "   PUT /users/1 -> {} {}",
374        resp.status().as_u16(),
375        resp.text()
376    );
377    if !check_eq(resp.status().as_u16(), 200, "Update user should return 200") {
378        return;
379    }
380
381    // 6. Validation: empty name
382    println!("\n6. Validation error (empty name)");
383    let resp = client
384        .post("/users")
385        .header("content-type", "application/json")
386        .body(r#"{"name": "", "email": "bad@example.com"}"#)
387        .send();
388    println!(
389        "   POST /users -> {} {}",
390        resp.status().as_u16(),
391        resp.text()
392    );
393    if !check_eq(resp.status().as_u16(), 400, "Empty name should return 400") {
394        return;
395    }
396
397    // 7. Validation: invalid email
398    println!("\n7. Validation error (invalid email)");
399    let resp = client
400        .post("/users")
401        .header("content-type", "application/json")
402        .body(r#"{"name": "Charlie", "email": "not-an-email"}"#)
403        .send();
404    println!(
405        "   POST /users -> {} {}",
406        resp.status().as_u16(),
407        resp.text()
408    );
409    if !check_eq(
410        resp.status().as_u16(),
411        400,
412        "Invalid email should return 400",
413    ) {
414        return;
415    }
416
417    // 8. Wrong Content-Type
418    println!("\n8. Wrong Content-Type");
419    let resp = client
420        .post("/users")
421        .header("content-type", "text/plain")
422        .body(r#"{"name": "Dan", "email": "dan@example.com"}"#)
423        .send();
424    println!(
425        "   POST /users -> {} {}",
426        resp.status().as_u16(),
427        resp.text()
428    );
429    if !check_eq(
430        resp.status().as_u16(),
431        415,
432        "Wrong Content-Type should return 415",
433    ) {
434        return;
435    }
436
437    // 9. Delete user
438    println!("\n9. Delete user");
439    let resp = client.delete("/users/2").send();
440    println!("   DELETE /users/2 -> {}", resp.status().as_u16());
441    if !check_eq(resp.status().as_u16(), 204, "Delete user should return 204") {
442        return;
443    }
444
445    // 10. Verify deletion
446    println!("\n10. Verify deletion");
447    let resp = client.get("/users/2").send();
448    println!(
449        "   GET /users/2 -> {} {}",
450        resp.status().as_u16(),
451        resp.text()
452    );
453    if !check_eq(
454        resp.status().as_u16(),
455        404,
456        "Deleted user should return 404",
457    ) {
458        return;
459    }
460
461    let resp = client.get("/users").send();
462    println!(
463        "   GET /users -> {} {}",
464        resp.status().as_u16(),
465        resp.text()
466    );
467    if !check_eq(resp.status().as_u16(), 200, "List users should return 200") {
468        return;
469    }
470
471    println!("\nAll CRUD operations passed!");
472}
examples/auth_example.rs (line 272)
267fn main() {
268    println!("fastapi_rust Authentication Example");
269    println!("====================================\n");
270
271    // Build the application with public and protected routes
272    let app = App::builder()
273        // Public endpoints - accessible to everyone
274        .get("/public", public_handler)
275        // Login endpoint - returns a token
276        .post("/login", login_handler)
277        // Protected endpoint - requires valid bearer token
278        .get("/protected", protected_handler)
279        .build();
280
281    println!("App created with {} route(s)\n", app.route_count());
282
283    // Create a test client
284    let client = TestClient::new(app);
285
286    // =========================================================================
287    // Test 1: Public endpoint - no auth required
288    // =========================================================================
289    println!("1. Public endpoint - no auth required");
290    let response = client.get("/public").send();
291    println!(
292        "   GET /public -> {} {}",
293        response.status().as_u16(),
294        response.status().canonical_reason()
295    );
296    if !check_eq(
297        response.status().as_u16(),
298        200,
299        "GET /public should return 200",
300    ) {
301        return;
302    }
303    if !check(
304        response.text().contains("public endpoint"),
305        "GET /public should include the public endpoint body",
306    ) {
307        return;
308    }
309
310    // =========================================================================
311    // Test 2: Protected endpoint - without token (should get 401)
312    // =========================================================================
313    println!("\n2. Protected endpoint - without token");
314    let response = client.get("/protected").send();
315    println!(
316        "   GET /protected -> {} {}",
317        response.status().as_u16(),
318        response.status().canonical_reason()
319    );
320    if !check_eq(
321        response.status().as_u16(),
322        401,
323        "Protected endpoint should return 401 without token",
324    ) {
325        return;
326    }
327
328    // Check for WWW-Authenticate header
329    let has_www_auth = response
330        .headers()
331        .iter()
332        .any(|(name, value)| name == "www-authenticate" && value == b"Bearer");
333    if !check(
334        has_www_auth,
335        "401 response should include WWW-Authenticate: Bearer header",
336    ) {
337        return;
338    }
339
340    // =========================================================================
341    // Test 3: Login endpoint - get a token
342    // =========================================================================
343    println!("\n3. Login endpoint - get a token");
344    let response = client
345        .post("/login")
346        .header("content-type", "application/json")
347        .body(r#"{"username":"test","password":"test123"}"#)
348        .send();
349    println!(
350        "   POST /login -> {} {}",
351        response.status().as_u16(),
352        response.status().canonical_reason()
353    );
354    if !check_eq(
355        response.status().as_u16(),
356        200,
357        "POST /login should return 200",
358    ) {
359        return;
360    }
361
362    // Parse the response to get the token
363    let body_text = response.text();
364    let body: serde_json::Value = match serde_json::from_str(body_text) {
365        Ok(body) => body,
366        Err(err) => {
367            eprintln!("Failed to parse login response JSON: {err}");
368            return;
369        }
370    };
371    let Some(bearer_value) = body.get("access_token").and_then(|value| value.as_str()) else {
372        eprintln!("Login response missing access_token");
373        return;
374    };
375    println!("   Bearer value: {bearer_value}");
376    if !check_eq(
377        bearer_value,
378        DEMO_BEARER_VALUE,
379        "Login should return the expected bearer value",
380    ) {
381        return;
382    }
383
384    // =========================================================================
385    // Test 4: Protected endpoint - with valid token (should get 200)
386    // =========================================================================
387    println!("\n4. Protected endpoint - with valid token");
388    let response = client
389        .get("/protected")
390        .header("authorization", format!("Bearer {DEMO_BEARER_VALUE}"))
391        .send();
392    println!(
393        "   GET /protected (Authorization: Bearer {}) -> {} {}",
394        DEMO_BEARER_VALUE,
395        response.status().as_u16(),
396        response.status().canonical_reason()
397    );
398    if !check_eq(
399        response.status().as_u16(),
400        200,
401        "Protected endpoint should return 200 with valid token",
402    ) {
403        return;
404    }
405    if !check(
406        response.text().contains("protected resource"),
407        "Protected endpoint should include protected resource body",
408    ) {
409        return;
410    }
411
412    // =========================================================================
413    // Test 5: Protected endpoint - with invalid token (should get 403)
414    // =========================================================================
415    println!("\n5. Protected endpoint - with invalid token");
416    let response = client
417        .get("/protected")
418        .header("authorization", "Bearer wrong_token")
419        .send();
420    println!(
421        "   GET /protected (Authorization: Bearer wrong_token) -> {} {}",
422        response.status().as_u16(),
423        response.status().canonical_reason()
424    );
425    if !check_eq(
426        response.status().as_u16(),
427        403,
428        "Protected endpoint should return 403 with invalid token",
429    ) {
430        return;
431    }
432
433    // =========================================================================
434    // Test 6: Protected endpoint - with wrong auth scheme (should get 401)
435    // =========================================================================
436    println!("\n6. Protected endpoint - with wrong auth scheme");
437    let response = client
438        .get("/protected")
439        .header("authorization", "Basic dXNlcjpwYXNz")
440        .send();
441    println!(
442        "   GET /protected (Authorization: Basic ...) -> {} {}",
443        response.status().as_u16(),
444        response.status().canonical_reason()
445    );
446    if !check_eq(
447        response.status().as_u16(),
448        401,
449        "Protected endpoint should return 401 with wrong auth scheme",
450    ) {
451        return;
452    }
453
454    // =========================================================================
455    // Test 7: Login with wrong Content-Type (should get 415)
456    // =========================================================================
457    println!("\n7. Login with wrong Content-Type");
458    let response = client
459        .post("/login")
460        .header("content-type", "text/plain")
461        .body("demo=true")
462        .send();
463    println!(
464        "   POST /login (Content-Type: text/plain) -> {} {}",
465        response.status().as_u16(),
466        response.status().canonical_reason()
467    );
468    if !check_eq(
469        response.status().as_u16(),
470        415,
471        "Login should return 415 with wrong Content-Type",
472    ) {
473        return;
474    }
475
476    // =========================================================================
477    // Test 8: Token case sensitivity (lowercase 'bearer')
478    // =========================================================================
479    println!("\n8. Token case sensitivity (lowercase 'bearer')");
480    let response = client
481        .get("/protected")
482        .header("authorization", format!("bearer {DEMO_BEARER_VALUE}"))
483        .send();
484    println!(
485        "   GET /protected (Authorization: bearer {}) -> {} {}",
486        DEMO_BEARER_VALUE,
487        response.status().as_u16(),
488        response.status().canonical_reason()
489    );
490    if !check_eq(
491        response.status().as_u16(),
492        200,
493        "Bearer scheme should be case-insensitive (lowercase accepted)",
494    ) {
495        return;
496    }
497
498    println!("\nAll authentication tests passed!");
499}
Source

pub fn test_client(self: Arc<App>) -> TestClient<Arc<App>>

Create an in-process test client for this app.

This takes Arc<Self> so tests can keep using shared Arc<App> values without extra boilerplate.

Source

pub fn test_client_with_seed(self: Arc<App>, seed: u64) -> TestClient<Arc<App>>

Create an in-process test client with a deterministic seed.

Source

pub fn config(&self) -> &AppConfig

Returns the application configuration.

Examples found in repository?
examples/getting_started.rs (line 104)
34fn main() {
35    println!("Getting Started Guide - Code Validation\n");
36
37    // === Basic App Example ===
38    println!("1. Basic app with two routes:");
39    let app = App::builder()
40        .get("/", hello)
41        .get("/health", health)
42        .build();
43
44    println!("   Routes: {}", app.route_count());
45    let client = TestClient::new(app);
46
47    let response = client.get("/").send();
48    println!(
49        "   GET / -> {} ({})",
50        response.status().as_u16(),
51        response.text()
52    );
53    if !check_eq(response.status().as_u16(), 200, "GET / should return 200") {
54        return;
55    }
56    if !check_eq(response.text(), "Hello, World!", "GET / should return body") {
57        return;
58    }
59
60    let response = client.get("/health").send();
61    println!(
62        "   GET /health -> {} ({})",
63        response.status().as_u16(),
64        response.text()
65    );
66    if !check_eq(
67        response.status().as_u16(),
68        200,
69        "GET /health should return 200",
70    ) {
71        return;
72    }
73
74    // === App with Middleware ===
75    println!("\n2. App with middleware:");
76    let app = App::builder()
77        .middleware(RequestIdMiddleware::new())
78        .middleware(SecurityHeaders::new())
79        .get("/", hello)
80        .build();
81
82    let client = TestClient::new(app);
83    let response = client.get("/").send();
84    println!("   GET / -> {}", response.status().as_u16());
85    if !check_eq(
86        response.status().as_u16(),
87        200,
88        "GET / with middleware should return 200",
89    ) {
90        return;
91    }
92
93    // === App with Configuration ===
94    println!("\n3. App with configuration:");
95    let config = AppConfig::new()
96        .name("My API")
97        .version("1.0.0")
98        .debug(true)
99        .max_body_size(10 * 1024 * 1024)
100        .request_timeout_ms(30_000);
101
102    let app = App::builder().config(config).get("/", hello).build();
103
104    println!("   App name: {}", app.config().name);
105    println!("   Version: {}", app.config().version);
106    if !check_eq(
107        app.config().name.as_str(),
108        "My API",
109        "Config name should match",
110    ) {
111        return;
112    }
113    if !check_eq(
114        app.config().version.as_str(),
115        "1.0.0",
116        "Config version should match",
117    ) {
118        return;
119    }
120
121    // === 404 for unknown routes ===
122    println!("\n4. 404 for unknown routes:");
123    let app = App::builder().get("/", hello).build();
124
125    let client = TestClient::new(app);
126    let response = client.get("/nonexistent").send();
127    println!("   GET /nonexistent -> {}", response.status().as_u16());
128    if !check_eq(
129        response.status().as_u16(),
130        404,
131        "Unknown routes should return 404",
132    ) {
133        return;
134    }
135
136    println!("\nAll getting started examples validated successfully!");
137}
Source

pub fn route_count(&self) -> usize

Returns the number of registered routes.

Examples found in repository?
examples/hello_world.rs (line 84)
61fn main() {
62    println!("fastapi_rust Hello World Example");
63    println!("================================\n");
64
65    // Build the application
66    //
67    // App::builder() creates a new application builder that lets you:
68    // - Add routes for different HTTP methods
69    // - Configure middleware
70    // - Set application state
71    // - Define exception handlers
72    let app = App::builder()
73        // Register a GET handler for the root path "/"
74        //
75        // This is equivalent to:
76        //   @app.get("/")
77        //   def hello():
78        //       return "Hello, World!"
79        // in Python FastAPI
80        .get("/", hello_handler)
81        // Build the final immutable App
82        .build();
83
84    println!("App created with {} route(s)\n", app.route_count());
85
86    // Create a test client to make requests to our app
87    //
88    // TestClient wraps any Handler (including App) and provides
89    // a convenient API for making HTTP requests in tests.
90    let client = TestClient::new(app);
91
92    // Make a GET request to "/"
93    println!("Making request: GET /");
94    let response = client.get("/").send();
95
96    // Check the response
97    println!(
98        "GET / -> {} {}",
99        response.status().as_u16(),
100        response.status().canonical_reason()
101    );
102    println!("Response: {}\n", response.text());
103
104    // Verify success
105    if !check_eq(response.status().as_u16(), 200, "GET / should return 200") {
106        return;
107    }
108    if !check_eq(response.text(), "Hello, World!", "GET / should return body") {
109        return;
110    }
111
112    // Try a path that doesn't exist - should get 404
113    println!("Making request: GET /not-found");
114    let response = client.get("/not-found").send();
115    println!(
116        "GET /not-found -> {} {}",
117        response.status().as_u16(),
118        response.status().canonical_reason()
119    );
120    if !check_eq(
121        response.status().as_u16(),
122        404,
123        "Unknown routes should return 404",
124    ) {
125        return;
126    }
127
128    println!("\nAll assertions passed!");
129}
More examples
Hide additional examples
examples/getting_started.rs (line 44)
34fn main() {
35    println!("Getting Started Guide - Code Validation\n");
36
37    // === Basic App Example ===
38    println!("1. Basic app with two routes:");
39    let app = App::builder()
40        .get("/", hello)
41        .get("/health", health)
42        .build();
43
44    println!("   Routes: {}", app.route_count());
45    let client = TestClient::new(app);
46
47    let response = client.get("/").send();
48    println!(
49        "   GET / -> {} ({})",
50        response.status().as_u16(),
51        response.text()
52    );
53    if !check_eq(response.status().as_u16(), 200, "GET / should return 200") {
54        return;
55    }
56    if !check_eq(response.text(), "Hello, World!", "GET / should return body") {
57        return;
58    }
59
60    let response = client.get("/health").send();
61    println!(
62        "   GET /health -> {} ({})",
63        response.status().as_u16(),
64        response.text()
65    );
66    if !check_eq(
67        response.status().as_u16(),
68        200,
69        "GET /health should return 200",
70    ) {
71        return;
72    }
73
74    // === App with Middleware ===
75    println!("\n2. App with middleware:");
76    let app = App::builder()
77        .middleware(RequestIdMiddleware::new())
78        .middleware(SecurityHeaders::new())
79        .get("/", hello)
80        .build();
81
82    let client = TestClient::new(app);
83    let response = client.get("/").send();
84    println!("   GET / -> {}", response.status().as_u16());
85    if !check_eq(
86        response.status().as_u16(),
87        200,
88        "GET / with middleware should return 200",
89    ) {
90        return;
91    }
92
93    // === App with Configuration ===
94    println!("\n3. App with configuration:");
95    let config = AppConfig::new()
96        .name("My API")
97        .version("1.0.0")
98        .debug(true)
99        .max_body_size(10 * 1024 * 1024)
100        .request_timeout_ms(30_000);
101
102    let app = App::builder().config(config).get("/", hello).build();
103
104    println!("   App name: {}", app.config().name);
105    println!("   Version: {}", app.config().version);
106    if !check_eq(
107        app.config().name.as_str(),
108        "My API",
109        "Config name should match",
110    ) {
111        return;
112    }
113    if !check_eq(
114        app.config().version.as_str(),
115        "1.0.0",
116        "Config version should match",
117    ) {
118        return;
119    }
120
121    // === 404 for unknown routes ===
122    println!("\n4. 404 for unknown routes:");
123    let app = App::builder().get("/", hello).build();
124
125    let client = TestClient::new(app);
126    let response = client.get("/nonexistent").send();
127    println!("   GET /nonexistent -> {}", response.status().as_u16());
128    if !check_eq(
129        response.status().as_u16(),
130        404,
131        "Unknown routes should return 404",
132    ) {
133        return;
134    }
135
136    println!("\nAll getting started examples validated successfully!");
137}
examples/crud_api.rs (line 292)
271fn main() {
272    // Initialize the global store
273    let mut guard = STORE
274        .lock()
275        .unwrap_or_else(std::sync::PoisonError::into_inner);
276    *guard = Some(UserDb {
277        users: HashMap::new(),
278        next_id: 1,
279    });
280
281    println!("fastapi_rust CRUD API Example");
282    println!("=============================\n");
283
284    let app = App::builder()
285        .post("/users", create_user)
286        .get("/users", list_users)
287        .get("/users/{id}", get_user)
288        .put("/users/{id}", update_user)
289        .delete("/users/{id}", delete_user)
290        .build();
291
292    println!("App created with {} route(s)\n", app.route_count());
293    let client = TestClient::new(app);
294
295    // 1. Create users
296    println!("1. Create users");
297    let resp = client
298        .post("/users")
299        .header("content-type", "application/json")
300        .body(r#"{"name": "Alice", "email": "alice@example.com"}"#)
301        .send();
302    println!(
303        "   POST /users -> {} {}",
304        resp.status().as_u16(),
305        resp.text()
306    );
307    if !check_eq(resp.status().as_u16(), 201, "Create user should return 201") {
308        return;
309    }
310
311    let resp = client
312        .post("/users")
313        .header("content-type", "application/json")
314        .body(r#"{"name": "Bob", "email": "bob@example.com"}"#)
315        .send();
316    println!(
317        "   POST /users -> {} {}",
318        resp.status().as_u16(),
319        resp.text()
320    );
321    if !check_eq(resp.status().as_u16(), 201, "Create user should return 201") {
322        return;
323    }
324
325    // 2. List users
326    println!("\n2. List all users");
327    let resp = client.get("/users").send();
328    println!(
329        "   GET /users -> {} {}",
330        resp.status().as_u16(),
331        resp.text()
332    );
333    if !check_eq(resp.status().as_u16(), 200, "List users should return 200") {
334        return;
335    }
336
337    // 3. Get user by ID
338    println!("\n3. Get user by ID");
339    let resp = client.get("/users/1").send();
340    println!(
341        "   GET /users/1 -> {} {}",
342        resp.status().as_u16(),
343        resp.text()
344    );
345    if !check_eq(resp.status().as_u16(), 200, "Get user should return 200") {
346        return;
347    }
348
349    // 4. Get nonexistent user
350    println!("\n4. Get nonexistent user");
351    let resp = client.get("/users/999").send();
352    println!(
353        "   GET /users/999 -> {} {}",
354        resp.status().as_u16(),
355        resp.text()
356    );
357    if !check_eq(
358        resp.status().as_u16(),
359        404,
360        "Missing user should return 404",
361    ) {
362        return;
363    }
364
365    // 5. Update user
366    println!("\n5. Update user");
367    let resp = client
368        .put("/users/1")
369        .header("content-type", "application/json")
370        .body(r#"{"name": "Alice Smith", "email": "alice.smith@example.com"}"#)
371        .send();
372    println!(
373        "   PUT /users/1 -> {} {}",
374        resp.status().as_u16(),
375        resp.text()
376    );
377    if !check_eq(resp.status().as_u16(), 200, "Update user should return 200") {
378        return;
379    }
380
381    // 6. Validation: empty name
382    println!("\n6. Validation error (empty name)");
383    let resp = client
384        .post("/users")
385        .header("content-type", "application/json")
386        .body(r#"{"name": "", "email": "bad@example.com"}"#)
387        .send();
388    println!(
389        "   POST /users -> {} {}",
390        resp.status().as_u16(),
391        resp.text()
392    );
393    if !check_eq(resp.status().as_u16(), 400, "Empty name should return 400") {
394        return;
395    }
396
397    // 7. Validation: invalid email
398    println!("\n7. Validation error (invalid email)");
399    let resp = client
400        .post("/users")
401        .header("content-type", "application/json")
402        .body(r#"{"name": "Charlie", "email": "not-an-email"}"#)
403        .send();
404    println!(
405        "   POST /users -> {} {}",
406        resp.status().as_u16(),
407        resp.text()
408    );
409    if !check_eq(
410        resp.status().as_u16(),
411        400,
412        "Invalid email should return 400",
413    ) {
414        return;
415    }
416
417    // 8. Wrong Content-Type
418    println!("\n8. Wrong Content-Type");
419    let resp = client
420        .post("/users")
421        .header("content-type", "text/plain")
422        .body(r#"{"name": "Dan", "email": "dan@example.com"}"#)
423        .send();
424    println!(
425        "   POST /users -> {} {}",
426        resp.status().as_u16(),
427        resp.text()
428    );
429    if !check_eq(
430        resp.status().as_u16(),
431        415,
432        "Wrong Content-Type should return 415",
433    ) {
434        return;
435    }
436
437    // 9. Delete user
438    println!("\n9. Delete user");
439    let resp = client.delete("/users/2").send();
440    println!("   DELETE /users/2 -> {}", resp.status().as_u16());
441    if !check_eq(resp.status().as_u16(), 204, "Delete user should return 204") {
442        return;
443    }
444
445    // 10. Verify deletion
446    println!("\n10. Verify deletion");
447    let resp = client.get("/users/2").send();
448    println!(
449        "   GET /users/2 -> {} {}",
450        resp.status().as_u16(),
451        resp.text()
452    );
453    if !check_eq(
454        resp.status().as_u16(),
455        404,
456        "Deleted user should return 404",
457    ) {
458        return;
459    }
460
461    let resp = client.get("/users").send();
462    println!(
463        "   GET /users -> {} {}",
464        resp.status().as_u16(),
465        resp.text()
466    );
467    if !check_eq(resp.status().as_u16(), 200, "List users should return 200") {
468        return;
469    }
470
471    println!("\nAll CRUD operations passed!");
472}
examples/auth_example.rs (line 281)
267fn main() {
268    println!("fastapi_rust Authentication Example");
269    println!("====================================\n");
270
271    // Build the application with public and protected routes
272    let app = App::builder()
273        // Public endpoints - accessible to everyone
274        .get("/public", public_handler)
275        // Login endpoint - returns a token
276        .post("/login", login_handler)
277        // Protected endpoint - requires valid bearer token
278        .get("/protected", protected_handler)
279        .build();
280
281    println!("App created with {} route(s)\n", app.route_count());
282
283    // Create a test client
284    let client = TestClient::new(app);
285
286    // =========================================================================
287    // Test 1: Public endpoint - no auth required
288    // =========================================================================
289    println!("1. Public endpoint - no auth required");
290    let response = client.get("/public").send();
291    println!(
292        "   GET /public -> {} {}",
293        response.status().as_u16(),
294        response.status().canonical_reason()
295    );
296    if !check_eq(
297        response.status().as_u16(),
298        200,
299        "GET /public should return 200",
300    ) {
301        return;
302    }
303    if !check(
304        response.text().contains("public endpoint"),
305        "GET /public should include the public endpoint body",
306    ) {
307        return;
308    }
309
310    // =========================================================================
311    // Test 2: Protected endpoint - without token (should get 401)
312    // =========================================================================
313    println!("\n2. Protected endpoint - without token");
314    let response = client.get("/protected").send();
315    println!(
316        "   GET /protected -> {} {}",
317        response.status().as_u16(),
318        response.status().canonical_reason()
319    );
320    if !check_eq(
321        response.status().as_u16(),
322        401,
323        "Protected endpoint should return 401 without token",
324    ) {
325        return;
326    }
327
328    // Check for WWW-Authenticate header
329    let has_www_auth = response
330        .headers()
331        .iter()
332        .any(|(name, value)| name == "www-authenticate" && value == b"Bearer");
333    if !check(
334        has_www_auth,
335        "401 response should include WWW-Authenticate: Bearer header",
336    ) {
337        return;
338    }
339
340    // =========================================================================
341    // Test 3: Login endpoint - get a token
342    // =========================================================================
343    println!("\n3. Login endpoint - get a token");
344    let response = client
345        .post("/login")
346        .header("content-type", "application/json")
347        .body(r#"{"username":"test","password":"test123"}"#)
348        .send();
349    println!(
350        "   POST /login -> {} {}",
351        response.status().as_u16(),
352        response.status().canonical_reason()
353    );
354    if !check_eq(
355        response.status().as_u16(),
356        200,
357        "POST /login should return 200",
358    ) {
359        return;
360    }
361
362    // Parse the response to get the token
363    let body_text = response.text();
364    let body: serde_json::Value = match serde_json::from_str(body_text) {
365        Ok(body) => body,
366        Err(err) => {
367            eprintln!("Failed to parse login response JSON: {err}");
368            return;
369        }
370    };
371    let Some(bearer_value) = body.get("access_token").and_then(|value| value.as_str()) else {
372        eprintln!("Login response missing access_token");
373        return;
374    };
375    println!("   Bearer value: {bearer_value}");
376    if !check_eq(
377        bearer_value,
378        DEMO_BEARER_VALUE,
379        "Login should return the expected bearer value",
380    ) {
381        return;
382    }
383
384    // =========================================================================
385    // Test 4: Protected endpoint - with valid token (should get 200)
386    // =========================================================================
387    println!("\n4. Protected endpoint - with valid token");
388    let response = client
389        .get("/protected")
390        .header("authorization", format!("Bearer {DEMO_BEARER_VALUE}"))
391        .send();
392    println!(
393        "   GET /protected (Authorization: Bearer {}) -> {} {}",
394        DEMO_BEARER_VALUE,
395        response.status().as_u16(),
396        response.status().canonical_reason()
397    );
398    if !check_eq(
399        response.status().as_u16(),
400        200,
401        "Protected endpoint should return 200 with valid token",
402    ) {
403        return;
404    }
405    if !check(
406        response.text().contains("protected resource"),
407        "Protected endpoint should include protected resource body",
408    ) {
409        return;
410    }
411
412    // =========================================================================
413    // Test 5: Protected endpoint - with invalid token (should get 403)
414    // =========================================================================
415    println!("\n5. Protected endpoint - with invalid token");
416    let response = client
417        .get("/protected")
418        .header("authorization", "Bearer wrong_token")
419        .send();
420    println!(
421        "   GET /protected (Authorization: Bearer wrong_token) -> {} {}",
422        response.status().as_u16(),
423        response.status().canonical_reason()
424    );
425    if !check_eq(
426        response.status().as_u16(),
427        403,
428        "Protected endpoint should return 403 with invalid token",
429    ) {
430        return;
431    }
432
433    // =========================================================================
434    // Test 6: Protected endpoint - with wrong auth scheme (should get 401)
435    // =========================================================================
436    println!("\n6. Protected endpoint - with wrong auth scheme");
437    let response = client
438        .get("/protected")
439        .header("authorization", "Basic dXNlcjpwYXNz")
440        .send();
441    println!(
442        "   GET /protected (Authorization: Basic ...) -> {} {}",
443        response.status().as_u16(),
444        response.status().canonical_reason()
445    );
446    if !check_eq(
447        response.status().as_u16(),
448        401,
449        "Protected endpoint should return 401 with wrong auth scheme",
450    ) {
451        return;
452    }
453
454    // =========================================================================
455    // Test 7: Login with wrong Content-Type (should get 415)
456    // =========================================================================
457    println!("\n7. Login with wrong Content-Type");
458    let response = client
459        .post("/login")
460        .header("content-type", "text/plain")
461        .body("demo=true")
462        .send();
463    println!(
464        "   POST /login (Content-Type: text/plain) -> {} {}",
465        response.status().as_u16(),
466        response.status().canonical_reason()
467    );
468    if !check_eq(
469        response.status().as_u16(),
470        415,
471        "Login should return 415 with wrong Content-Type",
472    ) {
473        return;
474    }
475
476    // =========================================================================
477    // Test 8: Token case sensitivity (lowercase 'bearer')
478    // =========================================================================
479    println!("\n8. Token case sensitivity (lowercase 'bearer')");
480    let response = client
481        .get("/protected")
482        .header("authorization", format!("bearer {DEMO_BEARER_VALUE}"))
483        .send();
484    println!(
485        "   GET /protected (Authorization: bearer {}) -> {} {}",
486        DEMO_BEARER_VALUE,
487        response.status().as_u16(),
488        response.status().canonical_reason()
489    );
490    if !check_eq(
491        response.status().as_u16(),
492        200,
493        "Bearer scheme should be case-insensitive (lowercase accepted)",
494    ) {
495        return;
496    }
497
498    println!("\nAll authentication tests passed!");
499}
Source

pub fn websocket_route_count(&self) -> usize

Returns the number of registered websocket routes.

Source

pub fn has_websocket_route(&self, path: &str) -> bool

Returns true if a websocket route matches the given path.

Source

pub fn routes(&self) -> impl Iterator<Item = (Method, &str)>

Returns an iterator over route metadata (method, path).

This is useful for generating OpenAPI specifications or debugging.

Source

pub fn openapi_spec(&self) -> Option<&str>

Returns the generated OpenAPI specification JSON, if OpenAPI is enabled.

§Example
let app = App::builder()
    .openapi(OpenApiConfig::new().title("My API"))
    .build();

if let Some(spec) = app.openapi_spec() {
    println!("OpenAPI spec: {}", spec);
}
Source

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

Returns the shared state container.

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 exception_handlers(&self) -> &Arc<ExceptionHandlers>

Returns the exception handlers registry.

Source

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

Register a fixed override value for a dependency type.

This is a test-focused convenience API; production code typically wires dependencies via crate::dependency::FromDependency implementations.

Source

pub fn clear_dependency_overrides(&self)

Clear all registered dependency overrides.

Source

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

Returns the shared dependency overrides registry.

Source

pub fn take_background_tasks(req: &mut Request) -> Option<BackgroundTasks>

Take request-scoped background tasks (if any) from a request.

The HTTP server calls this after writing the response so any deferred work runs outside the main request handler path.

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 async fn handle(&self, ctx: &RequestContext, req: &mut Request) -> Response

Handles an incoming request.

This matches the request against registered routes, runs middleware, and returns the response.

Source

pub async fn handle_websocket( &self, ctx: &RequestContext, req: &mut Request, ws: WebSocket, ) -> Result<(), WebSocketError>

Handles an incoming websocket upgrade request after the handshake has been accepted.

The HTTP server is responsible for validating the upgrade headers and writing the 101 response. This function only performs path matching and calls the websocket handler.

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.

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
Source§

impl OpenApiExt for App

Source§

fn openapi(&self) -> OpenApi

Generate an OpenAPI specification from the application. Read more
Source§

fn openapi_with<F>(&self, configure: F) -> OpenApi

Generate an OpenAPI specification with custom configuration.

Auto Trait Implementations§

§

impl !Freeze for App

§

impl !RefUnwindSafe for App

§

impl Send for App

§

impl Sync for App

§

impl Unpin for App

§

impl UnsafeUnpin 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