Skip to content

OpenAPI-Validator: Split request and response validation#2903

Open
predic8 wants to merge 5 commits intomasterfrom
openapi-validator-split-validation
Open

OpenAPI-Validator: Split request and response validation#2903
predic8 wants to merge 5 commits intomasterfrom
openapi-validator-split-validation

Conversation

@predic8
Copy link
Copy Markdown
Member

@predic8 predic8 commented Apr 8, 2026

Summary by CodeRabbit

  • Refactor

    • Reworked OpenAPI validation into a two-step flow with reusable validation plans per exchange, separating request and response validation and simplifying validators for clearer control flow.
  • Tests

    • Added tests verifying prepared validation plans work for request checks and are stored for response validation.
  • Chores

    • Added Apache 2.0 license headers to a couple of source files.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5f27b945-45b8-436a-a08b-9277f2fd8cd9

📥 Commits

Reviewing files that changed from the base of the PR and between 70cebfc and 2cff22f.

📒 Files selected for processing (2)
  • core/src/main/java/com/predic8/membrane/core/transport/http/UnableToTunnelException.java
  • core/src/test/java/com/predic8/membrane/test/TestAppender.java
✅ Files skipped from review due to trivial changes (2)
  • core/src/test/java/com/predic8/membrane/test/TestAppender.java
  • core/src/main/java/com/predic8/membrane/core/transport/http/UnableToTunnelException.java

📝 Walkthrough

Walkthrough

Validation was split into two phases: prepareValidation(Request) now returns a ValidationPlan encapsulating path/method resolution or an error; validateRequest(...) and validateResponse(...) delegate to the plan. Operation validation was moved to a per-method OperationValidator.resolve(...). Interceptor caches the plan per exchange.

Changes

Cohort / File(s) Summary
Validation plan & core refactor
core/src/main/java/com/predic8/membrane/core/openapi/OpenAPIValidator.java
Added prepareValidation(Request) and nested ValidationPlan (resolved/error factories, validateRequest, validateResponse); existing validate(...)/validateResponse(...) delegate to the plan. Path/method matching now produces a resolved plan or an error plan.
Interceptor integration & caching
core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptor.java
Introduced OPENAPI_VALIDATOR exchange property and per-exchange caching of ValidationPlan; request/response flows now obtain and use the plan; removed IOException from private validate methods and added helpers to build operation Request<?>.
Operation validation split & resolution
core/src/main/java/com/predic8/membrane/core/openapi/validators/OperationValidator.java
Refactored to resolve(OpenAPI, method, PathItem) returning an OperationValidator bound to a specific operation; split validateRequest(...) and validateResponse(...); removed old validateOperation(...) and tightened imports.
Tests updated/added
core/src/test/java/com/predic8/membrane/core/openapi/OpenAPIValidatorTest.java, core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptorTest.java
Added test verifying prepareValidation(...).validateRequest(...) and test ensuring a ValidationPlan is stored on the exchange for response validation.
License headers (tests/util)
core/src/main/java/com/predic8/membrane/core/transport/http/UnableToTunnelException.java, core/src/test/java/com/predic8/membrane/test/TestAppender.java
Added Apache-2.0 license headers to files; no behavioral changes.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client as Client
participant Interceptor as OpenAPIInterceptor
participant Exchange as Exchange
participant Validator as OpenAPIValidator
participant OpValidator as OperationValidator

Client->>Interceptor: send request
Interceptor->>Exchange: create exchange / store state
Interceptor->>Validator: prepareValidation(request)
Validator-->>Interceptor: ValidationPlan (resolved or error)
Interceptor->>Exchange: store OPENAPI_VALIDATOR = ValidationPlan
Interceptor->>ValidationPlan: validateRequest(request)
ValidationPlan->>OpValidator: validateRequest(ctx, request)
OpValidator-->>ValidationPlan: ValidationErrors
ValidationPlan-->>Interceptor: return ValidationErrors
Note right of Interceptor: on response
Interceptor->>Exchange: retrieve OPENAPI_VALIDATOR
Interceptor->>ValidationPlan: validateResponse(response)
ValidationPlan->>OpValidator: validateResponse(ctx, response)
OpValidator-->>ValidationPlan: ValidationErrors
ValidationPlan-->>Interceptor: return ValidationErrors

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • predic8
  • rrayst

Poem

🐇 I hopped through specs and matched each path,

Carried a plan to avoid a wrath.
Cached it softly in the exchange's nest,
Requests and responses put to rest.
— a rabbit, pleased with tidy tests 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 pull request title accurately describes the main refactoring: splitting request and response validation logic in OpenAPIValidator through a two-step flow with a new ValidationPlan class.

✏️ 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 openapi-validator-split-validation

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

