Skip to content

feat: implement response body validation with strict mode and improve…#2

Open
amirHdev wants to merge 2 commits intophumberdroz:mainfrom
amirHdev:feat/response-validation-strict-mode
Open

feat: implement response body validation with strict mode and improve…#2
amirHdev wants to merge 2 commits intophumberdroz:mainfrom
amirHdev:feat/response-validation-strict-mode

Conversation

@amirHdev
Copy link

@amirHdev amirHdev commented Feb 2, 2026

Features

  • Response body capture via custom responseBodyWriter
  • Full OpenAPI response validation using openapi3filter.ValidateResponse
  • New configuration: ValidatorOptions.StrictResponse bool (returns 500 on violation)
  • Custom error formatting for improved developer experience (e.g. integer type mismatch messages)
  • Modernized spec loading with openapi3.NewLoader (external refs allowed)
  • Structured logging of response validation failures

Tests

  • Added coverage for valid/invalid responses
  • Strict mode enforcement tests
  • Error message assertions

Documentation

  • Updated README with strict mode usage example

Motivation
Improves API contract enforcement and debugging experience without breaking.

Summary by CodeRabbit

  • New Features

    • Added a public option to enable strict OpenAPI response validation and enhanced request/response validation behavior.
  • Bug Fixes

    • Improved error handling and clearer validation failure messages for route and payload issues.
  • Documentation

    • Added usage examples showing basic and strict mode configurations.
  • Chores

    • Upgraded Go toolchain and core dependencies.
    • Added CI workflow for automated testing and linting.

…d errors

- Capture outgoing response body using custom responseBodyWriter

- Validate responses against OpenAPI spec using openapi3filter.ValidateResponse

- Introduce ValidatorOptions{StrictResponse: bool} → return 500 on invalid responses when enabled

- Add custom, user-friendly error messages for common schema violations

- Modernize OpenAPI document loading with openapi3.NewLoader

- Log validation failures with structured error context

- Add/update tests covering response validation, strict mode behavior, and error formatting

Signed-off-by: Amirhossein Akhlaghpour <m9.akhlaghpoor@gmail.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 2, 2026

📝 Walkthrough

Walkthrough

Adds a CI workflow, updates module dependencies and Go toolchain, and implements enhanced OpenAPI request/response validation middleware (response capture, strict-response option, router switch) with README usage examples.

Changes

Cohort / File(s) Summary
CI/CD Configuration
​.github/workflows/ci.yml
Adds GitHub Actions "CI" workflow running tests across Go 1.22–1.25 and tip, with verbose/race test runs and golangci-lint.
Documentation
README.md
Adds Usage section showing Validator(openapiYAMLBytes) and strict mode via ValidatorOptions{ StrictResponse: true }.
Dependency Management
go.mod
Bumps Go toolchain to 1.25.6 and upgrades major dependencies (e.g., github.com/getkin/kin-openapi v0.53.0→v0.133.0, github.com/gin-gonic/gin v1.7.1→v1.11.0, plus many indirect updates).
Validation Logic
pkg/gin-openapi-validator/validator.go, pkg/gin-openapi-validator/validationerror.go, pkg/gin-openapi-validator/validator_test.go
Implements response body capture, validates requests before handlers and responses after handlers, adds ValidatorOptions{ StrictResponse bool }, switches router implementation and updates error handling/messages; minor error-title adjustment in schema error conversion and test expectation updated.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Gin as Gin Handler
    participant Validator as Validator Middleware
    participant Router as OpenAPI Router
    participant RespWriter as Response Body Writer

    Client->>Gin: Send HTTP request
    Gin->>Validator: Enter middleware (Validator)
    Validator->>Router: Match route & validate request
    alt Request validation fails
        Validator->>Gin: Return validation error response
        Gin->>Client: Error response
    else Request valid
        Validator->>RespWriter: Wrap original response writer (capture headers/body/status)
        Gin->>RespWriter: Invoke handler (writes to wrapped writer)
        RespWriter->>Validator: Provide captured status, headers, body
        Validator->>Router: Validate response against OpenAPI schema
        alt StrictResponse true and validation fails
            Validator->>Client: Return 500 / strict-failure response
        else Validation passes or non-strict
            RespWriter->>Client: Flush captured response
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through modules, tidy and spry,
Wrapped responses so no schema can lie,
With strictness to guard and CI to test,
I validate happily — code at its best! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title summarizes the main feature of implementing response body validation with strict mode, which aligns with the PR's core changes across validator.go, validation error handling, and test coverage.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@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

🤖 Fix all issues with AI agents
In `@pkg/gin-openapi-validator/validator.go`:
- Around line 92-122: The response is currently written to the client inside
responseBodyWriter.Write, so ValidateResponse runs after the response is already
sent and AbortWithStatusJSON cannot replace it; change the flow so
responseBodyWriter.Write only buffers into its body buffer (do not write to the
original ResponseWriter), collect status and headers into
responseValidationInput, call openapi3filter.ValidateResponse, and only if
validation succeeds flush the buffered headers, status and body to the original
ResponseWriter (write header values, call WriteHeader with the captured status,
then write the buffered bytes); if validation fails and options.StrictResponse
is true, send the 500 JSON via the original ResponseWriter instead of attempting
AbortWithStatusJSON. Ensure you update the responseBodyWriter.Write
implementation and the post-c.Next logic around ValidateResponse, referencing
responseBodyWriter.Write, responseValidationInput, ValidateResponse, c.Next, and
AbortWithStatusJSON.
🧹 Nitpick comments (3)
.github/workflows/ci.yml (1)

26-29: Consider pinning golangci-lint version for reproducibility.

Using version: latest can lead to inconsistent CI results when new linter versions introduce breaking changes or new rules. Pinning to a specific version (e.g., v1.61.0) improves build reproducibility.

pkg/gin-openapi-validator/validator.go (1)

40-40: Format validator registration on every call.

DefineStringFormatValidator is called each time Validator() is invoked. While likely idempotent, consider moving this to a sync.Once or package init() to ensure it runs exactly once.

♻️ Suggested improvement
+var formatValidatorOnce sync.Once
+
 func Validator(doc []byte, opts ...ValidatorOptions) gin.HandlerFunc {
 	var options ValidatorOptions
 	if len(opts) > 0 {
 		options = opts[0]
 	}
 
-	openapi3.DefineStringFormatValidator("uuid", openapi3.NewRegexpFormatValidator(openapi3.FormatOfStringForUUIDOfRFC4122))
+	formatValidatorOnce.Do(func() {
+		openapi3.DefineStringFormatValidator("uuid", openapi3.NewRegexpFormatValidator(openapi3.FormatOfStringForUUIDOfRFC4122))
+	})
README.md (1)

8-14: Consider adding brief explanation of each mode's behavior.

The examples are correct, but users would benefit from knowing what happens in each mode:

  • Basic mode: Logs violations but allows the response through
  • Strict mode: Returns HTTP 500 when response violates the spec

This helps users choose the appropriate mode for their use case.

📝 Suggested documentation enhancement
 ```go
 import "github.com/phumberdroz/gin-openapi-validator"
 
-// Basic (logs violations only)
+// Basic mode: validates requests and responses, logs violations but allows invalid responses through
 r.Use(ginopenapivalidator.Validator(openapiYAMLBytes))
 
-// Strict mode: return 500 when response is invalid
+// Strict mode: returns HTTP 500 when the response body violates the OpenAPI specification
+// Use this in development/staging to catch API contract violations early
 r.Use(ginopenapivalidator.Validator(openapiYAMLBytes, ginopenapivalidator.ValidatorOptions{
     StrictResponse: true,
 }))
 ```

Signed-off-by: Amirhossein Akhlaghpour <m9.akhlaghpoor@gmail.com>
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.

1 participant