Skip to content

feat: oapi response validator#4283

Open
rolosp wants to merge 1 commit intomainfrom
feat/oapi-codegen-response-validator
Open

feat: oapi response validator#4283
rolosp wants to merge 1 commit intomainfrom
feat/oapi-codegen-response-validator

Conversation

@rolosp
Copy link
Copy Markdown
Collaborator

@rolosp rolosp commented May 4, 2026

Overview

Adds optional v3 response body validation against the OpenAPI spec. Violations are
logged as warnings; client responses are never affected.

Configured via server.responseValidation.mode:

  • off (default) — disabled
  • unstable — only routes marked x-unstable: true (~15 list/CRUD endpoints today)
  • all — every v3 route

The mode enum mirrors the existing AutoMigrate pattern in app/config/postgres.go.

Notes for reviewer

  • Validation buffers the response body in memory; unstable is the recommended
    rollout target. all is fine for staging but watch memory on large list pages.
  • mode: "off" is intentionally quoted in config.example.yaml — bare off is parsed
    as boolean false by YAML 1.1.
  • Violation logs include the kin-openapi error, which can echo offending response
    fields. Acceptable for observability but worth knowing.
  • v1 and request-side filtering aren't touched (out of scope)

Summary by CodeRabbit

  • New Features

    • Added configurable API response validation with modes: off (default), unstable (validates only unstable-marked operations), and all. Validation can be scoped per-route and can be skipped via a route filter hook; validation failures are logged as warnings while client responses remain unchanged.
  • Configuration

    • New server.responseValidation.mode setting (default: "off").
  • Tests

    • Added tests for valid vs invalid responses and for skipping validation via the route filter.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

📝 Walkthrough

Walkthrough

Adds configurable OpenAPI response validation: new server config and modes (off, unstable, all), middleware support for a per-route RouteFilterHook to skip validation, wiring through server/CLI, and tests covering violation, clean response, and route-filter behavior.

Changes

Response Validation

Layer / File(s) Summary
Configuration Types
app/config/server.go
Adds ResponseValidationConfig, ResponseValidationMode (off, unstable, all), Enabled()/Validate() methods, integrates into ServerConfig, and sets default to off.
Server Config Wiring
cmd/server/main.go, openmeter/server/server.go, api/v3/server/server.go
Propagates conf.Server.ResponseValidation into server/router config and v3server.Config.ResponseValidation. api/v3/server.go's Config struct gains ResponseValidation field.
Response Validation Middleware
api/v3/oasmiddleware/validator.go
ValidateResponseOption adds RouteFilterHook func(*routers.Route) bool. After route lookup, if hook is present and returns false, middleware skips response validation and returns the original response unchanged.
Route Filter Logic
api/v3/server/server.go
Adds buildResponseValidationRouteFilter(cfg) used when validation enabled; in unstable mode it enables validation only for operations with x-unstable: true (handles json.RawMessage and bool).
Config Examples & Tests
config.example.yaml, api/v3/oasmiddleware/validator_test.go, app/config/config_test.go
Example YAML documents server.responseValidation.mode: "off". Tests added for violation, clean response, and route-filter skipping; config test updated to expect ResponseValidation.Mode: off.
sequenceDiagram
    participant Client as Client
    participant Middleware as Middleware
    participant Router as Router
    participant Handler as Handler
    participant Validator as Validator
    participant ErrorHook as ErrorHook

    Client->>Middleware: send HTTP request
    Middleware->>Handler: invoke handler (buffer response)
    Handler-->>Middleware: buffered status + body
    Middleware->>Router: find matching route
    Router-->>Middleware: route
    Middleware->>Middleware: call RouteFilterHook(route)
    alt RouteFilterHook returns false
        Middleware-->>Client: write original buffered response
    else
        Middleware->>Validator: validate buffered response against route
        alt validation fails
            Validator-->>ErrorHook: report validation error
            ErrorHook-->>Middleware: hook executed
            Middleware-->>Client: write original buffered response
        else
            Middleware-->>Client: write original buffered response
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

