Skip to content

Commit

Permalink
compileAsync a schema with discriminator and $ref, fixes #2427 (#2433)
Browse files Browse the repository at this point in the history
* fix #2427 compileAsync a schema with discriminator and $ref

Make the discriminator code generation throw MissingRefError when the $ref does not synchronously resolve so that compileAsync can loadSchema and retry.

* test: fix errors in test

---------

Co-authored-by: Yonathan Randolph <[email protected]>
  • Loading branch information
jasoniangreen and yonran authored May 17, 2024
1 parent be38f34 commit 5f20d5f
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 1 deletion.
5 changes: 4 additions & 1 deletion lib/vocabularies/discriminator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 MissingRefError from "../../compile/ref_error"
import {schemaHasRulesButRef} from "../../compile/util"

export type DiscriminatorError = DiscrErrorObj<DiscrError.Tag> | DiscrErrorObj<DiscrError.Mapping>
Expand Down Expand Up @@ -66,8 +67,10 @@ const def: CodeKeywordDefinition = {
for (let i = 0; i < oneOf.length; i++) {
let sch = oneOf[i]
if (sch?.$ref && !schemaHasRulesButRef(sch, it.self.RULES)) {
sch = resolveRef.call(it.self, it.schemaEnv.root, it.baseId, sch?.$ref)
const ref = sch.$ref
sch = resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref)
if (sch instanceof SchemaEnv) sch = sch.schema
if (sch === undefined) throw new MissingRefError(it.opts.uriResolver, it.baseId, ref)
}
const propSch = sch?.properties?.[tagName]
if (typeof propSch != "object") {
Expand Down
61 changes: 61 additions & 0 deletions spec/discriminator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,67 @@ describe("discriminator keyword", function () {
})
})

describe("schema with external $refs", () => {
const schemas = {
main: {
type: "object",
discriminator: {propertyName: "foo"},
required: ["foo"],
oneOf: [
{
$ref: "schema1",
},
{
$ref: "schema2",
},
],
},
schema1: {
type: "object",
properties: {
foo: {const: "x"},
},
},
schema2: {
type: "object",
properties: {
foo: {enum: ["y", "z"]},
},
},
}

const data = {foo: "x"}
const badData = {foo: "w"}

it("compile should resolve each $ref to a schema that was added with addSchema", () => {
const opts = {
discriminator: true,
}
const ajv = new _Ajv(opts)
ajv.addSchema(schemas.main, "https://host/main")
ajv.addSchema(schemas.schema1, "https://host/schema1")
ajv.addSchema(schemas.schema2, "https://host/schema2")

const validate = ajv.compile({$ref: "https://host/main"})
assert.strictEqual(validate(data), true)
assert.strictEqual(validate(badData), false)
})
it("compileAsync should loadSchema each $ref", async () => {
const opts = {
discriminator: true,
loadSchema(url) {
if (!url.startsWith("https://host/")) return undefined
const name = url.substring("https://host/".length)
return schemas[name]
},
}
const ajv = new _Ajv(opts)
const validate = await ajv.compileAsync({$ref: "https://host/main"})
assert.strictEqual(validate(data), true)
assert.strictEqual(validate(badData), false)
})
})

describe("validation with deeply referenced schemas", () => {
const schema = [
{
Expand Down

0 comments on commit 5f20d5f

Please sign in to comment.