Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f8fd185
feat: add function to remove discriminator keywords from schema
harshit078 Apr 6, 2026
e54fc2f
Merge branch 'main' into fix-ajv-validation-discrimator
harshit078 Apr 6, 2026
636419c
Merge branch 'main' into fix-ajv-validation-discrimator
harshit078 Apr 6, 2026
c68873f
feat: integrated discriminator removal in schema validation process
harshit078 Apr 8, 2026
27817b6
Merge branch 'main' into fix-ajv-validation-discrimator
harshit078 Apr 8, 2026
e6378dd
feat: added tests for remove Discriminators function
harshit078 Apr 8, 2026
e999529
feat: added changeset
harshit078 Apr 8, 2026
24df583
Merge branch 'main' into fix-ajv-validation-discrimator
harshit078 Apr 8, 2026
1bc5103
Merge branch 'main' into fix-ajv-validation-discrimator
harshit078 Apr 9, 2026
ae9fbf6
fix: update from major to patch
harshit078 Apr 10, 2026
0063e79
Merge branch 'main' into fix-ajv-validation-discrimator
harshit078 Apr 10, 2026
c690a69
Merge branch 'main' into fix-ajv-validation-discrimator
harshit078 Apr 10, 2026
d7ca08e
fix: cursor comment
harshit078 Apr 10, 2026
a149430
revert: old logic of different discriminators
harshit078 Apr 13, 2026
18d93c5
fix: updated .md file
harshit078 Apr 13, 2026
dd7d0e0
fix: updated schema checker and made discriminator as false
harshit078 Apr 13, 2026
8c824f0
fix: updated tests and changeset
harshit078 Apr 13, 2026
467a7f4
fix: address cursor commentes
harshit078 Apr 13, 2026
f2da64e
Merge branch 'main' into fix-ajv-validation-discrimator
harshit078 Apr 14, 2026
1bd8c4f
Merge branch 'main' into fix-ajv-validation-discrimator
harshit078 Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/sixty-falcons-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@redocly/respect-core": patch
"@redocly/openapi-core": patch
---

