pub struct AppBuilder { /* 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();Implementations§
Source§impl AppBuilder
impl AppBuilder
Sourcepub fn new() -> AppBuilder
pub fn new() -> AppBuilder
Creates a new application builder.
Sourcepub fn config(self, config: AppConfig) -> AppBuilder
pub fn config(self, config: AppConfig) -> AppBuilder
Sets the application configuration.
Examples found in repository?
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}Sourcepub fn openapi(self, config: OpenApiConfig) -> AppBuilder
pub fn openapi(self, config: OpenApiConfig) -> AppBuilder
Enables and configures OpenAPI documentation.
When enabled, the application will automatically generate an OpenAPI 3.1 specification and serve it at the configured endpoint.
§Example
let app = App::builder()
.openapi(OpenApiConfig::new()
.title("My API")
.version("1.0.0")
.description("A sample API"))
.build();Sourcepub fn enable_docs(self, config: DocsConfig) -> AppBuilder
pub fn enable_docs(self, config: DocsConfig) -> AppBuilder
Enables and configures interactive API documentation endpoints.
This wires crate::docs::DocsConfig into the application build and ensures an
OpenAPI JSON endpoint is served at config.openapi_path unless overridden later.
Notes:
- Docs endpoints are appended during
build()so they don’t appear in the OpenAPI spec. - If OpenAPI is already configured, its
openapi_pathis updated to matchDocsConfig.
Sourcepub fn route<H, Fut>(
self,
path: impl Into<String>,
method: Method,
handler: H,
) -> AppBuilder
pub fn route<H, Fut>( self, path: impl Into<String>, method: Method, handler: H, ) -> AppBuilder
Adds a route to the application.
Routes are matched in the order they are added.
Sourcepub fn route_entry(self, entry: RouteEntry) -> AppBuilder
pub fn route_entry(self, entry: RouteEntry) -> AppBuilder
Adds a pre-built RouteEntry to the application.
This is primarily used by proc-macro generated route builders that already know the method/path and can build an extraction wrapper.
Sourcepub fn websocket<H, Fut>(
self,
path: impl Into<String>,
handler: H,
) -> AppBuilder
pub fn websocket<H, Fut>( self, path: impl Into<String>, handler: H, ) -> AppBuilder
Adds a websocket route to the application.
WebSocket routes are matched only when the server receives a valid websocket upgrade request. They do not appear in OpenAPI output.
Sourcepub fn get<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder
pub fn get<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder
Adds a GET route.
Examples found in repository?
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
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}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}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}Sourcepub fn post<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder
pub fn post<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder
Adds a POST route.
Examples found in repository?
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
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}Sourcepub fn put<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder
pub fn put<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder
Adds a PUT route.
Examples found in repository?
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}Sourcepub fn delete<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder
pub fn delete<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder
Adds a DELETE route.
Examples found in repository?
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}Sourcepub fn patch<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder
pub fn patch<H, Fut>(self, path: impl Into<String>, handler: H) -> AppBuilder
Adds a PATCH route.
Sourcepub fn middleware<M>(self, middleware: M) -> AppBuilderwhere
M: Middleware + 'static,
pub fn middleware<M>(self, middleware: M) -> AppBuilderwhere
M: Middleware + 'static,
Adds middleware to the application.
Middleware is executed in the order it is added:
beforehooks run first-to-lastafterhooks run last-to-first
Examples found in repository?
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}Sourcepub fn state<T>(self, state: T) -> AppBuilder
pub fn state<T>(self, state: T) -> AppBuilder
Adds shared state to the application.
State can be accessed by handlers through the State<T> extractor.
Sourcepub fn exception_handler<E, H>(self, handler: H) -> AppBuilder
pub fn exception_handler<E, H>(self, handler: H) -> AppBuilder
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();Sourcepub fn exception_handlers(self, handlers: ExceptionHandlers) -> AppBuilder
pub fn exception_handlers(self, handlers: ExceptionHandlers) -> AppBuilder
Sets the exception handlers registry.
This replaces any previously registered handlers.
Sourcepub fn with_default_exception_handlers(self) -> AppBuilder
pub fn with_default_exception_handlers(self) -> AppBuilder
Uses default exception handlers for common error types.
This registers handlers for:
HttpError→ JSON response with status/detailValidationErrors→ 422 with error list
Sourcepub fn on_startup<F>(self, hook: F) -> AppBuilder
pub fn on_startup<F>(self, hook: F) -> AppBuilder
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();Sourcepub fn on_startup_async<F, Fut>(self, hook: F) -> AppBuilder
pub fn on_startup_async<F, Fut>(self, hook: F) -> AppBuilder
Sourcepub fn on_shutdown<F>(self, hook: F) -> AppBuilder
pub fn on_shutdown<F>(self, hook: F) -> AppBuilder
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();Sourcepub fn on_shutdown_async<F, Fut>(self, hook: F) -> AppBuilder
pub fn on_shutdown_async<F, Fut>(self, hook: F) -> AppBuilder
Sourcepub fn startup_hook_count(&self) -> usize
pub fn startup_hook_count(&self) -> usize
Returns the number of registered startup hooks.
Sourcepub fn shutdown_hook_count(&self) -> usize
pub fn shutdown_hook_count(&self) -> usize
Returns the number of registered shutdown hooks.
Sourcepub fn build(self) -> App
pub fn build(self) -> App
Builds the application.
This consumes the builder and returns the configured App.
§Panics
Panics if any routes conflict (same method + structurally identical path pattern).
Examples found in repository?
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
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}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}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}