Skip to main content

RequestBuilder

Struct RequestBuilder 

Source
pub struct RequestBuilder<'a, H> { /* private fields */ }
Expand description

Builder for constructing test requests with a fluent API.

Use the methods on TestClient to create a request builder, then chain configuration methods and call send to execute.

§Example

let response = client
    .post("/api/items")
    .header("Content-Type", "application/json")
    .body(r#"{"name": "Widget"}"#)
    .send();

Implementations§

Source§

impl<'a, H> RequestBuilder<'a, H>
where H: Handler + 'static,

Source

pub fn query(self, key: &str, value: &str) -> RequestBuilder<'a, H>

Sets a query string parameter.

Multiple calls append parameters.

§Example
client.get("/search").query("q", "rust").query("limit", "10").send()
Source

pub fn header( self, name: impl Into<String>, value: impl Into<Vec<u8>>, ) -> RequestBuilder<'a, H>

Sets a request header.

§Example
client.get("/api").header("Authorization", "Bearer token").send()
Examples found in repository?
examples/crud_api.rs (line 299)
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 346)
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 header_str( self, name: impl Into<String>, value: &str, ) -> RequestBuilder<'a, H>

Sets a request header with a string value.

Source

pub fn body(self, body: impl Into<Vec<u8>>) -> RequestBuilder<'a, H>

Sets the request body as raw bytes.

§Example
client.post("/upload").body(b"binary data".to_vec()).send()
Examples found in repository?
examples/crud_api.rs (line 300)
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 347)
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 body_str(self, body: &str) -> RequestBuilder<'a, H>

Sets the request body as a string.

Source

pub fn json<T>(self, value: &T) -> RequestBuilder<'a, H>
where T: Serialize,

Sets the request body as JSON.

Automatically sets the Content-Type header to application/json.

§Example
#[derive(Serialize)]
struct CreateUser { name: String }

client.post("/users").json(&CreateUser { name: "Alice".into() }).send()
Source

pub fn cookie(self, name: &str, value: &str) -> RequestBuilder<'a, H>

Sets a cookie for this request only.

This does not affect the client’s cookie jar.

Source

pub fn send(self) -> TestResponse

Sends the request and returns the response.

§Example
let response = client.get("/").send();
Examples found in repository?
examples/hello_world.rs (line 94)
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 47)
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 301)
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 290)
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}

Auto Trait Implementations§

§

impl<'a, H> !Freeze for RequestBuilder<'a, H>

§

impl<'a, H> !RefUnwindSafe for RequestBuilder<'a, H>

§

impl<'a, H> Send for RequestBuilder<'a, H>
where H: Sync + Send,

§

impl<'a, H> Sync for RequestBuilder<'a, H>
where H: Sync + Send,

§

impl<'a, H> Unpin for RequestBuilder<'a, H>

§

impl<'a, H> !UnwindSafe for RequestBuilder<'a, H>

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