ajv
Advanced tools
Comparing version 7.0.4 to 7.1.0
@@ -25,6 +25,5 @@ "use strict"; | ||
super._addDefaultMetaSchema(); | ||
const { $data, meta } = this.opts; | ||
if (!meta) | ||
if (!this.opts.meta) | ||
return; | ||
const metaSchema = $data | ||
const metaSchema = this.opts.$data | ||
? this.$dataMetaSchema(draft7MetaSchema, META_SUPPORT_DATA) | ||
@@ -31,0 +30,0 @@ : draft7MetaSchema; |
@@ -12,3 +12,3 @@ import type { AddedKeywordDefinition, KeywordErrorCxt, KeywordCxtParams, AnySchemaObject } from "../types"; | ||
readonly $data?: string | false; | ||
readonly schema: any; | ||
schema: any; | ||
readonly schemaValue: Code | number | boolean; | ||
@@ -15,0 +15,0 @@ readonly schemaCode: Code | number | boolean; |
@@ -82,3 +82,3 @@ "use strict"; | ||
; | ||
(append ? errors_1.reportExtraError : errors_1.reportError)(this, this.def.error || errors_1.keywordError); | ||
(append ? errors_1.reportExtraError : errors_1.reportError)(this, this.def.error); | ||
} | ||
@@ -85,0 +85,0 @@ $dataError() { |
@@ -11,3 +11,3 @@ import type { ErrorObject } from "../types"; | ||
readonly missingSchema: string; | ||
constructor(baseId: string, ref: string); | ||
constructor(baseId: string, ref: string, msg?: string); | ||
} |
@@ -14,4 +14,4 @@ "use strict"; | ||
class MissingRefError extends Error { | ||
constructor(baseId, ref) { | ||
super(`can't resolve reference ${ref} from id ${baseId}`); | ||
constructor(baseId, ref, msg) { | ||
super(msg || `can't resolve reference ${ref} from id ${baseId}`); | ||
this.missingRef = resolve_1.resolveUrl(baseId, ref); | ||
@@ -18,0 +18,0 @@ this.missingSchema = resolve_1.normalizeId(resolve_1.getFullPath(this.missingRef)); |
@@ -5,5 +5,5 @@ import type { KeywordErrorCxt, KeywordErrorDefinition } from "../types"; | ||
export declare const keyword$DataError: KeywordErrorDefinition; | ||
export declare function reportError(cxt: KeywordErrorCxt, error: KeywordErrorDefinition, overrideAllErrors?: boolean): void; | ||
export declare function reportExtraError(cxt: KeywordErrorCxt, error: KeywordErrorDefinition): void; | ||
export declare function reportError(cxt: KeywordErrorCxt, error?: KeywordErrorDefinition, overrideAllErrors?: boolean): void; | ||
export declare function reportExtraError(cxt: KeywordErrorCxt, error?: KeywordErrorDefinition): void; | ||
export declare function resetErrorsCount(gen: CodeGen, errsCount: Name): void; | ||
export declare function extendErrors({ gen, keyword, schemaValue, data, errsCount, it, }: KeywordErrorCxt): void; |
@@ -14,3 +14,3 @@ "use strict"; | ||
}; | ||
function reportError(cxt, error, overrideAllErrors) { | ||
function reportError(cxt, error = exports.keywordError, overrideAllErrors) { | ||
const { it } = cxt; | ||
@@ -27,3 +27,3 @@ const { gen, compositeRule, allErrors } = it; | ||
exports.reportError = reportError; | ||
function reportExtraError(cxt, error) { | ||
function reportExtraError(cxt, error = exports.keywordError) { | ||
const { it } = cxt; | ||
@@ -82,7 +82,26 @@ const { gen, compositeRule, allErrors } = it; | ||
parentSchema: new codegen_1.Name("parentSchema"), | ||
// JTD error properties | ||
instancePath: new codegen_1.Name("instancePath"), | ||
}; | ||
function errorObjectCode(cxt, error) { | ||
const { keyword, data, schemaValue, it: { gen, createErrors, topSchemaRef, schemaPath, errorPath, errSchemaPath, propertyName, opts }, } = cxt; | ||
const { createErrors, opts } = cxt.it; | ||
if (createErrors === false) | ||
return codegen_1._ `{}`; | ||
return (opts.jtd && !opts.ajvErrors ? jtdErrorObject : ajvErrorObject)(cxt, error); | ||
} | ||
function jtdErrorObject(cxt, { message }) { | ||
const { gen, keyword, it } = cxt; | ||
const { errorPath, errSchemaPath, opts } = it; | ||
const keyValues = [ | ||
[E.instancePath, codegen_1.strConcat(names_1.default.dataPath, errorPath)], | ||
[E.schemaPath, codegen_1.str `${errSchemaPath}/${keyword}`], | ||
]; | ||
if (opts.messages) { | ||
keyValues.push([E.message, typeof message == "function" ? message(cxt) : message]); | ||
} | ||
return gen.object(...keyValues); | ||
} | ||
function ajvErrorObject(cxt, error) { | ||
const { gen, keyword, data, schemaValue, it } = cxt; | ||
const { topSchemaRef, schemaPath, errorPath, errSchemaPath, propertyName, opts } = it; | ||
const { params, message } = error; | ||
@@ -97,5 +116,4 @@ const keyValues = [ | ||
keyValues.push([E.propertyName, propertyName]); | ||
if (opts.messages !== false) { | ||
const msg = typeof message == "function" ? message(cxt) : message; | ||
keyValues.push([E.message, msg]); | ||
if (opts.messages) { | ||
keyValues.push([E.message, typeof message == "function" ? message(cxt) : message]); | ||
} | ||
@@ -102,0 +120,0 @@ if (opts.verbose) { |
@@ -26,3 +26,2 @@ import type { AnySchema, AnySchemaObject, AnyValidateFunction, EvaluatedProperties, EvaluatedItems } from "../types"; | ||
readonly schemaEnv: SchemaEnv; | ||
readonly strictSchema?: boolean; | ||
readonly rootId: string; | ||
@@ -37,2 +36,4 @@ baseId: string; | ||
items?: EvaluatedItems | Name; | ||
jtdDiscriminator?: string; | ||
jtdMetadata?: boolean; | ||
readonly createErrors?: boolean; | ||
@@ -39,0 +40,0 @@ readonly opts: InstanceOptions; |
@@ -67,7 +67,6 @@ "use strict"; | ||
schemaEnv: sch, | ||
strictSchema: true, | ||
rootId, | ||
baseId: sch.baseId || rootId, | ||
schemaPath: codegen_1.nil, | ||
errSchemaPath: "#", | ||
errSchemaPath: this.opts.jtd ? "" : "#", | ||
errorPath: codegen_1._ `""`, | ||
@@ -74,0 +73,0 @@ opts: this.opts, |
@@ -21,4 +21,4 @@ "use strict"; | ||
post: { rules: [] }, | ||
all: { type: true, $comment: true }, | ||
keywords: { type: true, $comment: true }, | ||
all: {}, | ||
keywords: {}, | ||
}; | ||
@@ -25,0 +25,0 @@ } |
@@ -13,3 +13,2 @@ import type { AnySchema } from "../types"; | ||
schema: AnySchema; | ||
strictSchema: boolean; | ||
schemaPath: Code; | ||
@@ -23,2 +22,4 @@ errSchemaPath: string; | ||
dataPropType: Type; | ||
jtdDiscriminator: string; | ||
jtdMetadata: boolean; | ||
compositeRule: true; | ||
@@ -25,0 +26,0 @@ createErrors: boolean; |
@@ -21,3 +21,3 @@ "use strict"; | ||
exports.applySubschema = applySubschema; | ||
function getSubschema(it, { keyword, schemaProp, schema, strictSchema, schemaPath, errSchemaPath, topSchemaRef, }) { | ||
function getSubschema(it, { keyword, schemaProp, schema, schemaPath, errSchemaPath, topSchemaRef }) { | ||
if (keyword !== undefined && schema !== undefined) { | ||
@@ -46,3 +46,2 @@ throw new Error('both "keyword" and "schema" passed, only one allowed'); | ||
schema, | ||
strictSchema, | ||
schemaPath, | ||
@@ -85,3 +84,3 @@ topSchemaRef, | ||
} | ||
function extendSubschemaMode(subschema, { compositeRule, createErrors, allErrors, strictSchema }) { | ||
function extendSubschemaMode(subschema, { jtdDiscriminator, jtdMetadata, compositeRule, createErrors, allErrors }) { | ||
if (compositeRule !== undefined) | ||
@@ -93,3 +92,4 @@ subschema.compositeRule = compositeRule; | ||
subschema.allErrors = allErrors; | ||
subschema.strictSchema = strictSchema; // not inherited | ||
subschema.jtdDiscriminator = jtdDiscriminator; // not inherited | ||
subschema.jtdMetadata = jtdMetadata; // not inherited | ||
} | ||
@@ -96,0 +96,0 @@ function getErrorPath(dataProp, dataPropType, jsPropertySyntax) { |
@@ -122,2 +122,4 @@ "use strict"; | ||
function typeAndKeywords(it, errsCount) { | ||
if (it.opts.jtd) | ||
return iterate_1.schemaKeywords(it, [], false, errsCount); | ||
const types = dataType_1.getSchemaTypes(it.schema); | ||
@@ -124,0 +126,0 @@ const checkedTypes = dataType_1.coerceAndCheckDataType(it, types); |
@@ -19,3 +19,4 @@ "use strict"; | ||
} | ||
checkStrictTypes(it, types); | ||
if (!opts.jtd) | ||
checkStrictTypes(it, types); | ||
gen.block(() => { | ||
@@ -22,0 +23,0 @@ for (const group of RULES.rules) |
@@ -20,3 +20,3 @@ export { Format, FormatDefinition, AsyncFormatDefinition, KeywordDefinition, KeywordErrorDefinition, CodeKeywordDefinition, MacroKeywordDefinition, FuncKeywordDefinition, Vocabulary, Schema, SchemaObject, AnySchemaObject, AsyncSchema, AnySchema, ValidateFunction, AsyncValidateFunction, AnyValidateFunction, ErrorObject, ErrorNoParams, } from "./types"; | ||
export declare type Options = CurrentOptions & DeprecatedOptions; | ||
interface CurrentOptions { | ||
export interface CurrentOptions { | ||
strict?: boolean | "log"; | ||
@@ -47,2 +47,3 @@ strictTypes?: boolean | "log"; | ||
dynamicRef?: boolean; | ||
jtd?: boolean; | ||
meta?: SchemaObject | boolean; | ||
@@ -60,2 +61,3 @@ defaultMeta?: string | AnySchemaObject; | ||
code?: CodeOptions; | ||
ajvErrors?: boolean; | ||
} | ||
@@ -62,0 +64,0 @@ export interface CodeOptions { |
@@ -412,4 +412,7 @@ "use strict"; | ||
_addSchema(schema, meta, validateSchema = this.opts.validateSchema, addSchema = this.opts.addUsedSchema) { | ||
if (typeof schema != "object" && typeof schema != "boolean") { | ||
throw new Error("schema must be object or boolean"); | ||
if (typeof schema != "object") { | ||
if (this.opts.jtd) | ||
throw new Error("schema must be object"); | ||
else if (typeof schema != "boolean") | ||
throw new Error("schema must be object or boolean"); | ||
} | ||
@@ -519,3 +522,3 @@ let sch = this._cache.get(schema); | ||
} | ||
const KEYWORD_NAME = /^[a-z_$][a-z0-9_$-]*$/i; | ||
const KEYWORD_NAME = /^[a-z_$][a-z0-9_$-:]*$/i; | ||
function checkKeyword(keyword, def) { | ||
@@ -522,0 +525,0 @@ const { RULES } = this; |
@@ -69,2 +69,3 @@ import type { CodeGen, Code, Name, ScopeValueSets, ValueScopeName } from "../compile/codegen"; | ||
dataPath: string; | ||
instancePath?: string; | ||
schemaPath: string; | ||
@@ -71,0 +72,0 @@ params: P; |
@@ -44,4 +44,9 @@ "use strict"; | ||
// TODO maybe an option instead of hard-coded 8? | ||
const hasProp = gen.scopeValue("func", { | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
ref: Object.prototype.hasOwnProperty, | ||
code: codegen_1._ `Object.prototype.hasOwnProperty`, | ||
}); | ||
const propsSchema = util_1.schemaRefOrVal(it, parentSchema.properties, "properties"); | ||
definedProp = codegen_1._ `${propsSchema}.hasOwnProperty(${key})`; | ||
definedProp = codegen_1._ `${hasProp}.call(${propsSchema}, ${key})`; | ||
} | ||
@@ -57,3 +62,3 @@ else if (props.length) { | ||
} | ||
return codegen_1._ `!(${definedProp})`; | ||
return codegen_1.not(definedProp); | ||
} | ||
@@ -96,3 +101,2 @@ function deleteAdditional(key) { | ||
dataPropType: subschema_1.Type.Str, | ||
strictSchema: it.strictSchema, | ||
}; | ||
@@ -99,0 +103,0 @@ if (errors === false) { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const codegen_1 = require("../../compile/codegen"); | ||
const util_1 = require("../../compile/util"); | ||
const code_1 = require("../code"); | ||
const def = { | ||
@@ -9,27 +8,3 @@ keyword: "anyOf", | ||
trackErrors: true, | ||
code(cxt) { | ||
const { gen, schema, it } = cxt; | ||
/* istanbul ignore if */ | ||
if (!Array.isArray(schema)) | ||
throw new Error("ajv implementation error"); | ||
const alwaysValid = schema.some((sch) => util_1.alwaysValidSchema(it, sch)); | ||
if (alwaysValid && !it.opts.unevaluated) | ||
return; | ||
const valid = gen.let("valid", false); | ||
const schValid = gen.name("_valid"); | ||
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)); | ||
})); | ||
cxt.result(valid, () => cxt.reset(), () => cxt.error(true)); | ||
}, | ||
code: code_1.validateUnion, | ||
error: { | ||
@@ -36,0 +11,0 @@ message: "should match some schema in anyOf", |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const codegen_1 = require("../../compile/codegen"); | ||
const subschema_1 = require("../../compile/subschema"); | ||
const util_1 = require("../../compile/util"); | ||
const validate_1 = require("../../compile/validate"); | ||
const code_1 = require("../code"); | ||
const def = { | ||
@@ -13,4 +13,3 @@ keyword: "items", | ||
code(cxt) { | ||
const { gen, schema, parentSchema, data, it } = cxt; | ||
const len = gen.const("len", codegen_1._ `${data}.length`); | ||
const { gen, schema, it } = cxt; | ||
if (Array.isArray(schema)) { | ||
@@ -24,7 +23,9 @@ if (it.opts.unevaluated && schema.length && it.items !== true) { | ||
it.items = true; | ||
if (!util_1.alwaysValidSchema(it, schema)) | ||
validateArray(); | ||
if (util_1.alwaysValidSchema(it, schema)) | ||
return; | ||
cxt.ok(code_1.validateArray(cxt)); | ||
} | ||
function validateTuple(schArr) { | ||
if (it.opts.strictTuples && !fullTupleSchema(schema.length, parentSchema)) { | ||
const { parentSchema, data } = cxt; | ||
if (it.opts.strictTuples && !fullTupleSchema(schArr.length, parentSchema)) { | ||
const msg = `"items" is ${schArr.length}-tuple, but minItems or maxItems/additionalItems are not specified or different`; | ||
@@ -34,2 +35,3 @@ validate_1.checkStrictMode(it, msg, it.opts.strictTuples); | ||
const valid = gen.name("valid"); | ||
const len = gen.const("len", codegen_1._ `${data}.length`); | ||
schArr.forEach((sch, i) => { | ||
@@ -42,3 +44,2 @@ if (util_1.alwaysValidSchema(it, sch)) | ||
dataProp: i, | ||
strictSchema: it.strictSchema, | ||
}, valid)); | ||
@@ -48,16 +49,2 @@ cxt.ok(valid); | ||
} | ||
function validateArray() { | ||
const valid = gen.name("valid"); | ||
gen.forRange("i", 0, len, (i) => { | ||
cxt.subschema({ | ||
keyword: "items", | ||
dataProp: i, | ||
dataPropType: subschema_1.Type.Num, | ||
strictSchema: it.strictSchema, | ||
}, valid); | ||
if (!it.allErrors) | ||
gen.if(codegen_1.not(valid), () => gen.break()); | ||
}); | ||
cxt.ok(valid); | ||
} | ||
}, | ||
@@ -64,0 +51,0 @@ }; |
@@ -55,3 +55,2 @@ "use strict"; | ||
dataPropType: subschema_1.Type.Str, | ||
strictSchema: it.strictSchema, | ||
}, valid); | ||
@@ -58,0 +57,0 @@ if (it.opts.unevaluated && props !== true) { |
@@ -45,3 +45,2 @@ "use strict"; | ||
dataProp: prop, | ||
strictSchema: it.strictSchema, | ||
}, valid); | ||
@@ -48,0 +47,0 @@ } |
@@ -27,3 +27,2 @@ "use strict"; | ||
compositeRule: true, | ||
strictSchema: it.strictSchema, | ||
}, valid); | ||
@@ -30,0 +29,0 @@ gen.if(codegen_1.not(valid), () => { |
@@ -14,1 +14,3 @@ import type { SchemaMap } from "../types"; | ||
export declare function usePattern(gen: CodeGen, pattern: string): Name; | ||
export declare function validateArray(cxt: KeywordCxt): Name; | ||
export declare function validateUnion(cxt: KeywordCxt): void; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.usePattern = exports.callValidateCode = exports.schemaProperties = exports.allSchemaProperties = exports.noPropertyInData = exports.propertyInData = exports.reportMissingProp = exports.checkMissingProp = exports.checkReportMissingProp = void 0; | ||
exports.validateUnion = exports.validateArray = exports.usePattern = exports.callValidateCode = exports.schemaProperties = exports.allSchemaProperties = exports.noPropertyInData = exports.propertyInData = exports.reportMissingProp = exports.checkMissingProp = exports.checkReportMissingProp = void 0; | ||
const codegen_1 = require("../compile/codegen"); | ||
const util_1 = require("../compile/util"); | ||
const subschema_1 = require("../compile/subschema"); | ||
const names_1 = require("../compile/names"); | ||
@@ -67,2 +68,52 @@ function checkReportMissingProp(cxt, prop) { | ||
exports.usePattern = usePattern; | ||
function validateArray(cxt) { | ||
const { gen, data, keyword, it } = cxt; | ||
const valid = gen.name("valid"); | ||
if (it.allErrors) { | ||
const validArr = gen.let("valid", true); | ||
validateItems(() => gen.assign(validArr, false)); | ||
return validArr; | ||
} | ||
gen.var(valid, true); | ||
validateItems(() => gen.break()); | ||
return valid; | ||
function validateItems(notValid) { | ||
const len = gen.const("len", codegen_1._ `${data}.length`); | ||
gen.forRange("i", 0, len, (i) => { | ||
cxt.subschema({ | ||
keyword, | ||
dataProp: i, | ||
dataPropType: subschema_1.Type.Num, | ||
}, valid); | ||
gen.if(codegen_1.not(valid), notValid); | ||
}); | ||
} | ||
} | ||
exports.validateArray = validateArray; | ||
function validateUnion(cxt) { | ||
const { gen, schema, keyword, it } = cxt; | ||
/* istanbul ignore if */ | ||
if (!Array.isArray(schema)) | ||
throw new Error("ajv implementation error"); | ||
const alwaysValid = schema.some((sch) => util_1.alwaysValidSchema(it, sch)); | ||
if (alwaysValid && !it.opts.unevaluated) | ||
return; | ||
const valid = gen.let("valid", false); | ||
const schValid = gen.name("_valid"); | ||
gen.block(() => schema.forEach((_sch, i) => { | ||
const schCxt = cxt.subschema({ | ||
keyword, | ||
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)); | ||
})); | ||
cxt.result(valid, () => cxt.reset(), () => cxt.error(true)); | ||
} | ||
exports.validateUnion = validateUnion; | ||
//# sourceMappingURL=code.js.map |
@@ -10,2 +10,3 @@ "use strict"; | ||
"$vocabulary", | ||
{ keyword: "$comment" }, | ||
"definitions", | ||
@@ -12,0 +13,0 @@ id_1.default, |
@@ -41,3 +41,2 @@ "use strict"; | ||
schema: sch, | ||
strictSchema: true, | ||
dataTypes: [], | ||
@@ -44,0 +43,0 @@ schemaPath: codegen_1.nil, |
@@ -47,3 +47,2 @@ "use strict"; | ||
dataPropType: subschema_1.Type.Str, | ||
strictSchema: it.strictSchema, | ||
}, valid); | ||
@@ -50,0 +49,0 @@ if (!allErrors) |
@@ -27,2 +27,3 @@ "use strict"; | ||
// any | ||
{ keyword: "type", schemaType: ["string", "array"] }, | ||
{ keyword: "nullable", schemaType: "boolean" }, | ||
@@ -29,0 +30,0 @@ const_1.default, |
@@ -254,12 +254,12 @@ # API Reference | ||
strict: true, | ||
strictTypes: "log", | ||
strictTuples: "log", | ||
allowUnionTypes: false, | ||
allowMatchingProperties: false, | ||
validateFormats: true, | ||
strictTypes: "log", // * | ||
strictTuples: "log", // * | ||
allowUnionTypes: false, // * | ||
allowMatchingProperties: false, // * | ||
validateFormats: true, // * | ||
// validation and reporting options: | ||
$data: false, | ||
$data: false, // * | ||
allErrors: false, | ||
verbose: false, | ||
$comment: false, | ||
verbose: false, // * | ||
$comment: false, // * | ||
formats: {}, | ||
@@ -269,7 +269,7 @@ keywords: {}, | ||
logger: undefined, | ||
loadSchema: undefined, // function(uri: string): Promise {} | ||
loadSchema: undefined, // *, function(uri: string): Promise {} | ||
// options to modify validated data: | ||
removeAdditional: false, | ||
useDefaults: false, | ||
coerceTypes: false, | ||
useDefaults: false, // * | ||
coerceTypes: false, // * | ||
// advanced options: | ||
@@ -281,7 +281,8 @@ meta: true, | ||
passContext: false, | ||
loopRequired: Infinity, | ||
loopRequired: Infinity, // * | ||
loopEnum: Infinity, // NEW | ||
ownProperties: false, | ||
multipleOfPrecision: undefined, | ||
messages: true, | ||
multipleOfPrecision: undefined, // * | ||
messages: true, // false with JTD | ||
ajvErrors: false // only with JTD | ||
code: { | ||
@@ -298,2 +299,4 @@ // NEW | ||
<sup>\*</sup> these options are not supported with JSON Type Definition schemas | ||
#### Strict mode options (NEW in v7) | ||
@@ -370,2 +373,3 @@ | ||
- _messages_: Include human-readable messages in errors. `true` by default. `false` can be passed when messages are generated outside of Ajv code (e.g. with [ajv-i18n](https://github.com/ajv-validator/ajv-i18n)). | ||
- _ajvErrors_: this option is only supported with JTD schemas to generate error objects with the properties described in the first part of [Validation errors](#validation-errors) section, otherwise JTD errors are generated when JTD schemas are used (see the second part of [the same section](#validation-errors)). | ||
- _code_ (new in v7): code generation options: | ||
@@ -401,3 +405,3 @@ | ||
Each error is an object with the following properties: | ||
Each error reported when validating against JSON Schema (also when validating against JTD schema with option `ajvErrors`) is an object with the following properties: | ||
@@ -422,2 +426,15 @@ ```typescript | ||
[JTD specification](./json-type-definition.md) defines strict format for validation errors, where each error is an object with the following properties: | ||
```typescript | ||
interface JTDErrorObject { | ||
instancePath: string // JSON Pointer to the location in the data instance | ||
schemaPath: string // JSON Pointer to the location in the schema | ||
} | ||
``` | ||
This error format is used when using JTD schemas. To simplify usage, you may still generate Ajv error objects using `ajvErrors` option. You can also add a human-readable error message to error objects using option `messages`. | ||
**Please note**: Ajv is not fully consistent with JTD regarding the error objects in some scenarios - it will be consistent by the time Ajv version 8 is released. Therefore it is not recommended yet to use error objects for any advanced application logic. | ||
### Error parameters | ||
@@ -424,0 +441,0 @@ |
@@ -1,23 +0,42 @@ | ||
## Strict mode | ||
# Strict mode | ||
Strict mode intends to prevent any unexpected behaviours or silently ignored mistakes in user schemas. It does not change any validation results compared with JSON Schema specification, but it makes some schemas invalid and throws exception or logs warning (with `strict: "log"` option) in case any restriction is violated. | ||
Strict mode intends to prevent any unexpected behaviours or silently ignored mistakes in user schemas. It does not change any validation results compared with the specification, but it makes some schemas invalid and throws exception or logs warning (with `strict: "log"` option) in case any restriction is violated. | ||
To disable all strict mode restrictions use option `strict: false`. Some of the restrictions can be changed with their own options | ||
- [Prohibit ignored keywords](#prohibit-ignored-keywords) | ||
- unknown keywords | ||
- ignored "additionalItems" keyword | ||
- ignored "if", "then", "else" keywords | ||
- ignored "contains", "maxContains" and "minContains" keywords | ||
- unknown formats | ||
- ignored defaults | ||
- [Prevent unexpected validation](#prevent-unexpected-validation) | ||
- overlap between "properties" and "patternProperties" keywords (also `allowMatchingProperties` option) | ||
- unconstrained tuples (also `strictTuples` option) | ||
- [Strict types](#strict-types) (also `strictTypes` option) | ||
- union types (also `allowUnionTypes` option) | ||
- contradictory types | ||
- require applicable types | ||
- [Strict number validation](#strict-number-validation) | ||
- [JSON Type Definition schemas](#json-type-definition-schemas) | ||
- [JSON Schema schemas](#json-schema-schemas) | ||
- [Prohibit ignored keywords](#prohibit-ignored-keywords) | ||
- unknown keywords | ||
- ignored "additionalItems" keyword | ||
- ignored "if", "then", "else" keywords | ||
- ignored "contains", "maxContains" and "minContains" keywords | ||
- unknown formats | ||
- ignored defaults | ||
- [Prevent unexpected validation](#prevent-unexpected-validation) | ||
- overlap between "properties" and "patternProperties" keywords (also `allowMatchingProperties` option) | ||
- unconstrained tuples (also `strictTuples` option) | ||
- [Strict types](#strict-types) (also `strictTypes` option) | ||
- union types (also `allowUnionTypes` option) | ||
- contradictory types | ||
- require applicable types | ||
- [Strict number validation](#strict-number-validation) | ||
## JSON Type Definition schemas | ||
JTD specification is strict - whether Ajv strict mode is enabled or not it will not allow schemas with ignored or ambiguous elements, including: | ||
- unknown schema keywords | ||
- combining multiple schema forms in one schema | ||
- defining the same property as both required and optional | ||
- re-defining discriminator tag inside properties, even if the definition is non-contradictory | ||
See [JSON Type Definition](./json-type-definition.md) for informal and [RFC8927](https://datatracker.ietf.org/doc/rfc8927/) for formal specification descriptions. | ||
The only change that strict mode introduces to JTD schemas, without changing their syntax or semantics, is the requirement that all members that are present in optional `metadata` members are defined as Ajv keywords. This restriction can be disabled with `strict: false` option, without any impact to other JTD features. | ||
## JSON Schema schemas | ||
JSON Schema specification is very permissive and allows many elements in the schema to be quietly ignored or be ambiguous. It is recommended to use JSON Schema with strict mode. | ||
### Prohibit ignored keywords | ||
@@ -24,0 +43,0 @@ |
@@ -104,3 +104,3 @@ # Data validation | ||
- `contentEncoding`: [RFC 2045](https://tools.ietf.org/html/rfc2045#section-6.1), e.g., "base64". | ||
- `contentMediaType`: [RFC 2046](https://tools.ietf.org/html/rfc2046), e.g., "image/png". | ||
- `contentMediaType`: [RFC 2046](https://datatracker.ietf.org/doc/rfc2046/), e.g., "image/png". | ||
@@ -137,3 +137,3 @@ **Please note**: Ajv does not implement validation of the keywords `examples`, `contentEncoding` and `contentMediaType` but it reserves them. If you want to create a plugin that implements any of them, it should remove these keywords from the instance. | ||
- _uri-reference_: URI reference, including full and relative URIs. | ||
- _uri-template_: URI template according to [RFC6570](https://tools.ietf.org/html/rfc6570) | ||
- _uri-template_: URI template according to [RFC6570](https://datatracker.ietf.org/doc/rfc6570/) | ||
- _url_ (deprecated): [URL record](https://url.spec.whatwg.org/#concept-url). | ||
@@ -145,4 +145,4 @@ - _email_: email address. | ||
- _regex_: tests whether a string is a valid regular expression by passing it to RegExp constructor. | ||
- _uuid_: Universally Unique IDentifier according to [RFC4122](http://tools.ietf.org/html/rfc4122). | ||
- _json-pointer_: JSON-pointer according to [RFC6901](https://tools.ietf.org/html/rfc6901). | ||
- _uuid_: Universally Unique Identifier according to [RFC4122](https://datatracker.ietf.org/doc/rfc4122/). | ||
- _json-pointer_: JSON-pointer according to [RFC6901](https://datatracker.ietf.org/doc/rfc6901/). | ||
- _relative-json-pointer_: relative JSON-pointer according to [this draft](http://tools.ietf.org/html/draft-luff-relative-json-pointer-00). | ||
@@ -281,3 +281,3 @@ | ||
The value of "$data" should be a [JSON-pointer](https://tools.ietf.org/html/rfc6901) to the data (the root is always the top level data object, even if the $data reference is inside a referenced subschema) or a [relative JSON-pointer](http://tools.ietf.org/html/draft-luff-relative-json-pointer-00) (it is relative to the current point in data; if the \$data reference is inside a referenced subschema it cannot point to the data outside of the root level for this subschema). | ||
The value of "$data" should be a [JSON-pointer](https://datatracker.ietf.org/doc/rfc6901/) to the data (the root is always the top level data object, even if the $data reference is inside a referenced subschema) or a [relative JSON-pointer](http://tools.ietf.org/html/draft-luff-relative-json-pointer-00) (it is relative to the current point in data; if the \$data reference is inside a referenced subschema it cannot point to the data outside of the root level for this subschema). | ||
@@ -329,3 +329,3 @@ Examples. | ||
With the package [ajv-merge-patch](https://github.com/ajv-validator/ajv-merge-patch) you can use the keywords `$merge` and `$patch` that allow extending JSON Schemas with patches using formats [JSON Merge Patch (RFC 7396)](https://tools.ietf.org/html/rfc7396) and [JSON Patch (RFC 6902)](https://tools.ietf.org/html/rfc6902). | ||
With the package [ajv-merge-patch](https://github.com/ajv-validator/ajv-merge-patch) you can use the keywords `$merge` and `$patch` that allow extending JSON Schemas with patches using formats [JSON Merge Patch (RFC 7396)](https://datatracker.ietf.org/doc/rfc7396/) and [JSON Patch (RFC 6902)](https://datatracker.ietf.org/doc/rfc6902/). | ||
@@ -332,0 +332,0 @@ To add keywords `$merge` and `$patch` to Ajv instance use this code: |
@@ -48,5 +48,4 @@ export { | ||
super._addDefaultMetaSchema() | ||
const {$data, meta} = this.opts | ||
if (!meta) return | ||
const metaSchema = $data | ||
if (!this.opts.meta) return | ||
const metaSchema = this.opts.$data | ||
? this.$dataMetaSchema(draft7MetaSchema, META_SUPPORT_DATA) | ||
@@ -53,0 +52,0 @@ : draft7MetaSchema |
@@ -11,9 +11,3 @@ import type { | ||
import {schemaRefOrVal, unescapeJsonPointer, mergeEvaluated} from "./util" | ||
import { | ||
reportError, | ||
reportExtraError, | ||
resetErrorsCount, | ||
keywordError, | ||
keyword$DataError, | ||
} from "./errors" | ||
import {reportError, reportExtraError, resetErrorsCount, keyword$DataError} from "./errors" | ||
import {CodeGen, _, nil, or, not, getProperty, Code, Name} from "./codegen" | ||
@@ -29,3 +23,3 @@ import N from "./names" | ||
readonly $data?: string | false | ||
readonly schema: any // keyword value in the schema | ||
schema: any // keyword value in the schema | ||
readonly schemaValue: Code | number | boolean // Code reference to keyword schema value or primitive value | ||
@@ -38,3 +32,3 @@ readonly schemaCode: Code | number | boolean // Code reference to resolved schema value (different if schema is $data) | ||
params: KeywordCxtParams // object to pass parameters to error messages from keyword code | ||
readonly it: SchemaObjCxt // schema compilation context (schema is guaranted to be an object, not boolean) | ||
readonly it: SchemaObjCxt // schema compilation context (schema is guaranteed to be an object, not boolean) | ||
readonly def: AddedKeywordDefinition | ||
@@ -108,3 +102,3 @@ | ||
error(append?: true): void { | ||
;(append ? reportExtraError : reportError)(this, this.def.error || keywordError) | ||
;(append ? reportExtraError : reportError)(this, this.def.error) | ||
} | ||
@@ -111,0 +105,0 @@ |
@@ -20,4 +20,4 @@ import type {ErrorObject} from "../types" | ||
constructor(baseId: string, ref: string) { | ||
super(`can't resolve reference ${ref} from id ${baseId}`) | ||
constructor(baseId: string, ref: string, msg?: string) { | ||
super(msg || `can't resolve reference ${ref} from id ${baseId}`) | ||
this.missingRef = resolveUrl(baseId, ref) | ||
@@ -24,0 +24,0 @@ this.missingSchema = normalizeId(getFullPath(this.missingRef)) |
@@ -20,3 +20,3 @@ import type {KeywordErrorCxt, KeywordErrorDefinition} from "../types" | ||
cxt: KeywordErrorCxt, | ||
error: KeywordErrorDefinition, | ||
error: KeywordErrorDefinition = keywordError, | ||
overrideAllErrors?: boolean | ||
@@ -34,3 +34,6 @@ ): void { | ||
export function reportExtraError(cxt: KeywordErrorCxt, error: KeywordErrorDefinition): void { | ||
export function reportExtraError( | ||
cxt: KeywordErrorCxt, | ||
error: KeywordErrorDefinition = keywordError | ||
): void { | ||
const {it} = cxt | ||
@@ -102,3 +105,3 @@ const {gen, compositeRule, allErrors} = it | ||
keyword: new Name("keyword"), | ||
schemaPath: new Name("schemaPath"), | ||
schemaPath: new Name("schemaPath"), // also used in JTD errors | ||
params: new Name("params"), | ||
@@ -109,12 +112,28 @@ propertyName: new Name("propertyName"), | ||
parentSchema: new Name("parentSchema"), | ||
// JTD error properties | ||
instancePath: new Name("instancePath"), | ||
} | ||
function errorObjectCode(cxt: KeywordErrorCxt, error: KeywordErrorDefinition): Code { | ||
const { | ||
keyword, | ||
data, | ||
schemaValue, | ||
it: {gen, createErrors, topSchemaRef, schemaPath, errorPath, errSchemaPath, propertyName, opts}, | ||
} = cxt | ||
const {createErrors, opts} = cxt.it | ||
if (createErrors === false) return _`{}` | ||
return (opts.jtd && !opts.ajvErrors ? jtdErrorObject : ajvErrorObject)(cxt, error) | ||
} | ||
function jtdErrorObject(cxt: KeywordErrorCxt, {message}: KeywordErrorDefinition): Code { | ||
const {gen, keyword, it} = cxt | ||
const {errorPath, errSchemaPath, opts} = it | ||
const keyValues: [Name, SafeExpr | string][] = [ | ||
[E.instancePath, strConcat(N.dataPath, errorPath)], | ||
[E.schemaPath, str`${errSchemaPath}/${keyword}`], | ||
] | ||
if (opts.messages) { | ||
keyValues.push([E.message, typeof message == "function" ? message(cxt) : message]) | ||
} | ||
return gen.object(...keyValues) | ||
} | ||
function ajvErrorObject(cxt: KeywordErrorCxt, error: KeywordErrorDefinition): Code { | ||
const {gen, keyword, data, schemaValue, it} = cxt | ||
const {topSchemaRef, schemaPath, errorPath, errSchemaPath, propertyName, opts} = it | ||
const {params, message} = error | ||
@@ -128,5 +147,4 @@ const keyValues: [Name, SafeExpr | string][] = [ | ||
if (propertyName) keyValues.push([E.propertyName, propertyName]) | ||
if (opts.messages !== false) { | ||
const msg = typeof message == "function" ? message(cxt) : message | ||
keyValues.push([E.message, msg]) | ||
if (opts.messages) { | ||
keyValues.push([E.message, typeof message == "function" ? message(cxt) : message]) | ||
} | ||
@@ -133,0 +151,0 @@ if (opts.verbose) { |
@@ -41,3 +41,2 @@ import type { | ||
readonly schemaEnv: SchemaEnv | ||
readonly strictSchema?: boolean | ||
readonly rootId: string | ||
@@ -55,2 +54,4 @@ baseId: string // the current schema base URI that should be used as the base for resolving URIs in references (\$ref) | ||
items?: EvaluatedItems | Name // last item evaluated by this schema - used by parent schema or assigned to validation function | ||
jtdDiscriminator?: string | ||
jtdMetadata?: boolean | ||
readonly createErrors?: boolean | ||
@@ -140,7 +141,6 @@ readonly opts: InstanceOptions // Ajv instance option. | ||
schemaEnv: sch, | ||
strictSchema: true, | ||
rootId, | ||
baseId: sch.baseId || rootId, | ||
schemaPath: nil, | ||
errSchemaPath: "#", | ||
errSchemaPath: this.opts.jtd ? "" : "#", | ||
errorPath: _`""`, | ||
@@ -147,0 +147,0 @@ opts: this.opts, |
@@ -47,5 +47,5 @@ import type {AddedKeywordDefinition} from "../types" | ||
post: {rules: []}, | ||
all: {type: true, $comment: true}, | ||
keywords: {type: true, $comment: true}, | ||
all: {}, | ||
keywords: {}, | ||
} | ||
} |
@@ -11,3 +11,2 @@ import type {AnySchema} from "../types" | ||
schema: AnySchema | ||
strictSchema?: boolean | ||
schemaPath: Code | ||
@@ -25,2 +24,4 @@ errSchemaPath: string | ||
propertyName?: Name | ||
jtdDiscriminator?: string | ||
jtdMetadata?: boolean | ||
compositeRule?: true | ||
@@ -40,3 +41,2 @@ createErrors?: boolean | ||
schema: AnySchema | ||
strictSchema: boolean | ||
schemaPath: Code | ||
@@ -50,2 +50,4 @@ errSchemaPath: string | ||
dataPropType: Type | ||
jtdDiscriminator: string | ||
jtdMetadata: boolean | ||
compositeRule: true | ||
@@ -67,11 +69,3 @@ createErrors: boolean | ||
it: SchemaObjCxt, | ||
{ | ||
keyword, | ||
schemaProp, | ||
schema, | ||
strictSchema, | ||
schemaPath, | ||
errSchemaPath, | ||
topSchemaRef, | ||
}: SubschemaArgs | ||
{keyword, schemaProp, schema, schemaPath, errSchemaPath, topSchemaRef}: SubschemaArgs | ||
): SubschemaContext { | ||
@@ -103,3 +97,2 @@ if (keyword !== undefined && schema !== undefined) { | ||
schema, | ||
strictSchema, | ||
schemaPath, | ||
@@ -154,3 +147,3 @@ topSchemaRef, | ||
subschema: SubschemaContext, | ||
{compositeRule, createErrors, allErrors, strictSchema}: SubschemaArgs | ||
{jtdDiscriminator, jtdMetadata, compositeRule, createErrors, allErrors}: SubschemaArgs | ||
): void { | ||
@@ -160,3 +153,4 @@ if (compositeRule !== undefined) subschema.compositeRule = compositeRule | ||
if (allErrors !== undefined) subschema.allErrors = allErrors | ||
subschema.strictSchema = strictSchema // not inherited | ||
subschema.jtdDiscriminator = jtdDiscriminator // not inherited | ||
subschema.jtdMetadata = jtdMetadata // not inherited | ||
} | ||
@@ -163,0 +157,0 @@ |
@@ -134,2 +134,3 @@ import type {AnySchema} from "../../types" | ||
function typeAndKeywords(it: SchemaObjCxt, errsCount?: Name): void { | ||
if (it.opts.jtd) return schemaKeywords(it, [], false, errsCount) | ||
const types = getSchemaTypes(it.schema) | ||
@@ -136,0 +137,0 @@ const checkedTypes = coerceAndCheckDataType(it, types) |
@@ -24,3 +24,3 @@ import type {SchemaObjCxt} from ".." | ||
} | ||
checkStrictTypes(it, types) | ||
if (!opts.jtd) checkStrictTypes(it, types) | ||
gen.block(() => { | ||
@@ -27,0 +27,0 @@ for (const group of RULES.rules) groupKeywords(group) |
@@ -80,3 +80,3 @@ export { | ||
interface CurrentOptions { | ||
export interface CurrentOptions { | ||
// strict mode options (NEW) | ||
@@ -109,2 +109,3 @@ strict?: boolean | "log" | ||
dynamicRef?: boolean // NEW | ||
jtd?: boolean // NEW | ||
meta?: SchemaObject | boolean | ||
@@ -122,2 +123,3 @@ defaultMeta?: string | AnySchemaObject | ||
code?: CodeOptions // NEW | ||
ajvErrors?: boolean | ||
} | ||
@@ -632,4 +634,5 @@ | ||
): SchemaEnv { | ||
if (typeof schema != "object" && typeof schema != "boolean") { | ||
throw new Error("schema must be object or boolean") | ||
if (typeof schema != "object") { | ||
if (this.opts.jtd) throw new Error("schema must be object") | ||
else if (typeof schema != "boolean") throw new Error("schema must be object or boolean") | ||
} | ||
@@ -746,3 +749,3 @@ let sch = this._cache.get(schema) | ||
const KEYWORD_NAME = /^[a-z_$][a-z0-9_$-]*$/i | ||
const KEYWORD_NAME = /^[a-z_$][a-z0-9_$-:]*$/i | ||
@@ -749,0 +752,0 @@ function checkKeyword(this: Ajv, keyword: string | string[], def?: KeywordDefinition): void { |
@@ -79,2 +79,3 @@ import type {CodeGen, Code, Name, ScopeValueSets, ValueScopeName} from "../compile/codegen" | ||
dataPath: string | ||
instancePath?: string | ||
schemaPath: string | ||
@@ -81,0 +82,0 @@ params: P |
@@ -55,4 +55,9 @@ import type { | ||
// TODO maybe an option instead of hard-coded 8? | ||
const hasProp = gen.scopeValue("func", { | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
ref: Object.prototype.hasOwnProperty, | ||
code: _`Object.prototype.hasOwnProperty`, | ||
}) | ||
const propsSchema = schemaRefOrVal(it, parentSchema.properties, "properties") | ||
definedProp = _`${propsSchema}.hasOwnProperty(${key})` | ||
definedProp = _`${hasProp}.call(${propsSchema}, ${key})` | ||
} else if (props.length) { | ||
@@ -66,3 +71,3 @@ definedProp = or(...props.map((p) => _`${key} === ${p}`)) | ||
} | ||
return _`!(${definedProp})` | ||
return not(definedProp) | ||
} | ||
@@ -107,3 +112,2 @@ | ||
dataPropType: Type.Str, | ||
strictSchema: it.strictSchema, | ||
} | ||
@@ -110,0 +114,0 @@ if (errors === false) { |
import type {CodeKeywordDefinition, ErrorNoParams, AnySchema} from "../../types" | ||
import type KeywordCxt from "../../compile/context" | ||
import {_, not} from "../../compile/codegen" | ||
import {alwaysValidSchema} from "../../compile/util" | ||
import {validateUnion} from "../code" | ||
@@ -12,36 +10,3 @@ export type AnyOfError = ErrorNoParams<"anyOf", AnySchema[]> | ||
trackErrors: true, | ||
code(cxt: KeywordCxt) { | ||
const {gen, schema, it} = cxt | ||
/* istanbul ignore if */ | ||
if (!Array.isArray(schema)) throw new Error("ajv implementation error") | ||
const alwaysValid = schema.some((sch: AnySchema) => alwaysValidSchema(it, sch)) | ||
if (alwaysValid && !it.opts.unevaluated) return | ||
const valid = gen.let("valid", false) | ||
const schValid = gen.name("_valid") | ||
gen.block(() => | ||
schema.forEach((_sch: AnySchema, i: number) => { | ||
const schCxt = cxt.subschema( | ||
{ | ||
keyword: "anyOf", | ||
schemaProp: i, | ||
compositeRule: true, | ||
}, | ||
schValid | ||
) | ||
gen.assign(valid, _`${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(not(valid)) | ||
}) | ||
) | ||
cxt.result( | ||
valid, | ||
() => cxt.reset(), | ||
() => cxt.error(true) | ||
) | ||
}, | ||
code: validateUnion, | ||
error: { | ||
@@ -48,0 +13,0 @@ message: "should match some schema in anyOf", |
import type {CodeKeywordDefinition, AnySchema} from "../../types" | ||
import type KeywordCxt from "../../compile/context" | ||
import {_, not} from "../../compile/codegen" | ||
import {Type} from "../../compile/subschema" | ||
import {_} from "../../compile/codegen" | ||
import {alwaysValidSchema, mergeEvaluated} from "../../compile/util" | ||
import {checkStrictMode} from "../../compile/validate" | ||
import {validateArray} from "../code" | ||
@@ -14,4 +14,3 @@ const def: CodeKeywordDefinition = { | ||
code(cxt: KeywordCxt) { | ||
const {gen, schema, parentSchema, data, it} = cxt | ||
const len = gen.const("len", _`${data}.length`) | ||
const {gen, schema, it} = cxt | ||
if (Array.isArray(schema)) { | ||
@@ -24,7 +23,9 @@ if (it.opts.unevaluated && schema.length && it.items !== true) { | ||
it.items = true | ||
if (!alwaysValidSchema(it, schema)) validateArray() | ||
if (alwaysValidSchema(it, schema)) return | ||
cxt.ok(validateArray(cxt)) | ||
} | ||
function validateTuple(schArr: AnySchema[]): void { | ||
if (it.opts.strictTuples && !fullTupleSchema(schema.length, parentSchema)) { | ||
const {parentSchema, data} = cxt | ||
if (it.opts.strictTuples && !fullTupleSchema(schArr.length, parentSchema)) { | ||
const msg = `"items" is ${schArr.length}-tuple, but minItems or maxItems/additionalItems are not specified or different` | ||
@@ -34,2 +35,3 @@ checkStrictMode(it, msg, it.opts.strictTuples) | ||
const valid = gen.name("valid") | ||
const len = gen.const("len", _`${data}.length`) | ||
schArr.forEach((sch: AnySchema, i: number) => { | ||
@@ -43,3 +45,2 @@ if (alwaysValidSchema(it, sch)) return | ||
dataProp: i, | ||
strictSchema: it.strictSchema, | ||
}, | ||
@@ -52,19 +53,2 @@ valid | ||
} | ||
function validateArray(): void { | ||
const valid = gen.name("valid") | ||
gen.forRange("i", 0, len, (i) => { | ||
cxt.subschema( | ||
{ | ||
keyword: "items", | ||
dataProp: i, | ||
dataPropType: Type.Num, | ||
strictSchema: it.strictSchema, | ||
}, | ||
valid | ||
) | ||
if (!it.allErrors) gen.if(not(valid), () => gen.break()) | ||
}) | ||
cxt.ok(valid) | ||
} | ||
}, | ||
@@ -71,0 +55,0 @@ } |
@@ -60,3 +60,2 @@ import type {CodeKeywordDefinition} from "../../types" | ||
dataPropType: Type.Str, | ||
strictSchema: it.strictSchema, | ||
}, | ||
@@ -63,0 +62,0 @@ valid |
@@ -46,3 +46,2 @@ import type {CodeKeywordDefinition} from "../../types" | ||
dataProp: prop, | ||
strictSchema: it.strictSchema, | ||
}, | ||
@@ -49,0 +48,0 @@ valid |
@@ -37,3 +37,2 @@ import type { | ||
compositeRule: true, | ||
strictSchema: it.strictSchema, | ||
}, | ||
@@ -40,0 +39,0 @@ valid |
import type {AnySchema, SchemaMap} from "../types" | ||
import type {SchemaCxt} from "../compile" | ||
import type KeywordCxt from "../compile/context" | ||
import {CodeGen, _, or, nil, strConcat, getProperty, Code, Name} from "../compile/codegen" | ||
import {CodeGen, _, or, not, nil, strConcat, getProperty, Code, Name} from "../compile/codegen" | ||
import {alwaysValidSchema} from "../compile/util" | ||
import {Type} from "../compile/subschema" | ||
import N from "../compile/names" | ||
@@ -86,1 +87,64 @@ | ||
} | ||
export function validateArray(cxt: KeywordCxt): Name { | ||
const {gen, data, keyword, it} = cxt | ||
const valid = gen.name("valid") | ||
if (it.allErrors) { | ||
const validArr = gen.let("valid", true) | ||
validateItems(() => gen.assign(validArr, false)) | ||
return validArr | ||
} | ||
gen.var(valid, true) | ||
validateItems(() => gen.break()) | ||
return valid | ||
function validateItems(notValid: () => void): void { | ||
const len = gen.const("len", _`${data}.length`) | ||
gen.forRange("i", 0, len, (i) => { | ||
cxt.subschema( | ||
{ | ||
keyword, | ||
dataProp: i, | ||
dataPropType: Type.Num, | ||
}, | ||
valid | ||
) | ||
gen.if(not(valid), notValid) | ||
}) | ||
} | ||
} | ||
export function validateUnion(cxt: KeywordCxt): void { | ||
const {gen, schema, keyword, it} = cxt | ||
/* istanbul ignore if */ | ||
if (!Array.isArray(schema)) throw new Error("ajv implementation error") | ||
const alwaysValid = schema.some((sch: AnySchema) => alwaysValidSchema(it, sch)) | ||
if (alwaysValid && !it.opts.unevaluated) return | ||
const valid = gen.let("valid", false) | ||
const schValid = gen.name("_valid") | ||
gen.block(() => | ||
schema.forEach((_sch: AnySchema, i: number) => { | ||
const schCxt = cxt.subschema( | ||
{ | ||
keyword, | ||
schemaProp: i, | ||
compositeRule: true, | ||
}, | ||
schValid | ||
) | ||
gen.assign(valid, _`${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(not(valid)) | ||
}) | ||
) | ||
cxt.result( | ||
valid, | ||
() => cxt.reset(), | ||
() => cxt.error(true) | ||
) | ||
} |
@@ -10,2 +10,3 @@ import type {Vocabulary} from "../../types" | ||
"$vocabulary", | ||
{keyword: "$comment"}, | ||
"definitions", | ||
@@ -12,0 +13,0 @@ idKeyword, |
@@ -13,3 +13,3 @@ import type {CodeKeywordDefinition, AnySchema} from "../../types" | ||
schemaType: "string", | ||
code(cxt: KeywordCxt) { | ||
code(cxt: KeywordCxt): void { | ||
const {gen, schema, it} = cxt | ||
@@ -45,3 +45,2 @@ const {baseId, schemaEnv: env, validateName, opts, self} = it | ||
schema: sch, | ||
strictSchema: true, | ||
dataTypes: [], | ||
@@ -48,0 +47,0 @@ schemaPath: nil, |
@@ -65,3 +65,2 @@ import type { | ||
dataPropType: Type.Str, | ||
strictSchema: it.strictSchema, | ||
}, | ||
@@ -68,0 +67,0 @@ valid |
@@ -27,2 +27,3 @@ import type {ErrorObject, Vocabulary} from "../../types" | ||
// any | ||
{keyword: "type", schemaType: ["string", "array"]}, | ||
{keyword: "nullable", schemaType: "boolean"}, | ||
@@ -29,0 +30,0 @@ constKeyword, |
{ | ||
"name": "ajv", | ||
"version": "7.0.4", | ||
"version": "7.1.0", | ||
"description": "Another JSON Schema Validator", | ||
@@ -22,4 +22,4 @@ "main": "dist/ajv.js", | ||
"test-cov": "nyc npm run test-spec", | ||
"bundle": "rm -rf bundle && node ./scripts/bundle.js ajv ajv7 ajv7 && node ./scripts/bundle.js 2019 ajv2019 ajv2019", | ||
"build": "rm -rf dist && tsc && cp -r lib/refs dist && rm dist/refs/json-schema-2019-09/index.ts", | ||
"bundle": "rm -rf bundle && node ./scripts/bundle.js ajv ajv7 ajv7 && node ./scripts/bundle.js 2019 ajv2019 ajv2019 && node ./scripts/bundle.js jtd ajvJTD ajvJTD", | ||
"build": "rm -rf dist && tsc && cp -r lib/refs dist && rm dist/refs/json-schema-2019-09/index.ts && rm dist/refs/jtd-schema.ts", | ||
"json-tests": "rm -rf spec/_json/*.js && node scripts/jsontests", | ||
@@ -85,3 +85,3 @@ "test-karma": "karma start", | ||
"glob": "^7.0.0", | ||
"husky": "^4.2.5", | ||
"husky": "^5.0.9", | ||
"if-node-version": "^1.0.0", | ||
@@ -88,0 +88,0 @@ "js-beautify": "^1.7.3", |
103
README.md
<img align="right" alt="Ajv logo" width="160" src="https://ajv.js.org/images/ajv_logo.png"> | ||
# Ajv: Another JSON Schema Validator | ||
# Ajv: Another JSON schema validator | ||
The fastest JSON Schema validator for Node.js and browser. Supports draft-06/07/2019-09 (draft-04 is supported in [version 6](https://github.com/ajv-validator/ajv/tree/v6)). | ||
The fastest JSON schema validator for Node.js and browser. Supports JSON Schema draft-06/07/2019-09 (draft-04 is supported in [version 6](https://github.com/ajv-validator/ajv/tree/v6)) and JSON Type Definition [RFC8927](https://datatracker.ietf.org/doc/rfc8927/). | ||
@@ -20,5 +20,6 @@ [![build](https://github.com/ajv-validator/ajv/workflows/build/badge.svg)](https://github.com/ajv-validator/ajv/actions?query=workflow%3Abuild) | ||
Ajv version 7 is released with these changes: | ||
Ajv version 7 has these new features: | ||
- support of JSON Schema draft-2019-09 features: [`unevaluatedProperties`](./docs/json-schema.md#unevaluatedproperties) and [`unevaluatedItems`](./docs/json-schema.md#unevaluateditems), [dynamic recursive references](./docs/validation.md#extending-recursive-schemas) and other [additional keywords](./docs/json-schema.md#json-schema-draft-2019-09). | ||
- NEW: support of JSON Type Definition [RFC8927](https://datatracker.ietf.org/doc/rfc8927/) (from [v7.1.0](https://github.com/ajv-validator/ajv-keywords/releases/tag/v7.1.0)) | ||
- 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. | ||
@@ -75,2 +76,5 @@ - 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%). | ||
- [Getting started](#usage) | ||
- [Choosing schema language](#choosing-schema-language) | ||
- [JSON Schema](#json-schema) | ||
- [JSON Type Definition](#json-type-definition) | ||
- [Frequently Asked Questions](./docs/faq.md) | ||
@@ -171,3 +175,3 @@ - [Using in browser](#using-in-browser) | ||
- Ajv implements full JSON Schema [draft-06/07](http://json-schema.org/) standards (draft-04 is supported in v6): | ||
- Ajv implements JSON Schema [draft-06/07/2019-09](http://json-schema.org/) standards (draft-04 is supported in v6): | ||
- all validation keywords (see [JSON Schema validation keywords](./docs/json-schema.md)) | ||
@@ -180,2 +184,6 @@ - keyword "nullable" from [Open API 3 specification](https://swagger.io/docs/specification/data-models/data-types/). | ||
- [validates schemas against meta-schema](./docs/api.md#api-validateschema) | ||
- NEW: supports [JSON Type Definition](https://datatracker.ietf.org/doc/rfc8927/): | ||
- all forms (see [JSON Type Definition schema forms](./docs/json-type-definition.md)) | ||
- meta-schema for JTD schemas | ||
- "union" keyword and user-defined keywords (can be used inside "metadata" member of the schema) | ||
- supports [browsers](#using-in-browser) and Node.js 0.10-14.x | ||
@@ -269,2 +277,22 @@ - [asynchronous loading](./docs/validation.md#asynchronous-schema-compilation) of referenced schemas during compilation | ||
With JSON Type Definition schema: | ||
```javascript | ||
const Ajv = require("ajv").default | ||
const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} | ||
const schema = { | ||
properties: { | ||
foo: {type: "float64"}, | ||
}, | ||
} | ||
const validate = ajv.compile(schema) | ||
const valid = validate({foo: 1}) // true | ||
if (!valid) console.log(validate.errors) | ||
// Unlike JSON Schema: | ||
const valid1 = validate(1) // false, bot an object | ||
const valid2 = validate({}) // false, foo is required | ||
const valid3 = validate({foo: 1, bar: 2}) // false, bar is additional | ||
``` | ||
See [this test](./spec/types/json-schema.spec.ts) for an advanced example, [API reference](./docs/api.md) and [Options](./docs/api.md#options) for more details. | ||
@@ -298,3 +326,3 @@ | ||
or to load the bundle that supports JSONSchema draft-2019-09: | ||
To load the bundle that supports JSON Schema draft-2019-09: | ||
@@ -311,2 +339,14 @@ ```html | ||
To load the bundle that supports JSON Type Definition: | ||
```html | ||
<script src="bundle/ajvJTD.min.js"></script> | ||
<script> | ||
;(function () { | ||
const Ajv = window.ajvJTD.default | ||
const ajv = new Ajv() | ||
})() | ||
</script> | ||
``` | ||
This bundle can be used with different module systems; it creates global `ajv` (or `ajv2019`) if no module system is found. | ||
@@ -318,2 +358,53 @@ | ||
## Choosing schema language | ||
Both JSON Schema and JSON Type Definition are cross-platform specifications with implementations in multiple programming languages that help you define the shape and requirements to your JSON data. | ||
This section compares their pros/cons to help decide which specification fits your application better. | ||
### JSON Schema | ||
- Pros | ||
- Wide specification adoption. | ||
- Used as part of OpenAPI specification. | ||
- Support of complex validation scenarios: | ||
- untagged unions and boolean logic | ||
- conditional schemas and dependencies | ||
- restrictions on the number ranges and the size of strings, arrays and objects | ||
- semantic validation with formats, patterns and content keywords | ||
- distribute strict record definitions across multiple schemas (with unevaluatedProperties) | ||
- Can be effectively used for validation of any JavaScript objects and configuration files. | ||
- Cons | ||
- Defines the collection of restrictions on the data, rather than the shape of the data. | ||
- No standard support for tagged unions. | ||
- Complex and error prone for the new users (Ajv has [strict mode](./docs/strict-mode) to compensate for it, but it is not cross-platform). | ||
- Some parts of specification are difficult to implement, creating the risk of implementations divergence: | ||
- reference resolution model | ||
- unevaluatedProperties/unevaluatedItems | ||
- dynamic recursive references | ||
- Internet draft status (rather than RFC) | ||
See [JSON Schema](./docs/json-schema.md) for the list of defined keywords. | ||
### JSON Type Definition | ||
- Pros: | ||
- Aligned with type systems of many languages - can be used to generate type definitions and efficient parsers and serializers to/from these types. | ||
- Very simple, enforcing the best practices for cross-platform JSON API modelling. | ||
- Simple to implement, ensuring consistency across implementations. | ||
- Defines the shape of JSON data via strictly defined schema forms (rather than the collection of restrictions). | ||
- Effective support for tagged unions. | ||
- Designed to protect against user mistakes. | ||
- Approved as [RFC8927](https://datatracker.ietf.org/doc/rfc8927/) | ||
- Cons: | ||
- Limited, compared with JSON Schema - no support for untagged unions<sup>\*</sup>, conditionals, references between different schema files<sup>\*\*</sup>, etc. | ||
- No meta-schema in the specification<sup>\*</sup>. | ||
- Brand new - limited industry adoption (as of January 2021). | ||
<sup>\*</sup> Ajv defines meta-schema for JTD schemas and non-standard keyword "union" that can be used inside "metadata" object. | ||
<sup>\*\*</sup> You can still combine schemas from multiple files in the application code. | ||
See [JSON Type Definition](./docs/json-type-definition.md) for the list of defined schema forms. | ||
## Using in ES5 environment | ||
@@ -345,3 +436,3 @@ | ||
- all Ajv options | ||
- reporting changes in data after validation in [JSON-patch](https://tools.ietf.org/html/rfc6902) format | ||
- reporting changes in data after validation in [JSON-patch](https://datatracker.ietf.org/doc/rfc6902/) format | ||
@@ -348,0 +439,0 @@ ## Extending Ajv |
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
972115
408
15661
512