Skip to content

feat(validation): field-level validation, return 422 on failure#259

Merged
nanotaboada merged 1 commit intomasterfrom
feat/253-257-input-validation-422
Apr 16, 2026
Merged

feat(validation): field-level validation, return 422 on failure#259
nanotaboada merged 1 commit intomasterfrom
feat/253-257-input-validation-422

Conversation

@nanotaboada
Copy link
Copy Markdown
Owner

@nanotaboada nanotaboada commented Apr 16, 2026

Summary

  • Adds binding struct tags to Player for field-level validation (required on 7 string fields, min=1,max=99 on squadNumber, omitempty on middleName, binding:"-" on ID)
  • Replaces BindJSON with ShouldBindJSON in POST and PUT handlers; uses errors.As(err, &validator.ValidationErrors{}) to return 422 Unprocessable Entity for validation failures and 400 Bad Request for malformed JSON
  • Adds @Failure 422 Swagger annotations on POST and PUT; regenerates docs
  • Adds TestRequestPOSTPlayersValidationResponseStatusUnprocessableEntity and TestRequestPUTPlayerBySquadNumberValidationResponseStatusUnprocessableEntity (3 subtests each); updates MakeUnknownPlayer() to carry all required fields

Closes #257
Closes #253

Test plan

  • All 38 tests pass (go test ./...)
  • Coverage: 100% across service, controller, route
  • go vet and golangci-lint clean
  • Docker build succeeds

🤖 Generated with Claude Code


This change is Reviewable

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced validation on player endpoints: required fields (name, position, team, league, date of birth) and squad number range constraints (1-99).
  • Bug Fixes

    • Improved API error responses: validation failures now return 422 (Unprocessable Entity) instead of 400 (Bad Request), providing clearer error distinction.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 16, 2026

Walkthrough

This PR implements input validation for player request payloads using Gin's validator framework. It adds binding tags to the Player struct defining required fields and value ranges, modifies controller handlers to distinguish validation errors (422) from JSON parse errors (400), adds comprehensive test coverage for validation scenarios, and regenerates Swagger documentation.

Changes

