feat: oapi response validator#4283
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds configurable OpenAPI response validation: new server config and modes ( ChangesResponse Validation
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Comment |
There was a problem hiding this comment.
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 winFail fast on invalid response-validation modes.
This new public knob is added to
Config, but it still bypassesConfig.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 tot.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
📒 Files selected for processing (8)
api/v3/oasmiddleware/validator.goapi/v3/oasmiddleware/validator_test.goapi/v3/server/server.goapp/config/config_test.goapp/config/server.gocmd/server/main.goconfig.example.yamlopenmeter/server/server.go
No change needed. Configuration.Validate() is already |
ddab792 to
43a4ecb
Compare
There was a problem hiding this comment.
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
📒 Files selected for processing (8)
api/v3/oasmiddleware/validator.goapi/v3/oasmiddleware/validator_test.goapi/v3/server/server.goapp/config/config_test.goapp/config/server.gocmd/server/main.goconfig.example.yamlopenmeter/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
43a4ecb to
0f8dc71
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (1)
api/v3/server/server.go (1)
393-395:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPrevious review note still applies: raw error logged here may leak sensitive response data.
slog.Any("error", err)fromopenapi3filter.ValidateResponsecan include offending response field values. The past review comment flagged this and suggested logging only method/path/status + error type at warn level, with raw details behind debug.🤖 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 error (slog.Any("error", err)) which can leak sensitive response fields from openapi3filter.ValidateResponse; change the hook to log only method, path and response status plus the error type at warn (e.g., slog.String("error_type", fmt.Sprintf("%T", err))) and move the full/error details into a debug log entry (slog.DebugContext) so raw response contents are not emitted at warn; update the ResponseValidationErrorHook handler to use r.Method, r.URL.Path, r.Context(), the response status if available, and fmt.Sprintf("%T", err) for the warn log and log the original err at debug level.
🧹 Nitpick comments (1)
api/v3/server/server.go (1)
418-440: 💤 Low value
buildResponseValidationRouteFilter— a switch would make the intent clearer.The current
if cfg.Mode != config.ResponseValidationModeUnstable { return nil }is correct today (only two enabled modes), but it implicitly relies on "anything non-unstable gets nil" which could silently misbehave if a new enabled mode is added later.A switch with explicit cases is more readable and easier to extend:
♻️ Suggested refactor
func buildResponseValidationRouteFilter(cfg config.ResponseValidationConfig) func(*routers.Route) bool { - if cfg.Mode != config.ResponseValidationModeUnstable { - return nil - } - return func(route *routers.Route) bool { + switch cfg.Mode { + case config.ResponseValidationModeAll: + return nil + case config.ResponseValidationModeUnstable: + return func(route *routers.Route) bool { + if route.Operation == nil { + return false + } + extVal, ok := route.Operation.Extensions["x-unstable"] + if !ok { + return false + } + switch v := extVal.(type) { + case json.RawMessage: + var b bool + return json.Unmarshal(v, &b) == nil && b + case bool: + return v + default: + return false + } + } + default: + return nil + } +} + +// (remove old closing brace below) - if route.Operation == nil { - return false - } - extVal, ok := route.Operation.Extensions["x-unstable"] - if !ok { - return false - } - switch v := extVal.(type) { - case json.RawMessage: - var b bool - return json.Unmarshal(v, &b) == nil && b - case bool: - return v - default: - return false - } - } }🤖 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 418 - 440, Replace the initial if-check in buildResponseValidationRouteFilter with an explicit switch on cfg.Mode (e.g., switch cfg.Mode { case config.ResponseValidationModeUnstable: /* return closure */ default: return nil }) so only the explicit enabled case(s) return the route-filter closure; keep the existing closure logic (checking route.Operation, inspecting route.Operation.Extensions["x-unstable"] and the json.RawMessage/bool handling) unchanged and place it under the ResponseValidationModeUnstable case to make intent explicit and easier to extend.
🤖 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.
Duplicate comments:
In `@api/v3/server/server.go`:
- Around line 393-395: The ResponseValidationErrorHook currently logs the raw
error (slog.Any("error", err)) which can leak sensitive response fields from
openapi3filter.ValidateResponse; change the hook to log only method, path and
response status plus the error type at warn (e.g., slog.String("error_type",
fmt.Sprintf("%T", err))) and move the full/error details into a debug log entry
(slog.DebugContext) so raw response contents are not emitted at warn; update the
ResponseValidationErrorHook handler to use r.Method, r.URL.Path, r.Context(),
the response status if available, and fmt.Sprintf("%T", err) for the warn log
and log the original err at debug level.
---
Nitpick comments:
In `@api/v3/server/server.go`:
- Around line 418-440: Replace the initial if-check in
buildResponseValidationRouteFilter with an explicit switch on cfg.Mode (e.g.,
switch cfg.Mode { case config.ResponseValidationModeUnstable: /* return closure
*/ default: return nil }) so only the explicit enabled case(s) return the
route-filter closure; keep the existing closure logic (checking route.Operation,
inspecting route.Operation.Extensions["x-unstable"] and the json.RawMessage/bool
handling) unchanged and place it under the ResponseValidationModeUnstable case
to make intent explicit and easier to extend.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2fa14188-ec5a-4feb-96f5-ad722f30e4ef
📒 Files selected for processing (8)
api/v3/oasmiddleware/validator.goapi/v3/oasmiddleware/validator_test.goapi/v3/server/server.goapp/config/config_test.goapp/config/server.gocmd/server/main.goconfig.example.yamlopenmeter/server/server.go
🚧 Files skipped from review as they are similar to previous changes (1)
- api/v3/oasmiddleware/validator.go
0f8dc71 to
2e720c1
Compare
2e720c1 to
f1f99a7
Compare
f1f99a7 to
ad37c2d
Compare
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:
The mode enum mirrors the existing AutoMigrate pattern in app/config/postgres.go.
Notes for reviewer
rollout target. all is fine for staging but watch memory on large list pages.
as boolean false by YAML 1.1.
fields. Acceptable for observability but worth knowing.
Summary by CodeRabbit
New Features
Configuration
Tests