Skip to main content

AppBuilder

Struct AppBuilder 

Source
pub struct AppBuilder<S = ()>
where S: StateRegistry,
{ /* private fields */ }
Expand description

Builder for constructing an App.

Use this to configure routes, middleware, and shared state before building the final application.

§Example

let app = App::builder()
    .config(AppConfig::new().name("My API"))
    .state(DatabasePool::new())
    .middleware(LoggingMiddleware::new())
    .on_startup(|| {
        println!("Server starting...");
        Ok(())
    })
    .on_shutdown(|| {
        println!("Server stopping...");
    })
    .route("/", Method::Get, index_handler)
    .route("/items", Method::Get, list_items)
    .route("/items", Method::Post, create_item)
    .route("/items/{id}", Method::Get, get_item)
    .build();

§Type-Safe State

The AppBuilder uses a type-state pattern to track registered state types at compile time. The generic parameter S represents the type-level set of registered state types.

When you call .with_state::<T>(value), the builder’s type changes from AppBuilder<S> to AppBuilder<(T, S)>, recording that T is now available.

Handlers that use State<T> extractors can optionally be constrained to require S: HasState<T>, ensuring the state is registered at compile time.

// Type changes: AppBuilder<()> -> AppBuilder<(DbPool, ())> -> AppBuilder<(Config, (DbPool, ()))>
let app = App::builder()
    .with_state(DbPool::new())  // Now has DbPool
    .with_state(Config::default())  // Now has DbPool + Config
    .build();

Implementations§

Source§

impl AppBuilder

Source

pub fn new() -> AppBuilder

Creates a new application builder with no registered state.

Source§

impl<S> AppBuilder<S>
where S: StateRegistry,

Source

pub fn config(self, config: AppConfig) -> AppBuilder<S>

Sets the application configuration.

Examples found in repository?
examples/getting_started.rs (line 102)
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<H, Fut>( self, path: impl Into<String>, method: Method, handler: H, ) -> AppBuilder<S>
where H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static, Fut: Future<Output = Response> + Send + 'static,

Adds a route to the application.

Routes are matched in the order they are added.

Source

pub fn get<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder<S>
where H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static, Fut: Future<Output = Response> + Send + 'static,

Adds a GET route.

Examples found in repository?
examples/hello_world.rs (line 80)
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 40)
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 286)
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 274)
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 post<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder<S>
where H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static, Fut: Future<Output = Response> + Send + 'static,

Adds a POST route.

Examples found in repository?
examples/crud_api.rs (line 285)
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}
More examples
Hide additional examples
examples/auth_example.rs (line 276)
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 put<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder<S>
where H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static, Fut: Future<Output = Response> + Send + 'static,

Adds a PUT route.

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

pub fn delete<H, Fut>( self, path: impl Into<String>, handler: H, ) -> AppBuilder<S>
where H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static, Fut: Future<Output = Response> + Send + 'static,

Adds a DELETE route.

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

pub fn patch<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder<S>
where H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static, Fut: Future<Output = Response> + Send + 'static,

Adds a PATCH route.

Source

pub fn middleware<M>(self, middleware: M) -> AppBuilder<S>
where M: Middleware + 'static,

Adds middleware to the application.

Middleware is executed in the order it is added:

  • before hooks run first-to-last
  • after hooks run last-to-first
Examples found in repository?
examples/getting_started.rs (line 77)
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 include_router(self, router: APIRouter) -> AppBuilder<S>

Includes routes from an APIRouter.

This adds all routes from the router to the application, applying the router’s prefix, tags, and dependencies.

§Example
use fastapi_core::api_router::APIRouter;

let users_router = APIRouter::new()
    .prefix("/users")
    .get("", list_users)
    .get("/{id}", get_user);

let app = App::builder()
    .include_router(users_router)
    .build();
Source

pub fn include_router_with_config( self, router: APIRouter, config: IncludeConfig, ) -> AppBuilder<S>

Includes routes from an APIRouter with configuration.

This allows applying additional configuration when including a router, such as prepending a prefix, adding tags, or injecting dependencies.

§Example
use fastapi_core::api_router::{APIRouter, IncludeConfig};

let users_router = APIRouter::new()
    .prefix("/users")
    .get("", list_users);

let config = IncludeConfig::new()
    .prefix("/api/v1")
    .tags(vec!["api"]);

let app = App::builder()
    .include_router_with_config(users_router, config)
    .build();
Source

pub fn mount(self, prefix: impl Into<String>, app: App) -> AppBuilder<S>

Mounts a sub-application at a path prefix.

Mounted applications are completely independent from the parent:

  • They have their own middleware stack (parent middleware does NOT apply)
  • They have their own state and configuration
  • Their OpenAPI schemas are NOT merged with the parent
  • Request paths have the prefix stripped before being passed to the sub-app