release-note/feature

Suggested reviewers

  • tothandras
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: oapi response validator' clearly and concisely summarizes the main change: adding an optional OpenAPI v3 response body validator with configurable validation modes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ 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/oapi-codegen-response-validator

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@rolosp rolosp changed the title feat: oapi codegen response validator feat: oapi response validator May 4, 2026
@rolosp rolosp marked this pull request as ready for review May 4, 2026 14:36
@rolosp rolosp requested a review from a team as a code owner May 4, 2026 14:36
@rolosp rolosp added the release-note/misc Miscellaneous changes label May 4, 2026
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.

Caution

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

⚠️ Outside diff range comments (1)
api/v3/server/server.go (1)

70-77: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fail fast on invalid response-validation modes.

This new public knob is added to Config, but it still bypasses Config.Validate(). A typo that reaches this constructor can be treated as enabled downstream instead of rejecting the config.

🔧 Suggested fix
 func (c *Config) Validate() error {
 	var errs []error
+
+	if err := c.ResponseValidation.Mode.Validate(); err != nil {
+		errs = append(errs, err)
+	}
 
 	if c.BaseURL == "" {
 		errs = append(errs, errors.New("base URL is required"))
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/v3/server/server.go` around lines 70 - 77, The ResponseValidation field
on Config is currently accepted without being checked; ensure the config is
validated at construction by calling Config.Validate() (or equivalent validation
helper) from the server constructor (e.g., wherever a new server is created) and
return an error if validation fails so typos in ResponseValidation are rejected;
specifically, add a validation call that inspects Config.ResponseValidation via
Config.Validate() before using it in server initialization.
🧹 Nitpick comments (1)
api/v3/oasmiddleware/validator_test.go (1)

23-31: Swap to t.Context() here and at lines 62 and 98.

These test setups should use the test context instead of context.Background(). It ties the router lifecycle to the test harness and matches the pattern already used throughout the codebase.

Suggested refactor
-	router, err := oasmiddleware.NewValidationRouter(context.Background(), swagger, &oasmiddleware.ValidationRouterOpts{
+	router, err := oasmiddleware.NewValidationRouter(t.Context(), swagger, &oasmiddleware.ValidationRouterOpts{
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/v3/oasmiddleware/validator_test.go` around lines 23 - 31, Replace
context.Background() with the test's cancellable context by using t.Context()
when calling NewValidationRouter in this test; specifically update the
NewValidationRouter invocation that currently passes context.Background() (and
the two other occurrences in the same test where context.Background() is used)
so the router lifecycle is tied to the test harness. Ensure you only change the
context argument passed into oasmiddleware.NewValidationRouter and leave the
rest of the ValidationRouterOpts (e.g., DeleteServers) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@api/v3/server/server.go`:
- Around line 70-77: The ResponseValidation field on Config is currently
accepted without being checked; ensure the config is validated at construction
by calling Config.Validate() (or equivalent validation helper) from the server
constructor (e.g., wherever a new server is created) and return an error if
validation fails so typos in ResponseValidation are rejected; specifically, add
a validation call that inspects Config.ResponseValidation via Config.Validate()
before using it in server initialization.

---

Nitpick comments:
In `@api/v3/oasmiddleware/validator_test.go`:
- Around line 23-31: Replace context.Background() with the test's cancellable
context by using t.Context() when calling NewValidationRouter in this test;
specifically update the NewValidationRouter invocation that currently passes
context.Background() (and the two other occurrences in the same test where
context.Background() is used) so the router lifecycle is tied to the test
harness. Ensure you only change the context argument passed into
oasmiddleware.NewValidationRouter and leave the rest of the ValidationRouterOpts
(e.g., DeleteServers) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 60d78ef9-3cdd-4f53-97b8-98cd8d76ff64

📥 Commits

Reviewing files that changed from the base of the PR and between acff67d and b4020a0.

📒 Files selected for processing (8)
  • api/v3/oasmiddleware/validator.go
  • api/v3/oasmiddleware/validator_test.go
  • api/v3/server/server.go
  • app/config/config_test.go
  • app/config/server.go
  • cmd/server/main.go
  • config.example.yaml
  • openmeter/server/server.go

@rolosp
Copy link
Copy Markdown
Collaborator Author

rolosp commented May 5, 2026

Caution

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

⚠️ Outside diff range comments (1)

api/v3/server/server.go (1)> 70-77: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fail fast on invalid response-validation modes.
This new public knob is added to Config, but it still bypasses Config.Validate(). A typo that reaches this constructor can be treated as enabled downstream instead of rejecting the config.

🔧 Suggested fix

 func (c *Config) Validate() error {
 	var errs []error
+
+	if err := c.ResponseValidation.Mode.Validate(); err != nil {
+		errs = append(errs, err)
+	}
 
 	if c.BaseURL == "" {
 		errs = append(errs, errors.New("base URL is required"))
 	}

🤖 Prompt for AI Agents

Verify each finding against the current code and only fix it if needed.

In `@api/v3/server/server.go` around lines 70 - 77, The ResponseValidation field
on Config is currently accepted without being checked; ensure the config is
validated at construction by calling Config.Validate() (or equivalent validation
helper) from the server constructor (e.g., wherever a new server is created) and
return an error if validation fails so typos in ResponseValidation are rejected;
specifically, add a validation call that inspects Config.ResponseValidation via
Config.Validate() before using it in server initialization.

🧹 Nitpick comments (1)

api/v3/oasmiddleware/validator_test.go (1)> 23-31: Swap to t.Context() here and at lines 62 and 98.

These test setups should use the test context instead of context.Background(). It ties the router lifecycle to the test harness and matches the pattern already used throughout the codebase.

Suggested refactor

-	router, err := oasmiddleware.NewValidationRouter(context.Background(), swagger, &oasmiddleware.ValidationRouterOpts{
+	router, err := oasmiddleware.NewValidationRouter(t.Context(), swagger, &oasmiddleware.ValidationRouterOpts{

🤖 Prompt for AI Agents

Verify each finding against the current code and only fix it if needed.

In `@api/v3/oasmiddleware/validator_test.go` around lines 23 - 31, Replace
context.Background() with the test's cancellable context by using t.Context()
when calling NewValidationRouter in this test; specifically update the
NewValidationRouter invocation that currently passes context.Background() (and
the two other occurrences in the same test where context.Background() is used)
so the router lifecycle is tied to the test harness. Ensure you only change the
context argument passed into oasmiddleware.NewValidationRouter and leave the
rest of the ValidationRouterOpts (e.g., DeleteServers) unchanged.

🤖 Prompt for all review comments with AI agents

Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@api/v3/server/server.go`:
- Around line 70-77: The ResponseValidation field on Config is currently
accepted without being checked; ensure the config is validated at construction
by calling Config.Validate() (or equivalent validation helper) from the server
constructor (e.g., wherever a new server is created) and return an error if
validation fails so typos in ResponseValidation are rejected; specifically, add
a validation call that inspects Config.ResponseValidation via Config.Validate()
before using it in server initialization.

---

Nitpick comments:
In `@api/v3/oasmiddleware/validator_test.go`:
- Around line 23-31: Replace context.Background() with the test's cancellable
context by using t.Context() when calling NewValidationRouter in this test;
specifically update the NewValidationRouter invocation that currently passes
context.Background() (and the two other occurrences in the same test where
context.Background() is used) so the router lifecycle is tied to the test
harness. Ensure you only change the context argument passed into
oasmiddleware.NewValidationRouter and leave the rest of the ValidationRouterOpts
(e.g., DeleteServers) unchanged.

ℹ️ Review info

No change needed. Configuration.Validate() is already
called at startup in cmd/server/main.go:65, which recursively runs
Server.Validate() (app/config/config.go:163), which in turn runs
ResponseValidation.Mode.Validate() (app/config/server.go:68). A typo
like mode: unstabel is rejected before the server is constructed,
with server: invalid response validation mode (allowed: off,
unstable, all). Adding another check at the v3 constructor would be
redundant.

Comment thread api/v3/oasmiddleware/validator.go Outdated
@rolosp rolosp force-pushed the feat/oapi-codegen-response-validator branch from ddab792 to 43a4ecb Compare May 5, 2026 13:54
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/v3/server/server.go`:
- Around line 393-395: The ResponseValidationErrorHook currently logs the raw
validation error (err) via slog.WarnContext which can leak sensitive response
fields; update the handler for ResponseValidationErrorHook to log only a
sanitized summary (HTTP method, path, response status or a short error
category/string) at warning level and remove slog.Any("error", err) from the
warn entry, and if you need full details send the original err to
slog.DebugContext or behind an explicit opt-in/redaction helper so raw error
contents are not written to standard warning logs.
🪄 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: d6df4e6d-29d4-4571-9fce-754c8646a304

📥 Commits

Reviewing files that changed from the base of the PR and between ddab792 and 43a4ecb.

📒 Files selected for processing (8)
  • api/v3/oasmiddleware/validator.go
  • api/v3/oasmiddleware/validator_test.go
  • api/v3/server/server.go
  • app/config/config_test.go
  • app/config/server.go
  • cmd/server/main.go
  • config.example.yaml
  • openmeter/server/server.go
✅ Files skipped from review due to trivial changes (2)
  • app/config/config_test.go
  • api/v3/oasmiddleware/validator_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/config/server.go
  • config.example.yaml
  • openmeter/server/server.go
  • api/v3/oasmiddleware/validator.go

Comment thread api/v3/server/server.go
Comment on lines +393 to +395
ResponseValidationErrorHook: func(err error, r *http.Request) {
slog.WarnContext(r.Context(), "response validation failed", slog.String("method", r.Method), slog.String("path", r.URL.Path), slog.Any("error", err))
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Please avoid logging raw validation errors here (possible sensitive response data leak).

err from response validation can include offending response fields; logging it verbatim at warning level can leak PII/secrets into logs. Consider logging a sanitized summary (method/path/status + error category) and keep raw details behind debug or explicit opt-in.

Suggested minimal hardening
-				ResponseValidationErrorHook: func(err error, r *http.Request) {
-					slog.WarnContext(r.Context(), "response validation failed", slog.String("method", r.Method), slog.String("path", r.URL.Path), slog.Any("error", err))
-				},
+				ResponseValidationErrorHook: func(err error, r *http.Request) {
+					slog.WarnContext(
+						r.Context(),
+						"response validation failed",
+						slog.String("method", r.Method),
+						slog.String("path", r.URL.Path),
+						slog.String("error_type", fmt.Sprintf("%T", err)),
+					)
+					// Optional (debug-only): log full details in controlled environments.
+					// slog.DebugContext(r.Context(), "response validation details", slog.Any("error", err))
+				},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/v3/server/server.go` around lines 393 - 395, The
ResponseValidationErrorHook currently logs the raw validation error (err) via
slog.WarnContext which can leak sensitive response fields; update the handler
for ResponseValidationErrorHook to log only a sanitized summary (HTTP method,
path, response status or a short error category/string) at warning level and
remove slog.Any("error", err) from the warn entry, and if you need full details
send the original err to slog.DebugContext or behind an explicit
opt-in/redaction helper so raw error contents are not written to standard
warning logs.

@rolosp rolosp requested a review from mark-vass-konghq May 5, 2026 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/misc Miscellaneous changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants