Skip to content

feat(api): adopt 422 Unprocessable Entity for validation errors (#469)#479

Merged
nanotaboada merged 1 commit intomasterfrom
feat/469-422-validation-errors
Apr 20, 2026
Merged

feat(api): adopt 422 Unprocessable Entity for validation errors (#469)#479
nanotaboada merged 1 commit intomasterfrom
feat/469-422-validation-errors

Conversation

@nanotaboada
Copy link
Copy Markdown
Owner

@nanotaboada nanotaboada commented Apr 19, 2026

Summary

  • Field validation failures now return 422 Unprocessable Entity instead of 400 Bad Request, correctly distinguishing syntactically valid but semantically invalid requests from malformed ones
  • 400 Bad Request is now reserved for genuinely malformed requests (unparseable JSON, wrong Content-Type, route/body squad number mismatch in PUT)
  • ExceptionMiddleware updated to return 422 for uncaught ValidationException as a safety net
  • Swagger/OpenAPI attributes and XML doc comments updated accordingly
  • All unit and integration tests updated to assert the new status code

Test plan

  • Post_Players_ValidationError_Returns422UnprocessableEntity — unit + integration
  • Put_PlayerBySquadNumber_ValidationError_Returns422UnprocessableEntity — unit + integration
  • Put_PlayerBySquadNumber_SquadNumberMismatch_Returns400BadRequest — unchanged, still 400
  • All 64 tests pass

Closes #469

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Changed
    • API validation failures now return HTTP 422 Unprocessable Entity (RFC 4918) instead of 400 Bad Request for improved HTTP semantics
    • HTTP 400 Bad Request is now reserved for malformed requests (unparseable JSON, incorrect Content-Type, route/body mismatch)

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

coderabbitai Bot commented Apr 19, 2026

Walkthrough

This pull request adopts HTTP status code 422 Unprocessable Entity (RFC 4918) for field validation failures, replacing 400 Bad Request, which is now reserved for malformed requests. Changes span controller attributes, exception middleware mapping, documentation, and test assertions across integration and unit test suites.

Changes

Cohort / File(s) Summary
Documentation
CHANGELOG.md
Updated the Unreleased → Changed section to document the shift from 400 Bad Request to 422 Unprocessable Entity for validation failures and clarified 400 is reserved for malformed requests per RFC 9457.
Controller & Metadata
src/Dotnet.Samples.AspNetCore.WebApi/Controllers/PlayerController.cs
Modified POST and PUT endpoints: replaced [ProducesResponseType(...400)] with [ProducesResponseType(...422)], changed validation-failure return from TypedResults.ValidationProblem(...) to TypedResults.Problem(...) with Status = 422, and updated XML documentation to reflect the new status code and clarify 400 for route/body mismatches.
Exception Handling
src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs
Updated exception-to-HTTP status mapping so FluentValidation.ValidationException responses now return 422 Unprocessable Entity instead of 400 Bad Request.
Integration Tests
test/Dotnet.Samples.AspNetCore.WebApi.Tests/Integration/PlayerWebApplicationTests.cs
Updated POST and PUT validation-failure tests to assert 422 Unprocessable Entity response code and verify ProblemDetails.Status is StatusCodes.Status422UnprocessableEntity.
Unit Tests
test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerControllerTests.cs
Updated validation-failure test methods to assert Status422UnprocessableEntity, changed expected result type from ValidationProblem to ProblemHttpResult, and renamed test methods to reflect 422 behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Assessment against linked issues

Objective Addressed Explanation
Field validation failures return 422 Unprocessable Entity [#469]
400 Bad Request reserved for malformed requests [#469]
Swagger/OpenAPI docs updated to declare 422 instead of 400 [#469]
Tests updated to assert new status code [#469]
CHANGELOG.md updated [#469]

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits format with 'feat(api):' prefix, is 70 characters (under 80), and accurately describes the main change of adopting 422 Unprocessable Entity for validation errors.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ 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/469-422-validation-errors
  • 🛠️ 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.

@sonarqubecloud
Copy link
Copy Markdown

@nanotaboada
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

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

Copy link
Copy Markdown

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/Dotnet.Samples.AspNetCore.WebApi.Tests/Integration/PlayerWebApplicationTests.cs (1)

200-204: ⚠️ Potential issue | 🟡 Minor

Update the stale validation-status comment.

This branch now returns 422 for validation failures, so the comment still saying “400 validation error” is misleading.

📝 Proposed comment fix
-    // BeUniqueSquadNumber, which returns a 400 validation error before the
+    // BeUniqueSquadNumber, which returns a 422 validation error before the
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@test/Dotnet.Samples.AspNetCore.WebApi.Tests/Integration/PlayerWebApplicationTests.cs`
around lines 200 - 204, Update the stale comment in PlayerWebApplicationTests.cs
to reflect that validation failures now return 422 instead of 400: change the
sentence that currently reads “400 validation error” to “422 validation error
(Unprocessable Entity)”; keep references to the controller's 409 Conflict
branch, the "Create" validation rule set, and BeUniqueSquadNumber, and note that
the 409 path is still covered by the unit test
Post_Players_Existing_Returns409Conflict where validation is mocked.
🧹 Nitpick comments (1)
test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerControllerTests.cs (1)

66-67: Add payload assertions to validation error tests.

Both validation tests (lines 66-67 and 342-343) currently assert only the result type and status code. Per the division of responsibilities across test layers, unit tests should fully assert the HttpValidationProblemDetails payload including the Status and Errors dictionary. Add assertions for the validation error details.

🧪 Proposed assertion coverage
         var httpResult = result.Should().BeOfType<ProblemHttpResult>().Subject;
         httpResult.StatusCode.Should().Be(StatusCodes.Status422UnprocessableEntity);
+        var problemDetails = httpResult
+            .ProblemDetails.Should()
+            .BeOfType<HttpValidationProblemDetails>()
+            .Subject;
+        problemDetails.Status.Should().Be(StatusCodes.Status422UnprocessableEntity);
+        problemDetails
+            .Errors.Should()
+            .ContainKey("SquadNumber")
+            .WhoseValue.Should()
+            .Contain("SquadNumber must be greater than 0.");

Apply the same assertions to both Post_Players_ValidationError_Returns422UnprocessableEntity() (lines 66-67) and Put_PlayerBySquadNumber_ValidationError_Returns422UnprocessableEntity() (lines 342-343).

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

In `@test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerControllerTests.cs`
around lines 66 - 67, The tests
Post_Players_ValidationError_Returns422UnprocessableEntity and
Put_PlayerBySquadNumber_ValidationError_Returns422UnprocessableEntity only
assert the response type and status code; add assertions to verify the
ProblemHttpResult.Value is an HttpValidationProblemDetails and its Status equals
StatusCodes.Status422UnprocessableEntity and that the Errors dictionary contains
the expected validation entries. In each test, after obtaining var httpResult =
result.Should().BeOfType<ProblemHttpResult>().Subject, cast/inspect
httpResult.Value as HttpValidationProblemDetails and assert its Status property
equals 422 and that the Errors collection contains the expected key(s) and
message(s) for the validated fields (e.g., model property names used in the
test), ensuring the payload is fully validated alongside the result type and
status.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Line 49: Update the CHANGELOG line that currently reads "Field validation
failures now return `422 Unprocessable Entity` (RFC 4918) instead of `400 Bad
Request`; `400 Bad Request` is now reserved for malformed requests (unparseable
JSON, wrong `Content-Type`, route/body mismatch) per RFC 9457" to separate the
status-code guidance from Problem Details attribution: state that validation
failures return `422 Unprocessable Entity` (RFC 4918) and `400 Bad Request` is
reserved for malformed requests (unparseable JSON, wrong `Content-Type`,
route/body mismatch), and add a separate sentence that Problem Details
formatting follows RFC 9457; replace the original single sentence with these two
clearer sentences in CHANGELOG.md.

---

Outside diff comments:
In
`@test/Dotnet.Samples.AspNetCore.WebApi.Tests/Integration/PlayerWebApplicationTests.cs`:
- Around line 200-204: Update the stale comment in PlayerWebApplicationTests.cs
to reflect that validation failures now return 422 instead of 400: change the
sentence that currently reads “400 validation error” to “422 validation error
(Unprocessable Entity)”; keep references to the controller's 409 Conflict
branch, the "Create" validation rule set, and BeUniqueSquadNumber, and note that
the 409 path is still covered by the unit test
Post_Players_Existing_Returns409Conflict where validation is mocked.

---

Nitpick comments:
In `@test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerControllerTests.cs`:
- Around line 66-67: The tests
Post_Players_ValidationError_Returns422UnprocessableEntity and
Put_PlayerBySquadNumber_ValidationError_Returns422UnprocessableEntity only
assert the response type and status code; add assertions to verify the
ProblemHttpResult.Value is an HttpValidationProblemDetails and its Status equals
StatusCodes.Status422UnprocessableEntity and that the Errors dictionary contains
the expected validation entries. In each test, after obtaining var httpResult =
result.Should().BeOfType<ProblemHttpResult>().Subject, cast/inspect
httpResult.Value as HttpValidationProblemDetails and assert its Status property
equals 422 and that the Errors collection contains the expected key(s) and
message(s) for the validated fields (e.g., model property names used in the
test), ensuring the payload is fully validated alongside the result type and
status.
🪄 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: e31967c3-f8c1-4ad4-9324-86e345803b5e

📥 Commits

Reviewing files that changed from the base of the PR and between 6b73d75 and ff31667.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • src/Dotnet.Samples.AspNetCore.WebApi/Controllers/PlayerController.cs
  • src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs
  • test/Dotnet.Samples.AspNetCore.WebApi.Tests/Integration/PlayerWebApplicationTests.cs
  • test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerControllerTests.cs

Comment thread CHANGELOG.md
@nanotaboada nanotaboada merged commit da5313b into master Apr 20, 2026
9 checks passed
@nanotaboada nanotaboada deleted the feat/469-422-validation-errors branch April 20, 2026 18:30
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.

Adopt 422 Unprocessable Entity for payload validation errors

1 participant