This differs from include_router, which merges routes into the parent app and applies parent middleware.

§Use Cases
  • Mount admin panels at /admin
  • Mount Swagger UI at /docs
  • Mount static file servers
  • Integrate legacy or third-party apps
§Path Stripping Behavior

When a request arrives at /admin/users, and an app is mounted at /admin, the sub-app receives the request with path /users.

§Example
use fastapi_core::app::App;

// Create an admin sub-application
let admin_app = App::builder()
    .get("/users", admin_list_users)
    .get("/settings", admin_settings)
    .middleware(AdminAuthMiddleware::new())
    .build();

// Mount it at /admin
let main_app = App::builder()
    .get("/", home_page)
    .mount("/admin", admin_app)
    .build();

// Now:
// - GET /           -> home_page
// - GET /admin/users -> admin_list_users (with AdminAuthMiddleware)
// - GET /admin/settings -> admin_settings (with AdminAuthMiddleware)
Source

pub fn mounted_app_count(&self) -> usize

Returns the number of mounted sub-applications.

Source

pub fn state<T>(self, value: T) -> AppBuilder<S>
where T: Send + Sync + 'static,

👎Deprecated since 0.2.0: Use with_state for compile-time state type verification

Adds shared state to the application (legacy method).

State can be accessed by handlers through the State<T> extractor.

Note: This method is deprecated in favor of with_state, which provides compile-time verification that state types are registered.

Source

pub fn with_state<T>(self, value: T) -> AppBuilder<(T, S)>
where T: Send + Sync + 'static,

Adds typed state to the application with compile-time registration.

This method registers state using a type-state pattern, which enables compile-time verification that state types are properly registered before they are used by handlers.

The return type changes from AppBuilder<S> to AppBuilder<(T, S)>, recording that type T is now available in the state registry.

§Example
use fastapi_core::app::App;

struct DbPool { /* ... */ }
struct Config { api_key: String }

// Type evolves: () -> (DbPool, ()) -> (Config, (DbPool, ()))
let app = App::builder()
    .with_state(DbPool::new())    // Now has DbPool
    .with_state(Config::default()) // Now has DbPool + Config
    .build();
§Compile-Time Safety

When used with the RequiresState trait, handlers can declare their state dependencies and the compiler will verify they are met:

// This handler requires DbPool to be registered
fn handler_requiring_db<S: HasState<DbPool>>(app: AppBuilder<S>) { /* ... */ }
Source

pub fn override_dependency<T, F, Fut>(self, f: F) -> AppBuilder<S>
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) -> AppBuilder<S>
where T: FromDependency,

Registers a fixed dependency override value.

Source

pub fn clear_dependency_overrides(self) -> AppBuilder<S>

Clears all registered dependency overrides.

Source

pub fn exception_handler<E, H>(self, handler: H) -> AppBuilder<S>
where E: Error + Send + Sync + 'static, H: Fn(&RequestContext, E) -> Response + Send + Sync + 'static,

Registers a custom exception handler for a specific error type.

When an error of type E occurs during request handling, the registered handler will be called to convert it into a response.

§Example
#[derive(Debug)]
struct AuthError(String);

impl std::fmt::Display for AuthError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Auth error: {}", self.0)
    }
}

impl std::error::Error for AuthError {}

let app = App::builder()
    .exception_handler(|_ctx, err: AuthError| {
        Response::with_status(StatusCode::UNAUTHORIZED)
            .header("www-authenticate", b"Bearer".to_vec())
            .body_json(&json!({"error": err.0}))
    })
    .build();
Source

pub fn exception_handlers(self, handlers: ExceptionHandlers) -> AppBuilder<S>

Sets the exception handlers registry.

This replaces any previously registered handlers.

Source

pub fn with_default_exception_handlers(self) -> AppBuilder<S>

Uses default exception handlers for common error types.

This registers handlers for:

Source

pub fn on_startup<F>(self, hook: F) -> AppBuilder<S>
where F: FnOnce() -> Result<(), StartupHookError> + Send + 'static,

Registers a synchronous startup hook.

Startup hooks run before the server starts accepting connections, in the order they are registered (FIFO).

§Example
let app = App::builder()
    .on_startup(|| {
        println!("Connecting to database...");
        Ok(())
    })
    .on_startup(|| {
        println!("Loading configuration...");
        Ok(())
    })
    .build();
Source

pub fn on_startup_async<F, Fut>(self, hook: F) -> AppBuilder<S>
where F: FnOnce() -> Fut + Send + 'static, Fut: Future<Output = Result<(), StartupHookError>> + Send + 'static,

Registers an async startup hook.

Async startup hooks are awaited in registration order.

