Skip to content

feat: implement OpenAPI discriminator support for oneOf/anyOf#4

Merged
nic-6443 merged 2 commits intomainfrom
feat/discriminator-support-v2
Apr 21, 2026
Merged

feat: implement OpenAPI discriminator support for oneOf/anyOf#4
nic-6443 merged 2 commits intomainfrom
feat/discriminator-support-v2

Conversation

@jarvis9443
Copy link
Copy Markdown
Contributor

@jarvis9443 jarvis9443 commented Apr 21, 2026

Summary

Adds discriminator support to the JSON body validation path, fixing false positives/negatives when oneOf/anyOf branches aren't mutually exclusive (no additionalProperties: false).

Changes

lib/resty/openapi_validator/body.lua

  • Added resolve_discriminator(schema, body_data) — reads the discriminator property from the request body, finds the matching oneOf/anyOf branch, and returns it for validation instead of the full composite schema
  • Uses mapping-based lookup via _ref annotations when discriminator.mapping exists, falls back to enum-based matching
  • Walks into allOf sub-schemas when searching for matching branches

lib/resty/openapi_validator/refs.lua

  • Annotates resolved $ref nodes with _ref field preserving the original ref path, so discriminator mapping can match branches after ref resolution

t/conformance/test_validation_discriminator.lua

  • Updated existing test to expect success (discriminator now correctly selects the matching branch)
  • Added tests for: valid objB body, unknown discriminator value, missing discriminator property

Summary by CodeRabbit

  • New Features

    • Added discriminator support for JSON body validation to resolve correct schema branches in oneOf/anyOf scenarios.
    • Enhanced request body validation for optional and required properties.
    • Improved path parameter validation across various data types and constraints.
  • Tests

    • Added comprehensive conformance tests covering discriminator resolution, request validation edge cases, and path parameter handling.

jarvis9443 and others added 2 commits April 21, 2026 18:04
- issue201: duplicate path template with different methods
- issue639: request body decode edge cases (empty objects, optional body,
  additional properties, nested validation)
- issue707: path parameter edge cases (type coercion, min/max constraints,
  multiple params)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add resolve_discriminator() in body.lua that selects the correct
oneOf/anyOf branch based on the discriminator property value, avoiding
false positives/negatives when branches aren't mutually exclusive.

The ref resolver now annotates resolved nodes with _ref so the
discriminator mapping can match branches by their original $ref path.
Falls back to enum-based matching when mapping is absent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

📝 Walkthrough

Walkthrough

This pull request adds discriminator resolution support for JSON body validation in OpenAPI specifications. When a schema contains a discriminator, the validator now resolves the correct oneOf/anyOf branch using the discriminator property value before validation. Supporting infrastructure changes and conformance tests are included.

Changes

Cohort / File(s) Summary
Discriminator Resolution Implementation
lib/resty/openapi_validator/body.lua
Added resolve_discriminator helper function that reads the discriminator property from request body data, matches it against oneOf/anyOf branches via mapping or enum fallback (including allOf-wrapped refs), and returns the selected branch schema for validation. Missing discriminator property or no matching branch returns an error.
Ref Tracking Enhancement
lib/resty/openapi_validator/refs.lua
Assigned _ref field to resolved ref targets after deep-copy and recursive resolution, enabling tracking of original reference paths.
Conformance Tests
t/conformance/test_issue201.lua, t/conformance/test_issue639.lua, t/conformance/test_issue707.lua, t/conformance/test_validation_discriminator.lua
Added new test files validating path parameter edge cases, optional/required request body handling, and path parameter type constraints. Updated discriminator test to expect success with proper discriminator support and validate error cases for missing/unknown discriminator values.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
E2e Test Quality Review ⚠️ Warning PR fails critical E2E test coverage requirements with gaps in anyOf support, enum-based fallback testing, and unrelated test files. Add test cases for anyOf with discriminator, enum-based fallback matching, validate strict_spec test, and move unrelated test files to separate commits.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: implement OpenAPI discriminator support for oneOf/anyOf' directly and clearly summarizes the main change: adding discriminator support for oneOf/anyOf validation, which is the central focus of the PR across multiple files.
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.
Security Check ✅ Passed The discriminator implementation does not contain any sensitive data logging, database operations, authorization bypasses, TLS issues, secret exposures, or cross-resource access violations.

✏️ 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/discriminator-support-v2

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

🧹 Nitpick comments (3)
t/conformance/test_issue707.lua (2)

56-57: Preserve compile error details in the assertion message.

Line 57 currently hides the underlying compile failure reason, which makes debugging harder when this test fails in CI.