Fixed AJV validation error when schemas use a discriminator property with complex constraint patterns like `allOf + not`.AJV's `discriminator: true` option requires every discriminator property to have a direct `const` or `enum`, which is incompatible with valid OpenAPI schemas that use composition patterns. Disabled AJV's discriminator keyword in both the respect schema checker and lint example validation, and removed the silent error swallowing in `validateExample` that was skipping validation for schemas with discriminators.
2 changes: 1 addition & 1 deletion packages/core/src/rules/ajv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function getAjv(resolve: ResolveFn, dialect: AjvDialect): AnyAjv {
strictSchema: false,
inlineRefs: false,
validateSchema: false,
discriminator: true,
discriminator: false,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Disabling discriminator breaks oneOf validation for overlapping schemas

High Severity

Setting discriminator: false globally disables AJV's discriminator-based schema selection for all oneOf/anyOf schemas. When enabled, AJV uses the discriminator property value to pick exactly one sub-schema to validate against. When disabled, AJV falls back to trying all sub-schemas and requires exactly one to match for oneOf. For the very common OpenAPI pattern where sub-schemas share overlapping properties and rely on the discriminator to disambiguate, multiple sub-schemas may now match, causing oneOf validation to fail on previously valid data. This is a regression for any schema using discriminator with overlapping oneOf branches.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1bd8c4f. Configure here.

allowUnionTypes: true,
validateFormats: true,
passContext: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ describe('no-invalid-media-type-examples', () => {
`);
});

it('should not report if allOf used with discriminator', async () => {
it('should validate example against schema with allOf and discriminator', async () => {
const document = parseYamlToDocument(
outdent`
openapi: 3.0.0
Expand Down
4 changes: 0 additions & 4 deletions packages/core/src/rules/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,6 @@ export function validateExample(
}
}
} catch (e) {
if (e.message === 'discriminator: requires oneOf or anyOf composite keyword') {
return;
}

report({
message: `Example validation errored: ${e.message}.`,
location: parentLocation.child('schema'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,118 @@ describe('checkSchema', () => {
]);
});

it('should pass schema check for discriminator with allOf + not pattern', () => {
const discriminatorDescriptionOperation = {
...descriptionOperation,
responses: {
'200': {
description: 'successful operation',
content: {
'application/json': {
schema: {
oneOf: [
{
type: 'object',
properties: {
method: {
allOf: [
{ enum: ['cash', 'payment-card', 'paypal'] },
{ not: { enum: ['payment-card', 'paypal'] } },
],
},
},
},
{
type: 'object',
properties: {
method: { const: 'payment-card' },
},
},
],
discriminator: { propertyName: 'method' },
},
},
},
},
},
};

const result = checkSchema({
stepCallCtx: {
...stepCallCtx,
$response: {
body: { method: 'cash' },
statusCode: 200,
header: { 'content-type': 'application/json' },
contentType: 'application/json',
},
} as unknown as StepCallContext,
descriptionOperation: discriminatorDescriptionOperation,
ctx,
});

const schemaCheck = result.find((c) => c.name === CHECKS.SCHEMA_CHECK);
expect(schemaCheck).toBeDefined();
expect(schemaCheck?.passed).toBe(true);
expect(schemaCheck?.message).not.toContain('Ajv error');
});

it('should fail schema check for discriminator with allOf + not when body does not match', () => {
const discriminatorDescriptionOperation = {
...descriptionOperation,
responses: {
'200': {
description: 'successful operation',
content: {
'application/json': {
schema: {
oneOf: [
{
type: 'object',
properties: {
method: {
allOf: [
{ enum: ['cash', 'payment-card', 'paypal'] },
{ not: { enum: ['payment-card', 'paypal'] } },
],
},
},
},
{
type: 'object',
properties: {
method: { const: 'payment-card' },
},
},
],
discriminator: { propertyName: 'method' },
},
},
},
},
},
};

const result = checkSchema({
stepCallCtx: {
...stepCallCtx,
$response: {
body: { method: 'bitcoin' },
statusCode: 200,
header: { 'content-type': 'application/json' },
contentType: 'application/json',
},
} as unknown as StepCallContext,
descriptionOperation: discriminatorDescriptionOperation,
ctx,
});

const schemaCheck = result.find((c) => c.name === CHECKS.SCHEMA_CHECK);
expect(schemaCheck).toBeDefined();
expect(schemaCheck?.passed).toBe(false);
expect(schemaCheck?.message).not.toContain('Ajv error');
});

it('should return empty checks if no response available', () => {
const stepCtx = {
$request: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const ajvStrict = new Ajv({
strictSchema: false,
inlineRefs: false,
validateSchema: false,
discriminator: true,
discriminator: false,
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.

I’d suggest taking a deeper look into this issue instead of disabling discriminator in Ajv options. It would be worthwhile to review the Ajv implementation and verify how it handles this particular case.

allowUnionTypes: true,
validateFormats: true,
logger: false,
Expand Down Expand Up @@ -79,18 +79,14 @@ function checkSchemaFromDescription({

if (schemaFromDescription && !isSchemaWithCircularRef) {
try {
const processedSchema = removeWriteOnlyProperties(
Comment thread
cursor[bot] marked this conversation as resolved.
schemaFromDescription as JSONSchemaType<unknown>
);
checks.push({
name: CHECKS.SCHEMA_CHECK,
passed: ajvStrict.validate(
removeWriteOnlyProperties(schemaFromDescription as JSONSchemaType<unknown>),
resultBody
),
passed: ajvStrict.validate(processedSchema, resultBody),
message: ajvStrict.errors
? printAjvErrors(
removeWriteOnlyProperties(schemaFromDescription as JSONSchemaType<unknown>),
resultBody,
ajvStrict.errors
)
? printAjvErrors(processedSchema, resultBody, ajvStrict.errors)
: '',
severity: ctx.severity['SCHEMA_CHECK'],
});
Expand Down
Loading