Skip to content

Commit 5f20d5f

Browse files
compileAsync a schema with discriminator and $ref, fixes #2427 (#2433)
* 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]>
1 parent be38f34 commit 5f20d5f

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed

lib/vocabularies/discriminator/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {KeywordCxt} from "../../compile/validate"
33
import {_, getProperty, Name} from "../../compile/codegen"
44
import {DiscrError, DiscrErrorObj} from "../discriminator/types"
55
import {resolveRef, SchemaEnv} from "../../compile"
6+
import MissingRefError from "../../compile/ref_error"
67
import {schemaHasRulesButRef} from "../../compile/util"
78

89
export type DiscriminatorError = DiscrErrorObj<DiscrError.Tag> | DiscrErrorObj<DiscrError.Mapping>
@@ -66,8 +67,10 @@ const def: CodeKeywordDefinition = {
6667
for (let i = 0; i < oneOf.length; i++) {
6768
let sch = oneOf[i]
6869
if (sch?.$ref && !schemaHasRulesButRef(sch, it.self.RULES)) {
69-
sch = resolveRef.call(it.self, it.schemaEnv.root, it.baseId, sch?.$ref)
70+
const ref = sch.$ref
71+
sch = resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref)
7072
if (sch instanceof SchemaEnv) sch = sch.schema
73+
if (sch === undefined) throw new MissingRefError(it.opts.uriResolver, it.baseId, ref)
7174
}
7275
const propSch = sch?.properties?.[tagName]
7376
if (typeof propSch != "object") {

spec/discriminator.spec.ts

+61
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,67 @@ describe("discriminator keyword", function () {
159159
})
160160
})
161161

162+
describe("schema with external $refs", () => {
163+
const schemas = {
164+
main: {
165+
type: "object",
166+
discriminator: {propertyName: "foo"},
167+
required: ["foo"],
168+
oneOf: [
169+
{
170+
$ref: "schema1",
171+
},
172+
{
173+
$ref: "schema2",
174+
},
175+
],
176+
},
177+
schema1: {
178+
type: "object",
179+
properties: {
180+
foo: {const: "x"},
181+
},
182+
},
183+
schema2: {
184+
type: "object",
185+
properties: {
186+
foo: {enum: ["y", "z"]},
187+
},
188+
},
189+
}
190+
191+
const data = {foo: "x"}
192+
const badData = {foo: "w"}
193+
194+
it("compile should resolve each $ref to a schema that was added with addSchema", () => {
195+
const opts = {
196+
discriminator: true,
197+
}
198+
const ajv = new _Ajv(opts)
199+
ajv.addSchema(schemas.main, "https://host/main")
200+
ajv.addSchema(schemas.schema1, "https://host/schema1")
201+
ajv.addSchema(schemas.schema2, "https://host/schema2")
202+
203+
const validate = ajv.compile({$ref: "https://host/main"})
204+
assert.strictEqual(validate(data), true)
205+
assert.strictEqual(validate(badData), false)
206+
})
207+
it("compileAsync should loadSchema each $ref", async () => {
208+
const opts = {
209+
discriminator: true,
210+
loadSchema(url) {
211+
if (!url.startsWith("https://host/")) return undefined
212+
const name = url.substring("https://host/".length)
213+
return schemas[name]
214+
},
215+
}
216+
const ajv = new _Ajv(opts)
217+
const validate = await ajv.compileAsync({$ref: "https://host/main"})
218+
assert.strictEqual(validate(data), true)
219+
assert.strictEqual(validate(badData), false)
220+
})
221+
})
222+
162223
describe("validation with deeply referenced schemas", () => {
163224
const schema = [
164225
{

0 commit comments

Comments
 (0)