redocly-cli
redocly-cli copied to clipboard
Bundler and linter fail when discriminator value is component name
Describe the bug
The OAS 3.1 spec states that the value of a discriminator mapping can be be a string that is the name of a component schema in the file:
The mapping entry maps a specific property value to either a different schema component name, or to a schema identified by a URI
And the spec provides examples like:
discriminator: propertyName: petType mapping: dog: Dog
However when such a OAS schema is sent to the redocly cli linter or bundler it results in an error: "Can't resolve $ref"
To Reproduce
Pass this file to redocly lint or redocly bundle
openapi: "3.1.0"
info:
title: pets
version: 1.0.0
license:
name: Apache 2.0
identifier: Apache-2.0
servers:
- url: http://not.real.com/pets.yaml
paths:
/get-pets:
get:
description: get pets
summary: get pets
operationId: get_pets
responses:
200:
description: ok
content:
application/json:
schema:
$ref: "#/components/schemas/Animals"
400:
description: not ok
components:
schemas:
Animals:
type: object
properties:
elements:
type: array
items:
oneOf:
- $ref: "#/components/schemas/Cat"
- $ref: "#/components/schemas/Dog"
discriminator:
propertyName: species
mapping:
dog: Dog
cat: Cat
Dog:
type: object
required:
- species
properties:
species:
type: string
const: dog
rating:
type: number
Cat:
type: object
required:
- species
properties:
species:
type: string
const: cat
tuxedo:
type: boolean
securitySchemes:
Bearer:
type: http
scheme: bearer
description: JWT auth0 token (without the word 'Bearer')
bearerFormat: JWT
security:
- Bearer: []
Expected behavior
The linter should not report errors.
Logs
OpenAPI description
Redocly Version(s)
1.28.0
Node.js Version(s)
v20.18.1
OS, environment
Mac OS 14.7.2
Additional context
The linter and bundler can be made to pass by changing the file:
discriminator:
propertyName: species
mapping:
dog: "#/components/schemas/Dog"
cat: "#/components/schemas/Cat"
related to #1602
This particular comment has a good breakdown of each type of reference.
https://github.com/Redocly/redocly-cli/issues/1602#issuecomment-2203806975
The bug described here is directly related to this.
Explicit Mapping - Value: If the mapping section is provided and the value DOES NOT being with ./ then it will be inferred as a name for a schema found under the Component section of the spec
From the updated 3.1.1 spec.
The mapping entry maps a specific property value to either a different schema component name, or to a schema identified by a URI. When using implicit or explicit schema component names, inline oneOf or anyOf subschemas are not considered. The behavior of a mapping value that is both a valid schema name and a valid relative URI reference is implementation-defined, but it is RECOMMENDED that it be treated as a schema name.
Here the discriminating value of dog will map to the schema #/components/schemas/Dog, rather than the default (implicit) value of #/components/schemas/dog. If the discriminating value does not match an implicit or explicit mapping, no schema can be determined and validation SHOULD fail.
components:
schemas:
Pet:
type: object
required:
- petType
properties:
petType:
type: string
discriminator:
propertyName: petType
mapping:
dog: Dog
Cat:
allOf:
- $ref: '#/components/schemas/Pet'
- type: object
# all other properties specific to a `Cat`
properties:
name:
type: string
Dog:
allOf:
- $ref: '#/components/schemas/Pet'
- type: object
# all other properties specific to a `Dog`
properties:
bark:
type: string
Lizard:
allOf:
- $ref: '#/components/schemas/Pet'
- type: object
# all other properties specific to a `Lizard`
properties:
lovesRocks:
type: boolean
OK, narrowed this down to the rule no-unresolved-refs which is incorrectly reporting the error.
https://github.com/Redocly/redocly-cli/blob/fa280a302aa8172d1b91a4a84b6376b635619c5d/packages/core/src/rules/no-unresolved-refs.ts#L15-L19
The discriminator mapping itself is working correctly, this can be verified by this example
openapi: 3.1.0
info:
title: pets
version: 1.0.0
license:
name: Apache 2.0
identifier: Apache-2.0
servers:
- url: http://not.real.com/pets.yaml
paths:
/get-pets:
get:
description: get pets
summary: get pets
operationId: get_pets
responses:
200:
description: ok
content:
application/json:
schema:
$ref: "#/components/schemas/Animals"
examples:
valid_test:
value:
elements:
- species: dog
rating: 1
invalid_test:
value:
elements:
- species: dog
rating: true
400:
description: not ok
components:
schemas:
Animals:
type: object
properties:
elements:
type: array
items:
oneOf:
- $ref: "#/components/schemas/Cat"
- $ref: "#/components/schemas/Dog"
discriminator:
propertyName: species
mapping:
dog: Dog
cat: Cat
Dog:
type: object
required:
- species
properties:
species:
const: dog
rating:
type: number
Cat:
type: object
required:
- species
properties:
species:
const: cat
tuxedo:
type: boolean
securitySchemes:
Bearer:
type: http
scheme: bearer
description: JWT auth0 token (without the word 'Bearer')
bearerFormat: JWT
security:
- Bearer: []
The expected error is from the invalid_test example.
Example value must conform to the schema: `rating` property type must be number.
played with a few changes but nothing fixed just yet. Seems this is where it's failing to resolve
https://github.com/Redocly/redocly-cli/blob/fa280a302aa8172d1b91a4a84b6376b635619c5d/packages/core/src/walk.ts#L143
other changes i've made,
from this: https://github.com/Redocly/redocly-cli/blob/fa280a302aa8172d1b91a4a84b6376b635619c5d/packages/core/src/rules/no-unresolved-refs.ts#L15-L17 to this:
DiscriminatorMapping(mapping, { report, resolve, location }) {
for (const mappingName in mapping) {
const resolved = resolve({ $ref: `${mapping[mappingName]}` });
added this test to the no-unresolved-refs.test.ts
it('should not report on discriminator mapping explicit value', async () => {
const document = parseYamlToDocument(
outdent`
openapi: 3.1.0
info:
title: pets
version: 1.0.0
servers:
- url: http://not.real.com/pets.yaml
paths:
/get-pets:
get:
description: get pets
responses:
200:
description: ok
content:
application/json:
schema:
$ref: "#/components/schemas/Animals"
examples:
valid_test:
value:
elements:
- species: dog
rating: 1
invalid_test:
value:
elements:
- species: dog
rating: true
400:
description: not ok
components:
schemas:
Animals:
type: object
properties:
elements:
type: array
items:
oneOf:
- $ref: "#/components/schemas/Cat"
- $ref: "#/components/schemas/Dog"
discriminator:
propertyName: species
mapping:
dog: Dog
cat: Cat
Dog:
type: object
required:
- species
properties:
species:
const: dog
rating:
type: number
Cat:
type: object
required:
- species
properties:
species:
const: cat
tuxedo:
type: boolean
`,
path.join(__dirname, 'foobar.yaml')
);
const results = await lintDocument({
externalRefResolver: new BaseResolver(),
document,
config: await makeConfig({
rules: {
'no-unresolved-refs': 'error',
},
}),
});
expect(replaceSourceWithRef(results, __dirname)).toMatchInlineSnapshot(``);
});
https://github.com/Redocly/redocly-cli/blob/fa280a302aa8172d1b91a4a84b6376b635619c5d/packages/core/src/ref-utils.ts#L77-L86
This is tangentially related and could be another method to resolve these mapping refs rather than the resolvedRefMap method.
Looks like a bug indeed. Until it's fixed, you can try working around it with a custom decorator to prepend the schema names in DiscriminatorMapping with #/components/schemas/ or something similar.