From 105a976fb830a5c20f14fcd7919b7b3eced27d1e Mon Sep 17 00:00:00 2001 From: Dirk Feufel Date: Fri, 12 Nov 2021 17:11:57 +0100 Subject: [PATCH 01/11] resolve references before validating the discriminator fixes #1554 --- lib/vocabularies/discriminator/index.ts | 9 ++- spec/discriminator.spec.ts | 76 +++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/lib/vocabularies/discriminator/index.ts b/lib/vocabularies/discriminator/index.ts index f8656d8d2..0d2d39418 100644 --- a/lib/vocabularies/discriminator/index.ts +++ b/lib/vocabularies/discriminator/index.ts @@ -2,6 +2,7 @@ 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} from "../../compile" export type DiscriminatorError = DiscrErrorObj | DiscrErrorObj @@ -62,8 +63,12 @@ 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 = sch.properties?.[tagName] + if (typeof propSch == "undefined" && sch?.["$ref"]) { + sch = resolveRef.call(it.self, it.schemaEnv, "", sch?.["$ref"]) + propSch = sch?.properties?.[tagName] + } if (typeof propSch != "object") { throw new Error(`discriminator: oneOf schemas must have "properties/${tagName}"`) } diff --git a/spec/discriminator.spec.ts b/spec/discriminator.spec.ts index cf7843880..ab24c37a6 100644 --- a/spec/discriminator.spec.ts +++ b/spec/discriminator.spec.ts @@ -81,6 +81,82 @@ 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( From afb1781e4455904ad7b8aa32394c8bfb55206282 Mon Sep 17 00:00:00 2001 From: Dirk Feufel Date: Fri, 12 Nov 2021 17:45:37 +0100 Subject: [PATCH 02/11] prettier style --- spec/discriminator.spec.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/spec/discriminator.spec.ts b/spec/discriminator.spec.ts index ab24c37a6..b305c9c67 100644 --- a/spec/discriminator.spec.ts +++ b/spec/discriminator.spec.ts @@ -96,17 +96,17 @@ describe("discriminator keyword", function () { b: {type: "string"}, }, required: ["foo", "b"], - } + }, } const mainSchema1 = { type: "object", discriminator: {propertyName: "foo"}, oneOf: [ { - "$ref": "#/definitions/schema1" + $ref: "#/definitions/schema1", }, { - "$ref": "#/definitions/schema2" + $ref: "#/definitions/schema2", }, ], } @@ -125,7 +125,7 @@ describe("discriminator keyword", function () { b: {type: "string"}, }, required: ["b"], - } + }, } const mainSchema2 = { type: "object", @@ -133,16 +133,18 @@ describe("discriminator keyword", function () { required: ["foo"], oneOf: [ { - "$ref": "#/definitions/schema1" + $ref: "#/definitions/schema1", }, { - "$ref": "#/definitions/schema2" + $ref: "#/definitions/schema2", }, ], } - - const schema = [{definitions: definitions1, ...mainSchema1}, {definitions: definitions2, ...mainSchema2}] + const schema = [ + {definitions: definitions1, ...mainSchema1}, + {definitions: definitions2, ...mainSchema2}, + ] it("should validate data", () => { assertValid(schema, {foo: "x", a: "a"}) From 61f7471248afcb3fff13a9c7ba4f388b0198471d Mon Sep 17 00:00:00 2001 From: Dirk Feufel Date: Mon, 15 Nov 2021 08:27:01 +0100 Subject: [PATCH 03/11] adjust according to comments, add some doc --- docs/json-schema.md | 2 +- lib/vocabularies/discriminator/index.ts | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/json-schema.md b/docs/json-schema.md index 5616e24aa..fc5aa6d01 100644 --- a/docs/json-schema.md +++ b/docs/json-schema.md @@ -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. Here, the subschemas can also be included by reference (`$ref` keyword). - 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. diff --git a/lib/vocabularies/discriminator/index.ts b/lib/vocabularies/discriminator/index.ts index 0d2d39418..9d4db1892 100644 --- a/lib/vocabularies/discriminator/index.ts +++ b/lib/vocabularies/discriminator/index.ts @@ -64,13 +64,20 @@ const def: CodeKeywordDefinition = { let tagRequired = true for (let i = 0; i < oneOf.length; i++) { let sch = oneOf[i] - let propSch = sch.properties?.[tagName] - if (typeof propSch == "undefined" && sch?.["$ref"]) { - sch = resolveRef.call(it.self, it.schemaEnv, "", sch?.["$ref"]) + let propSch + if (sch?.["$ref"] && Object.keys(sch).length === 1) { + sch = resolveRef.call(it.self, it.schemaEnv, it.baseId, sch?.["$ref"]) propSch = sch?.properties?.[tagName] - } - if (typeof propSch != "object") { - throw new Error(`discriminator: oneOf schemas must have "properties/${tagName}"`) + if (typeof propSch != "object") { + throw new Error( + `discriminator: referenced oneOf schemas must have "properties/${tagName}"` + ) + } + } else { + propSch = sch.properties?.[tagName] + if (typeof propSch != "object") { + throw new Error(`discriminator: oneOf schemas must have "properties/${tagName}"`) + } } tagRequired = tagRequired && (topRequired || hasRequired(sch)) addMappings(propSch, i) From 3fc732a65d2afaca85a4aae1c9fa554f9e741387 Mon Sep 17 00:00:00 2001 From: Dirk Feufel Date: Wed, 17 Nov 2021 09:51:37 +0100 Subject: [PATCH 04/11] resolve schema from SchemaEnv --- lib/vocabularies/discriminator/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/vocabularies/discriminator/index.ts b/lib/vocabularies/discriminator/index.ts index 9d4db1892..206a3cefc 100644 --- a/lib/vocabularies/discriminator/index.ts +++ b/lib/vocabularies/discriminator/index.ts @@ -2,7 +2,7 @@ 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} from "../../compile" +import {resolveRef, SchemaEnv} from "../../compile" export type DiscriminatorError = DiscrErrorObj | DiscrErrorObj @@ -67,6 +67,9 @@ const def: CodeKeywordDefinition = { let propSch if (sch?.["$ref"] && Object.keys(sch).length === 1) { sch = resolveRef.call(it.self, it.schemaEnv, it.baseId, sch?.["$ref"]) + if (sch instanceof SchemaEnv) { + sch = sch.schema + } propSch = sch?.properties?.[tagName] if (typeof propSch != "object") { throw new Error( From 569162aad1d911e60a2ebe40bb7586f0555bb30f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 15 Jan 2022 10:54:44 +0000 Subject: [PATCH 05/11] simplify code, change comment --- docs/json-schema.md | 2 +- lib/vocabularies/discriminator/index.ts | 25 +++++++++---------------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/docs/json-schema.md b/docs/json-schema.md index fbbe7bfd6..e94e3226b 100644 --- a/docs/json-schema.md +++ b/docs/json-schema.md @@ -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. Here, the subschemas can also be included by reference (`$ref` keyword). +- 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. diff --git a/lib/vocabularies/discriminator/index.ts b/lib/vocabularies/discriminator/index.ts index 206a3cefc..37b9d5738 100644 --- a/lib/vocabularies/discriminator/index.ts +++ b/lib/vocabularies/discriminator/index.ts @@ -65,22 +65,15 @@ const def: CodeKeywordDefinition = { for (let i = 0; i < oneOf.length; i++) { let sch = oneOf[i] let propSch - if (sch?.["$ref"] && Object.keys(sch).length === 1) { - sch = resolveRef.call(it.self, it.schemaEnv, it.baseId, sch?.["$ref"]) - if (sch instanceof SchemaEnv) { - sch = sch.schema - } - propSch = sch?.properties?.[tagName] - if (typeof propSch != "object") { - throw new Error( - `discriminator: referenced oneOf schemas must have "properties/${tagName}"` - ) - } - } else { - propSch = sch.properties?.[tagName] - if (typeof propSch != "object") { - throw new Error(`discriminator: oneOf schemas must have "properties/${tagName}"`) - } + 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] + if (typeof propSch != "object") { + throw new Error( + `discriminator: oneOf subschema (or referenced schema) must have "properties/${tagName}"` + ) } tagRequired = tagRequired && (topRequired || hasRequired(sch)) addMappings(propSch, i) From d4bb1d97f98dd679e4fe4a35b1f7e1715ac8aed2 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 15 Jan 2022 11:08:05 +0000 Subject: [PATCH 06/11] add import --- lib/vocabularies/discriminator/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/vocabularies/discriminator/index.ts b/lib/vocabularies/discriminator/index.ts index 37b9d5738..2b7ee4aec 100644 --- a/lib/vocabularies/discriminator/index.ts +++ b/lib/vocabularies/discriminator/index.ts @@ -3,6 +3,7 @@ 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 "./util" export type DiscriminatorError = DiscrErrorObj | DiscrErrorObj From 97ca393e3ca2167265a0b0c6191942a1ce33088c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 15 Jan 2022 11:11:17 +0000 Subject: [PATCH 07/11] Update lib/vocabularies/discriminator/index.ts --- lib/vocabularies/discriminator/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vocabularies/discriminator/index.ts b/lib/vocabularies/discriminator/index.ts index 2b7ee4aec..87b973ce7 100644 --- a/lib/vocabularies/discriminator/index.ts +++ b/lib/vocabularies/discriminator/index.ts @@ -3,7 +3,7 @@ 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 "./util" +import {schemaHasRulesButRef} from "../../compile/util" export type DiscriminatorError = DiscrErrorObj | DiscrErrorObj From 7ee06cfb62560720a0bdaf9e810a4da117c961c9 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 15 Jan 2022 11:18:29 +0000 Subject: [PATCH 08/11] let to conts --- lib/vocabularies/discriminator/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/vocabularies/discriminator/index.ts b/lib/vocabularies/discriminator/index.ts index 87b973ce7..570c6fa36 100644 --- a/lib/vocabularies/discriminator/index.ts +++ b/lib/vocabularies/discriminator/index.ts @@ -65,12 +65,11 @@ const def: CodeKeywordDefinition = { let tagRequired = true for (let i = 0; i < oneOf.length; i++) { let sch = oneOf[i] - let propSch 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] + const propSch = sch?.properties?.[tagName] if (typeof propSch != "object") { throw new Error( `discriminator: oneOf subschema (or referenced schema) must have "properties/${tagName}"` From e7f3d6d79fd42a711685edc7acae0bb5378274b3 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 15 Jan 2022 11:42:59 +0000 Subject: [PATCH 09/11] Update lib/vocabularies/discriminator/index.ts --- lib/vocabularies/discriminator/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vocabularies/discriminator/index.ts b/lib/vocabularies/discriminator/index.ts index 570c6fa36..ec00064f9 100644 --- a/lib/vocabularies/discriminator/index.ts +++ b/lib/vocabularies/discriminator/index.ts @@ -72,7 +72,7 @@ const def: CodeKeywordDefinition = { const propSch = sch?.properties?.[tagName] if (typeof propSch != "object") { throw new Error( - `discriminator: oneOf subschema (or referenced schema) must have "properties/${tagName}"` + `discriminator: oneOf subschemas (or referenced schemas) must have "properties/${tagName}"` ) } tagRequired = tagRequired && (topRequired || hasRequired(sch)) From 3451db8aabe9f61a492e10403173766c2c57f131 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 15 Jan 2022 11:45:05 +0000 Subject: [PATCH 10/11] update error message --- spec/discriminator.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/discriminator.spec.ts b/spec/discriminator.spec.ts index b305c9c67..d3ff06a7a 100644 --- a/spec/discriminator.spec.ts +++ b/spec/discriminator.spec.ts @@ -175,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"/ ) }) From c6839a8a40372c56fe2729824df6688a1929ddd2 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 15 Jan 2022 12:21:34 +0000 Subject: [PATCH 11/11] fix regexp in the test --- spec/discriminator.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/discriminator.spec.ts b/spec/discriminator.spec.ts index d3ff06a7a..e44d60de0 100644 --- a/spec/discriminator.spec.ts +++ b/spec/discriminator.spec.ts @@ -175,7 +175,7 @@ describe("discriminator keyword", function () { required: ["foo"], oneOf: [{properties: {}}], }, - /discriminator: oneOf subschemas (or referenced schemas) must have "properties\/foo"/ + /discriminator: oneOf subschemas \(or referenced schemas\) must have "properties\/foo"/ ) })