Comparing version 7.0.0-beta.2 to 7.0.0-beta.3
@@ -27,2 +27,4 @@ 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; | ||
$data?: boolean; | ||
@@ -29,0 +31,0 @@ allErrors?: boolean; |
@@ -27,2 +27,4 @@ "use strict"; | ||
const metadata_1 = require("./vocabularies/metadata"); | ||
const next_1 = __importDefault(require("./vocabularies/next")); | ||
const unevaluated_1 = __importDefault(require("./vocabularies/unevaluated")); | ||
const util_1 = require("./compile/util"); | ||
@@ -120,2 +122,6 @@ const data_json_1 = __importDefault(require("./refs/data.json")); | ||
this.addVocabulary(metadata_1.contentVocabulary); | ||
if (opts.next) | ||
this.addVocabulary(next_1.default); | ||
if (opts.unevaluated) | ||
this.addVocabulary(unevaluated_1.default); | ||
addDefaultMetaSchema.call(this); | ||
@@ -122,0 +128,0 @@ if (opts.keywords) |
import type { AddedKeywordDefinition, KeywordErrorCxt, KeywordCxtParams, AnySchemaObject } from "../types"; | ||
import { SchemaObjCxt } from "./index"; | ||
import { SchemaCxt, SchemaObjCxt } from "./index"; | ||
import { JSONType } from "./rules"; | ||
@@ -34,3 +34,5 @@ import { CodeGen, Code, Name } from "./codegen"; | ||
invalid$data(): Code; | ||
subschema(appl: SubschemaArgs, valid: Name): void; | ||
subschema(appl: SubschemaArgs, valid: Name): SchemaCxt; | ||
mergeEvaluated(schemaCxt: SchemaCxt, toName?: typeof Name): void; | ||
mergeValidEvaluated(schemaCxt: SchemaCxt, valid: Name): boolean | void; | ||
} |
@@ -147,4 +147,22 @@ "use strict"; | ||
subschema(appl, valid) { | ||
subschema_1.applySubschema(this.it, appl, valid); | ||
return subschema_1.applySubschema(this.it, appl, valid); | ||
} | ||
mergeEvaluated(schemaCxt, toName) { | ||
const { it, gen } = this; | ||
if (!it.opts.unevaluated) | ||
return; | ||
if (it.props !== true && schemaCxt.props !== undefined) { | ||
it.props = util_1.mergeEvaluated.props(gen, schemaCxt.props, it.props, toName); | ||
} | ||
if (it.items !== true && schemaCxt.items !== undefined) { | ||
it.items = util_1.mergeEvaluated.items(gen, schemaCxt.items, it.items, toName); | ||
} | ||
} | ||
mergeValidEvaluated(schemaCxt, valid) { | ||
const { it, gen } = this; | ||
if (it.opts.unevaluated && (it.props !== true || it.items !== true)) { | ||
gen.if(valid, () => this.mergeEvaluated(schemaCxt, codegen_1.Name)); | ||
return true; | ||
} | ||
} | ||
} | ||
@@ -151,0 +169,0 @@ exports.default = KeywordCxt; |
@@ -1,2 +0,2 @@ | ||
import type { AnySchema, AnySchemaObject, AnyValidateFunction } from "../types"; | ||
import type { AnySchema, AnySchemaObject, AnyValidateFunction, EvaluatedProperties, EvaluatedItems } from "../types"; | ||
import type Ajv from "../ajv"; | ||
@@ -22,2 +22,3 @@ import type { InstanceOptions } from "../ajv"; | ||
readonly validateName: Name; | ||
evaluated?: Name; | ||
readonly ValidationError?: Name; | ||
@@ -34,2 +35,4 @@ readonly schema: AnySchema; | ||
readonly compositeRule?: boolean; | ||
props?: EvaluatedProperties | Name; | ||
items?: EvaluatedItems | Name; | ||
readonly createErrors?: boolean; | ||
@@ -36,0 +39,0 @@ readonly opts: InstanceOptions; |
@@ -101,2 +101,11 @@ "use strict"; | ||
} | ||
if (this.opts.unevaluated) { | ||
const { props, items } = schemaCxt; | ||
validate.evaluated = { | ||
props: props instanceof codegen_1.Name ? undefined : props, | ||
items: items instanceof codegen_1.Name ? undefined : items, | ||
dynamicProps: props instanceof codegen_1.Name, | ||
dynamicItems: items instanceof codegen_1.Name, | ||
}; | ||
} | ||
sch.validate = validate; | ||
@@ -103,0 +112,0 @@ return sch; |
@@ -19,3 +19,3 @@ "use strict"; | ||
types: { ...groups, integer: true, boolean: true, null: true }, | ||
rules: [groups.number, groups.string, groups.array, groups.object, { rules: [] }], | ||
rules: [groups.number, groups.string, { rules: [] }, groups.array, groups.object], | ||
all: { type: true, $comment: true }, | ||
@@ -22,0 +22,0 @@ keywords: { type: true, $comment: true }, |
import type { AnySchema } from "../types"; | ||
import type { SchemaObjCxt } from "./index"; | ||
import type { SchemaObjCxt, SchemaCxt } from "./index"; | ||
import { Code, Name } from "./codegen"; | ||
@@ -26,2 +26,2 @@ import { JSONType } from "./rules"; | ||
}>; | ||
export declare function applySubschema(it: SchemaObjCxt, appl: SubschemaArgs, valid: Name): void; | ||
export declare function applySubschema(it: SchemaObjCxt, appl: SubschemaArgs, valid: Name): SchemaCxt; |
@@ -16,4 +16,5 @@ "use strict"; | ||
extendSubschemaMode(subschema, appl); | ||
const nextContext = { ...it, ...subschema }; | ||
const nextContext = { ...it, ...subschema, items: undefined, props: undefined }; | ||
validate_1.subschemaCode(nextContext, valid); | ||
return nextContext; | ||
} | ||
@@ -20,0 +21,0 @@ exports.applySubschema = applySubschema; |
@@ -1,4 +0,4 @@ | ||
import type { AnySchema } from "../types"; | ||
import type { AnySchema, EvaluatedProperties, EvaluatedItems } from "../types"; | ||
import type { SchemaCxt, SchemaObjCxt } from "."; | ||
import { Code } from "./codegen"; | ||
import { Code, Name, CodeGen } from "./codegen"; | ||
import type { Rule, ValidationRules } from "./rules"; | ||
@@ -21,1 +21,13 @@ export declare function toHash<T extends string = string>(arr: T[]): { | ||
export declare function ucs2length(str: string): number; | ||
declare type SomeEvaluated = EvaluatedProperties | EvaluatedItems; | ||
declare type MergeEvaluatedFunc<T extends SomeEvaluated> = (gen: CodeGen, from: Name | T, to: Name | Exclude<T, true> | undefined, toName?: typeof Name) => Name | T; | ||
interface MergeEvaluated { | ||
props: MergeEvaluatedFunc<EvaluatedProperties>; | ||
items: MergeEvaluatedFunc<EvaluatedItems>; | ||
} | ||
export declare const mergeEvaluated: MergeEvaluated; | ||
export declare function evaluatedPropsToName(gen: CodeGen, ps?: EvaluatedProperties): Name; | ||
export declare function setEvaluated(gen: CodeGen, props: Name, ps: { | ||
[K in string]?: true; | ||
}): void; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.ucs2length = exports.eachItem = exports.unescapeJsonPointer = exports.escapeJsonPointer = exports.escapeFragment = exports.unescapeFragment = exports.schemaRefOrVal = exports.schemaHasRulesButRef = exports.schemaHasRules = exports.checkUnknownRules = exports.alwaysValidSchema = exports.toHash = void 0; | ||
exports.setEvaluated = exports.evaluatedPropsToName = exports.mergeEvaluated = exports.ucs2length = exports.eachItem = exports.unescapeJsonPointer = exports.escapeJsonPointer = exports.escapeFragment = exports.unescapeFragment = exports.schemaRefOrVal = exports.schemaHasRulesButRef = exports.schemaHasRules = exports.checkUnknownRules = exports.alwaysValidSchema = exports.toHash = void 0; | ||
const codegen_1 = require("./codegen"); | ||
@@ -112,2 +112,51 @@ const validate_1 = require("./validate"); | ||
exports.ucs2length = ucs2length; | ||
function makeMergeEvaluated({ mergeNames, mergeToName, mergeValues, resultToName, }) { | ||
return (gen, from, to, toName) => { | ||
const res = to === undefined | ||
? from | ||
: to instanceof codegen_1.Name | ||
? (from instanceof codegen_1.Name ? mergeNames(gen, from, to) : mergeToName(gen, from, to), to) | ||
: from instanceof codegen_1.Name | ||
? (mergeToName(gen, to, from), from) | ||
: mergeValues(from, to); | ||
return toName === codegen_1.Name && !(res instanceof codegen_1.Name) ? resultToName(gen, res) : res; | ||
}; | ||
} | ||
exports.mergeEvaluated = { | ||
props: makeMergeEvaluated({ | ||
mergeNames: (gen, from, to) => gen.if(codegen_1._ `${to} !== true && ${from} !== undefined`, () => { | ||
gen.if(codegen_1._ `${from} === true`, () => gen.assign(to, true), () => gen.code(codegen_1._ `Object.assign(${to}, ${from})`)); | ||
}), | ||
mergeToName: (gen, from, to) => gen.if(codegen_1._ `${to} !== true`, () => { | ||
if (from === true) { | ||
gen.assign(to, true); | ||
} | ||
else { | ||
gen.assign(to, codegen_1._ `${to} || {}`); | ||
setEvaluated(gen, to, from); | ||
} | ||
}), | ||
mergeValues: (from, to) => (from === true ? true : { ...from, ...to }), | ||
resultToName: evaluatedPropsToName, | ||
}), | ||
items: makeMergeEvaluated({ | ||
mergeNames: (gen, from, to) => gen.if(codegen_1._ `${to} !== true && ${from} !== undefined`, () => gen.assign(to, codegen_1._ `${from} === true ? true : ${to} > ${from} ? ${to} : ${from}`)), | ||
mergeToName: (gen, from, to) => gen.if(codegen_1._ `${to} !== true`, () => gen.assign(to, from === true ? true : codegen_1._ `${to} > ${from} ? ${to} : ${from}`)), | ||
mergeValues: (from, to) => (from === true ? true : Math.max(from, to)), | ||
resultToName: (gen, items) => gen.var("items", items), | ||
}), | ||
}; | ||
function evaluatedPropsToName(gen, ps) { | ||
if (ps === true) | ||
return gen.var("props", true); | ||
const props = gen.var("props", codegen_1._ `{}`); | ||
if (ps !== undefined) | ||
setEvaluated(gen, props, ps); | ||
return props; | ||
} | ||
exports.evaluatedPropsToName = evaluatedPropsToName; | ||
function setEvaluated(gen, props, ps) { | ||
Object.keys(ps).forEach((p) => gen.assign(codegen_1._ `${props}${codegen_1.getProperty(p)}`, true)); | ||
} | ||
exports.setEvaluated = setEvaluated; | ||
//# sourceMappingURL=util.js.map |
@@ -56,2 +56,4 @@ "use strict"; | ||
gen.let(names_1.default.errors, 0); | ||
if (opts.unevaluated) | ||
resetEvaluated(it); | ||
typeAndKeywords(it); | ||
@@ -62,2 +64,9 @@ returnResults(it); | ||
} | ||
function resetEvaluated(it) { | ||
// TODO maybe some hook to execute it in the end to check whether props/items are Name, as in assignEvaluated | ||
const { gen, validateName } = it; | ||
it.evaluated = gen.const("evaluated", codegen_1._ `${validateName}.evaluated`); | ||
gen.if(codegen_1._ `${it.evaluated}.dynamicProps`, () => gen.assign(codegen_1._ `${it.evaluated}.props`, codegen_1._ `undefined`)); | ||
gen.if(codegen_1._ `${it.evaluated}.dynamicItems`, () => gen.assign(codegen_1._ `${it.evaluated}.items`, codegen_1._ `undefined`)); | ||
} | ||
function funcSourceUrl(schema, opts) { | ||
@@ -143,4 +152,6 @@ return typeof schema == "object" && schema.$id && (opts.code.source || opts.code.process) | ||
} | ||
function returnResults({ gen, schemaEnv, validateName, ValidationError }) { | ||
function returnResults(it) { | ||
const { gen, schemaEnv, validateName, ValidationError, opts } = it; | ||
if (schemaEnv.$async) { | ||
// TODO assign unevaluated | ||
gen.if(codegen_1._ `${names_1.default.errors} === 0`, () => gen.return(names_1.default.data), () => gen.throw(codegen_1._ `new ${ValidationError}(${names_1.default.vErrors})`)); | ||
@@ -150,5 +161,13 @@ } | ||
gen.assign(codegen_1._ `${validateName}.errors`, names_1.default.vErrors); | ||
if (opts.unevaluated) | ||
assignEvaluated(it); | ||
gen.return(codegen_1._ `${names_1.default.errors} === 0`); | ||
} | ||
} | ||
function assignEvaluated({ gen, evaluated, props, items }) { | ||
if (props instanceof codegen_1.Name) | ||
gen.assign(codegen_1._ `${evaluated}.props`, props); | ||
if (items instanceof codegen_1.Name) | ||
gen.assign(codegen_1._ `${evaluated}.items`, items); | ||
} | ||
function checkStrictMode(it, msg, mode = it.opts.strict) { | ||
@@ -155,0 +174,0 @@ if (!mode) |
@@ -39,2 +39,3 @@ import type { CodeGen, Code, Name, Scope } from "../compile/codegen"; | ||
errors?: null | ErrorObject[]; | ||
evaluated?: Evaluated; | ||
schema: AnySchema; | ||
@@ -44,2 +45,12 @@ schemaEnv: SchemaEnv; | ||
} | ||
export declare type EvaluatedProperties = { | ||
[K in string]?: true; | ||
} | true; | ||
export declare type EvaluatedItems = number | true; | ||
export interface Evaluated { | ||
props?: EvaluatedProperties; | ||
items?: EvaluatedItems; | ||
dynamicProps: boolean; | ||
dynamicItems: boolean; | ||
} | ||
export interface AsyncValidateFunction<T = unknown> extends ValidateFunction<T> { | ||
@@ -46,0 +57,0 @@ (...args: Parameters<ValidateFunction<T>>): Promise<T>; |
@@ -19,3 +19,2 @@ "use strict"; | ||
const { gen, schema, parentSchema, data, it } = cxt; | ||
const len = gen.const("len", codegen_1._ `${data}.length`); | ||
const { items } = parentSchema; | ||
@@ -26,2 +25,4 @@ if (!Array.isArray(items)) { | ||
} | ||
it.items = true; | ||
const len = gen.const("len", codegen_1._ `${data}.length`); | ||
if (schema === false) { | ||
@@ -28,0 +29,0 @@ cxt.setParams({ len: items.length }); |
@@ -28,2 +28,3 @@ "use strict"; | ||
const { allErrors, opts } = it; | ||
it.props = true; | ||
if (opts.removeAdditional !== "all" && util_1.alwaysValidSchema(it, schema)) | ||
@@ -34,4 +35,3 @@ return; | ||
checkAdditionalProperties(); | ||
if (!allErrors) | ||
gen.if(codegen_1._ `${errsCount} === ${names_1.default.errors}`); | ||
cxt.ok(codegen_1._ `${errsCount} === ${names_1.default.errors}`); | ||
function checkAdditionalProperties() { | ||
@@ -38,0 +38,0 @@ gen.forIn("key", data, (key) => { |
@@ -16,4 +16,5 @@ "use strict"; | ||
return; | ||
cxt.subschema({ keyword: "allOf", schemaProp: i }, valid); | ||
const schCxt = cxt.subschema({ keyword: "allOf", schemaProp: i }, valid); | ||
cxt.ok(valid); | ||
cxt.mergeEvaluated(schCxt); | ||
}); | ||
@@ -20,0 +21,0 @@ }, |
@@ -15,17 +15,19 @@ "use strict"; | ||
const alwaysValid = schema.some((sch) => util_1.alwaysValidSchema(it, sch)); | ||
if (alwaysValid) | ||
if (alwaysValid && !it.opts.unevaluated) | ||
return; | ||
const valid = gen.let("valid", false); | ||
const schValid = gen.name("_valid"); | ||
gen.block(() => { | ||
schema.forEach((_sch, i) => { | ||
cxt.subschema({ | ||
keyword: "anyOf", | ||
schemaProp: i, | ||
compositeRule: true, | ||
}, schValid); | ||
gen.assign(valid, codegen_1._ `${valid} || ${schValid}`); | ||
gen.block(() => schema.forEach((_sch, i) => { | ||
const schCxt = cxt.subschema({ | ||
keyword: "anyOf", | ||
schemaProp: i, | ||
compositeRule: true, | ||
}, schValid); | ||
gen.assign(valid, codegen_1._ `${valid} || ${schValid}`); | ||
const merged = cxt.mergeValidEvaluated(schCxt, schValid); | ||
// can short-circuit if `unevaluatedProperties/Items` not supported (opts.unevaluated !== true) | ||
// or if all properties and items were evaluated (it.props === true && it.items === true) | ||
if (!merged) | ||
gen.if(codegen_1.not(valid)); | ||
}); | ||
}, schema.length); | ||
})); | ||
cxt.result(valid, () => cxt.reset(), () => cxt.error(true)); | ||
@@ -32,0 +34,0 @@ }, |
@@ -1,3 +0,7 @@ | ||
import type { CodeKeywordDefinition } from "../../types"; | ||
import type { CodeKeywordDefinition, ErrorObject } from "../../types"; | ||
export declare type ContainsError = ErrorObject<"contains", { | ||
minContains: number; | ||
maxContains?: number; | ||
}>; | ||
declare const def: CodeKeywordDefinition; | ||
export default def; |
@@ -6,2 +6,9 @@ "use strict"; | ||
const util_1 = require("../../compile/util"); | ||
const validate_1 = require("../../compile/validate"); | ||
const error = { | ||
message: ({ params: { min, max } }) => max === undefined | ||
? codegen_1.str `should contain at least ${min} valid item(s)` | ||
: codegen_1.str `should contain at least ${min} and no more than ${max} valid item(s)`, | ||
params: ({ params: { min, max } }) => max === undefined ? codegen_1._ `{minContains: ${min}}` : codegen_1._ `{minContains: ${min}, maxContains: ${max}}`, | ||
}; | ||
const def = { | ||
@@ -13,25 +20,72 @@ keyword: "contains", | ||
trackErrors: true, | ||
error, | ||
code(cxt) { | ||
const { gen, schema, data, it } = cxt; | ||
const { gen, schema, parentSchema, data, it } = cxt; | ||
let min; | ||
let max; | ||
const { minContains, maxContains } = parentSchema; | ||
if (it.opts.next) { | ||
min = minContains === undefined ? 1 : minContains; | ||
max = maxContains; | ||
} | ||
else { | ||
min = 1; | ||
} | ||
const len = gen.const("len", codegen_1._ `${data}.length`); | ||
cxt.setParams({ min, max }); | ||
if (max === undefined && min === 0) { | ||
validate_1.checkStrictMode(it, `"minContains" == 0 without "maxContains": "contains" keyword ignored`); | ||
return; | ||
} | ||
if (max !== undefined && min > max) { | ||
validate_1.checkStrictMode(it, `"minContains" > "maxContains" is always invalid`); | ||
cxt.fail(); | ||
return; | ||
} | ||
if (util_1.alwaysValidSchema(it, schema)) { | ||
cxt.fail(codegen_1._ `${data}.length === 0`); | ||
let cond = codegen_1._ `${len} >= ${min}`; | ||
if (max !== undefined) | ||
cond = codegen_1._ `${cond} && ${len} <= ${max}`; | ||
cxt.pass(cond); | ||
return; | ||
} | ||
it.items = true; | ||
const valid = gen.name("valid"); | ||
gen.forRange("i", 0, codegen_1._ `${data}.length`, (i) => { | ||
cxt.subschema({ | ||
keyword: "contains", | ||
dataProp: i, | ||
dataPropType: subschema_1.Type.Num, | ||
compositeRule: true, | ||
}, valid); | ||
gen.if(valid, () => gen.break()); | ||
}); | ||
if (max === undefined && min === 1) { | ||
validateItems(valid, () => gen.if(valid, () => gen.break())); | ||
} | ||
else { | ||
gen.let(valid, false); | ||
const schValid = gen.name("_valid"); | ||
const count = gen.let("count", 0); | ||
validateItems(schValid, () => gen.if(schValid, () => checkLimits(count))); | ||
} | ||
cxt.result(valid, () => cxt.reset()); | ||
function validateItems(_valid, block) { | ||
gen.forRange("i", 0, len, (i) => { | ||
cxt.subschema({ | ||
keyword: "contains", | ||
dataProp: i, | ||
dataPropType: subschema_1.Type.Num, | ||
compositeRule: true, | ||
}, _valid); | ||
block(); | ||
}); | ||
} | ||
function checkLimits(count) { | ||
gen.code(codegen_1._ `${count}++`); | ||
if (max === undefined) { | ||
gen.if(codegen_1._ `${count} >= ${min}`, () => gen.assign(valid, true).break()); | ||
} | ||
else { | ||
gen.if(codegen_1._ `${count} > ${max}`, () => gen.assign(valid, false).break()); | ||
if (min === 1) | ||
gen.assign(valid, true); | ||
else | ||
gen.if(codegen_1._ `${count} >= ${min}`, () => gen.assign(valid, true)); | ||
} | ||
} | ||
}, | ||
error: { | ||
message: "should contain a valid item", | ||
}, | ||
}; | ||
exports.default = def; | ||
//# sourceMappingURL=contains.js.map |
@@ -1,2 +0,3 @@ | ||
import type { CodeKeywordDefinition, ErrorObject } from "../../types"; | ||
import type { CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition, SchemaMap } from "../../types"; | ||
import type KeywordCxt from "../../compile/context"; | ||
export declare type DependenciesError = ErrorObject<"dependencies", { | ||
@@ -8,3 +9,8 @@ property: string; | ||
}>; | ||
export declare const error: KeywordErrorDefinition; | ||
declare const def: CodeKeywordDefinition; | ||
export declare function validatePropertyDeps(cxt: KeywordCxt, propertyDeps?: { | ||
[x: string]: string[]; | ||
}): void; | ||
export declare function validateSchemaDeps(cxt: KeywordCxt, schemaDeps?: SchemaMap): void; | ||
export default def; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.validateSchemaDeps = exports.validatePropertyDeps = exports.error = void 0; | ||
const codegen_1 = require("../../compile/codegen"); | ||
const util_1 = require("../../compile/util"); | ||
const code_1 = require("../code"); | ||
const error = { | ||
exports.error = { | ||
message: ({ params: { property, depsCount, deps } }) => { | ||
@@ -20,60 +21,66 @@ const property_ies = depsCount === 1 ? "property" : "properties"; | ||
schemaType: "object", | ||
error, | ||
error: exports.error, | ||
code(cxt) { | ||
const { gen, schema, data, it } = cxt; | ||
const [propDeps, schDeps] = splitDependencies(); | ||
const valid = gen.name("valid"); | ||
validatePropertyDeps(propDeps); | ||
validateSchemaDeps(schDeps); | ||
function splitDependencies() { | ||
const propertyDeps = {}; | ||
const schemaDeps = {}; | ||
for (const key in schema) { | ||
if (key === "__proto__") | ||
continue; | ||
const deps = Array.isArray(schema[key]) ? propertyDeps : schemaDeps; | ||
deps[key] = schema[key]; | ||
} | ||
return [propertyDeps, schemaDeps]; | ||
} | ||
function validatePropertyDeps(propertyDeps) { | ||
if (Object.keys(propertyDeps).length === 0) | ||
return; | ||
const missing = gen.let("missing"); | ||
for (const prop in propertyDeps) { | ||
const deps = propertyDeps[prop]; | ||
if (deps.length === 0) | ||
continue; | ||
const hasProperty = code_1.propertyInData(data, prop, it.opts.ownProperties); | ||
cxt.setParams({ | ||
property: prop, | ||
depsCount: deps.length, | ||
deps: deps.join(", "), | ||
}); | ||
if (it.allErrors) { | ||
gen.if(hasProperty, () => { | ||
for (const depProp of deps) { | ||
code_1.checkReportMissingProp(cxt, depProp); | ||
} | ||
}); | ||
const [propDeps, schDeps] = splitDependencies(cxt); | ||
validatePropertyDeps(cxt, propDeps); | ||
validateSchemaDeps(cxt, schDeps); | ||
}, | ||
}; | ||
function splitDependencies({ schema }) { | ||
const propertyDeps = {}; | ||
const schemaDeps = {}; | ||
for (const key in schema) { | ||
if (key === "__proto__") | ||
continue; | ||
const deps = Array.isArray(schema[key]) ? propertyDeps : schemaDeps; | ||
deps[key] = schema[key]; | ||
} | ||
return [propertyDeps, schemaDeps]; | ||
} | ||
function validatePropertyDeps(cxt, propertyDeps = cxt.schema) { | ||
const { gen, data, it } = cxt; | ||
if (Object.keys(propertyDeps).length === 0) | ||
return; | ||
const missing = gen.let("missing"); | ||
for (const prop in propertyDeps) { | ||
const deps = propertyDeps[prop]; | ||
if (deps.length === 0) | ||
continue; | ||
const hasProperty = code_1.propertyInData(data, prop, it.opts.ownProperties); | ||
cxt.setParams({ | ||
property: prop, | ||
depsCount: deps.length, | ||
deps: deps.join(", "), | ||
}); | ||
if (it.allErrors) { | ||
gen.if(hasProperty, () => { | ||
for (const depProp of deps) { | ||
code_1.checkReportMissingProp(cxt, depProp); | ||
} | ||
else { | ||
gen.if(codegen_1._ `${hasProperty} && (${code_1.checkMissingProp(cxt, deps, missing)})`); | ||
code_1.reportMissingProp(cxt, missing); | ||
gen.else(); | ||
} | ||
} | ||
}); | ||
} | ||
function validateSchemaDeps(schemaDeps) { | ||
for (const prop in schemaDeps) { | ||
if (util_1.alwaysValidSchema(it, schemaDeps[prop])) | ||
continue; | ||
gen.if(code_1.propertyInData(data, prop, it.opts.ownProperties), () => cxt.subschema({ keyword: "dependencies", schemaProp: prop }, valid), () => gen.var(valid, true) // TODO var | ||
); | ||
cxt.ok(valid); | ||
} | ||
else { | ||
gen.if(codegen_1._ `${hasProperty} && (${code_1.checkMissingProp(cxt, deps, missing)})`); | ||
code_1.reportMissingProp(cxt, missing); | ||
gen.else(); | ||
} | ||
}, | ||
}; | ||
} | ||
} | ||
exports.validatePropertyDeps = validatePropertyDeps; | ||
function validateSchemaDeps(cxt, schemaDeps = cxt.schema) { | ||
const { gen, data, keyword, it } = cxt; | ||
const valid = gen.name("valid"); | ||
for (const prop in schemaDeps) { | ||
if (util_1.alwaysValidSchema(it, schemaDeps[prop])) | ||
continue; | ||
gen.if(code_1.propertyInData(data, prop, it.opts.ownProperties), () => { | ||
const schCxt = cxt.subschema({ keyword, schemaProp: prop }, valid); | ||
cxt.mergeValidEvaluated(schCxt, valid); | ||
}, () => gen.var(valid, true) // TODO var | ||
); | ||
cxt.ok(valid); | ||
} | ||
} | ||
exports.validateSchemaDeps = validateSchemaDeps; | ||
exports.default = def; | ||
//# sourceMappingURL=dependencies.js.map |
@@ -41,3 +41,3 @@ "use strict"; | ||
function validateIf() { | ||
cxt.subschema({ | ||
const schCxt = cxt.subschema({ | ||
keyword: "if", | ||
@@ -48,7 +48,9 @@ compositeRule: true, | ||
}, schValid); | ||
cxt.mergeEvaluated(schCxt); | ||
} | ||
function validateClause(keyword, ifClause) { | ||
return () => { | ||
cxt.subschema({ keyword }, schValid); | ||
const schCxt = cxt.subschema({ keyword }, schValid); | ||
gen.assign(valid, schValid); | ||
cxt.mergeValidEvaluated(schCxt, valid); | ||
if (ifClause) | ||
@@ -55,0 +57,0 @@ gen.assign(ifClause, codegen_1._ `${keyword}`); |
import type { ErrorObject, Vocabulary } from "../../types"; | ||
import { AdditionalItemsError } from "./additionalItems"; | ||
import { ContainsError } from "./contains"; | ||
import { DependenciesError } from "./dependencies"; | ||
@@ -10,3 +11,3 @@ import { PropertyNamesError } from "./propertyNames"; | ||
export default applicator; | ||
export declare type ApplicatorKeywordError = ErrorWithoutParams | AdditionalItemsError | AdditionalPropertiesError | DependenciesError | IfKeywordError | OneOfError | PropertyNamesError; | ||
export declare type ErrorWithoutParams = ErrorObject<"anyOf" | "contains" | "not" | "false schema", Record<string, never>>; | ||
export declare type ApplicatorKeywordError = ErrorWithoutParams | AdditionalItemsError | ContainsError | AdditionalPropertiesError | DependenciesError | IfKeywordError | OneOfError | PropertyNamesError; | ||
export declare type ErrorWithoutParams = ErrorObject<"anyOf" | "not" | "false schema", Record<string, never>>; |
@@ -21,2 +21,9 @@ "use strict"; | ||
const applicator = [ | ||
// any | ||
not_1.default, | ||
anyOf_1.default, | ||
oneOf_1.default, | ||
allOf_1.default, | ||
if_1.default, | ||
thenElse_1.default, | ||
// array | ||
@@ -27,16 +34,9 @@ additionalItems_1.default, | ||
// object | ||
dependencies_1.default, | ||
propertyNames_1.default, | ||
additionalProperties_1.default, | ||
dependencies_1.default, | ||
properties_1.default, | ||
patternProperties_1.default, | ||
// any | ||
not_1.default, | ||
anyOf_1.default, | ||
oneOf_1.default, | ||
allOf_1.default, | ||
if_1.default, | ||
thenElse_1.default, | ||
]; | ||
exports.default = applicator; | ||
//# sourceMappingURL=index.js.map |
@@ -16,6 +16,11 @@ "use strict"; | ||
if (Array.isArray(schema)) { | ||
if (it.opts.unevaluated && schema.length && it.items !== true) { | ||
it.items = util_1.mergeEvaluated.items(gen, schema.length, it.items); | ||
} | ||
validateTuple(schema); | ||
} | ||
else if (!util_1.alwaysValidSchema(it, schema)) { | ||
validateArray(); | ||
else { | ||
it.items = true; | ||
if (!util_1.alwaysValidSchema(it, schema)) | ||
validateArray(); | ||
} | ||
@@ -22,0 +27,0 @@ function validateTuple(schArr) { |
@@ -29,2 +29,3 @@ "use strict"; | ||
schArr.forEach((sch, i) => { | ||
let schCxt; | ||
if (util_1.alwaysValidSchema(it, sch)) { | ||
@@ -34,3 +35,3 @@ gen.var(schValid, true); | ||
else { | ||
cxt.subschema({ | ||
schCxt = cxt.subschema({ | ||
keyword: "oneOf", | ||
@@ -48,3 +49,8 @@ schemaProp: i, | ||
} | ||
gen.if(schValid, () => gen.assign(valid, true).assign(passing, i)); | ||
gen.if(schValid, () => { | ||
gen.assign(valid, true); | ||
gen.assign(passing, i); | ||
if (schCxt) | ||
cxt.mergeEvaluated(schCxt, codegen_1.Name); | ||
}); | ||
}); | ||
@@ -51,0 +57,0 @@ } |
@@ -7,2 +7,3 @@ "use strict"; | ||
const validate_1 = require("../../compile/validate"); | ||
const util_1 = require("../../compile/util"); | ||
const def = { | ||
@@ -16,2 +17,3 @@ keyword: "patternProperties", | ||
const patterns = code_1.schemaProperties(it, schema); | ||
// TODO mark properties matching patterns with always valid schemas as evaluated | ||
if (patterns.length === 0) | ||
@@ -21,2 +23,6 @@ return; | ||
const valid = gen.name("valid"); | ||
if (it.props !== true && !(it.props instanceof codegen_1.Name)) { | ||
it.props = util_1.evaluatedPropsToName(gen, it.props); | ||
} | ||
const { props } = it; | ||
validatePatternProperties(); | ||
@@ -54,4 +60,10 @@ function validatePatternProperties() { | ||
}, valid); | ||
if (!it.allErrors) | ||
if (it.opts.unevaluated && props !== true) { | ||
gen.assign(codegen_1._ `${props}[${key}]`, true); | ||
} | ||
else if (!it.allErrors) { | ||
// can short-circuit if `unevaluatedProperties` is not supported (opts.next === false) | ||
// or if all properties were evaluated (props === true) | ||
gen.if(codegen_1.not(valid), () => gen.break()); | ||
} | ||
}); | ||
@@ -58,0 +70,0 @@ }); |
@@ -8,2 +8,3 @@ "use strict"; | ||
const code_1 = require("../code"); | ||
const util_1 = require("../../compile/util"); | ||
const additionalProperties_1 = __importDefault(require("./additionalProperties")); | ||
@@ -19,3 +20,7 @@ const def = { | ||
} | ||
const properties = code_1.schemaProperties(it, schema); | ||
const allProps = code_1.allSchemaProperties(schema); | ||
if (it.opts.unevaluated && allProps.length && it.props !== true) { | ||
it.props = util_1.mergeEvaluated.props(gen, util_1.toHash(allProps), it.props); | ||
} | ||
const properties = allProps.filter((p) => !util_1.alwaysValidSchema(it, schema[p])); | ||
if (properties.length === 0) | ||
@@ -22,0 +27,0 @@ return; |
@@ -11,2 +11,3 @@ "use strict"; | ||
const compile_1 = require("../../compile"); | ||
const util_1 = require("../../compile/util"); | ||
const def = { | ||
@@ -29,5 +30,5 @@ keyword: "$ref", | ||
if (env === env.root) | ||
return callRef(validateName, env.$async); | ||
return callRef(validateName, env, env.$async); | ||
const rootName = gen.scopeValue("root", { ref: env.root }); | ||
return callRef(codegen_1._ `${rootName}.validate`, env.root.$async); | ||
return callRef(codegen_1._ `${rootName}.validate`, env.root, env.root.$async); | ||
} | ||
@@ -44,9 +45,9 @@ function callValidate(sch) { | ||
} | ||
callRef(v, sch.$async); | ||
callRef(v, sch, sch.$async); | ||
} | ||
function callRef(v, $async) { | ||
function callRef(v, sch, $async) { | ||
if ($async) | ||
callAsyncRef(v); | ||
callAsyncRef(v, sch); | ||
else | ||
callSyncRef(v); | ||
callSyncRef(v, sch); | ||
} | ||
@@ -56,3 +57,3 @@ function inlineRefSchema(sch) { | ||
const valid = gen.name("valid"); | ||
cxt.subschema({ | ||
const schCxt = cxt.subschema({ | ||
schema: sch, | ||
@@ -65,5 +66,6 @@ strictSchema: true, | ||
}, valid); | ||
cxt.mergeEvaluated(schCxt); | ||
cxt.ok(valid); | ||
} | ||
function callAsyncRef(v) { | ||
function callAsyncRef(v, sch) { | ||
if (!env.$async) | ||
@@ -74,2 +76,3 @@ throw new Error("async schema referenced by sync schema"); | ||
gen.code(codegen_1._ `await ${code_1.callValidateCode(cxt, v, passCxt)}`); | ||
addEvaluatedFrom(v, sch); | ||
if (!allErrors) | ||
@@ -85,4 +88,4 @@ gen.assign(valid, true); | ||
} | ||
function callSyncRef(v) { | ||
cxt.pass(code_1.callValidateCode(cxt, v, passCxt), () => addErrorsFrom(v)); | ||
function callSyncRef(v, sch) { | ||
cxt.result(code_1.callValidateCode(cxt, v, passCxt), () => addEvaluatedFrom(v, sch), () => addErrorsFrom(v)); | ||
} | ||
@@ -94,2 +97,31 @@ function addErrorsFrom(source) { | ||
} | ||
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); | ||
} | ||
} | ||
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); | ||
} | ||
} | ||
} | ||
}, | ||
@@ -96,0 +128,0 @@ }; |
@@ -5,2 +5,5 @@ import type { TypeError } from "../compile/validate/dataType"; | ||
import type { FormatError } from "./format/format"; | ||
export declare type DefinedError = TypeError | ApplicatorKeywordError | ValidationKeywordError | FormatError; | ||
import type { UnevaluatedPropertiesError } from "./applicator/unevaluatedProperties"; | ||
import type { UnevaluatedItemsError } from "./applicator/unevaluatedItems"; | ||
import type { DependentRequiredError } from "./validation/dependentRequired"; | ||
export declare type DefinedError = TypeError | ApplicatorKeywordError | ValidationKeywordError | FormatError | UnevaluatedPropertiesError | UnevaluatedItemsError | DependentRequiredError; |
@@ -21,2 +21,3 @@ "use strict"; | ||
}); | ||
// TODO optimize for scalar values in schema | ||
cxt.fail$data(codegen_1._ `!${eql}(${cxt.data}, ${cxt.schemaCode})`); | ||
@@ -23,0 +24,0 @@ }, |
@@ -260,2 +260,4 @@ # API Reference | ||
// validation and reporting options: | ||
next: false, | ||
unevaluated: false, | ||
$data: false, | ||
@@ -317,2 +319,4 @@ allErrors: 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. | ||
- _\$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). | ||
@@ -319,0 +323,0 @@ - _allErrors_: check all rules collecting all errors. Default is to return after the first error. |
@@ -22,3 +22,5 @@ # JSON Schema validation keywords | ||
- [additionalItems](#additionalitems) | ||
- [contains](#contains) (added in draft-06) | ||
- [contains](#contains) | ||
- [maxContains/minContains](#maxcontains--mincontains) | ||
- [unevaluatedItems](#unevaluateditems) (NEW: added in draft 2019-09) | ||
- [Keywords for objects](#keywords-for-objects) | ||
@@ -30,4 +32,7 @@ - [maxProperties/minProperties](#maxproperties--minproperties) | ||
- [additionalProperties](#additionalproperties) | ||
- [dependencies](#dependencies) | ||
- [propertyNames](#propertynames) (added in draft-06) | ||
- [dependencies](#dependencies) (deprecated from draft 2019-09) | ||
- [dependentRequired](#dependentrequired) (NEW: added in draft 2019-09) | ||
- [dependentSchemas](#dependentschemas) (NEW: added in draft 2019-09) | ||
- [propertyNames](#propertynames) | ||
- [unevaluatedProperties](#unevaluatedproperties) (NEW: added in draft 2019-09) | ||
- [Keywords for all types](#keywords-for-all-types) | ||
@@ -41,3 +46,3 @@ - [enum](#enum) | ||
- [allOf](#allof) | ||
- [if/then/else](#ifthenelse) (NEW in draft-07) | ||
- [if/then/else](#ifthenelse) | ||
@@ -302,2 +307,70 @@ ## `type` | ||
### `maxContains` / `minContains` | ||
The value of these keywords should be an integer. | ||
Without `contains` keyword they are ignored (logs error or throws exception in ajv [strict mode](./strict-mode.md)). | ||
The array is valid if it contains at least `minContains` items and no more than `maxContains` items that are valid against the schema in `contains` keyword. | ||
**Example** | ||
_schema_: | ||
```javascript | ||
{ | ||
type: "array", | ||
contains: {type: "integer"}, | ||
minContains: 2, | ||
maxContains: 3 | ||
} | ||
``` | ||
_valid_: `[1, 2]`, `[1, 2, 3, "foo"]`, any array with 2 or 3 integers | ||
_invalid_: `[]`, `[1, "foo"]`, `[1, 2, 3, 4]`, any array with fewer than 2 or more than 3 integers | ||
### `unevaluatedItems` | ||
The value of this keyword is a JSON Schema (can be a boolean). | ||
This schema will be applied to all array items that were not evaluated by other keywords for items (`items`, `additionalItems` and `contains`) in the current schema and all sub-schemas that were valid for this data instance. It includes: | ||
- all subschemas schemas in `allOf` and `$ref` keywords | ||
- valid sub-schemas in `oneOf` and `anyOf` keywords | ||
- sub-schema in `if` keyword | ||
- sub-schemas in `then` or `else` keywords that were applied based on the validation result by `if` keyword. | ||
The only scenario when this keyword would be applied to some items is when `items` keyword value is an array of schemas and `additionalItems` was not present (or did not apply, in case it was present in some invalid subschema). | ||
Some user-defined keywords can also make items "evaluated". | ||
**Example** | ||
_schema_: | ||
```javascript | ||
{ | ||
type: "array", | ||
items: [ | ||
{type: "number"}, | ||
{type: "number"} | ||
], | ||
unevaluatedItems: false, | ||
anyOf: [ | ||
{items: [true, true, {type: "number"}]}, | ||
{items: [true, true, {type: "boolean"}]} | ||
] | ||
} | ||
``` | ||
_valid_: `[1, 2, 3]`, `[1, 2, true]` | ||
_invalid_: | ||
- `[1, 2]` - the third item is not present | ||
- `[1, 2, "3"]` - the third item is "unevaluated" | ||
See [tests](https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/master/tests/draft2019-09/unevaluatedItems.json) for `unevaluatedItems` keyword for other examples. | ||
## Keywords for objects | ||
@@ -465,4 +538,6 @@ | ||
The value of the keyword is a map with keys equal to data object properties. Each value in the map should be either an array of unique property names ("property dependency") or a JSON Schema ("schema dependency"). | ||
This keyword is deprecated. The same functionality is available with keywords `dependentRequired` and `dependentSchemas`. | ||
The value of the keyword is a map with keys equal to data object properties. Each value in the map should be either an array of unique property names ("property dependency" - see [`dependentRequired`](#`dependentrequired`) keyword) or a JSON Schema ("schema dependency" - see [`dependentSchemas`](#`dependentschemas`) keyword). | ||
For property dependency, if the data object contains a property that is a key in the keyword value, then to be valid the data object should also contain all properties from the array of properties. | ||
@@ -489,3 +564,3 @@ | ||
2) _schema (schema dependency)_: | ||
2. _schema (schema dependency)_: | ||
@@ -509,2 +584,52 @@ ```javascript | ||
### `dependentRequired` | ||
The value of this keyword should be a map with keys equal to data object properties. Each value in the map should be an array of unique property names. | ||
If the data object contains a property that is a key in the keyword value, then to be valid the data object should also contain all properties from the corresponding array of properties in this keyword. | ||
**Example** | ||
_schema_: | ||
```javascript | ||
{ | ||
type: "object", | ||
dependentRequired: { | ||
foo: ["bar", "baz"] | ||
} | ||
} | ||
``` | ||
_valid_: `{foo: 1, bar: 2, baz: 3}`, `{}`, `{a: 1}` | ||
_invalid_: `{foo: 1}`, `{foo: 1, bar: 2}`, `{foo: 1, baz: 3}` | ||
### `dependentSchemas` | ||
The value of the keyword should be a map with keys equal to data object properties. Each value in the map should be a JSON Schema. | ||
If the data object contains a property that is a key in the keyword value, then to be valid the data object itself (NOT the property value) should be valid according to the corresponding schema in this keyword. | ||
**Example** | ||
_schema_: | ||
```javascript | ||
{ | ||
type: "object", | ||
dependentSchemas: { | ||
foo: { | ||
properties: { | ||
bar: {type: "number"} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
_valid_: `{}`, `{foo: 1}`, `{foo: 1, bar: 2}`, `{a: 1}` | ||
_invalid_: `{foo: 1, bar: "a"}` | ||
### `propertyNames` | ||
@@ -533,2 +658,48 @@ | ||
### `unevaluatedProperties` | ||
The value of this keyword is a JSON Schema (can be a boolean). | ||
This schema will be applied to all properties that were not evaluated by other keywords for properties (`properties`, `patternProperties` and `additionalProperties`) in the current schema and all sub-schemas that were valid for this data instance. It includes: | ||
- all subschemas schemas in `allOf` and `$ref` keywords | ||
- valid sub-schemas in `oneOf` and `anyOf` keywords | ||
- sub-schema in `if` keyword | ||
- sub-schemas in `then` or `else` keywords that were applied based on the validation result by `if` keyword. | ||
Some user-defined keywords can also make properties "evaluated". | ||
**Example** | ||
_schema_: | ||
```javascript | ||
{ | ||
type: "object", | ||
required: ["foo"], | ||
properties: {foo: {type: "number"}}, | ||
unevaluatedProperties: false, | ||
anyOf: [ | ||
{ | ||
required: ["bar"], | ||
properties: {bar: {type: "number"}} | ||
} | ||
{ | ||
required: ["baz"], | ||
properties: {baz: {type: "number"}} | ||
} | ||
] | ||
} | ||
``` | ||
_valid_: `{foo: 1, bar: 2}`, `{foo: 1, baz: 2}`, `{foo: 1, bar: 2, baz: 3}` | ||
_invalid_: | ||
- `{foo: 1}` - neither `bar` nor `baz` are present | ||
- `{foo: 1, bar: 2, boo: 3}` - `boo` is unevaluated | ||
- `{foo: 1, bar: 2, baz: "3"}` - not valid against the 2nd subschema, so `baz` is "unevaluated". | ||
See [tests](https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/master/tests/draft2019-09/unevaluatedProperties.json) for `unevaluatedProperties` keyword for other examples. | ||
## Keywords for all types | ||
@@ -535,0 +706,0 @@ |
@@ -11,2 +11,3 @@ ## Strict mode | ||
- ignored "if", "then", "else" keywords | ||
- ignored "contains", "maxContains" and "minContains" keywords | ||
- unknown formats | ||
@@ -58,2 +59,10 @@ - ignored defaults | ||
#### Prohibit ignored "contains", "maxContains" and "minContains" keywords | ||
JSON Schema sections [6.4.4, 6.4.5](https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.4.4) require to ignore keywords "maxContains" and "minContains" if "contains" keyword is absent. | ||
It is also implied that when "minContains" is 0 and "maxContains" is absent, "contains" keyword is always valid. | ||
By default Ajv fails schema compilation in these cases. | ||
#### Prohibit unknown formats | ||
@@ -60,0 +69,0 @@ |
@@ -62,2 +62,4 @@ export { | ||
import {metadataVocabulary, contentVocabulary} from "./vocabularies/metadata" | ||
import nextVocabulary from "./vocabularies/next" | ||
import unevaluatedVocabulary from "./vocabularies/unevaluated" | ||
import {eachItem} from "./compile/util" | ||
@@ -95,2 +97,4 @@ import $dataRefSchema from "./refs/data.json" | ||
// validation and reporting options: | ||
next?: boolean | ||
unevaluated?: boolean | ||
$data?: boolean | ||
@@ -280,2 +284,4 @@ allErrors?: boolean | ||
this.addVocabulary(contentVocabulary) | ||
if (opts.next) this.addVocabulary(nextVocabulary) | ||
if (opts.unevaluated) this.addVocabulary(unevaluatedVocabulary) | ||
addDefaultMetaSchema.call(this) | ||
@@ -282,0 +288,0 @@ if (opts.keywords) addInitialKeywords.call(this, opts.keywords) |
@@ -10,3 +10,3 @@ import type { | ||
import {checkDataTypes, DataType} from "./validate/dataType" | ||
import {schemaRefOrVal, unescapeJsonPointer} from "./util" | ||
import {schemaRefOrVal, unescapeJsonPointer, mergeEvaluated} from "./util" | ||
import { | ||
@@ -170,5 +170,24 @@ reportError, | ||
subschema(appl: SubschemaArgs, valid: Name): void { | ||
applySubschema(this.it, appl, valid) | ||
subschema(appl: SubschemaArgs, valid: Name): SchemaCxt { | ||
return applySubschema(this.it, appl, valid) | ||
} | ||
mergeEvaluated(schemaCxt: SchemaCxt, toName?: typeof Name): void { | ||
const {it, gen} = this | ||
if (!it.opts.unevaluated) return | ||
if (it.props !== true && schemaCxt.props !== undefined) { | ||
it.props = mergeEvaluated.props(gen, schemaCxt.props, it.props, toName) | ||
} | ||
if (it.items !== true && schemaCxt.items !== undefined) { | ||
it.items = mergeEvaluated.items(gen, schemaCxt.items, it.items, toName) | ||
} | ||
} | ||
mergeValidEvaluated(schemaCxt: SchemaCxt, valid: Name): boolean | void { | ||
const {it, gen} = this | ||
if (it.opts.unevaluated && (it.props !== true || it.items !== true)) { | ||
gen.if(valid, () => this.mergeEvaluated(schemaCxt, Name)) | ||
return true | ||
} | ||
} | ||
} | ||
@@ -175,0 +194,0 @@ |
@@ -1,2 +0,9 @@ | ||
import type {AnySchema, AnySchemaObject, AnyValidateFunction, AsyncValidateFunction} from "../types" | ||
import type { | ||
AnySchema, | ||
AnySchemaObject, | ||
AnyValidateFunction, | ||
AsyncValidateFunction, | ||
EvaluatedProperties, | ||
EvaluatedItems, | ||
} from "../types" | ||
import type Ajv from "../ajv" | ||
@@ -30,2 +37,3 @@ import type {InstanceOptions} from "../ajv" | ||
readonly validateName: Name | ||
evaluated?: Name | ||
readonly ValidationError?: Name | ||
@@ -45,2 +53,4 @@ readonly schema: AnySchema // current schema object - equal to parentSchema passed via KeywordCxt | ||
// You only need to use it if you have many steps in your keywords and potentially can define multiple errors. | ||
props?: EvaluatedProperties | Name // properties evaluated by this schema - used by parent schema or assigned to validation function | ||
items?: EvaluatedItems | Name // last item evaluated by this schema - used by parent schema or assigned to validation function | ||
readonly createErrors?: boolean | ||
@@ -158,2 +168,11 @@ readonly opts: InstanceOptions // Ajv instance option. | ||
} | ||
if (this.opts.unevaluated) { | ||
const {props, items} = schemaCxt | ||
validate.evaluated = { | ||
props: props instanceof Name ? undefined : props, | ||
items: items instanceof Name ? undefined : items, | ||
dynamicProps: props instanceof Name, | ||
dynamicItems: items instanceof Name, | ||
} | ||
} | ||
sch.validate = validate | ||
@@ -160,0 +179,0 @@ return sch |
@@ -44,3 +44,3 @@ import type {AddedKeywordDefinition} from "../types" | ||
types: {...groups, integer: true, boolean: true, null: true}, | ||
rules: [groups.number, groups.string, groups.array, groups.object, {rules: []}], | ||
rules: [groups.number, groups.string, {rules: []}, groups.array, groups.object], | ||
all: {type: true, $comment: true}, | ||
@@ -47,0 +47,0 @@ keywords: {type: true, $comment: true}, |
import type {AnySchema} from "../types" | ||
import type {SchemaObjCxt} from "./index" | ||
import type {SchemaObjCxt, SchemaCxt} from "./index" | ||
import {subschemaCode} from "./validate" | ||
@@ -52,8 +52,9 @@ import {escapeFragment, escapeJsonPointer} from "./util" | ||
export function applySubschema(it: SchemaObjCxt, appl: SubschemaArgs, valid: Name): void { | ||
export function applySubschema(it: SchemaObjCxt, appl: SubschemaArgs, valid: Name): SchemaCxt { | ||
const subschema = getSubschema(it, appl) | ||
extendSubschemaData(subschema, it, appl) | ||
extendSubschemaMode(subschema, appl) | ||
const nextContext = {...it, ...subschema} | ||
const nextContext = {...it, ...subschema, items: undefined, props: undefined} | ||
subschemaCode(nextContext, valid) | ||
return nextContext | ||
} | ||
@@ -60,0 +61,0 @@ |
@@ -1,4 +0,4 @@ | ||
import type {AnySchema} from "../types" | ||
import type {AnySchema, EvaluatedProperties, EvaluatedItems} from "../types" | ||
import type {SchemaCxt, SchemaObjCxt} from "." | ||
import {_, getProperty, Code} from "./codegen" | ||
import {_, getProperty, Code, Name, CodeGen} from "./codegen" | ||
import type {Rule, ValidationRules} from "./rules" | ||
@@ -102,1 +102,88 @@ import {checkStrictMode} from "./validate" | ||
} | ||
type SomeEvaluated = EvaluatedProperties | EvaluatedItems | ||
type MergeEvaluatedFunc<T extends SomeEvaluated> = ( | ||
gen: CodeGen, | ||
from: Name | T, | ||
to: Name | Exclude<T, true> | undefined, | ||
toName?: typeof Name | ||
) => Name | T | ||
interface MakeMergeFuncArgs<T extends SomeEvaluated> { | ||
mergeNames: (gen: CodeGen, from: Name, to: Name) => void | ||
mergeToName: (gen: CodeGen, from: T, to: Name) => void | ||
mergeValues: (from: T, to: Exclude<T, true>) => T | ||
resultToName: (gen: CodeGen, res?: T) => Name | ||
} | ||
function makeMergeEvaluated<T extends SomeEvaluated>({ | ||
mergeNames, | ||
mergeToName, | ||
mergeValues, | ||
resultToName, | ||
}: MakeMergeFuncArgs<T>): MergeEvaluatedFunc<T> { | ||
return (gen, from, to, toName) => { | ||
const res = | ||
to === undefined | ||
? from | ||
: to instanceof Name | ||
? (from instanceof Name ? mergeNames(gen, from, to) : mergeToName(gen, from, to), to) | ||
: from instanceof Name | ||
? (mergeToName(gen, to, from), from) | ||
: mergeValues(from, to) | ||
return toName === Name && !(res instanceof Name) ? resultToName(gen, res) : res | ||
} | ||
} | ||
interface MergeEvaluated { | ||
props: MergeEvaluatedFunc<EvaluatedProperties> | ||
items: MergeEvaluatedFunc<EvaluatedItems> | ||
} | ||
export const mergeEvaluated: MergeEvaluated = { | ||
props: makeMergeEvaluated({ | ||
mergeNames: (gen, from, to) => | ||
gen.if(_`${to} !== true && ${from} !== undefined`, () => { | ||
gen.if( | ||
_`${from} === true`, | ||
() => gen.assign(to, true), | ||
() => gen.code(_`Object.assign(${to}, ${from})`) | ||
) | ||
}), | ||
mergeToName: (gen, from, to) => | ||
gen.if(_`${to} !== true`, () => { | ||
if (from === true) { | ||
gen.assign(to, true) | ||
} else { | ||
gen.assign(to, _`${to} || {}`) | ||
setEvaluated(gen, to, from) | ||
} | ||
}), | ||
mergeValues: (from, to) => (from === true ? true : {...from, ...to}), | ||
resultToName: evaluatedPropsToName, | ||
}), | ||
items: makeMergeEvaluated({ | ||
mergeNames: (gen, from, to) => | ||
gen.if(_`${to} !== true && ${from} !== undefined`, () => | ||
gen.assign(to, _`${from} === true ? true : ${to} > ${from} ? ${to} : ${from}`) | ||
), | ||
mergeToName: (gen, from, to) => | ||
gen.if(_`${to} !== true`, () => | ||
gen.assign(to, from === true ? true : _`${to} > ${from} ? ${to} : ${from}`) | ||
), | ||
mergeValues: (from, to) => (from === true ? true : Math.max(from, to)), | ||
resultToName: (gen, items) => gen.var("items", items), | ||
}), | ||
} | ||
export function evaluatedPropsToName(gen: CodeGen, ps?: EvaluatedProperties): Name { | ||
if (ps === true) return gen.var("props", true) | ||
const props = gen.var("props", _`{}`) | ||
if (ps !== undefined) setEvaluated(gen, props, ps) | ||
return props | ||
} | ||
export function setEvaluated(gen: CodeGen, props: Name, ps: {[K in string]?: true}): void { | ||
Object.keys(ps).forEach((p) => gen.assign(_`${props}${getProperty(p)}`, true)) | ||
} |
@@ -69,2 +69,3 @@ import type {AnySchema} from "../../types" | ||
gen.let(N.errors, 0) | ||
if (opts.unevaluated) resetEvaluated(it) | ||
typeAndKeywords(it) | ||
@@ -76,2 +77,10 @@ returnResults(it) | ||
function resetEvaluated(it: SchemaObjCxt): void { | ||
// TODO maybe some hook to execute it in the end to check whether props/items are Name, as in assignEvaluated | ||
const {gen, validateName} = it | ||
it.evaluated = gen.const("evaluated", _`${validateName}.evaluated`) | ||
gen.if(_`${it.evaluated}.dynamicProps`, () => gen.assign(_`${it.evaluated}.props`, _`undefined`)) | ||
gen.if(_`${it.evaluated}.dynamicItems`, () => gen.assign(_`${it.evaluated}.items`, _`undefined`)) | ||
} | ||
function funcSourceUrl(schema: AnySchema, opts: InstanceOptions): Code { | ||
@@ -160,4 +169,6 @@ return typeof schema == "object" && schema.$id && (opts.code.source || opts.code.process) | ||
function returnResults({gen, schemaEnv, validateName, ValidationError}: SchemaCxt): void { | ||
function returnResults(it: SchemaCxt): void { | ||
const {gen, schemaEnv, validateName, ValidationError, opts} = it | ||
if (schemaEnv.$async) { | ||
// TODO assign unevaluated | ||
gen.if( | ||
@@ -170,2 +181,3 @@ _`${N.errors} === 0`, | ||
gen.assign(_`${validateName}.errors`, N.vErrors) | ||
if (opts.unevaluated) assignEvaluated(it) | ||
gen.return(_`${N.errors} === 0`) | ||
@@ -175,2 +187,7 @@ } | ||
function assignEvaluated({gen, evaluated, props, items}: SchemaCxt): void { | ||
if (props instanceof Name) gen.assign(_`${evaluated}.props`, props) | ||
if (items instanceof Name) gen.assign(_`${evaluated}.items`, items) | ||
} | ||
export function checkStrictMode(it: SchemaCxt, msg: string, mode = it.opts.strict): void { | ||
@@ -177,0 +194,0 @@ if (!mode) return |
@@ -49,2 +49,3 @@ import type {CodeGen, Code, Name, Scope} from "../compile/codegen" | ||
errors?: null | ErrorObject[] | ||
evaluated?: Evaluated | ||
schema: AnySchema | ||
@@ -55,2 +56,15 @@ schemaEnv: SchemaEnv | ||
export type EvaluatedProperties = {[K in string]?: true} | true | ||
export type EvaluatedItems = number | true | ||
export interface Evaluated { | ||
// determined at compile time if staticProps/Items is true | ||
props?: EvaluatedProperties | ||
items?: EvaluatedItems | ||
// whether props/items determined at compile time | ||
dynamicProps: boolean | ||
dynamicItems: boolean | ||
} | ||
export interface AsyncValidateFunction<T = unknown> extends ValidateFunction<T> { | ||
@@ -57,0 +71,0 @@ (...args: Parameters<ValidateFunction<T>>): Promise<T> |
@@ -23,3 +23,2 @@ import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" | ||
const {gen, schema, parentSchema, data, it} = cxt | ||
const len = gen.const("len", _`${data}.length`) | ||
const {items} = parentSchema | ||
@@ -30,2 +29,4 @@ if (!Array.isArray(items)) { | ||
} | ||
it.items = true | ||
const len = gen.const("len", _`${data}.length`) | ||
if (schema === false) { | ||
@@ -32,0 +33,0 @@ cxt.setParams({len: items.length}) |
@@ -35,2 +35,3 @@ import type { | ||
const {allErrors, opts} = it | ||
it.props = true | ||
if (opts.removeAdditional !== "all" && alwaysValidSchema(it, schema)) return | ||
@@ -40,3 +41,3 @@ const props = allSchemaProperties(parentSchema.properties) | ||
checkAdditionalProperties() | ||
if (!allErrors) gen.if(_`${errsCount} === ${N.errors}`) | ||
cxt.ok(_`${errsCount} === ${N.errors}`) | ||
@@ -43,0 +44,0 @@ function checkAdditionalProperties(): void { |
@@ -15,4 +15,5 @@ import type {CodeKeywordDefinition, AnySchema} from "../../types" | ||
if (alwaysValidSchema(it, sch)) return | ||
cxt.subschema({keyword: "allOf", schemaProp: i}, valid) | ||
const schCxt = cxt.subschema({keyword: "allOf", schemaProp: i}, valid) | ||
cxt.ok(valid) | ||
cxt.mergeEvaluated(schCxt) | ||
}) | ||
@@ -19,0 +20,0 @@ }, |
@@ -15,3 +15,3 @@ import type {CodeKeywordDefinition, AnySchema} from "../../types" | ||
const alwaysValid = schema.some((sch: AnySchema) => alwaysValidSchema(it, sch)) | ||
if (alwaysValid) return | ||
if (alwaysValid && !it.opts.unevaluated) return | ||
@@ -21,5 +21,5 @@ const valid = gen.let("valid", false) | ||
gen.block(() => { | ||
gen.block(() => | ||
schema.forEach((_sch: AnySchema, i: number) => { | ||
cxt.subschema( | ||
const schCxt = cxt.subschema( | ||
{ | ||
@@ -33,5 +33,8 @@ keyword: "anyOf", | ||
gen.assign(valid, _`${valid} || ${schValid}`) | ||
gen.if(not(valid)) | ||
const merged = cxt.mergeValidEvaluated(schCxt, schValid) | ||
// can short-circuit if `unevaluatedProperties/Items` not supported (opts.unevaluated !== true) | ||
// or if all properties and items were evaluated (it.props === true && it.items === true) | ||
if (!merged) gen.if(not(valid)) | ||
}) | ||
}, schema.length) | ||
) | ||
@@ -38,0 +41,0 @@ cxt.result( |
@@ -1,7 +0,19 @@ | ||
import type {CodeKeywordDefinition} from "../../types" | ||
import type {CodeKeywordDefinition, KeywordErrorDefinition, ErrorObject} from "../../types" | ||
import type KeywordCxt from "../../compile/context" | ||
import {_} from "../../compile/codegen" | ||
import {_, str, Name} from "../../compile/codegen" | ||
import {Type} from "../../compile/subschema" | ||
import {alwaysValidSchema} from "../../compile/util" | ||
import {checkStrictMode} from "../../compile/validate" | ||
export type ContainsError = ErrorObject<"contains", {minContains: number; maxContains?: number}> | ||
const error: KeywordErrorDefinition = { | ||
message: ({params: {min, max}}) => | ||
max === undefined | ||
? str`should contain at least ${min} valid item(s)` | ||
: str`should contain at least ${min} and no more than ${max} valid item(s)`, | ||
params: ({params: {min, max}}) => | ||
max === undefined ? _`{minContains: ${min}}` : _`{minContains: ${min}, maxContains: ${max}}`, | ||
} | ||
const def: CodeKeywordDefinition = { | ||
@@ -13,31 +25,72 @@ keyword: "contains", | ||
trackErrors: true, | ||
error, | ||
code(cxt: KeywordCxt) { | ||
const {gen, schema, data, it} = cxt | ||
const {gen, schema, parentSchema, data, it} = cxt | ||
let min: number | ||
let max: number | undefined | ||
const {minContains, maxContains} = parentSchema | ||
if (it.opts.next) { | ||
min = minContains === undefined ? 1 : minContains | ||
max = maxContains | ||
} else { | ||
min = 1 | ||
} | ||
const len = gen.const("len", _`${data}.length`) | ||
cxt.setParams({min, max}) | ||
if (max === undefined && min === 0) { | ||
checkStrictMode(it, `"minContains" == 0 without "maxContains": "contains" keyword ignored`) | ||
return | ||
} | ||
if (max !== undefined && min > max) { | ||
checkStrictMode(it, `"minContains" > "maxContains" is always invalid`) | ||
cxt.fail() | ||
return | ||
} | ||
if (alwaysValidSchema(it, schema)) { | ||
cxt.fail(_`${data}.length === 0`) | ||
let cond = _`${len} >= ${min}` | ||
if (max !== undefined) cond = _`${cond} && ${len} <= ${max}` | ||
cxt.pass(cond) | ||
return | ||
} | ||
it.items = true | ||
const valid = gen.name("valid") | ||
gen.forRange("i", 0, _`${data}.length`, (i) => { | ||
cxt.subschema( | ||
{ | ||
keyword: "contains", | ||
dataProp: i, | ||
dataPropType: Type.Num, | ||
compositeRule: true, | ||
}, | ||
valid | ||
) | ||
gen.if(valid, () => gen.break()) | ||
}) | ||
if (max === undefined && min === 1) { | ||
validateItems(valid, () => gen.if(valid, () => gen.break())) | ||
} else { | ||
gen.let(valid, false) | ||
const schValid = gen.name("_valid") | ||
const count = gen.let("count", 0) | ||
validateItems(schValid, () => gen.if(schValid, () => checkLimits(count))) | ||
} | ||
cxt.result(valid, () => cxt.reset()) | ||
cxt.result(valid, () => cxt.reset()) | ||
function validateItems(_valid: Name, block: () => void): void { | ||
gen.forRange("i", 0, len, (i) => { | ||
cxt.subschema( | ||
{ | ||
keyword: "contains", | ||
dataProp: i, | ||
dataPropType: Type.Num, | ||
compositeRule: true, | ||
}, | ||
_valid | ||
) | ||
block() | ||
}) | ||
} | ||
function checkLimits(count: Name): void { | ||
gen.code(_`${count}++`) | ||
if (max === undefined) { | ||
gen.if(_`${count} >= ${min}`, () => gen.assign(valid, true).break()) | ||
} else { | ||
gen.if(_`${count} > ${max}`, () => gen.assign(valid, false).break()) | ||
if (min === 1) gen.assign(valid, true) | ||
else gen.if(_`${count} >= ${min}`, () => gen.assign(valid, true)) | ||
} | ||
} | ||
}, | ||
error: { | ||
message: "should contain a valid item", | ||
}, | ||
} | ||
export default def |
@@ -29,3 +29,3 @@ import type { | ||
const error: KeywordErrorDefinition = { | ||
export const error: KeywordErrorDefinition = { | ||
message: ({params: {property, depsCount, deps}}) => { | ||
@@ -48,59 +48,66 @@ const property_ies = depsCount === 1 ? "property" : "properties" | ||
code(cxt: KeywordCxt) { | ||
const {gen, schema, data, it} = cxt | ||
const [propDeps, schDeps] = splitDependencies() | ||
const valid = gen.name("valid") | ||
validatePropertyDeps(propDeps) | ||
validateSchemaDeps(schDeps) | ||
const [propDeps, schDeps] = splitDependencies(cxt) | ||
validatePropertyDeps(cxt, propDeps) | ||
validateSchemaDeps(cxt, schDeps) | ||
}, | ||
} | ||
function splitDependencies(): [PropertyDependencies, SchemaDependencies] { | ||
const propertyDeps: PropertyDependencies = {} | ||
const schemaDeps: SchemaDependencies = {} | ||
for (const key in schema) { | ||
if (key === "__proto__") continue | ||
const deps = Array.isArray(schema[key]) ? propertyDeps : schemaDeps | ||
deps[key] = schema[key] | ||
} | ||
return [propertyDeps, schemaDeps] | ||
} | ||
function splitDependencies({schema}: KeywordCxt): [PropertyDependencies, SchemaDependencies] { | ||
const propertyDeps: PropertyDependencies = {} | ||
const schemaDeps: SchemaDependencies = {} | ||
for (const key in schema) { | ||
if (key === "__proto__") continue | ||
const deps = Array.isArray(schema[key]) ? propertyDeps : schemaDeps | ||
deps[key] = schema[key] | ||
} | ||
return [propertyDeps, schemaDeps] | ||
} | ||
function validatePropertyDeps(propertyDeps: {[x: string]: string[]}): void { | ||
if (Object.keys(propertyDeps).length === 0) return | ||
const missing = gen.let("missing") | ||
for (const prop in propertyDeps) { | ||
const deps = propertyDeps[prop] | ||
if (deps.length === 0) continue | ||
const hasProperty = propertyInData(data, prop, it.opts.ownProperties) | ||
cxt.setParams({ | ||
property: prop, | ||
depsCount: deps.length, | ||
deps: deps.join(", "), | ||
}) | ||
if (it.allErrors) { | ||
gen.if(hasProperty, () => { | ||
for (const depProp of deps) { | ||
checkReportMissingProp(cxt, depProp) | ||
} | ||
}) | ||
} else { | ||
gen.if(_`${hasProperty} && (${checkMissingProp(cxt, deps, missing)})`) | ||
reportMissingProp(cxt, missing) | ||
gen.else() | ||
export function validatePropertyDeps( | ||
cxt: KeywordCxt, | ||
propertyDeps: {[x: string]: string[]} = cxt.schema | ||
): void { | ||
const {gen, data, it} = cxt | ||
if (Object.keys(propertyDeps).length === 0) return | ||
const missing = gen.let("missing") | ||
for (const prop in propertyDeps) { | ||
const deps = propertyDeps[prop] | ||
if (deps.length === 0) continue | ||
const hasProperty = propertyInData(data, prop, it.opts.ownProperties) | ||
cxt.setParams({ | ||
property: prop, | ||
depsCount: deps.length, | ||
deps: deps.join(", "), | ||
}) | ||
if (it.allErrors) { | ||
gen.if(hasProperty, () => { | ||
for (const depProp of deps) { | ||
checkReportMissingProp(cxt, depProp) | ||
} | ||
} | ||
}) | ||
} else { | ||
gen.if(_`${hasProperty} && (${checkMissingProp(cxt, deps, missing)})`) | ||
reportMissingProp(cxt, missing) | ||
gen.else() | ||
} | ||
} | ||
} | ||
function validateSchemaDeps(schemaDeps: SchemaMap): void { | ||
for (const prop in schemaDeps) { | ||
if (alwaysValidSchema(it, schemaDeps[prop] as AnySchema)) continue | ||
gen.if( | ||
propertyInData(data, prop, it.opts.ownProperties), | ||
() => cxt.subschema({keyword: "dependencies", schemaProp: prop}, valid), | ||
() => gen.var(valid, true) // TODO var | ||
) | ||
cxt.ok(valid) | ||
} | ||
} | ||
}, | ||
export function validateSchemaDeps(cxt: KeywordCxt, schemaDeps: SchemaMap = cxt.schema): void { | ||
const {gen, data, keyword, it} = cxt | ||
const valid = gen.name("valid") | ||
for (const prop in schemaDeps) { | ||
if (alwaysValidSchema(it, schemaDeps[prop] as AnySchema)) continue | ||
gen.if( | ||
propertyInData(data, prop, it.opts.ownProperties), | ||
() => { | ||
const schCxt = cxt.subschema({keyword, schemaProp: prop}, valid) | ||
cxt.mergeValidEvaluated(schCxt, valid) | ||
}, | ||
() => gen.var(valid, true) // TODO var | ||
) | ||
cxt.ok(valid) | ||
} | ||
} | ||
export default def |
@@ -47,3 +47,3 @@ import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" | ||
function validateIf(): void { | ||
cxt.subschema( | ||
const schCxt = cxt.subschema( | ||
{ | ||
@@ -57,2 +57,3 @@ keyword: "if", | ||
) | ||
cxt.mergeEvaluated(schCxt) | ||
} | ||
@@ -62,4 +63,5 @@ | ||
return () => { | ||
cxt.subschema({keyword}, schValid) | ||
const schCxt = cxt.subschema({keyword}, schValid) | ||
gen.assign(valid, schValid) | ||
cxt.mergeValidEvaluated(schCxt, valid) | ||
if (ifClause) gen.assign(ifClause, _`${keyword}`) | ||
@@ -66,0 +68,0 @@ else cxt.setParams({ifClause: keyword}) |
import type {ErrorObject, Vocabulary} from "../../types" | ||
import additionalItems, {AdditionalItemsError} from "./additionalItems" | ||
import items from "./items" | ||
import contains from "./contains" | ||
import contains, {ContainsError} from "./contains" | ||
import dependencies, {DependenciesError} from "./dependencies" | ||
@@ -18,2 +18,9 @@ import propertyNames, {PropertyNamesError} from "./propertyNames" | ||
const applicator: Vocabulary = [ | ||
// any | ||
notKeyword, | ||
anyOf, | ||
oneOf, | ||
allOf, | ||
ifKeyword, | ||
thenElse, | ||
// array | ||
@@ -24,14 +31,7 @@ additionalItems, | ||
// object | ||
dependencies, | ||
propertyNames, | ||
additionalProperties, | ||
dependencies, | ||
properties, | ||
patternProperties, | ||
// any | ||
notKeyword, | ||
anyOf, | ||
oneOf, | ||
allOf, | ||
ifKeyword, | ||
thenElse, | ||
] | ||
@@ -44,2 +44,3 @@ | ||
| AdditionalItemsError | ||
| ContainsError | ||
| AdditionalPropertiesError | ||
@@ -52,4 +53,4 @@ | DependenciesError | ||
export type ErrorWithoutParams = ErrorObject< | ||
"anyOf" | "contains" | "not" | "false schema", | ||
"anyOf" | "not" | "false schema", | ||
Record<string, never> | ||
> |
@@ -5,3 +5,3 @@ import type {CodeKeywordDefinition, AnySchema} from "../../types" | ||
import {Type} from "../../compile/subschema" | ||
import {alwaysValidSchema} from "../../compile/util" | ||
import {alwaysValidSchema, mergeEvaluated} from "../../compile/util" | ||
import {checkStrictMode} from "../../compile/validate" | ||
@@ -18,5 +18,9 @@ | ||
if (Array.isArray(schema)) { | ||
if (it.opts.unevaluated && schema.length && it.items !== true) { | ||
it.items = mergeEvaluated.items(gen, schema.length, it.items) | ||
} | ||
validateTuple(schema) | ||
} else if (!alwaysValidSchema(it, schema)) { | ||
validateArray() | ||
} else { | ||
it.items = true | ||
if (!alwaysValidSchema(it, schema)) validateArray() | ||
} | ||
@@ -23,0 +27,0 @@ |
@@ -8,4 +8,5 @@ import type { | ||
import type KeywordCxt from "../../compile/context" | ||
import {_} from "../../compile/codegen" | ||
import {_, Name} from "../../compile/codegen" | ||
import {alwaysValidSchema} from "../../compile/util" | ||
import {SchemaCxt} from "../../compile" | ||
@@ -45,6 +46,7 @@ export type OneOfError = ErrorObject<"oneOf", {passingSchemas: [number, number]}> | ||
schArr.forEach((sch: AnySchema, i: number) => { | ||
let schCxt: SchemaCxt | undefined | ||
if (alwaysValidSchema(it, sch)) { | ||
gen.var(schValid, true) | ||
} else { | ||
cxt.subschema( | ||
schCxt = cxt.subschema( | ||
{ | ||
@@ -67,3 +69,7 @@ keyword: "oneOf", | ||
gen.if(schValid, () => gen.assign(valid, true).assign(passing, i)) | ||
gen.if(schValid, () => { | ||
gen.assign(valid, true) | ||
gen.assign(passing, i) | ||
if (schCxt) cxt.mergeEvaluated(schCxt, Name) | ||
}) | ||
}) | ||
@@ -70,0 +76,0 @@ } |
import type {CodeKeywordDefinition} from "../../types" | ||
import type KeywordCxt from "../../compile/context" | ||
import {schemaProperties, usePattern} from "../code" | ||
import {_, not} from "../../compile/codegen" | ||
import {_, not, Name} from "../../compile/codegen" | ||
import {Type} from "../../compile/subschema" | ||
import {checkStrictMode} from "../../compile/validate" | ||
import {evaluatedPropsToName} from "../../compile/util" | ||
@@ -16,5 +17,10 @@ const def: CodeKeywordDefinition = { | ||
const patterns = schemaProperties(it, schema) | ||
// TODO mark properties matching patterns with always valid schemas as evaluated | ||
if (patterns.length === 0) return | ||
const checkProperties = opts.strict && !opts.allowMatchingProperties && parentSchema.properties | ||
const valid = gen.name("valid") | ||
if (it.props !== true && !(it.props instanceof Name)) { | ||
it.props = evaluatedPropsToName(gen, it.props) | ||
} | ||
const {props} = it | ||
validatePatternProperties() | ||
@@ -59,3 +65,9 @@ | ||
) | ||
if (!it.allErrors) gen.if(not(valid), () => gen.break()) | ||
if (it.opts.unevaluated && props !== true) { | ||
gen.assign(_`${props}[${key}]`, true) | ||
} else if (!it.allErrors) { | ||
// can short-circuit if `unevaluatedProperties` is not supported (opts.next === false) | ||
// or if all properties were evaluated (props === true) | ||
gen.if(not(valid), () => gen.break()) | ||
} | ||
}) | ||
@@ -62,0 +74,0 @@ }) |
import type {CodeKeywordDefinition} from "../../types" | ||
import KeywordCxt from "../../compile/context" | ||
import {propertyInData, schemaProperties} from "../code" | ||
import {propertyInData, allSchemaProperties} from "../code" | ||
import {alwaysValidSchema, toHash, mergeEvaluated} from "../../compile/util" | ||
import apDef from "./additionalProperties" | ||
@@ -15,3 +16,7 @@ | ||
} | ||
const properties = schemaProperties(it, schema) | ||
const allProps = allSchemaProperties(schema) | ||
if (it.opts.unevaluated && allProps.length && it.props !== true) { | ||
it.props = mergeEvaluated.props(gen, toHash(allProps), it.props) | ||
} | ||
const properties = allProps.filter((p) => !alwaysValidSchema(it, schema[p])) | ||
if (properties.length === 0) return | ||
@@ -18,0 +23,0 @@ const valid = gen.name("valid") |
@@ -8,2 +8,3 @@ import type {CodeKeywordDefinition, AnySchema} from "../../types" | ||
import {SchemaEnv, resolveRef} from "../../compile" | ||
import {mergeEvaluated} from "../../compile/util" | ||
@@ -24,5 +25,5 @@ const def: CodeKeywordDefinition = { | ||
function callRootRef(): void { | ||
if (env === env.root) return callRef(validateName, env.$async) | ||
if (env === env.root) return callRef(validateName, env, env.$async) | ||
const rootName = gen.scopeValue("root", {ref: env.root}) | ||
return callRef(_`${rootName}.validate`, env.root.$async) | ||
return callRef(_`${rootName}.validate`, env.root, env.root.$async) | ||
} | ||
@@ -39,8 +40,8 @@ | ||
} | ||
callRef(v, sch.$async) | ||
callRef(v, sch, sch.$async) | ||
} | ||
function callRef(v: Code, $async?: boolean): void { | ||
if ($async) callAsyncRef(v) | ||
else callSyncRef(v) | ||
function callRef(v: Code, sch: SchemaEnv, $async?: boolean): void { | ||
if ($async) callAsyncRef(v, sch) | ||
else callSyncRef(v, sch) | ||
} | ||
@@ -51,3 +52,3 @@ | ||
const valid = gen.name("valid") | ||
cxt.subschema( | ||
const schCxt = cxt.subschema( | ||
{ | ||
@@ -63,6 +64,7 @@ schema: sch, | ||
) | ||
cxt.mergeEvaluated(schCxt) | ||
cxt.ok(valid) | ||
} | ||
function callAsyncRef(v: Code): void { | ||
function callAsyncRef(v: Code, sch: SchemaEnv): void { | ||
if (!env.$async) throw new Error("async schema referenced by sync schema") | ||
@@ -73,2 +75,3 @@ const valid = gen.let("valid") | ||
gen.code(_`await ${callValidateCode(cxt, v, passCxt)}`) | ||
addEvaluatedFrom(v, sch) | ||
if (!allErrors) gen.assign(valid, true) | ||
@@ -85,4 +88,8 @@ }, | ||
function callSyncRef(v: Code): void { | ||
cxt.pass(callValidateCode(cxt, v, passCxt), () => addErrorsFrom(v)) | ||
function callSyncRef(v: Code, sch: SchemaEnv): void { | ||
cxt.result( | ||
callValidateCode(cxt, v, passCxt), | ||
() => addEvaluatedFrom(v, sch), | ||
() => addErrorsFrom(v) | ||
) | ||
} | ||
@@ -95,2 +102,28 @@ | ||
} | ||
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) | ||
} | ||
} | ||
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) | ||
} | ||
} | ||
} | ||
}, | ||
@@ -97,0 +130,0 @@ } |
@@ -5,3 +5,13 @@ 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 {DependentRequiredError} from "./validation/dependentRequired" | ||
export type DefinedError = TypeError | ApplicatorKeywordError | ValidationKeywordError | FormatError | ||
export type DefinedError = | ||
| TypeError | ||
| ApplicatorKeywordError | ||
| ValidationKeywordError | ||
| FormatError | ||
| UnevaluatedPropertiesError | ||
| UnevaluatedItemsError | ||
| DependentRequiredError |
@@ -22,2 +22,3 @@ import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" | ||
}) | ||
// TODO optimize for scalar values in schema | ||
cxt.fail$data(_`!${eql}(${cxt.data}, ${cxt.schemaCode})`) | ||
@@ -24,0 +25,0 @@ }, |
{ | ||
"name": "ajv", | ||
"version": "7.0.0-beta.2", | ||
"version": "7.0.0-beta.3", | ||
"description": "Another JSON Schema Validator", | ||
@@ -5,0 +5,0 @@ "main": "dist/ajv.js", |
@@ -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.2) | ||
[![npm (beta)](https://img.shields.io/npm/v/ajv/beta)](https://www.npmjs.com/package/ajv/v/7.0.0-beta.3) | ||
[![npm downloads](https://img.shields.io/npm/dm/ajv.svg)](https://www.npmjs.com/package/ajv) | ||
@@ -12,0 +12,0 @@ [![Coverage Status](https://coveralls.io/repos/github/ajv-validator/ajv/badge.svg?branch=master)](https://coveralls.io/github/ajv-validator/ajv?branch=master) |
@@ -6,2 +6,3 @@ "use strict" | ||
draft7: "spec/JSON-Schema-Test-Suite/tests/draft7/", | ||
draft2019: "spec/JSON-Schema-Test-Suite/tests/draft2019-09/", | ||
tests: "spec/tests/", | ||
@@ -8,0 +9,0 @@ security: "spec/security/", |
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
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
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
795088
283
12623