🛠️ Proposed improvement
-local v = ov.compile(spec)
-assert(v, "compile failed")
+local v, compile_err = ov.compile(spec)
+assert(v, "compile failed: " .. tostring(compile_err))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@t/conformance/test_issue707.lua` around lines 56 - 57, The assertion hides
the underlying compile error; change the call to capture the compile error
returned by ov.compile and include it in the assertion message (e.g., call
ov.compile as local v, err = ov.compile(spec) and update the assert to include
tostring(err) so the failure message shows the real compile error), referring to
the ov.compile(spec) call and the variable v used in the current assertion.

102-124: Add an explicit maximum-boundary success case (/bounded/100).

Line 118 verifies “above maximum” failure, but there’s no direct assertion that the exact maximum endpoint passes. Adding it makes boundary coverage complete.

➕ Proposed test addition
 T.describe("issue707: bounded path param at minimum (valid)", function()
     local ok, err = v:validate_request({
         method = "GET",
         path = "/bounded/1",
     })
     T.ok(ok, "should pass: " .. tostring(err))
 end)

+T.describe("issue707: bounded path param at maximum (valid)", function()
+    local ok, err = v:validate_request({
+        method = "GET",
+        path = "/bounded/100",
+    })
+    T.ok(ok, "should pass: " .. tostring(err))
+end)
+
 T.describe("issue707: bounded path param below minimum (fail)", function()
     local ok, err = v:validate_request({
         method = "GET",
         path = "/bounded/0",
     })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@t/conformance/test_issue707.lua` around lines 102 - 124, Add a new test case
mirroring the existing "issue707: bounded path param at minimum (valid)" block
but for the maximum boundary: call v:validate_request with method = "GET" and
path = "/bounded/100" and assert T.ok(ok, "should pass: " .. tostring(err)) to
ensure the exact maximum value passes; place this T.describe block alongside the
other bounded tests so it covers the upper-bound success case for the same
validation logic.
lib/resty/openapi_validator/body.lua (1)

399-415: Consider returning early or skipping validation on discriminator error.

When disc_err is set, the error is recorded but validation continues against the original composite schema. This may produce confusing additional errors (e.g., "does not match any schema" from the discriminator, plus oneOf validation errors).

Consider either returning early or skipping JSON schema validation when discriminator resolution fails:

♻️ Option: Return early on discriminator error
         local effective_schema = schema
         local disc_schema, disc_err = resolve_discriminator(schema, body_data)
         if disc_err then
             tab_insert(errs, errors.new("body", nil, disc_err))
+            return false, errs
         elseif disc_schema then
             effective_schema = disc_schema
         end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/resty/openapi_validator/body.lua` around lines 399 - 415, When
resolve_discriminator(schema, body_data) sets disc_err, stop further JSON Schema
validation to avoid confusing follow-up errors: after inserting the
discriminator error into errs (where disc_err is handled), immediately return
(or skip calling get_validator/validator) so no validation is run against the
original composite schema; modify the logic around resolve_discriminator,
effective_schema, get_validator and validator in this function to short-circuit
on disc_err (with the existing errors.new("body", nil, disc_err) insertion)
instead of proceeding to run validator(body_data).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/resty/openapi_validator/body.lua`:
- Around line 399-415: When resolve_discriminator(schema, body_data) sets
disc_err, stop further JSON Schema validation to avoid confusing follow-up
errors: after inserting the discriminator error into errs (where disc_err is
handled), immediately return (or skip calling get_validator/validator) so no
validation is run against the original composite schema; modify the logic around
resolve_discriminator, effective_schema, get_validator and validator in this
function to short-circuit on disc_err (with the existing errors.new("body", nil,
disc_err) insertion) instead of proceeding to run validator(body_data).

In `@t/conformance/test_issue707.lua`:
- Around line 56-57: The assertion hides the underlying compile error; change
the call to capture the compile error returned by ov.compile and include it in
the assertion message (e.g., call ov.compile as local v, err = ov.compile(spec)
and update the assert to include tostring(err) so the failure message shows the
real compile error), referring to the ov.compile(spec) call and the variable v
used in the current assertion.
- Around line 102-124: Add a new test case mirroring the existing "issue707:
bounded path param at minimum (valid)" block but for the maximum boundary: call
v:validate_request with method = "GET" and path = "/bounded/100" and assert
T.ok(ok, "should pass: " .. tostring(err)) to ensure the exact maximum value
passes; place this T.describe block alongside the other bounded tests so it
covers the upper-bound success case for the same validation logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eae90cbc-713c-419d-916f-4cacf0e28c8d

📥 Commits

Reviewing files that changed from the base of the PR and between 04a4a1f and df87529.

📒 Files selected for processing (6)
  • lib/resty/openapi_validator/body.lua
  • lib/resty/openapi_validator/refs.lua
  • t/conformance/test_issue201.lua
  • t/conformance/test_issue639.lua
  • t/conformance/test_issue707.lua
  • t/conformance/test_validation_discriminator.lua

@nic-6443 nic-6443 merged commit fdaf82b into main Apr 21, 2026
3 checks passed
@nic-6443 nic-6443 deleted the feat/discriminator-support-v2 branch April 21, 2026 10:23
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