Skip to content

feat(routes): add payload validation, return 422 on failure (#97, #94)#98

Merged
nanotaboada merged 2 commits into
masterfrom
feat/94-97-input-validation-422
Apr 16, 2026
Merged

feat(routes): add payload validation, return 422 on failure (#97, #94)#98
nanotaboada merged 2 commits into
masterfrom
feat/94-97-input-validation-422

Conversation

@nanotaboada

@nanotaboada nanotaboada commented Apr 16, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds field-level validation to PlayerRequest using the validator crate (#[derive(Validate)])
  • Required string fields (first_name, last_name, date_of_birth, position, abbr_position, team, league) must be non-empty; middle_name is intentionally left unvalidated (optional)
  • squad_number must be in range 1–99
  • POST and PUT handlers now call .validate() before the service layer, returning 422 Unprocessable Entity on failure; 400 Bad Request remains reserved for malformed JSON

Test plan

  • test_request_post_players_body_empty_first_name_response_status_unprocessable_entity
  • test_request_post_players_body_squad_number_zero_response_status_unprocessable_entity
  • test_request_post_players_body_squad_number_above_maximum_response_status_unprocessable_entity
  • test_request_put_player_squadnumber_body_empty_last_name_response_status_unprocessable_entity
  • test_request_put_player_squadnumber_body_squad_number_zero_response_status_unprocessable_entity
  • test_request_put_player_squadnumber_body_squad_number_above_maximum_response_status_unprocessable_entity
  • All 44 existing tests continue to pass
  • cargo clippy clean, docker compose build success, CodeRabbit: no findings

Closes #97
Closes #94

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Input validation for player requests: required text fields must be non-empty and squad numbers must be 1–99.
    • API endpoints now return 422 Unprocessable Entity for request-body validation failures (400 reserved for malformed JSON/incorrect Content-Type).
  • Tests

    • Added integration tests verifying 422 responses for invalid player request payloads.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 16, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 04a00231-e9dd-47ab-8054-e6862f55ace6

📥 Commits

Reviewing files that changed from the base of the PR and between 1ec44a5 and ffbe49a.

📒 Files selected for processing (2)
  • src/routes/players.rs
  • tests/player_routes_tests.rs
✅ Files skipped from review due to trivial changes (1)
  • src/routes/players.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/player_routes_tests.rs

Walkthrough

Field-level validation was added to PlayerRequest with the validator crate; route handlers call validation and return 422 Unprocessable Entity on validation failures. Six integration tests and CHANGELOG updates were added; a new dependency was added to Cargo.toml.

Changes

Cohort / File(s) Summary
Dependencies
Cargo.toml
Added validator = { version = "0.18", features = ["derive"] }.
Model Validation
src/models/player.rs
PlayerRequest now #[derive(Validate)]; required string fields annotated with #[validate(length(min = 1))]; squad_number annotated #[validate(range(min = 1, max = 99))].
Route Handlers
src/routes/players.rs
Added validate_payload helper and call to payload.validate() in create_player and update_player; handlers return 422 on validation failure and pass validated payload to service.
Integration Tests
tests/player_routes_tests.rs
Added six tests asserting 422 Unprocessable Entity for empty string fields and out-of-range squad_number on POST and PUT endpoints.
Documentation
CHANGELOG.md
Documented validation rules, tests, and HTTP semantics (422 for validation failures; 400 reserved for malformed JSON).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Route as Route (create/update player)
    participant Validator as Validator (validator::Validate)
    participant Service as Service/DB

    Client->>Route: POST/PUT /players with JSON payload
    Route->>Validator: payload.validate()
    alt validation succeeds
        Route->>Service: call create/update with validated payload
        Service-->>Route: success response
        Route-->>Client: 200/201
    else validation fails
        Validator-->>Route: Validation errors
        Route-->>Client: 422 Unprocessable Entity
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
Add field-level validation on PlayerRequest and enforce non-empty string fields and squad_number range (1–99) [#97]
Call validate() in route handlers and return 422 on field validation failures [#97, #94]
Reserve 400 Bad Request for malformed requests; ensure parse errors remain 400 [#94]
Add tests covering validation failure scenarios and update CHANGELOG [#97, #94]

Possibly related PRs

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits format (feat:), is 70 characters (under 80), descriptive, specific, and accurately reflects the main changes: adding payload validation and returning 422 on validation failures.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #97

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/94-97-input-validation-422
  • 🛠️ sync documentation: Commit on current branch
  • 🛠️ sync documentation: Create PR
  • 🛠️ enforce http error handling: Commit on current branch
  • 🛠️ enforce http error handling: Create PR
  • 🛠️ idiomatic review: Commit on current branch
  • 🛠️ idiomatic review: Create PR
  • 🛠️ verify api contract: Commit on current branch
  • 🛠️ verify api contract: Create PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Apr 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.88%. Comparing base (279fb5b) to head (ffbe49a).
⚠️ Report is 3 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #98      +/-   ##
==========================================
+ Coverage   84.33%   84.88%   +0.54%     
==========================================
  Files           6        6              
  Lines         166      172       +6     
==========================================
+ Hits          140      146       +6     
  Misses         26       26              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@nanotaboada

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 16, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/routes/players.rs (1)

109-112: Optional: centralize duplicated payload validation block.

create_player and update_player now share identical validation logic. A small helper can keep behavior aligned and reduce drift risk.

♻️ Suggested DRY refactor
+fn validate_payload(payload: &PlayerRequest) -> Result<(), Status> {
+    if payload.validate().is_err() {
+        return Err(Status::UnprocessableEntity);
+    }
+    Ok(())
+}
+
 fn create_player(
     player_request: Json<PlayerRequest>,
     players: &State<PlayerCollection>,
 ) -> Result<(Status, Json<PlayerResponse>), Status> {
     let payload = player_request.into_inner();
-    if payload.validate().is_err() {
-        return Err(Status::UnprocessableEntity);
-    }
+    validate_payload(&payload)?;
     let mut connection = players.get().map_err(|_| Status::InternalServerError)?;
 
     match player_service::create(&mut connection, payload) {
@@
 fn update_player(
@@
 ) -> Result<Status, Status> {
     let payload = player_request.into_inner();
-    if payload.validate().is_err() {
-        return Err(Status::UnprocessableEntity);
-    }
+    validate_payload(&payload)?;
     let mut connection = players.get().map_err(|_| Status::InternalServerError)?;

Also applies to: 147-150

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/players.rs` around lines 109 - 112, Both create_player and
update_player contain identical payload validation (payload.validate().is_err()
-> Err(Status::UnprocessableEntity)); extract this into a small helper (e.g., fn
validate_player_payload(req: &PlayerRequest) -> Result<(), Status> or fn
validate_payload<T: Validate>(payload: &T) -> Result<(), Status>) and call it
from create_player and update_player instead of duplicating the block; ensure
the helper calls payload.validate() and maps failures to
Status::UnprocessableEntity so behavior remains identical.
tests/player_routes_tests.rs (1)

358-369: Use shared JSON fixtures and mutate fields instead of ad-hoc payload literals.

These six tests duplicate full request bodies inline. Please start from player_request_for_creation_json() / player_request_for_update_json() and override only the invalid field under test.

♻️ Suggested fixture-based pattern
+fn invalid_creation_payload(mutator: impl FnOnce(&mut serde_json::Value)) -> serde_json::Value {
+    let mut body = player_request_for_creation_json();
+    mutator(&mut body);
+    body
+}
+
+fn invalid_update_payload(mutator: impl FnOnce(&mut serde_json::Value)) -> serde_json::Value {
+    let mut body = player_request_for_update_json();
+    mutator(&mut body);
+    body
+}
@@
-    let body = serde_json::json!({
-        "firstName": "",
-        ...
-    });
+    let body = invalid_creation_payload(|b| b["firstName"] = serde_json::json!(""));
@@
-    let body = serde_json::json!({
-        ...
-        "squadNumber": 0,
-        ...
-    });
+    let body = invalid_creation_payload(|b| b["squadNumber"] = serde_json::json!(0));
@@
-    let body = serde_json::json!({
-        ...
-        "lastName": "",
-        ...
-    });
+    let body = invalid_update_payload(|b| b["lastName"] = serde_json::json!(""));
As per coding guidelines: "Use `player_request_for_creation_json()` and `player_request_for_update_json()` fixtures — no ad-hoc JSON payloads".

Also applies to: 385-396, 413-424, 442-453, 470-481, 498-509

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/player_routes_tests.rs` around lines 358 - 369, The tests inline
duplicate full player JSON payloads; replace each ad-hoc body with a clone of
the shared fixture (use player_request_for_creation_json() for create tests and
player_request_for_update_json() for update tests), then mutate only the
specific field under test (e.g., set "firstName" to empty) before
serializing/sending; locate the current local `body` assignment in the tests and
change it to start from the fixture, modify the target key, and reuse the rest
of the payload unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/routes/players.rs`:
- Around line 109-112: Both create_player and update_player contain identical
payload validation (payload.validate().is_err() ->
Err(Status::UnprocessableEntity)); extract this into a small helper (e.g., fn
validate_player_payload(req: &PlayerRequest) -> Result<(), Status> or fn
validate_payload<T: Validate>(payload: &T) -> Result<(), Status>) and call it
from create_player and update_player instead of duplicating the block; ensure
the helper calls payload.validate() and maps failures to
Status::UnprocessableEntity so behavior remains identical.

In `@tests/player_routes_tests.rs`:
- Around line 358-369: The tests inline duplicate full player JSON payloads;
replace each ad-hoc body with a clone of the shared fixture (use
player_request_for_creation_json() for create tests and
player_request_for_update_json() for update tests), then mutate only the
specific field under test (e.g., set "firstName" to empty) before
serializing/sending; locate the current local `body` assignment in the tests and
change it to start from the fixture, modify the target key, and reuse the rest
of the payload unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 11cbb70b-b09d-4a8b-a8b2-7ac4a86bfad7

📥 Commits

Reviewing files that changed from the base of the PR and between 279fb5b and 1ec44a5.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • CHANGELOG.md
  • Cargo.toml
  • src/models/player.rs
  • src/routes/players.rs
  • tests/player_routes_tests.rs

…ixtures

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@nanotaboada
nanotaboada merged commit 066eb28 into master Apr 16, 2026
10 checks passed
@nanotaboada
nanotaboada deleted the feat/94-97-input-validation-422 branch April 16, 2026 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add input validation for player request payloads Adopt 422 Unprocessable Entity for payload validation errors

1 participant