🧹 Nitpick comments (2)
core/src/main/java/com/predic8/membrane/core/openapi/validators/OperationValidator.java (1)

49-49: Move inline split TODO to a tracked issue.

Line 49 leaves implementation intent inside core validation flow. Prefer referencing a tracked issue ID (or removing the TODO after implementation) to keep this path clean.

I can draft a small follow-up patch that replaces this inline TODO with an issue-linked comment and scaffolds the split points.

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

In
`@core/src/main/java/com/predic8/membrane/core/openapi/validators/OperationValidator.java`
at line 49, Replace the inline "// `@TODO` Split till here." comment in
OperationValidator (class OperationValidator in the shown diff) with a
tracked-issue reference or remove it: create or reference a ticket (e.g.,
ISSUE-1234 or a GitHub issue URL) describing the intended split and then update
the comment to something like "See ISSUE-1234: planned split of validation flow"
and optionally add brief scaffold markers (e.g., "split-point A/B") near the
current location to guide the future refactor; ensure the updated comment
mentions OperationValidator so it's discoverable.
core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptor.java (1)

203-224: Complete split by reusing a single OpenAPIValidator per exchange.

Right now Line 217 and Line 224 create separate validator instances. Given the TODO plan, storing/reusing one validator in Exchange will better support split request/response validation and avoid duplicate initialization work.

Proposed refactor sketch
+ private static final String OPENAPI_VALIDATOR = "membrane.openapi.validator";
+
+ private OpenAPIValidator getOrCreateValidator(OpenAPIRecord rec, Exchange exc) {
+     OpenAPIValidator validator = exc.getProperty(OPENAPI_VALIDATOR, OpenAPIValidator.class);
+     if (validator == null) {
+         validator = new OpenAPIValidator(router.getConfiguration().getUriFactory(), rec);
+         exc.setProperty(OPENAPI_VALIDATOR, validator);
+     }
+     return validator;
+ }
...
- return new OpenAPIValidator(router.getConfiguration().getUriFactory(), rec).validate(getOpenapiValidatorRequest(exc));
+ return getOrCreateValidator(rec, exc).validate(getOpenapiValidatorRequest(exc));
...
- return new OpenAPIValidator(router.getConfiguration().getUriFactory(), rec).validateResponse(getOpenapiValidatorRequest(exc), getOpenapiValidatorResponse(exc));
+ return getOrCreateValidator(rec, exc).validateResponse(getOpenapiValidatorRequest(exc), getOpenapiValidatorResponse(exc));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptor.java`
around lines 203 - 224, Both validateRequest and validateResponse currently
instantiate separate OpenAPIValidator instances; change them to create a single
OpenAPIValidator per Exchange and store it under the key
"membrane.openapi.validator" so the same validator is reused for request and
response validation. Implement logic in validateRequest: if shouldValidate(...,
REQUESTS) create and store new
OpenAPIValidator(router.getConfiguration().getUriFactory(), rec) on the Exchange
and call validate(getOpenapiValidatorRequest(exc)); in validateResponse:
retrieve the validator from the Exchange (and if validateResponse is true but no
validator exists, create and store one using the same constructor), then call
validateResponse(getOpenapiValidatorRequest(exc),
getOpenapiValidatorResponse(exc)); use the existing methods OpenAPIValidator,
getOpenapiValidatorRequest, getOpenapiValidatorResponse, and shouldValidate, and
ensure the Exchange property key is "membrane.openapi.validator".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptor.java`:
- Around line 203-224: Both validateRequest and validateResponse currently
instantiate separate OpenAPIValidator instances; change them to create a single
OpenAPIValidator per Exchange and store it under the key
"membrane.openapi.validator" so the same validator is reused for request and
response validation. Implement logic in validateRequest: if shouldValidate(...,
REQUESTS) create and store new
OpenAPIValidator(router.getConfiguration().getUriFactory(), rec) on the Exchange
and call validate(getOpenapiValidatorRequest(exc)); in validateResponse:
retrieve the validator from the Exchange (and if validateResponse is true but no
validator exists, create and store one using the same constructor), then call
validateResponse(getOpenapiValidatorRequest(exc),
getOpenapiValidatorResponse(exc)); use the existing methods OpenAPIValidator,
getOpenapiValidatorRequest, getOpenapiValidatorResponse, and shouldValidate, and
ensure the Exchange property key is "membrane.openapi.validator".

In
`@core/src/main/java/com/predic8/membrane/core/openapi/validators/OperationValidator.java`:
- Line 49: Replace the inline "// `@TODO` Split till here." comment in
OperationValidator (class OperationValidator in the shown diff) with a
tracked-issue reference or remove it: create or reference a ticket (e.g.,
ISSUE-1234 or a GitHub issue URL) describing the intended split and then update
the comment to something like "See ISSUE-1234: planned split of validation flow"
and optionally add brief scaffold markers (e.g., "split-point A/B") near the
current location to guide the future refactor; ensure the updated comment
mentions OperationValidator so it's discoverable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e8288c48-2b86-454c-a677-aeb0eac98ec4

