Comparing version 7.0.0-beta.3 to 7.0.0-beta.4
@@ -27,4 +27,3 @@ export { Format, FormatDefinition, AsyncFormatDefinition, KeywordDefinition, KeywordErrorDefinition, CodeKeywordDefinition, MacroKeywordDefinition, FuncKeywordDefinition, Vocabulary, Schema, SchemaObject, AnySchemaObject, AsyncSchema, AnySchema, ValidateFunction, AsyncValidateFunction, ErrorObject, } from "./types"; | ||
validateFormats?: boolean; | ||
next?: boolean; | ||
unevaluated?: boolean; | ||
draft2019?: boolean; | ||
$data?: boolean; | ||
@@ -35,3 +34,3 @@ allErrors?: boolean; | ||
formats?: { | ||
[name: string]: Format; | ||
[Name in string]?: Format; | ||
}; | ||
@@ -47,2 +46,5 @@ keywords?: Vocabulary; | ||
coerceTypes?: boolean | "array"; | ||
next?: boolean; | ||
unevaluated?: boolean; | ||
dynamicRef?: boolean; | ||
meta?: SchemaObject | boolean; | ||
@@ -49,0 +51,0 @@ defaultMeta?: string | AnySchemaObject; |
@@ -29,2 +29,3 @@ "use strict"; | ||
const unevaluated_1 = __importDefault(require("./vocabularies/unevaluated")); | ||
const dynamic_1 = __importDefault(require("./vocabularies/dynamic")); | ||
const util_1 = require("./compile/util"); | ||
@@ -94,2 +95,3 @@ const data_json_1 = __importDefault(require("./refs/data.json")); | ||
constructor(opts = {}) { | ||
var _a, _b, _c; | ||
// shared external scope values for compiled functions | ||
@@ -107,2 +109,7 @@ this.scope = new codegen_2.ValueScope({ scope: {}, prefixes: EXT_SCOPE_NAMES }); | ||
}; | ||
if (opts.draft2019) { | ||
(_a = opts.next) !== null && _a !== void 0 ? _a : (opts.next = true); | ||
(_b = opts.unevaluated) !== null && _b !== void 0 ? _b : (opts.unevaluated = true); | ||
(_c = opts.dynamicRef) !== null && _c !== void 0 ? _c : (opts.dynamicRef = true); | ||
} | ||
this.logger = getLogger(opts.logger); | ||
@@ -118,2 +125,4 @@ const formatOpt = opts.validateFormats; | ||
this.addVocabulary(["$async"]); | ||
if (opts.dynamicRef) | ||
this.addVocabulary(dynamic_1.default); | ||
this.addVocabulary(core_1.default); | ||
@@ -525,3 +534,4 @@ this.addVocabulary(validation_1.default); | ||
const format = this.opts.formats[name]; | ||
this.addFormat(name, format); | ||
if (format) | ||
this.addFormat(name, format); | ||
} | ||
@@ -528,0 +538,0 @@ } |
@@ -29,2 +29,5 @@ import type { AnySchema, AnySchemaObject, AnyValidateFunction, EvaluatedProperties, EvaluatedItems } from "../types"; | ||
baseId: string; | ||
dynamicAnchors: { | ||
[Ref in string]?: true; | ||
}; | ||
readonly schemaPath: Code; | ||
@@ -64,5 +67,5 @@ readonly errSchemaPath: string; | ||
export declare function compileSchema(this: Ajv, sch: SchemaEnv): SchemaEnv; | ||
export declare function resolveRef(this: Ajv, root: SchemaEnv, baseId: string, ref: string): AnySchema | AnyValidateFunction | SchemaEnv | undefined; | ||
export declare function resolveRef(this: Ajv, root: SchemaEnv, baseId: string, ref: string): AnySchema | SchemaEnv | undefined; | ||
export declare function resolveSchema(this: Ajv, root: SchemaEnv, // root object with properties schema, refs TODO below SchemaEnv is assigned to it | ||
ref: string): SchemaEnv | undefined; | ||
export {}; |
@@ -70,2 +70,3 @@ "use strict"; | ||
baseId: sch.baseId || rootId, | ||
dynamicAnchors: {}, | ||
schemaPath: codegen_1.nil, | ||
@@ -72,0 +73,0 @@ errSchemaPath: "#", |
import { Name } from "./codegen"; | ||
declare const names: { | ||
data: Name; | ||
dataCxt: Name; | ||
valCxt: Name; | ||
dataPath: Name; | ||
@@ -9,2 +9,3 @@ parentData: Name; | ||
rootData: Name; | ||
dynamicAnchors: Name; | ||
vErrors: Name; | ||
@@ -11,0 +12,0 @@ errors: Name; |
@@ -8,3 +8,3 @@ "use strict"; | ||
// args passed from referencing schema | ||
dataCxt: new codegen_1.Name("dataCxt"), | ||
valCxt: new codegen_1.Name("valCxt"), | ||
dataPath: new codegen_1.Name("dataPath"), | ||
@@ -14,2 +14,3 @@ parentData: new codegen_1.Name("parentData"), | ||
rootData: new codegen_1.Name("rootData"), | ||
dynamicAnchors: new codegen_1.Name("dynamicAnchors"), | ||
// function scoped variables | ||
@@ -16,0 +17,0 @@ vErrors: new codegen_1.Name("vErrors"), |
@@ -40,5 +40,12 @@ "use strict"; | ||
exports.inlineRef = inlineRef; | ||
const REF_KEYWORDS = new Set([ | ||
"$ref", | ||
"$recursiveRef", | ||
"$recursiveAnchor", | ||
"$dynamicRef", | ||
"$dynamicAnchor", | ||
]); | ||
function hasRef(schema) { | ||
for (const key in schema) { | ||
if (key === "$ref") | ||
if (REF_KEYWORDS.has(key)) | ||
return true; | ||
@@ -90,2 +97,3 @@ const sch = schema[key]; | ||
exports.resolveUrl = resolveUrl; | ||
const ANCHOR = /^[a-z_][-a-z0-9._]*$/i; | ||
function getSchemaRefs(schema) { | ||
@@ -98,2 +106,3 @@ if (typeof schema == "boolean") | ||
const localRefs = {}; | ||
const schemaRefs = new Set(); | ||
json_schema_traverse_1.default(schema, { allKeys: true }, (sch, jsonPtr, _, parentJsonPtr) => { | ||
@@ -103,32 +112,48 @@ if (parentJsonPtr === undefined) | ||
const fullPath = pathPrefix + jsonPtr; | ||
let id = sch.$id; | ||
let baseId = baseIds[parentJsonPtr]; | ||
if (typeof id == "string") { | ||
id = baseId = normalizeId(baseId ? URI.resolve(baseId, id) : id); | ||
let schOrRef = this.refs[id]; | ||
if (typeof sch.$id == "string") | ||
baseId = addRef.call(this, sch.$id); | ||
addAnchor.call(this, sch.$anchor); | ||
addAnchor.call(this, sch.$dynamicAnchor); | ||
baseIds[jsonPtr] = baseId; | ||
function addRef(ref) { | ||
ref = normalizeId(baseId ? URI.resolve(baseId, ref) : ref); | ||
if (schemaRefs.has(ref)) | ||
throw ambiguos(ref); | ||
schemaRefs.add(ref); | ||
let schOrRef = this.refs[ref]; | ||
if (typeof schOrRef == "string") | ||
schOrRef = this.refs[schOrRef]; | ||
if (typeof schOrRef == "object") { | ||
checkAmbiguosId(sch, schOrRef.schema, id); | ||
checkAmbiguosRef(sch, schOrRef.schema, ref); | ||
} | ||
else if (id !== normalizeId(fullPath)) { | ||
if (id[0] === "#") { | ||
checkAmbiguosId(sch, localRefs[id], id); | ||
localRefs[id] = sch; | ||
else if (ref !== normalizeId(fullPath)) { | ||
if (ref[0] === "#") { | ||
checkAmbiguosRef(sch, localRefs[ref], ref); | ||
localRefs[ref] = sch; | ||
} | ||
else { | ||
this.refs[id] = fullPath; | ||
this.refs[ref] = fullPath; | ||
} | ||
} | ||
return ref; | ||
} | ||
baseIds[jsonPtr] = baseId; | ||
function addAnchor(anchor) { | ||
if (typeof anchor == "string") { | ||
if (!ANCHOR.test(anchor)) | ||
throw new Error(`invalid anchor "${anchor}"`); | ||
addRef.call(this, `#${anchor}`); | ||
} | ||
} | ||
}); | ||
return localRefs; | ||
function checkAmbiguosId(sch1, sch2, id) { | ||
if (sch2 !== undefined && !fast_deep_equal_1.default(sch1, sch2)) { | ||
throw new Error(`id "${id}" resolves to more than one schema`); | ||
} | ||
function checkAmbiguosRef(sch1, sch2, ref) { | ||
if (sch2 !== undefined && !fast_deep_equal_1.default(sch1, sch2)) | ||
throw ambiguos(ref); | ||
} | ||
function ambiguos(ref) { | ||
return new Error(`reference "${ref}" resolves to more than one schema`); | ||
} | ||
} | ||
exports.getSchemaRefs = getSchemaRefs; | ||
//# sourceMappingURL=resolve.js.map |
@@ -19,3 +19,3 @@ "use strict"; | ||
types: { ...groups, integer: true, boolean: true, null: true }, | ||
rules: [groups.number, groups.string, { rules: [] }, groups.array, groups.object], | ||
rules: [{ rules: [] }, groups.number, groups.string, groups.array, groups.object], | ||
all: { type: true, $comment: true }, | ||
@@ -22,0 +22,0 @@ keywords: { type: true, $comment: true }, |
@@ -28,15 +28,20 @@ "use strict"; | ||
gen.return(() => opts.code.es5 | ||
? gen.func(validateName, codegen_1._ `${names_1.default.data}, ${names_1.default.dataCxt}`, schemaEnv.$async, () => { | ||
? gen.func(validateName, codegen_1._ `${names_1.default.data}, ${names_1.default.valCxt}`, schemaEnv.$async, () => { | ||
gen.code(codegen_1._ `"use strict"; ${funcSourceUrl(schema, opts)}`); | ||
destructureDataCxtES5(gen); | ||
destructureValCxtES5(gen, opts); | ||
gen.code(body); | ||
}) | ||
: gen.func(validateName, codegen_1._ `${names_1.default.data}, {${names_1.default.dataPath}="", ${names_1.default.parentData}, ${names_1.default.parentDataProperty}, ${names_1.default.rootData}=${names_1.default.data}}={}`, schemaEnv.$async, () => gen.code(funcSourceUrl(schema, opts)).code(body))); | ||
: gen.func(validateName, codegen_1._ `${names_1.default.data}, ${destructureValCxt(opts)}`, schemaEnv.$async, () => gen.code(funcSourceUrl(schema, opts)).code(body))); | ||
} | ||
function destructureDataCxtES5(gen) { | ||
gen.if(names_1.default.dataCxt, () => { | ||
gen.var(names_1.default.dataPath, codegen_1._ `${names_1.default.dataCxt}.${names_1.default.dataPath}`); | ||
gen.var(names_1.default.parentData, codegen_1._ `${names_1.default.dataCxt}.${names_1.default.parentData}`); | ||
gen.var(names_1.default.parentDataProperty, codegen_1._ `${names_1.default.dataCxt}.${names_1.default.parentDataProperty}`); | ||
gen.var(names_1.default.rootData, codegen_1._ `${names_1.default.dataCxt}.${names_1.default.rootData}`); | ||
function destructureValCxt(opts) { | ||
return codegen_1._ `{${names_1.default.dataPath}="", ${names_1.default.parentData}, ${names_1.default.parentDataProperty}, ${names_1.default.rootData}=${names_1.default.data}${opts.dynamicRef ? codegen_1._ `, ${names_1.default.dynamicAnchors}={}` : codegen_1.nil}}={}`; | ||
} | ||
function destructureValCxtES5(gen, opts) { | ||
gen.if(names_1.default.valCxt, () => { | ||
gen.var(names_1.default.dataPath, codegen_1._ `${names_1.default.valCxt}.${names_1.default.dataPath}`); | ||
gen.var(names_1.default.parentData, codegen_1._ `${names_1.default.valCxt}.${names_1.default.parentData}`); | ||
gen.var(names_1.default.parentDataProperty, codegen_1._ `${names_1.default.valCxt}.${names_1.default.parentDataProperty}`); | ||
gen.var(names_1.default.rootData, codegen_1._ `${names_1.default.valCxt}.${names_1.default.rootData}`); | ||
if (opts.dynamicRef) | ||
gen.var(names_1.default.dynamicAnchors, codegen_1._ `${names_1.default.valCxt}.${names_1.default.dynamicAnchors}`); | ||
}, () => { | ||
@@ -47,2 +52,4 @@ gen.var(names_1.default.dataPath, codegen_1._ `""`); | ||
gen.var(names_1.default.rootData, names_1.default.data); | ||
if (opts.dynamicRef) | ||
gen.var(names_1.default.dynamicAnchors, codegen_1._ `{}`); | ||
}); | ||
@@ -49,0 +56,0 @@ } |
@@ -35,2 +35,5 @@ import type { CodeGen, Code, Name, Scope } from "../compile/codegen"; | ||
rootData: Record<string, any> | any[]; | ||
dynamicAnchors: { | ||
[Ref in string]?: ValidateFunction; | ||
}; | ||
} | ||
@@ -37,0 +40,0 @@ export interface ValidateFunction<T = unknown> { |
@@ -21,3 +21,3 @@ "use strict"; | ||
if (!Array.isArray(items)) { | ||
validate_1.checkStrictMode(it, '"additionalItems" without "items" is ignored'); | ||
validate_1.checkStrictMode(it, '"additionalItems" is ignored when "items" is not an array of schemas'); | ||
return; | ||
@@ -24,0 +24,0 @@ } |
@@ -50,4 +50,11 @@ "use strict"; | ||
const dataAndSchema = passSchema ? codegen_1._ `${schemaCode}, ${data}, ${topSchemaRef}${schemaPath}` : data; | ||
const dataCxt = gen.object([names_1.default.dataPath, codegen_1.strConcat(names_1.default.dataPath, errorPath)], [names_1.default.parentData, it.parentData], [names_1.default.parentDataProperty, it.parentDataProperty], [names_1.default.rootData, names_1.default.rootData]); | ||
const args = codegen_1._ `${dataAndSchema}, ${dataCxt}`; | ||
const valCxt = [ | ||
[names_1.default.dataPath, codegen_1.strConcat(names_1.default.dataPath, errorPath)], | ||
[names_1.default.parentData, it.parentData], | ||
[names_1.default.parentDataProperty, it.parentDataProperty], | ||
[names_1.default.rootData, names_1.default.rootData], | ||
]; | ||
if (it.opts.dynamicRef) | ||
valCxt.push([names_1.default.dynamicAnchors, names_1.default.dynamicAnchors]); | ||
const args = codegen_1._ `${dataAndSchema}, ${gen.object(...valCxt)}`; | ||
return context !== codegen_1.nil ? codegen_1._ `${func}.call(${context}, ${args})` : codegen_1._ `${func}(${args})`; | ||
@@ -54,0 +61,0 @@ } |
@@ -8,4 +8,12 @@ "use strict"; | ||
const ref_1 = __importDefault(require("./ref")); | ||
const core = ["$schema", "$id", "$defs", "definitions", id_1.default, ref_1.default]; | ||
const core = [ | ||
"$schema", | ||
"$id", | ||
"$defs", | ||
"$vocabulary", | ||
"definitions", | ||
id_1.default, | ||
ref_1.default, | ||
]; | ||
exports.default = core; | ||
//# sourceMappingURL=index.js.map |
import type { CodeKeywordDefinition } from "../../types"; | ||
import type KeywordCxt from "../../compile/context"; | ||
import { Code } from "../../compile/codegen"; | ||
import { SchemaEnv } from "../../compile"; | ||
declare const def: CodeKeywordDefinition; | ||
export declare function callRef(cxt: KeywordCxt, v: Code, sch?: SchemaEnv, $async?: boolean): void; | ||
export default def; |
@@ -6,2 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.callRef = void 0; | ||
const error_classes_1 = require("../../compile/error_classes"); | ||
@@ -18,17 +19,16 @@ const code_1 = require("../code"); | ||
const { gen, schema, it } = cxt; | ||
const { allErrors, baseId, schemaEnv: env, opts, validateName, self } = it; | ||
const passCxt = opts.passContext ? names_1.default.this : codegen_1.nil; | ||
const { baseId, schemaEnv: env, validateName, self } = it; | ||
if (schema === "#" || schema === "#/") | ||
return callRootRef(); | ||
const schOrFunc = compile_1.resolveRef.call(self, env.root, baseId, schema); | ||
if (schOrFunc === undefined) | ||
const schOrEnv = compile_1.resolveRef.call(self, env.root, baseId, schema); | ||
if (schOrEnv === undefined) | ||
throw new error_classes_1.MissingRefError(baseId, schema); | ||
if (schOrFunc instanceof compile_1.SchemaEnv) | ||
return callValidate(schOrFunc); | ||
return inlineRefSchema(schOrFunc); | ||
if (schOrEnv instanceof compile_1.SchemaEnv) | ||
return callValidate(schOrEnv); | ||
return inlineRefSchema(schOrEnv); | ||
function callRootRef() { | ||
if (env === env.root) | ||
return callRef(validateName, env, env.$async); | ||
return callRef(cxt, validateName, env, env.$async); | ||
const rootName = gen.scopeValue("root", { ref: env.root }); | ||
return callRef(codegen_1._ `${rootName}.validate`, env.root, env.root.$async); | ||
return callRef(cxt, codegen_1._ `${rootName}.validate`, env.root, env.root.$async); | ||
} | ||
@@ -45,10 +45,4 @@ function callValidate(sch) { | ||
} | ||
callRef(v, sch, sch.$async); | ||
callRef(cxt, v, sch, sch.$async); | ||
} | ||
function callRef(v, sch, $async) { | ||
if ($async) | ||
callAsyncRef(v, sch); | ||
else | ||
callSyncRef(v, sch); | ||
} | ||
function inlineRefSchema(sch) { | ||
@@ -68,59 +62,69 @@ const schName = gen.scopeValue("schema", { ref: sch }); | ||
} | ||
function callAsyncRef(v, sch) { | ||
if (!env.$async) | ||
throw new Error("async schema referenced by sync schema"); | ||
const valid = gen.let("valid"); | ||
gen.try(() => { | ||
gen.code(codegen_1._ `await ${code_1.callValidateCode(cxt, v, passCxt)}`); | ||
addEvaluatedFrom(v, sch); | ||
if (!allErrors) | ||
gen.assign(valid, true); | ||
}, (e) => { | ||
gen.if(codegen_1._ `!(${e} instanceof ${it.ValidationError})`, () => gen.throw(e)); | ||
addErrorsFrom(e); | ||
if (!allErrors) | ||
gen.assign(valid, false); | ||
}); | ||
cxt.ok(valid); | ||
} | ||
function callSyncRef(v, sch) { | ||
cxt.result(code_1.callValidateCode(cxt, v, passCxt), () => addEvaluatedFrom(v, sch), () => addErrorsFrom(v)); | ||
} | ||
function addErrorsFrom(source) { | ||
const errs = codegen_1._ `${source}.errors`; | ||
gen.assign(names_1.default.vErrors, codegen_1._ `${names_1.default.vErrors} === null ? ${errs} : ${names_1.default.vErrors}.concat(${errs})`); // TODO tagged | ||
gen.assign(names_1.default.errors, codegen_1._ `${names_1.default.vErrors}.length`); | ||
} | ||
function addEvaluatedFrom(source, sch) { | ||
var _a; | ||
if (!it.opts.unevaluated) | ||
return; | ||
const schEvaluated = (_a = sch.validate) === null || _a === void 0 ? void 0 : _a.evaluated; | ||
// TODO refactor | ||
if (it.props !== true) { | ||
if (schEvaluated && !schEvaluated.dynamicProps) { | ||
if (schEvaluated.props !== undefined) { | ||
it.props = util_1.mergeEvaluated.props(gen, schEvaluated.props, it.props); | ||
} | ||
}, | ||
}; | ||
function callRef(cxt, v, sch, $async) { | ||
const { gen, it } = cxt; | ||
const { allErrors, schemaEnv: env, opts } = it; | ||
const passCxt = opts.passContext ? names_1.default.this : codegen_1.nil; | ||
if ($async) | ||
callAsyncRef(); | ||
else | ||
callSyncRef(); | ||
function callAsyncRef() { | ||
if (!env.$async) | ||
throw new Error("async schema referenced by sync schema"); | ||
const valid = gen.let("valid"); | ||
gen.try(() => { | ||
gen.code(codegen_1._ `await ${code_1.callValidateCode(cxt, v, passCxt)}`); | ||
addEvaluatedFrom(v); // TODO will not work with async, it has to be returned with the result | ||
if (!allErrors) | ||
gen.assign(valid, true); | ||
}, (e) => { | ||
gen.if(codegen_1._ `!(${e} instanceof ${it.ValidationError})`, () => gen.throw(e)); | ||
addErrorsFrom(e); | ||
if (!allErrors) | ||
gen.assign(valid, false); | ||
}); | ||
cxt.ok(valid); | ||
} | ||
function callSyncRef() { | ||
cxt.result(code_1.callValidateCode(cxt, v, passCxt), () => addEvaluatedFrom(v), () => addErrorsFrom(v)); | ||
} | ||
function addErrorsFrom(source) { | ||
const errs = codegen_1._ `${source}.errors`; | ||
gen.assign(names_1.default.vErrors, codegen_1._ `${names_1.default.vErrors} === null ? ${errs} : ${names_1.default.vErrors}.concat(${errs})`); // TODO tagged | ||
gen.assign(names_1.default.errors, codegen_1._ `${names_1.default.vErrors}.length`); | ||
} | ||
function addEvaluatedFrom(source) { | ||
var _a; | ||
if (!it.opts.unevaluated) | ||
return; | ||
const schEvaluated = (_a = sch === null || sch === void 0 ? void 0 : sch.validate) === null || _a === void 0 ? void 0 : _a.evaluated; | ||
// TODO refactor | ||
if (it.props !== true) { | ||
if (schEvaluated && !schEvaluated.dynamicProps) { | ||
if (schEvaluated.props !== undefined) { | ||
it.props = util_1.mergeEvaluated.props(gen, schEvaluated.props, it.props); | ||
} | ||
else { | ||
const props = gen.var("props", codegen_1._ `${source}.evaluated.props`); | ||
it.props = util_1.mergeEvaluated.props(gen, props, it.props, codegen_1.Name); | ||
} | ||
} | ||
if (it.items !== true) { | ||
if (schEvaluated && !schEvaluated.dynamicItems) { | ||
if (schEvaluated.items !== undefined) { | ||
it.items = util_1.mergeEvaluated.items(gen, schEvaluated.items, it.items); | ||
} | ||
else { | ||
const props = gen.var("props", codegen_1._ `${source}.evaluated.props`); | ||
it.props = util_1.mergeEvaluated.props(gen, props, it.props, codegen_1.Name); | ||
} | ||
} | ||
if (it.items !== true) { | ||
if (schEvaluated && !schEvaluated.dynamicItems) { | ||
if (schEvaluated.items !== undefined) { | ||
it.items = util_1.mergeEvaluated.items(gen, schEvaluated.items, it.items); | ||
} | ||
else { | ||
const items = gen.var("items", codegen_1._ `${source}.evaluated.items`); | ||
it.items = util_1.mergeEvaluated.items(gen, items, it.items, codegen_1.Name); | ||
} | ||
} | ||
else { | ||
const items = gen.var("items", codegen_1._ `${source}.evaluated.items`); | ||
it.items = util_1.mergeEvaluated.items(gen, items, it.items, codegen_1.Name); | ||
} | ||
} | ||
}, | ||
}; | ||
} | ||
} | ||
exports.callRef = callRef; | ||
exports.default = def; | ||
//# sourceMappingURL=ref.js.map |
@@ -5,5 +5,5 @@ import type { TypeError } from "../compile/validate/dataType"; | ||
import type { FormatError } from "./format/format"; | ||
import type { UnevaluatedPropertiesError } from "./applicator/unevaluatedProperties"; | ||
import type { UnevaluatedItemsError } from "./applicator/unevaluatedItems"; | ||
import type { UnevaluatedPropertiesError } from "./unevaluated/unevaluatedProperties"; | ||
import type { UnevaluatedItemsError } from "./unevaluated/unevaluatedItems"; | ||
import type { DependentRequiredError } from "./validation/dependentRequired"; | ||
export declare type DefinedError = TypeError | ApplicatorKeywordError | ValidationKeywordError | FormatError | UnevaluatedPropertiesError | UnevaluatedItemsError | DependentRequiredError; |
@@ -252,3 +252,3 @@ # API Reference | ||
const defaultOptions = { | ||
// strict mode options | ||
// strict mode options (NEW) | ||
strict: true, | ||
@@ -261,4 +261,3 @@ strictTypes: "log", | ||
// validation and reporting options: | ||
next: false, | ||
unevaluated: false, | ||
draft2019: false // NEW | ||
$data: false, | ||
@@ -278,2 +277,5 @@ allErrors: false, | ||
// advanced options: | ||
next: false, // NEW | ||
unevaluated: false, // NEW | ||
dynamicRef: false, // NEW | ||
meta: true, | ||
@@ -285,7 +287,7 @@ validateSchema: true, | ||
loopRequired: Infinity, | ||
loopEnum: Infinity, | ||
loopEnum: Infinity, // NEW | ||
ownProperties: false, | ||
multipleOfPrecision: false, | ||
messages: true, | ||
code: { | ||
code: { // NEW | ||
es5: false, | ||
@@ -322,4 +324,3 @@ lines: false, | ||
- _next_: add support for the keywords from the next JSON-Schema draft (currently it is draft 2019-09): [`dependentRequired`](./json-schema.md#dependentrequired), [`dependentSchemas`](./json-schema.md#dependentschemas), [`maxContains`/`minContain`](./json-schema.md#maxcontains--mincontains). This option will be removed once the next draft is fully supported. | ||
- _unevaluated_: to track evaluated properties/items and support keywords [`unevaluatedProperties`](./json-schema.md#unevaluatedproperties) and [`unevaluatedItems`](./json-schema.md#unevaluateditems). Supporting these keywords may add additional validation-time logic even to validation functions where these keywords are not used. When possible, Ajv determines which properties/items are "unevaluated" at compilation time. | ||
- _draft2019_ (NEW in v7): add JSON Schema draft-2019-09 support. This option is equivalent to the combination of options `next`, `unevaluated` and `dynamicRef` (see [Advanced options](#advanced-options)). This option enables support for additional keywords (including `unevaluatedProperties` and `unevaluatedItems`) and for dynamic recursive references (see [Extending recursive schemas](./validation.md#extending-recursive-schemas)), but it does not add draft-2019-09 meta-schema - the default JSON Schema draft remains drat-07. See code example how to add it in [JSON Schema draft-2019-09](./validation.md#json-schema-draft-2019-09) | ||
- _\$data_: support [\$data references](./validation.md#data-reference). Draft 6 meta-schema that is added by default will be extended to allow them. If you want to use another meta-schema you need to use $dataMetaSchema method to add support for $data reference. See [API](#ajv-constructor-and-methods). | ||
@@ -358,2 +359,5 @@ - _allErrors_: check all rules collecting all errors. Default is to return after the first error. | ||
- _next_ (NEW in v7): add support for the keywords from the next JSON-Schema draft (currently it is draft 2019-09): [`dependentRequired`](./json-schema.md#dependentrequired), [`dependentSchemas`](./json-schema.md#dependentschemas), [`maxContains`/`minContain`](./json-schema.md#maxcontains--mincontains). This option will be removed once the next draft is fully supported. | ||
- _unevaluated_ (NEW in v7): to track evaluated properties/items and support keywords [`unevaluatedProperties`](./json-schema.md#unevaluatedproperties) and [`unevaluatedItems`](./json-schema.md#unevaluateditems). Supporting these keywords may add additional validation-time logic even to validation functions where these keywords are not used. When possible, Ajv determines which properties/items are "unevaluated" at compilation time. | ||
- _dynamicRef_ (NEW in v7): to support `recursiveRef`/`recursiveAnchor` keywords (JSON Schema draft-2019-09) and `dynamicRef`/`dynamicAnchor` keywords (the upcoming JSON Schema draft). See [Extending recursive schemas](./validation.md#extending-recursive-schemas) | ||
- _meta_: add [meta-schema](http://json-schema.org/documentation.html) so it can be used by other schemas (true by default). If an object is passed, it will be used as the default meta-schema for schemas that have no `$schema` keyword. This default meta-schema MUST have `$schema` keyword. | ||
@@ -360,0 +364,0 @@ - _validateSchema_: validate added/compiled schemas against meta-schema (true by default). `$schema` property in the schema can be http://json-schema.org/draft-07/schema or absent (draft-07 meta-schema will be used) or can be a reference to the schema previously added with `addMetaSchema` method. Option values: |
@@ -7,4 +7,17 @@ # JSON Schema validation keywords | ||
## Keywords | ||
## JSON Schema draft-2019-09 | ||
v7 added support for all new keywords in draft-2019-09: | ||
- [unevaluatedProperties](#unevaluatedproperties) | ||
- [unevaluatedItems](#unevaluateditems) | ||
- [dependentRequired](#dependentrequired) | ||
- [dependentSchemas](#dependentschemas) | ||
- [maxContains/minContains](#maxcontains--mincontains) | ||
- [$recursiveAnchor/$recursiveRef](./validation.md#extending-recursive-schemas) | ||
There is also support for [$dynamicAnchor/$dynamicRef](./validation.md#extending-recursive-schemas) from the next version of JSON Schema draft that will replace `$recursiveAnchor`/`$recursiveRef`. | ||
## Included keywords | ||
- [type](#type) | ||
@@ -11,0 +24,0 @@ - [Keywords for numbers](#keywords-for-numbers) |
# Data validation | ||
- [Data validation](#data-validation) | ||
- [JSON Schema draft-2019-09](#json-schema-draft-2019-09) | ||
- [Validation basics](#validation-basics) | ||
@@ -10,2 +11,3 @@ - [JSON Schema validation keywords](#json-schema-validation-keywords) | ||
- [<a name="ref"></a>Combining schemas with \$ref](#combining-schemas-with-ref) | ||
- [Extending recursive schemas](#extending-recursive-schemas) | ||
- [\$data reference](#data-reference) | ||
@@ -22,2 +24,21 @@ - [$merge and $patch keywords](#merge-and-patch-keywords) | ||
## JSON Schema draft-2019-09 | ||
To enable JSON Schema draft-2019-09 support: | ||
```javascript | ||
const ajv = new Ajv({draft2019: true}) | ||
const addMetaSchema2019 = require("ajv/dist/refs/json-schema-2019-09") | ||
addMetaSchema2019(ajv) // to add draft-2019-09 meta-schema without making it default | ||
// addMetaSchema2019(ajv, true) // to add it and make default | ||
``` | ||
Option `draft2019: true` enables the following features: | ||
- keywords [`unevaluatedProperties`](./json-schema.md#unevaluatedproperties) and [`unevaluatedItems`](./json-schema.md#unevaluateditems) | ||
- keywords [`dependentRequired`](./json-schema.md#dependentrequired), [`dependentSchemas`](./json-schema.md#dependentschemas), [`maxContains`/`minContain`](./json-schema.md#maxcontains--mincontains) | ||
- dynamic recursive references with [`recursiveAnchor`/`recursiveReference`] - see [Extending recursive schemas](#extending-recursive-schemas) | ||
**Please note**: option `draft2019` is off by default because both `unevaluated*` keywords and dynamic recursive references may add additional code to compiled validation functions, depending on the schema, even if they are not used - so unless these features are used it is better to have them disabled. They can also be enabled separately - see [Advanced options](./api.md#advanced-options). | ||
## Validation basics | ||
@@ -140,2 +161,70 @@ | ||
### Extending recursive schemas | ||
While statically defined `$ref` keyword allows to split schemas to multiple files, it is difficult to extend recursive schemas - the recursive reference(s) in the original schema points to the original schema, and not to the extended one. So in JSON Schema draft-07 the only available solution to extend the recursive schema was to redifine all sections of the original schema that have recursion. | ||
It was particularly repetitive when extending meta-schema, as it has many recursive references, but even in a schema with a single recursive reference extending it was very verbose. | ||
JSON Schema draft-2019-09 and the upcoming draft defined the mechanism for dynamic recursion using keywords `$recursiveRef`/`$recursiveAnchor` (draft-2019-09) or `$dynamicRef`/`$dynamicAnchor` (the next JSON Schema draft) that is somewhat similar to "open recursion" in functional programming. | ||
Consider this recursive schema with static recursion: | ||
```javascript | ||
const treeSchema = { | ||
$id: "https://example.com/tree", | ||
type: "object", | ||
required: ["data"], | ||
properties: { | ||
data: true, | ||
children: { | ||
type: "array", | ||
items: {$ref: "#"}, | ||
}, | ||
}, | ||
} | ||
``` | ||
The only way to extend this schema to prohibit additional properties is by adding `additionalProperties` keyword right in the schema - this approach can be impossible if you do not control the source of the original schema. Ajv also provided the additional keywords in [ajv-merge-patch](https://github.com/ajv-validator/ajv-merge-patch) package to extend schemas by treating them as plain JSON data. While this approach works, it is non-standard. | ||
The new keywords for dynamic recursive references allow extending this schema without modifying it: | ||
```javascript | ||
const treeSchema = { | ||
$id: "https://example.com/tree", | ||
$recursiveAnchor: true, | ||
type: "object", | ||
required: ["data"], | ||
properties: { | ||
data: true, | ||
children: { | ||
type: "array", | ||
items: {$recursiveRef: "#"}, | ||
}, | ||
}, | ||
} | ||
const strictTreeSchema = { | ||
$id: "https://example.com/strict-tree", | ||
$recursiveAnchor: true, | ||
$ref: "tree", | ||
unevaluatedProperties: false, | ||
} | ||
const ajv = new Ajv({ | ||
dynamicRef: true, // to support dynamic recursive references | ||
unevaluated: true, // to support unevaluatedProperties | ||
schemas: [treeSchema, strictTreeSchema], | ||
}) | ||
const validate = ajv.getSchema("https://example.com/strict-tree") | ||
``` | ||
See [dynamic-refs](../spec/dynamic-ref.spec.ts) test for the example using `$dynamicAnchor`/`$dynamicRef`. | ||
At the moment Ajv implements the spec for dynamic recursive references with these limitations: | ||
- `$recursiveAnchor`/`$dynamicAnchor` can only be used in the schema root. | ||
- `$recursiveRef`/`$dynamicRef` can only be hash fragments, without URI. | ||
Ajv also does not support dynamic references in [asynchronous schemas](#asynchronous-validation) (Ajv spec extension), it is assumed that the referenced schema is synchronous - there is no validation-time check. | ||
### \$data reference | ||
@@ -142,0 +231,0 @@ |
@@ -64,2 +64,3 @@ export { | ||
import unevaluatedVocabulary from "./vocabularies/unevaluated" | ||
import dynamicVocabulary from "./vocabularies/dynamic" | ||
import {eachItem} from "./compile/util" | ||
@@ -89,3 +90,3 @@ import $dataRefSchema from "./refs/data.json" | ||
interface CurrentOptions { | ||
// strict mode options | ||
// strict mode options (NEW) | ||
strict?: boolean | "log" | ||
@@ -98,4 +99,3 @@ strictTypes?: boolean | "log" | ||
// validation and reporting options: | ||
next?: boolean | ||
unevaluated?: boolean | ||
draft2019?: boolean // NEW | ||
$data?: boolean | ||
@@ -107,3 +107,3 @@ allErrors?: boolean | ||
| ((comment: string, schemaPath?: string, rootSchema?: AnySchemaObject) => unknown) | ||
formats?: {[name: string]: Format} | ||
formats?: {[Name in string]?: Format} | ||
keywords?: Vocabulary | ||
@@ -118,2 +118,5 @@ schemas?: AnySchema[] | {[key: string]: AnySchema} | ||
// advanced options: | ||
next?: boolean // NEW | ||
unevaluated?: boolean // NEW | ||
dynamicRef?: boolean // NEW | ||
meta?: SchemaObject | boolean | ||
@@ -126,7 +129,7 @@ defaultMeta?: string | AnySchemaObject | ||
loopRequired?: number | ||
loopEnum?: number | ||
loopEnum?: number // NEW | ||
ownProperties?: boolean | ||
multipleOfPrecision?: boolean | number | ||
messages?: boolean | ||
code?: CodeOptions | ||
code?: CodeOptions // NEW | ||
} | ||
@@ -271,2 +274,7 @@ | ||
} | ||
if (opts.draft2019) { | ||
opts.next ??= true | ||
opts.unevaluated ??= true | ||
opts.dynamicRef ??= true | ||
} | ||
this.logger = getLogger(opts.logger) | ||
@@ -283,2 +291,3 @@ const formatOpt = opts.validateFormats | ||
this.addVocabulary(["$async"]) | ||
if (opts.dynamicRef) this.addVocabulary(dynamicVocabulary) | ||
this.addVocabulary(coreVocabulary) | ||
@@ -742,3 +751,3 @@ this.addVocabulary(validationVocabulary) | ||
const format = this.opts.formats[name] | ||
this.addFormat(name, format) | ||
if (format) this.addFormat(name, format) | ||
} | ||
@@ -745,0 +754,0 @@ } |
@@ -44,2 +44,3 @@ import type { | ||
baseId: string // the current schema base URI that should be used as the base for resolving URIs in references (\$ref) | ||
dynamicAnchors: {[Ref in string]?: true} | ||
readonly schemaPath: Code // the run-time expression that evaluates to the property name of the current schema | ||
@@ -136,2 +137,3 @@ readonly errSchemaPath: string // this is actual string, should not be changed to Code | ||
baseId: sch.baseId || rootId, | ||
dynamicAnchors: {}, | ||
schemaPath: nil, | ||
@@ -195,3 +197,3 @@ errSchemaPath: "#", | ||
ref: string | ||
): AnySchema | AnyValidateFunction | SchemaEnv | undefined { | ||
): AnySchema | SchemaEnv | undefined { | ||
ref = resolveUrl(baseId, ref) | ||
@@ -198,0 +200,0 @@ const schOrFunc = root.refs[ref] |
@@ -7,3 +7,3 @@ import {Name} from "./codegen" | ||
// args passed from referencing schema | ||
dataCxt: new Name("dataCxt"), // should not be used directly, it is destructured to the names below | ||
valCxt: new Name("valCxt"), // validation/data context - should not be used directly, it is destructured to the names below | ||
dataPath: new Name("dataPath"), | ||
@@ -13,2 +13,3 @@ parentData: new Name("parentData"), | ||
rootData: new Name("rootData"), // root data - same as the data passed to the first/top validation function | ||
dynamicAnchors: new Name("dynamicAnchors"), // used to support recursiveRef and dynamicRef | ||
// function scoped variables | ||
@@ -15,0 +16,0 @@ vErrors: new Name("vErrors"), // null or array of validation errors |
@@ -40,5 +40,13 @@ import type {AnySchema, AnySchemaObject} from "../types" | ||
const REF_KEYWORDS = new Set([ | ||
"$ref", | ||
"$recursiveRef", | ||
"$recursiveAnchor", | ||
"$dynamicRef", | ||
"$dynamicAnchor", | ||
]) | ||
function hasRef(schema: AnySchemaObject): boolean { | ||
for (const key in schema) { | ||
if (key === "$ref") return true | ||
if (REF_KEYWORDS.has(key)) return true | ||
const sch = schema[key] | ||
@@ -85,2 +93,4 @@ if (Array.isArray(sch) && sch.some(hasRef)) return true | ||
const ANCHOR = /^[a-z_][-a-z0-9._]*$/i | ||
export function getSchemaRefs(this: Ajv, schema: AnySchema): LocalRefs { | ||
@@ -92,2 +102,3 @@ if (typeof schema == "boolean") return {} | ||
const localRefs: LocalRefs = {} | ||
const schemaRefs: Set<string> = new Set() | ||
@@ -97,20 +108,33 @@ traverse(schema, {allKeys: true}, (sch, jsonPtr, _, parentJsonPtr) => { | ||
const fullPath = pathPrefix + jsonPtr | ||
let id = sch.$id | ||
let baseId = baseIds[parentJsonPtr] | ||
if (typeof id == "string") { | ||
id = baseId = normalizeId(baseId ? URI.resolve(baseId, id) : id) | ||
let schOrRef = this.refs[id] | ||
if (typeof sch.$id == "string") baseId = addRef.call(this, sch.$id) | ||
addAnchor.call(this, sch.$anchor) | ||
addAnchor.call(this, sch.$dynamicAnchor) | ||
baseIds[jsonPtr] = baseId | ||
function addRef(this: Ajv, ref: string): string { | ||
ref = normalizeId(baseId ? URI.resolve(baseId, ref) : ref) | ||
if (schemaRefs.has(ref)) throw ambiguos(ref) | ||
schemaRefs.add(ref) | ||
let schOrRef = this.refs[ref] | ||
if (typeof schOrRef == "string") schOrRef = this.refs[schOrRef] | ||
if (typeof schOrRef == "object") { | ||
checkAmbiguosId(sch, schOrRef.schema, id) | ||
} else if (id !== normalizeId(fullPath)) { | ||
if (id[0] === "#") { | ||
checkAmbiguosId(sch, localRefs[id], id) | ||
localRefs[id] = sch | ||
checkAmbiguosRef(sch, schOrRef.schema, ref) | ||
} else if (ref !== normalizeId(fullPath)) { | ||
if (ref[0] === "#") { | ||
checkAmbiguosRef(sch, localRefs[ref], ref) | ||
localRefs[ref] = sch | ||
} else { | ||
this.refs[id] = fullPath | ||
this.refs[ref] = fullPath | ||
} | ||
} | ||
return ref | ||
} | ||
baseIds[jsonPtr] = baseId | ||
function addAnchor(this: Ajv, anchor: unknown): void { | ||
if (typeof anchor == "string") { | ||
if (!ANCHOR.test(anchor)) throw new Error(`invalid anchor "${anchor}"`) | ||
addRef.call(this, `#${anchor}`) | ||
} | ||
} | ||
}) | ||
@@ -120,7 +144,9 @@ | ||
function checkAmbiguosId(sch1: AnySchema, sch2: AnySchema | undefined, id: string): void { | ||
if (sch2 !== undefined && !equal(sch1, sch2)) { | ||
throw new Error(`id "${id}" resolves to more than one schema`) | ||
} | ||
function checkAmbiguosRef(sch1: AnySchema, sch2: AnySchema | undefined, ref: string): void { | ||
if (sch2 !== undefined && !equal(sch1, sch2)) throw ambiguos(ref) | ||
} | ||
function ambiguos(ref: string): Error { | ||
return new Error(`reference "${ref}" resolves to more than one schema`) | ||
} | ||
} |
@@ -44,3 +44,3 @@ import type {AddedKeywordDefinition} from "../types" | ||
types: {...groups, integer: true, boolean: true, null: true}, | ||
rules: [groups.number, groups.string, {rules: []}, groups.array, groups.object], | ||
rules: [{rules: []}, groups.number, groups.string, groups.array, groups.object], | ||
all: {type: true, $comment: true}, | ||
@@ -47,0 +47,0 @@ keywords: {type: true, $comment: true}, |
@@ -30,12 +30,9 @@ import type {AnySchema} from "../../types" | ||
opts.code.es5 | ||
? gen.func(validateName, _`${N.data}, ${N.dataCxt}`, schemaEnv.$async, () => { | ||
? gen.func(validateName, _`${N.data}, ${N.valCxt}`, schemaEnv.$async, () => { | ||
gen.code(_`"use strict"; ${funcSourceUrl(schema, opts)}`) | ||
destructureDataCxtES5(gen) | ||
destructureValCxtES5(gen, opts) | ||
gen.code(body) | ||
}) | ||
: gen.func( | ||
validateName, | ||
_`${N.data}, {${N.dataPath}="", ${N.parentData}, ${N.parentDataProperty}, ${N.rootData}=${N.data}}={}`, | ||
schemaEnv.$async, | ||
() => gen.code(funcSourceUrl(schema, opts)).code(body) | ||
: gen.func(validateName, _`${N.data}, ${destructureValCxt(opts)}`, schemaEnv.$async, () => | ||
gen.code(funcSourceUrl(schema, opts)).code(body) | ||
) | ||
@@ -45,10 +42,17 @@ ) | ||
function destructureDataCxtES5(gen: CodeGen): void { | ||
function destructureValCxt(opts: InstanceOptions): Code { | ||
return _`{${N.dataPath}="", ${N.parentData}, ${N.parentDataProperty}, ${N.rootData}=${N.data}${ | ||
opts.dynamicRef ? _`, ${N.dynamicAnchors}={}` : nil | ||
}}={}` | ||
} | ||
function destructureValCxtES5(gen: CodeGen, opts: InstanceOptions): void { | ||
gen.if( | ||
N.dataCxt, | ||
N.valCxt, | ||
() => { | ||
gen.var(N.dataPath, _`${N.dataCxt}.${N.dataPath}`) | ||
gen.var(N.parentData, _`${N.dataCxt}.${N.parentData}`) | ||
gen.var(N.parentDataProperty, _`${N.dataCxt}.${N.parentDataProperty}`) | ||
gen.var(N.rootData, _`${N.dataCxt}.${N.rootData}`) | ||
gen.var(N.dataPath, _`${N.valCxt}.${N.dataPath}`) | ||
gen.var(N.parentData, _`${N.valCxt}.${N.parentData}`) | ||
gen.var(N.parentDataProperty, _`${N.valCxt}.${N.parentDataProperty}`) | ||
gen.var(N.rootData, _`${N.valCxt}.${N.rootData}`) | ||
if (opts.dynamicRef) gen.var(N.dynamicAnchors, _`${N.valCxt}.${N.dynamicAnchors}`) | ||
}, | ||
@@ -60,2 +64,3 @@ () => { | ||
gen.var(N.rootData, N.data) | ||
if (opts.dynamicRef) gen.var(N.dynamicAnchors, _`{}`) | ||
} | ||
@@ -62,0 +67,0 @@ ) |
@@ -44,2 +44,3 @@ import type {CodeGen, Code, Name, Scope} from "../compile/codegen" | ||
rootData: Record<string, any> | any[] | ||
dynamicAnchors: {[Ref in string]?: ValidateFunction} | ||
} | ||
@@ -46,0 +47,0 @@ |
@@ -25,3 +25,3 @@ import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" | ||
if (!Array.isArray(items)) { | ||
checkStrictMode(it, '"additionalItems" without "items" is ignored') | ||
checkStrictMode(it, '"additionalItems" is ignored when "items" is not an array of schemas') | ||
return | ||
@@ -28,0 +28,0 @@ } |
@@ -68,9 +68,10 @@ import type {AnySchema, SchemaMap} from "../types" | ||
const dataAndSchema = passSchema ? _`${schemaCode}, ${data}, ${topSchemaRef}${schemaPath}` : data | ||
const dataCxt = gen.object( | ||
const valCxt: [Name, Code | number][] = [ | ||
[N.dataPath, strConcat(N.dataPath, errorPath)], | ||
[N.parentData, it.parentData], | ||
[N.parentDataProperty, it.parentDataProperty], | ||
[N.rootData, N.rootData] | ||
) | ||
const args = _`${dataAndSchema}, ${dataCxt}` | ||
[N.rootData, N.rootData], | ||
] | ||
if (it.opts.dynamicRef) valCxt.push([N.dynamicAnchors, N.dynamicAnchors]) | ||
const args = _`${dataAndSchema}, ${gen.object(...valCxt)}` | ||
return context !== nil ? _`${func}.call(${context}, ${args})` : _`${func}(${args})` | ||
@@ -77,0 +78,0 @@ } |
@@ -5,4 +5,12 @@ import type {Vocabulary} from "../../types" | ||
const core: Vocabulary = ["$schema", "$id", "$defs", "definitions", idKeyword, refKeyword] | ||
const core: Vocabulary = [ | ||
"$schema", | ||
"$id", | ||
"$defs", | ||
"$vocabulary", | ||
"definitions", | ||
idKeyword, | ||
refKeyword, | ||
] | ||
export default core |
@@ -15,14 +15,13 @@ import type {CodeKeywordDefinition, AnySchema} from "../../types" | ||
const {gen, schema, it} = cxt | ||
const {allErrors, baseId, schemaEnv: env, opts, validateName, self} = it | ||
const passCxt = opts.passContext ? N.this : nil | ||
const {baseId, schemaEnv: env, validateName, self} = it | ||
if (schema === "#" || schema === "#/") return callRootRef() | ||
const schOrFunc = resolveRef.call(self, env.root, baseId, schema) | ||
if (schOrFunc === undefined) throw new MissingRefError(baseId, schema) | ||
if (schOrFunc instanceof SchemaEnv) return callValidate(schOrFunc) | ||
return inlineRefSchema(schOrFunc) | ||
const schOrEnv = resolveRef.call(self, env.root, baseId, schema) | ||
if (schOrEnv === undefined) throw new MissingRefError(baseId, schema) | ||
if (schOrEnv instanceof SchemaEnv) return callValidate(schOrEnv) | ||
return inlineRefSchema(schOrEnv) | ||
function callRootRef(): void { | ||
if (env === env.root) return callRef(validateName, env, env.$async) | ||
if (env === env.root) return callRef(cxt, validateName, env, env.$async) | ||
const rootName = gen.scopeValue("root", {ref: env.root}) | ||
return callRef(_`${rootName}.validate`, env.root, env.root.$async) | ||
return callRef(cxt, _`${rootName}.validate`, env.root, env.root.$async) | ||
} | ||
@@ -39,10 +38,5 @@ | ||
} | ||
callRef(v, sch, sch.$async) | ||
callRef(cxt, v, sch, sch.$async) | ||
} | ||
function callRef(v: Code, sch: SchemaEnv, $async?: boolean): void { | ||
if ($async) callAsyncRef(v, sch) | ||
else callSyncRef(v, sch) | ||
} | ||
function inlineRefSchema(sch: AnySchema): void { | ||
@@ -65,63 +59,71 @@ const schName = gen.scopeValue("schema", {ref: sch}) | ||
} | ||
}, | ||
} | ||
function callAsyncRef(v: Code, sch: SchemaEnv): void { | ||
if (!env.$async) throw new Error("async schema referenced by sync schema") | ||
const valid = gen.let("valid") | ||
gen.try( | ||
() => { | ||
gen.code(_`await ${callValidateCode(cxt, v, passCxt)}`) | ||
addEvaluatedFrom(v, sch) | ||
if (!allErrors) gen.assign(valid, true) | ||
}, | ||
(e) => { | ||
gen.if(_`!(${e} instanceof ${it.ValidationError as Name})`, () => gen.throw(e)) | ||
addErrorsFrom(e) | ||
if (!allErrors) gen.assign(valid, false) | ||
} | ||
) | ||
cxt.ok(valid) | ||
} | ||
export function callRef(cxt: KeywordCxt, v: Code, sch?: SchemaEnv, $async?: boolean): void { | ||
const {gen, it} = cxt | ||
const {allErrors, schemaEnv: env, opts} = it | ||
const passCxt = opts.passContext ? N.this : nil | ||
if ($async) callAsyncRef() | ||
else callSyncRef() | ||
function callSyncRef(v: Code, sch: SchemaEnv): void { | ||
cxt.result( | ||
callValidateCode(cxt, v, passCxt), | ||
() => addEvaluatedFrom(v, sch), | ||
() => addErrorsFrom(v) | ||
) | ||
} | ||
function callAsyncRef(): void { | ||
if (!env.$async) throw new Error("async schema referenced by sync schema") | ||
const valid = gen.let("valid") | ||
gen.try( | ||
() => { | ||
gen.code(_`await ${callValidateCode(cxt, v, passCxt)}`) | ||
addEvaluatedFrom(v) // TODO will not work with async, it has to be returned with the result | ||
if (!allErrors) gen.assign(valid, true) | ||
}, | ||
(e) => { | ||
gen.if(_`!(${e} instanceof ${it.ValidationError as Name})`, () => gen.throw(e)) | ||
addErrorsFrom(e) | ||
if (!allErrors) gen.assign(valid, false) | ||
} | ||
) | ||
cxt.ok(valid) | ||
} | ||
function addErrorsFrom(source: Code): void { | ||
const errs = _`${source}.errors` | ||
gen.assign(N.vErrors, _`${N.vErrors} === null ? ${errs} : ${N.vErrors}.concat(${errs})`) // TODO tagged | ||
gen.assign(N.errors, _`${N.vErrors}.length`) | ||
} | ||
function callSyncRef(): void { | ||
cxt.result( | ||
callValidateCode(cxt, v, passCxt), | ||
() => addEvaluatedFrom(v), | ||
() => addErrorsFrom(v) | ||
) | ||
} | ||
function addEvaluatedFrom(source: Code, sch: SchemaEnv): void { | ||
if (!it.opts.unevaluated) return | ||
const schEvaluated = sch.validate?.evaluated | ||
// TODO refactor | ||
if (it.props !== true) { | ||
if (schEvaluated && !schEvaluated.dynamicProps) { | ||
if (schEvaluated.props !== undefined) { | ||
it.props = mergeEvaluated.props(gen, schEvaluated.props, it.props) | ||
} | ||
} else { | ||
const props = gen.var("props", _`${source}.evaluated.props`) | ||
it.props = mergeEvaluated.props(gen, props, it.props, Name) | ||
function addErrorsFrom(source: Code): void { | ||
const errs = _`${source}.errors` | ||
gen.assign(N.vErrors, _`${N.vErrors} === null ? ${errs} : ${N.vErrors}.concat(${errs})`) // TODO tagged | ||
gen.assign(N.errors, _`${N.vErrors}.length`) | ||
} | ||
function addEvaluatedFrom(source: Code): void { | ||
if (!it.opts.unevaluated) return | ||
const schEvaluated = sch?.validate?.evaluated | ||
// TODO refactor | ||
if (it.props !== true) { | ||
if (schEvaluated && !schEvaluated.dynamicProps) { | ||
if (schEvaluated.props !== undefined) { | ||
it.props = mergeEvaluated.props(gen, schEvaluated.props, it.props) | ||
} | ||
} else { | ||
const props = gen.var("props", _`${source}.evaluated.props`) | ||
it.props = mergeEvaluated.props(gen, props, it.props, Name) | ||
} | ||
if (it.items !== true) { | ||
if (schEvaluated && !schEvaluated.dynamicItems) { | ||
if (schEvaluated.items !== undefined) { | ||
it.items = mergeEvaluated.items(gen, schEvaluated.items, it.items) | ||
} | ||
} else { | ||
const items = gen.var("items", _`${source}.evaluated.items`) | ||
it.items = mergeEvaluated.items(gen, items, it.items, Name) | ||
} | ||
if (it.items !== true) { | ||
if (schEvaluated && !schEvaluated.dynamicItems) { | ||
if (schEvaluated.items !== undefined) { | ||
it.items = mergeEvaluated.items(gen, schEvaluated.items, it.items) | ||
} | ||
} else { | ||
const items = gen.var("items", _`${source}.evaluated.items`) | ||
it.items = mergeEvaluated.items(gen, items, it.items, Name) | ||
} | ||
} | ||
}, | ||
} | ||
} | ||
export default def |
@@ -5,4 +5,4 @@ import type {TypeError} from "../compile/validate/dataType" | ||
import type {FormatError} from "./format/format" | ||
import type {UnevaluatedPropertiesError} from "./applicator/unevaluatedProperties" | ||
import type {UnevaluatedItemsError} from "./applicator/unevaluatedItems" | ||
import type {UnevaluatedPropertiesError} from "./unevaluated/unevaluatedProperties" | ||
import type {UnevaluatedItemsError} from "./unevaluated/unevaluatedItems" | ||
import type {DependentRequiredError} from "./validation/dependentRequired" | ||
@@ -9,0 +9,0 @@ |
{ | ||
"name": "ajv", | ||
"version": "7.0.0-beta.3", | ||
"version": "7.0.0-beta.4", | ||
"description": "Another JSON Schema Validator", | ||
@@ -23,3 +23,3 @@ "main": "dist/ajv.js", | ||
"bundle": "rm -rf bundle && node ./scripts/bundle.js", | ||
"build": "rm -rf dist && tsc && cp -r lib/refs dist", | ||
"build": "rm -rf dist && tsc && cp -r lib/refs dist && rm dist/refs/json-schema-2019-09/index.ts", | ||
"json-tests": "rm -rf spec/_json/*.js && node scripts/jsontests", | ||
@@ -77,3 +77,3 @@ "test-karma": "karma start", | ||
"@typescript-eslint/parser": "^3.8.0", | ||
"ajv-formats": "^0.3.2", | ||
"ajv-formats": "^0.5.0", | ||
"browserify": "^16.2.0", | ||
@@ -80,0 +80,0 @@ "chai": "^4.0.1", |
@@ -9,3 +9,3 @@ <img align="right" alt="Ajv logo" width="160" src="https://ajv.js.org/images/ajv_logo.png"> | ||
[![npm](https://img.shields.io/npm/v/ajv.svg)](https://www.npmjs.com/package/ajv) | ||
[![npm (beta)](https://img.shields.io/npm/v/ajv/beta)](https://www.npmjs.com/package/ajv/v/7.0.0-beta.3) | ||
[![npm (beta)](https://img.shields.io/npm/v/ajv/beta)](https://www.npmjs.com/package/ajv/v/7.0.0-beta.4) | ||
[![npm downloads](https://img.shields.io/npm/dm/ajv.svg)](https://www.npmjs.com/package/ajv) | ||
@@ -20,6 +20,7 @@ [![Coverage Status](https://coveralls.io/repos/github/ajv-validator/ajv/badge.svg?branch=master)](https://coveralls.io/github/ajv-validator/ajv?branch=master) | ||
- support of JSON Schema draft-2019-09 features: [`unevaluatedProperties`](./json-schema.md#unevaluatedproperties) and [`unevaluatedItems`](./json-schema.md#unevaluateditems), [dynamic recursive references](./validation.md#extending-recursive-schemas) and other [additional keywords](./json-schema.md#json-schema-draft-2019-09). | ||
- to reduce the mistakes in JSON schemas and unexpected validation results, [strict mode](./docs/strict-mode.md) is added - it prohibits ignored or ambiguous JSON Schema elements. | ||
- to make code injection from untrusted schemas impossible, [code generation](./docs/codegen.md) is fully re-written to be safe and to allow code optimization (compiled schema code size is reduced by more than 10%). | ||
- to simplify Ajv extensions, the new keyword API that is used by pre-defined keywords is available to user-defined keywords - it is much easier to define any keywords now, especially with subschemas. | ||
- schemas are compiled to ES6 code (ES5 code generation is supported with an option). | ||
- to simplify Ajv extensions, the new keyword API that is used by pre-defined keywords is available to user-defined keywords - it is much easier to define any keywords now, especially with subschemas. [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package was updated to use the new API (in [v4.0.0-beta.0](https://github.com/ajv-validator/ajv-keywords/releases/tag/v4.0.0-beta.0)) | ||
- schemas are compiled to ES6 code (ES5 code generation is also supported with an option). | ||
- to improve reliability and maintainability the code is migrated to TypeScript. | ||
@@ -26,0 +27,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
838351
321
13604
372