Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resolve references before validating the discriminator #1815

Merged
merged 15 commits into from Jan 15, 2022
Merged
2 changes: 1 addition & 1 deletion docs/json-schema.md
Expand Up @@ -992,7 +992,7 @@ There are following requirements and limitations of using `discriminator` keywor
- `mapping` in discriminator object is not supported.
- [oneOf](#oneof) keyword must be present in the same schema.
- discriminator property should be [requried](#required) either on the top level, as in the example, or in all `oneOf` subschemas.
- each `oneOf` subschema must have [properties](#properties) keyword with discriminator property.
- each `oneOf` subschema must have [properties](#properties) keyword with discriminator property. The subschemas should be either inlined or included as direct references (only `$ref` keyword without any extra keywords is allowed).
- schema for discriminator property in each `oneOf` subschema must be [const](#const) or [enum](#enum), with unique values across all subschemas.

Not meeting any of these requirements would fail schema compilation.
Expand Down
15 changes: 12 additions & 3 deletions lib/vocabularies/discriminator/index.ts
Expand Up @@ -2,6 +2,8 @@ import type {CodeKeywordDefinition, AnySchemaObject, KeywordErrorDefinition} fro
import type {KeywordCxt} from "../../compile/validate"
import {_, getProperty, Name} from "../../compile/codegen"
import {DiscrError, DiscrErrorObj} from "../discriminator/types"
import {resolveRef, SchemaEnv} from "../../compile"
epoberezkin marked this conversation as resolved.
Show resolved Hide resolved
import {schemaHasRulesButRef} from "../../compile/util"

export type DiscriminatorError = DiscrErrorObj<DiscrError.Tag> | DiscrErrorObj<DiscrError.Mapping>

Expand Down Expand Up @@ -62,10 +64,17 @@ const def: CodeKeywordDefinition = {
const topRequired = hasRequired(parentSchema)
let tagRequired = true
for (let i = 0; i < oneOf.length; i++) {
const sch = oneOf[i]
const propSch = sch.properties?.[tagName]
let sch = oneOf[i]
let propSch
epoberezkin marked this conversation as resolved.
Show resolved Hide resolved
if (sch?.$ref && !schemaHasRulesButRef(sch, it.self.RULES)) {
sch = resolveRef.call(it.self, it.schemaEnv, it.baseId, sch?.$ref)
if (sch instanceof SchemaEnv) sch = sch.schema
}
propSch = sch?.properties?.[tagName]
epoberezkin marked this conversation as resolved.
Show resolved Hide resolved
if (typeof propSch != "object") {
throw new Error(`discriminator: oneOf schemas must have "properties/${tagName}"`)
throw new Error(
`discriminator: oneOf subschema (or referenced schema) must have "properties/${tagName}"`
epoberezkin marked this conversation as resolved.
Show resolved Hide resolved
)
}
tagRequired = tagRequired && (topRequired || hasRequired(sch))
addMappings(propSch, i)
Expand Down
78 changes: 78 additions & 0 deletions spec/discriminator.spec.ts
Expand Up @@ -81,6 +81,84 @@ describe("discriminator keyword", function () {
})
})

describe("validation with referenced schemas", () => {
const definitions1 = {
schema1: {
properties: {
foo: {const: "x"},
a: {type: "string"},
},
required: ["foo", "a"],
},
schema2: {
properties: {
foo: {enum: ["y", "z"]},
b: {type: "string"},
},
required: ["foo", "b"],
},
}
const mainSchema1 = {
type: "object",
discriminator: {propertyName: "foo"},
oneOf: [
{
$ref: "#/definitions/schema1",
},
{
$ref: "#/definitions/schema2",
},
],
}

const definitions2 = {
schema1: {
properties: {
foo: {const: "x"},
a: {type: "string"},
},
required: ["a"],
},
schema2: {
properties: {
foo: {enum: ["y", "z"]},
b: {type: "string"},
},
required: ["b"],
},
}
const mainSchema2 = {
type: "object",
discriminator: {propertyName: "foo"},
required: ["foo"],
oneOf: [
{
$ref: "#/definitions/schema1",
},
{
$ref: "#/definitions/schema2",
},
],
}

const schema = [
{definitions: definitions1, ...mainSchema1},
{definitions: definitions2, ...mainSchema2},
]

it("should validate data", () => {
assertValid(schema, {foo: "x", a: "a"})
assertValid(schema, {foo: "y", b: "b"})
assertValid(schema, {foo: "z", b: "b"})
assertInvalid(schema, {})
assertInvalid(schema, {foo: 1})
assertInvalid(schema, {foo: "bar"})
assertInvalid(schema, {foo: "x", b: "b"})
assertInvalid(schema, {foo: "y", a: "a"})
assertInvalid(schema, {foo: "z", a: "a"})
})
})

describe("valid schemas", () => {
it("should have oneOf", () => {
invalidSchema(
Expand Down