Skip to content

Commit

Permalink
resolve references before validating the discriminator (#1815)
Browse files Browse the repository at this point in the history
* resolve references before validating the discriminator
fixes #1554

* prettier style

* adjust according to comments, add some doc

* resolve schema from SchemaEnv

* simplify code, change comment

* add import

* Update lib/vocabularies/discriminator/index.ts

* let to conts

* Update lib/vocabularies/discriminator/index.ts

* update error message

* fix regexp in the test

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
  • Loading branch information
dfeufel and epoberezkin committed Jan 15, 2022
1 parent ec96c87 commit e7bc009
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 5 deletions.
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
14 changes: 11 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"
import {schemaHasRulesButRef} from "../../compile/util"

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

Expand Down Expand Up @@ -62,10 +64,16 @@ 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]
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
}
const propSch = sch?.properties?.[tagName]
if (typeof propSch != "object") {
throw new Error(`discriminator: oneOf schemas must have "properties/${tagName}"`)
throw new Error(
`discriminator: oneOf subschemas (or referenced schemas) must have "properties/${tagName}"`
)
}
tagRequired = tagRequired && (topRequired || hasRequired(sch))
addMappings(propSch, i)
Expand Down
80 changes: 79 additions & 1 deletion 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 All @@ -97,7 +175,7 @@ describe("discriminator keyword", function () {
required: ["foo"],
oneOf: [{properties: {}}],
},
/discriminator: oneOf schemas must have "properties\/foo"/
/discriminator: oneOf subschemas \(or referenced schemas\) must have "properties\/foo"/
)
})

Expand Down

0 comments on commit e7bc009

Please sign in to comment.