§Example
let app = App::builder()
    .on_startup_async(|| async {
        let pool = connect_to_database().await?;
        Ok(())
    })
    .build();
Source

pub fn on_shutdown<F>(self, hook: F) -> AppBuilder<S>
where F: FnOnce() + Send + 'static,

Registers a synchronous shutdown hook.

Shutdown hooks run after the server stops accepting connections and all in-flight requests complete (or are cancelled).

Shutdown hooks run in reverse registration order (LIFO), matching typical resource cleanup patterns (last acquired, first released).

§Example
let app = App::builder()
    .on_shutdown(|| {
        println!("Closing database connections...");
    })
    .build();
Source

pub fn on_shutdown_async<F, Fut>(self, hook: F) -> AppBuilder<S>
where F: FnOnce() -> Fut + Send + 'static, Fut: Future<Output = ()> + Send + 'static,

Registers an async shutdown hook.

Async shutdown hooks are awaited in reverse registration order (LIFO).

§Example
let app = App::builder()
    .on_shutdown_async(|| async {
        flush_metrics().await;
    })
    .build();
Source

pub fn lifespan<F, Fut, T>(self, lifespan_fn: F) -> AppBuilder<S>
where F: FnOnce() -> Fut + Send + 'static, Fut: Future<Output = Result<LifespanScope<T>, LifespanError>> + Send + 'static, T: Send + Sync + 'static,

Registers a lifespan context manager for async startup/shutdown.

The lifespan pattern is preferred over separate on_startup/on_shutdown hooks because it allows sharing state between the startup and shutdown phases. This is especially useful for resources like database connections, HTTP clients, or background task managers.

The lifespan function runs during application startup. It should:

  1. Initialize resources (connect to database, start background tasks, etc.)
  2. Return a LifespanScope containing:
    • State to be added to the application (accessible via State<T> extractor)
    • An optional cleanup closure to run during shutdown

If the lifespan function returns an error, application startup is aborted.

Note: When a lifespan is provided, it runs before any on_startup hooks. The lifespan cleanup runs after all on_shutdown hooks during shutdown.

§Example
use fastapi_core::app::{App, LifespanScope, LifespanError};

#[derive(Clone)]
struct DatabasePool { /* ... */ }

impl DatabasePool {
    async fn connect(url: &str) -> Result<Self, Error> { /* ... */ }
    async fn close(&self) { /* ... */ }
}

let app = App::builder()
    .lifespan(|| async {
        // Startup: connect to database
        println!("Connecting to database...");
        let pool = DatabasePool::connect("postgres://localhost/mydb")
            .await
            .map_err(|e| LifespanError::with_source("database connection failed", e))?;

        // Clone for use in cleanup
        let pool_for_cleanup = pool.clone();

        // Return state and cleanup
        Ok(LifespanScope::new(pool)
            .on_shutdown(async move {
                println!("Closing database connections...");
                pool_for_cleanup.close().await;
            }))
    })
    .get("/users", get_users)  // Handler can use State<DatabasePool>
    .build();
§Multiple State Types

To provide multiple state types from a single lifespan, use a tuple or define a struct containing all your state:

#[derive(Clone)]
struct AppState {
    db: DatabasePool,
    cache: RedisClient,
    config: AppConfig,
}

let app = App::builder()
    .lifespan(|| async {
        let db = DatabasePool::connect("...").await?;
        let cache = RedisClient::connect("...").await?;
        let config = load_config().await?;

        let state = AppState { db, cache, config };
        let state_for_cleanup = state.clone();

        Ok(LifespanScope::new(state)
            .on_shutdown(async move {
                state_for_cleanup.db.close().await;
                state_for_cleanup.cache.close().await;
            }))
    })
    .build();
Source

pub fn has_lifespan(&self) -> bool

Returns true if a lifespan function has been registered.

Source

pub fn startup_hook_count(&self) -> usize

Returns the number of registered startup hooks.

Source

pub fn shutdown_hook_count(&self) -> usize

Returns the number of registered shutdown hooks.

Source

pub fn build(self) -> App

Builds the application.

This consumes the builder and returns the configured App.

Examples found in repository?
examples/hello_world.rs (line 82)
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 42)
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 290)
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 279)
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}

Trait Implementations§

Source§

impl<S> Debug for AppBuilder<S>
where S: StateRegistry,

Source§

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

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

impl Default for AppBuilder

Source§

fn default() -> AppBuilder

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<S> Freeze for AppBuilder<S>

§

impl<S = ()> !RefUnwindSafe for AppBuilder<S>

§

impl<S> Send for AppBuilder<S>

§

impl<S = ()> !Sync for AppBuilder<S>

§

impl<S> Unpin for AppBuilder<S>
where S: Unpin,

§

impl<S = ()> !UnwindSafe for AppBuilder<S>

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