diff --git a/lib/vocabularies/discriminator/index.ts b/lib/vocabularies/discriminator/index.ts index 98f0f8cfb..5296cd3b0 100644 --- a/lib/vocabularies/discriminator/index.ts +++ b/lib/vocabularies/discriminator/index.ts @@ -1,7 +1,7 @@ -import type {CodeKeywordDefinition, AnySchemaObject, KeywordErrorDefinition} from "../../types" +import type {AnySchemaObject, CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, getProperty, Name} from "../../compile/codegen" -import {DiscrError, DiscrErrorObj} from "../discriminator/types" +import {DiscrError, DiscrErrorObj} from "./types" import {resolveRef, SchemaEnv} from "../../compile" import {schemaHasRulesButRef} from "../../compile/util" @@ -69,18 +69,46 @@ const def: CodeKeywordDefinition = { sch = resolveRef.call(it.self, it.schemaEnv.root, it.baseId, sch?.$ref) if (sch instanceof SchemaEnv) sch = sch.schema } - const propSch = sch?.properties?.[tagName] - if (typeof propSch != "object") { + let propSch = sch?.properties?.[tagName] + let hasSubSchRequired = false + if (!propSch && sch?.allOf) { + const {hasRequired, propertyObject} = mapDiscriminatorFromAllOf(propSch, sch) + hasSubSchRequired = hasRequired + propSch = propertyObject + } + if (!propSch || typeof propSch != "object") { throw new Error( `discriminator: oneOf subschemas (or referenced schemas) must have "properties/${tagName}"` ) } - tagRequired = tagRequired && (topRequired || hasRequired(sch)) + tagRequired = tagRequired && (topRequired || hasRequired(sch) || hasSubSchRequired) addMappings(propSch, i) } if (!tagRequired) throw new Error(`discriminator: "${tagName}" must be required`) return oneOfMapping + function mapDiscriminatorFromAllOf( + propSch: any, + sch: any + ): {hasRequired: boolean; propertyObject: any} { + let subSchObj: any = null + for (const subSch of sch.allOf) { + if (subSch?.properties) { + propSch = subSch.properties[tagName] + subSchObj = subSch + } else if (subSch?.$ref) { + subSchObj = resolveRef.call(it.self, it.schemaEnv.root, it.baseId, subSch.$ref) + if (subSchObj instanceof SchemaEnv) subSchObj = subSchObj.schema + propSch = subSchObj?.properties?.[tagName] + } + if (propSch) { + //found discriminator mapping in one of the allOf objects, stop searching + return {hasRequired: hasRequired(subSchObj), propertyObject: propSch} + } + } + return {hasRequired: false, propertyObject: null} + } + function hasRequired({required}: AnySchemaObject): boolean { return Array.isArray(required) && required.includes(tagName) } diff --git a/spec/discriminator.spec.ts b/spec/discriminator.spec.ts index 28ff12146..2522c4383 100644 --- a/spec/discriminator.spec.ts +++ b/spec/discriminator.spec.ts @@ -159,6 +159,84 @@ describe("discriminator keyword", function () { }) }) + describe("validation with referenced schemas and allOf", () => { + const definitions = { + baseObject: { + properties: { + base: {type: "string"}, + }, + required: ["base"], + }, + schema1: { + properties: { + foo: {const: "x"}, + a: {type: "string"}, + }, + required: ["foo", "a"], + }, + schema2: { + properties: { + foo: {enum: ["y", "w"]}, + b: {type: "string"}, + }, + required: ["foo", "b"], + }, + schema1Object: { + allOf: [ + { + $ref: "#/definitions/baseObject", + }, + { + $ref: "#/definitions/schema1", + }, + ], + }, + } + const mainSchema = { + type: "object", + discriminator: {propertyName: "foo"}, + oneOf: [ + { + //referenced allOf + $ref: "#/definitions/schema1Object", + }, + { + //inline allOf + allOf: [ + { + $ref: "#/definitions/baseObject", + }, + { + $ref: "#/definitions/schema2", + }, + ], + }, + { + //plain object + properties: { + base: {type: "string"}, + foo: {const: "z"}, + c: {type: "string"}, + }, + required: ["base", "foo", "c"], + }, + ], + } + + const schema = [{definitions: definitions, ...mainSchema}] + + it("should validate data", () => { + assertValid(schema, {foo: "x", a: "a", base: "base"}) + assertValid(schema, {foo: "y", b: "b", base: "base"}) + assertValid(schema, {foo: "z", c: "c", base: "base"}) + assertInvalid(schema, {}) + assertInvalid(schema, {foo: 1}) + assertInvalid(schema, {foo: "bar"}) + assertInvalid(schema, {foo: "x", b: "b"}) + assertInvalid(schema, {foo: "y", a: "a"}) + }) + }) + describe("validation with deeply referenced schemas", () => { const schema = [ {