Cohort / File(s) Summary
Model Validation Tags
model/player_model.go
Added binding tags to Player struct: required for firstName, lastName, dateOfBirth, position, abbrPosition, team, league; min=1,max=99 for squadNumber; omitempty for middleName; binding:"-" for ID to exclude it from validation.
Controller Validation Handling
controller/player_controller.go
Switched from BindJSON to ShouldBindJSON in POST and PUT handlers. Added runtime detection of validation errors using errors.As() to return 422 Unprocessable Entity for field validation failures, while malformed JSON continues returning 400 Bad Request. Updated Swagger annotations to document 422 responses.
Test Coverage
tests/main_test.go, tests/player_fake.go
Added two new table-driven test functions (TestRequestPOSTPlayersValidationResponseStatusUnprocessableEntity and TestRequestPUTPlayerBySquadNumberValidationResponseStatusUnprocessableEntity) to verify 422 responses for missing required fields and out-of-range squadNumber. Updated MakeUnknownPlayer() to return a fully populated Player so binding validation passes before service-layer 404 tests.
Documentation
docs/..., CHANGELOG.md
Regenerated Swagger output to include @Failure 422 annotations on POST and PUT endpoints. Updated CHANGELOG.md with summary of validation changes.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Controller as Player Controller
    participant Binder as ShouldBindJSON
    participant Validator as go-playground/validator
    
    Client->>Controller: POST/PUT with JSON payload
    Controller->>Binder: ShouldBindJSON(&player)
    
    alt Valid JSON, Invalid Fields
        Binder->>Validator: Validate struct tags
        Validator-->>Binder: ValidationErrors
        Binder-->>Controller: validator.ValidationErrors
        Controller-->>Client: 422 Unprocessable Entity
    else Malformed JSON
        Binder-->>Controller: JSON parse error (not ValidationErrors)
        Controller-->>Client: 400 Bad Request
    else Valid JSON, Valid Fields
        Binder-->>Controller: nil error
        Controller->>Controller: Process request (POST/PUT logic)
        Controller-->>Client: 200/201/404
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
Add field-level validation with binding tags for required fields and squadNumber range (#257)
Return 422 for validation errors and 400 for malformed JSON (#253, #257)
Add test cases covering validation failure scenarios (#257)
Update Swagger documentation with 422 responses (#253, #257)
All existing tests continue to pass (#257, #253)

Possibly related PRs

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title uses Conventional Commits format (feat:), is descriptive of the main changes (field-level validation and 422 response), stays well under 80 characters (63 chars), and directly aligns with the PR objectives.

✏️ 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: #257

✨ 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/253-257-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.

…#253)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nanotaboada nanotaboada force-pushed the feat/253-257-input-validation-422 branch from 90ccdf1 to b0f32be Compare April 16, 2026 12:10
@nanotaboada nanotaboada changed the title feat(validation): add field-level validation, return 422 for invalid payloads feat(validation): field-level validation, return 422 on failure Apr 16, 2026
@sonarqubecloud
Copy link
Copy Markdown

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (36a9abd) to head (b0f32be).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff            @@
##            master      #259   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            3         3           
  Lines          130       141   +11     
=========================================
+ Hits           130       141   +11     

☔ 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@controller/player_controller.go`:
- Around line 69-75: The request handler uses context.ShouldBindJSON(&player)
but doesn't enforce the Content-Type header, allowing non-application/json
requests (e.g., text/plain) with JSON bodies to slip through; before calling
context.ShouldBindJSON (in the player binding blocks where
context.ShouldBindJSON(&player) is used) check the request Content-Type (e.g.,
context.GetHeader("Content-Type") or context.ContentType()) and ensure it is
application/json (or starts with "application/json", case-insensitive); if not,
immediately respond with http.StatusBadRequest and do not attempt binding. Apply
the same Content-Type guard to both binding sites (the one at the first
ShouldBindJSON(&player) and the second occurrence around lines 206-213).

In `@tests/main_test.go`:
- Around line 211-242: The test table in tests/main_test.go (the cases slice
built using MakeNonexistentPlayer and modified FirstName/SquadNumber) lacks
wrong-JSON-type payloads; add entries that send incorrect types (e.g., set
squadNumber to the JSON string "23" and set firstName to a non-string like 123)
by building those bodies via json.Marshal of a map or by creating a copy of
MakeNonexistentPlayer and injecting wrong-typed fields, then assert the same
UnprocessableEntity response as for the other validation cases; mirror these
additions in the other similar test suite mentioned (the other cases block) to
ensure type-validation coverage.
- Around line 220-221: The test builders currently call json.Marshal(p) and
discard the error (b, _ := json.Marshal(p)) which hides serialization failures;
update each builder used in the POST and PUT validation tests to handle the
error instead of ignoring it—either return an (string, error) from the builder
or accept *testing.T and call t.Fatalf/t.Fatalf-like helper on marshal error;
update callers accordingly for the six occurrences (the three POST builders
around the snippet with json.Marshal(p) and the three PUT builders later) so
that any json.Marshal failure surfaces and fails the test rather than producing
a silent invalid payload.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c54f8e52-b867-4dad-9ab0-c8643f1dc548

📥 Commits

Reviewing files that changed from the base of the PR and between 36a9abd and 90ccdf1.

⛔ Files ignored due to path filters (3)
  • docs/docs.go is excluded by !**/docs/docs.go
  • docs/swagger.json is excluded by !**/docs/swagger.json
  • docs/swagger.yaml is excluded by !**/docs/swagger.yaml
📒 Files selected for processing (5)
  • CHANGELOG.md
  • controller/player_controller.go
  • model/player_model.go
  • tests/main_test.go
  • tests/player_fake.go

Comment thread controller/player_controller.go
Comment thread tests/main_test.go
Comment thread tests/main_test.go
@nanotaboada nanotaboada merged commit 51f22a9 into master Apr 16, 2026
12 checks passed
@nanotaboada nanotaboada deleted the feat/253-257-input-validation-422 branch April 16, 2026 12:49
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