📥 Commits

Reviewing files that changed from the base of the PR and between cd97d5d and b685025.

📒 Files selected for processing (3)
  • core/src/main/java/com/predic8/membrane/core/openapi/OpenAPIValidator.java
  • core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptor.java
  • core/src/main/java/com/predic8/membrane/core/openapi/validators/OperationValidator.java

@christiangoerdes christiangoerdes marked this pull request as draft April 10, 2026 06:36
christiangoerdes and others added 2 commits April 10, 2026 12:05
…es. Improve handling of validation logic by introducing ValidationPlan class to streamline request and response validations. Update related tests to reflect changes.
@christiangoerdes
Copy link
Copy Markdown
Collaborator

/ok-to-test

@membrane-ci-server
Copy link
Copy Markdown

This pull request needs "/ok-to-test" from an authorized committer.

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)
core/src/main/java/com/predic8/membrane/core/openapi/validators/OperationValidator.java (1)

71-79: ⚠️ Potential issue | 🟠 Major

Support for valid OpenAPI HTTP methods is incomplete.

The getOperation(...) method currently omits HEAD, OPTIONS, and TRACE—all of which are explicitly supported in the OpenAPI 3.0.2 specification and available in the PathItem class via getHead(), getOptions(), and getTrace() methods. Specs declaring these operations will be rejected as method-not-allowed even when valid.

Proposed fix
 private static Operation getOperation(String method, PathItem pi) throws MethodNotAllowException {
     return switch (method.toUpperCase()) {
         case "GET" -> pi.getGet();
         case "POST" -> pi.getPost();
         case "PUT" -> pi.getPut();
         case "DELETE" -> pi.getDelete();
         case "PATCH" -> pi.getPatch();
+        case "HEAD" -> pi.getHead();
+        case "OPTIONS" -> pi.getOptions();
+        case "TRACE" -> pi.getTrace();
         default -> throw new MethodNotAllowException();
     };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/src/main/java/com/predic8/membrane/core/openapi/validators/OperationValidator.java`
around lines 71 - 79, The getOperation method rejects valid OpenAPI methods
because it only handles GET/POST/PUT/DELETE/PATCH; update the
getOperation(String method, PathItem pi) function to also handle "HEAD",
"OPTIONS", and "TRACE" by mapping them to pi.getHead(), pi.getOptions(), and
pi.getTrace() respectively, keeping the existing behavior of throwing
MethodNotAllowException for unknown methods; locate the getOperation method and
add those cases to the switch (using method.toUpperCase()) so specs with
head/options/trace operations are accepted.
🤖 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
`@core/src/main/java/com/predic8/membrane/core/openapi/validators/OperationValidator.java`:
- Around line 71-79: The getOperation method rejects valid OpenAPI methods
because it only handles GET/POST/PUT/DELETE/PATCH; update the
getOperation(String method, PathItem pi) function to also handle "HEAD",
"OPTIONS", and "TRACE" by mapping them to pi.getHead(), pi.getOptions(), and
pi.getTrace() respectively, keeping the existing behavior of throwing
MethodNotAllowException for unknown methods; locate the getOperation method and
add those cases to the switch (using method.toUpperCase()) so specs with
head/options/trace operations are accepted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d882bfd9-9217-49a3-b7c8-08432ac55bbb

📥 Commits

Reviewing files that changed from the base of the PR and between b685025 and 70cebfc.

📒 Files selected for processing (5)
  • core/src/main/java/com/predic8/membrane/core/openapi/OpenAPIValidator.java
  • core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptor.java
  • core/src/main/java/com/predic8/membrane/core/openapi/validators/OperationValidator.java
  • core/src/test/java/com/predic8/membrane/core/openapi/OpenAPIValidatorTest.java
  • core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptorTest.java
✅ Files skipped from review due to trivial changes (1)
  • core/src/test/java/com/predic8/membrane/core/openapi/OpenAPIValidatorTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIInterceptor.java
  • core/src/main/java/com/predic8/membrane/core/openapi/OpenAPIValidator.java

@christiangoerdes christiangoerdes marked this pull request as ready for review April 10, 2026 10:49
@christiangoerdes
Copy link
Copy Markdown
Collaborator

@predic8

@predic8
Copy link
Copy Markdown
Member Author

predic8 commented Apr 10, 2026

@christiangoerdes looks good. Could you take care about the ⚠️ Outside diff range comments (1) too?

Is there a test for:

  • Request validation no, Response validation yes
  • Request yes, Response no
    ?

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.

2 participants