diff --git a/README.md b/README.md index 2443b6381..00921e19a 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ const schema: JSONSchemaType = { additionalProperties: false } -// validate is a type guard for MyData because schema was typed +// validate is a type guard for MyData - type is inferred from schema type const validate = ajv.compile(schema) // or, if you did not use type annotation for the schema, @@ -316,7 +316,7 @@ To reiterate, neither this nor other strict mode restrictions change the validat #### Prohibit unknown formats -By default unknown formats throw exception during schema compilation (and fail validation in case format keyword value is [\$data reference](#data-reference)). It is possible to opt out of format validation completely with `format: false` option. You can define all known formats with `addFormat` method or `formats` option - to have some format ignored pass `true` as its definition: +By default unknown formats throw exception during schema compilation (and fail validation in case format keyword value is [\$data reference](#data-reference)). It is possible to opt out of format validation completely with options `validateFormats: false`. You can define all known formats with `addFormat` method or `formats` option - to have some format ignored pass `true` as its definition: ```javascript const ajv = new Ajv({formats: { @@ -992,7 +992,7 @@ See [Coercion rules](https://github.com/ajv-validator/ajv/blob/master/COERCION.m ## API -#### new Ajv(opts: object) +#### new Ajv(options: object) Create Ajv instance: @@ -1000,6 +1000,8 @@ Create Ajv instance: const ajv = new Ajv() ``` +See [Options](#options) + #### ajv.compile(schema: object): (data: any) =\> boolean | Promise\ Generate validating function and cache the compiled schema for future use. @@ -1194,7 +1196,7 @@ interface KeywordDefinition { // (the latter can be used in addition to `compile` or `macro`). $dataError?: object // error definition object for invalid \$data schema - see types.ts async?: true // if the validation function is asynchronous - // (whether it is compiled or passed in `validate` property). + // (whether it is returned from `compile` or passed in `validate` property). // It should return a promise that resolves with a value `true` or `false`. // This option is ignored in case of "macro" and "code" keywords. errors?: boolean | "full" // whether keyword returns errors. @@ -1237,15 +1239,15 @@ const defaultOptions = { // strict mode options strict: true, allowMatchingProperties: false, + validateFormats: true, // validation and reporting options: + $data: false, allErrors: false, verbose: false, $comment: false, - format: true, formats: {}, keywords: {}, schemas: {}, - $data: false // | true, logger: undefined, // referenced schema options: missingRefs: true, @@ -1274,13 +1276,16 @@ const defaultOptions = { } ``` -##### Strict mode options +##### Strict mode options (NEW in v7) -- _strict_ (NEW in v7): By default Ajv executes in strict mode, that is designed to prevent any unexpected behaviours or silently ignored mistakes in schemas (see [Strict Mode](#strict-mode) for more details). It does not change any validation results, but it makes some schemas invalid that would be otherwise valid according to JSON Schema specification. Option values: +- _strict_: By default Ajv executes in strict mode, that is designed to prevent any unexpected behaviours or silently ignored mistakes in schemas (see [Strict Mode](#strict-mode) for more details). It does not change any validation results, but it makes some schemas invalid that would be otherwise valid according to JSON Schema specification. Option values: - `true` (default) - use strict mode and throw an exception when any strict mode restriction is violated. - `"log"` - log warning when any strict mode restriction is violated. - `false` - ignore all strict mode restrictions. -- _allowMatchingProperties_ (NEW in v7): pass true to allow overlap between "properties" and "patternProperties". Does not affect other strict mode restrictions. See [Strict Mode](#strict-mode). +- _allowMatchingProperties_: pass true to allow overlap between "properties" and "patternProperties". Does not affect other strict mode restrictions. See [Strict Mode](#strict-mode). +- _validateFormats_: format validation. Option values: + - `true` (default) - validate formats (see [Formats](#formats)). In [strict mode](#strict-mode) unknown formats will throw exception during schema compilation (and fail validation in case format keyword value is [\$data reference](#data-reference)). + - `false` - do not validate any format keywords (TODO they will still collect annotations once supported). ##### Validation and reporting options @@ -1291,9 +1296,6 @@ const defaultOptions = { - `false` (default): ignore \$comment keyword. - `true`: log the keyword value to console. - function: pass the keyword value, its schema path and root schema to the specified function -- _format_: format validation. Option values: - - `true` (default) - validate added formats (see [Formats](#formats)). In strict mode unknown formats will throw exception during schema compilation (and fail validation in case format keyword value is [\$data reference](#data-reference)). - - `false` - ignore all format keywords. - _formats_: an object with format definitions. Keys and values will be passed to `addFormat` method. Pass `true` as format definition to ignore some formats. - _keywords_: an array of keyword definitions or strings. Values will be passed to `addKeyword` method. - _schemas_: an array or object of schemas that will be added to the instance. In case you pass the array the schemas must have IDs in them. When the object is passed the method `addSchema(value, key)` will be called for each schema in this object. @@ -1338,9 +1340,9 @@ const defaultOptions = { - `false` - skip schema validation. - _addUsedSchema_: by default methods `compile` and `validate` add schemas to the instance if they have `$id` (or `id`) property that doesn't start with "#". If `$id` is present and it is not unique the exception will be thrown. Set this option to `false` to skip adding schemas to the instance and the `$id` uniqueness check when these methods are used. This option does not affect `addSchema` method. - _inlineRefs_: Affects compilation of referenced schemas. Option values: - - `true` (default) - the referenced schemas that don't have refs in them are inlined, regardless of their size - that substantially improves performance at the cost of the bigger size of compiled schema functions. - - `false` - to not inline referenced schemas (they will be compiled as separate functions). - - integer number - to limit the maximum number of keywords of the schema that will be inlined. + - `true` (default) - the referenced schemas that don't have refs in them are inlined, regardless of their size - it improves performance. + - `false` - to not inline referenced schemas (they will always be compiled as separate functions). + - integer number - to limit the maximum number of keywords of the schema that will be inlined (to balance the total size of compiled functions and performance). - _passContext_: pass validation context to _compile_ and _validate_ keyword functions. If this option is `true` and you pass some context to the compiled validation function with `validate.call(context, data)`, the `context` will be available as `this` in your keywords. By default `this` is Ajv instance. - _loopRequired_: by default `required` keyword is compiled into a single expression (or a sequence of statements in `allErrors` mode). In case of a very large number of properties in this keyword it may result in a very big validation function. Pass integer to set the number of properties above which `required` keyword will be validated in a loop - smaller validation function size but also worse performance. - _loopEnum_ (NEW in v7): by default `enum` keyword is compiled into a single expression. In case of a very large number of allowed values it may result in a large validation function. Pass integer to set the number of values above which `enum` keyword will be validated in a loop. diff --git a/lib/ajv.ts b/lib/ajv.ts index f00cd3b0c..a5617c48a 100644 --- a/lib/ajv.ts +++ b/lib/ajv.ts @@ -38,6 +38,7 @@ import type { Options, InstanceOptions, RemovedOptions, + DeprecatedOptions, AnyValidateFunction, ValidateFunction, AsyncValidateFunction, @@ -67,7 +68,7 @@ import draft7MetaSchema from "./refs/json-schema-draft-07.json" const META_SCHEMA_ID = "http://json-schema.org/draft-07/schema" -const META_IGNORE_OPTIONS = ["removeAdditional", "useDefaults", "coerceTypes"] as const +const META_IGNORE_OPTIONS: (keyof Options)[] = ["removeAdditional", "useDefaults", "coerceTypes"] const META_SUPPORT_DATA = ["/properties"] const EXT_SCOPE_NAMES = new Set([ "validate", @@ -90,6 +91,28 @@ const optsDefaults = { addUsedSchema: true, } +type OptionsInfo = { + [key in keyof T]-?: string | undefined +} + +const removedOptions: OptionsInfo = { + errorDataPath: "", + format: "`validateFormats: false` can be used instead.", + nullable: '"nullable" keyword is supported by default.', + jsonPointers: "Deprecated jsPropertySyntax can be used instead.", + schemaId: "JSON Schema draft-04 is not supported in Ajv v7.", + strictDefaults: "It is default now, see option `strict`.", + strictKeywords: "It is default now, see option `strict`.", + strictNumbers: "It is default now, see option `strict`.", + uniqueItems: '"uniqueItems" keyword is always validated.', + unknownFormats: "Disable strict mode or pass `true` to `ajv.addFormat` (or `formats` option).", +} + +const deprecatedOptions: OptionsInfo = { + jsPropertySyntax: "", + unicode: '"minLength"/"maxLength" account for unicode characters by default.', +} + export default class Ajv { opts: InstanceOptions errors?: ErrorObject[] | null // errors from the last validation @@ -115,14 +138,16 @@ export default class Ajv { serialize: opts.serialize === false ? (x) => x : opts.serialize ?? stableStringify, addUsedSchema: opts.addUsedSchema ?? true, validateSchema: opts.validateSchema ?? true, + validateFormats: opts.validateFormats ?? true, } this.logger = getLogger(opts.logger) - const formatOpt = opts.format - opts.format = false + const formatOpt = opts.validateFormats + opts.validateFormats = false this._cache = opts.cache || new Cache() this.RULES = getRules() - checkDeprecatedOptions.call(this, opts) + checkOptions.call(this, removedOptions, opts, "NOT SUPPORTED") + checkOptions.call(this, deprecatedOptions, opts, "DEPRECATED", "warn") this._metaOpts = getMetaSchemaOptions.call(this) if (opts.formats) addInitialFormats.call(this) @@ -137,7 +162,7 @@ export default class Ajv { addDefaultMetaSchema.call(this) if (typeof opts.meta == "object") this.addMetaSchema(opts.meta) addInitialSchemas.call(this) - opts.format = formatOpt + opts.validateFormats = formatOpt } // Validate data using schema @@ -520,13 +545,17 @@ export interface ErrorsTextOptions { dataVar?: string } -function checkDeprecatedOptions(this: Ajv, opts: Options & RemovedOptions): void { - if (opts.errorDataPath !== undefined) this.logger.error("NOT SUPPORTED: option errorDataPath") - if (opts.schemaId !== undefined) this.logger.error("NOT SUPPORTED: option schemaId") - if (opts.uniqueItems !== undefined) this.logger.error("NOT SUPPORTED: option uniqueItems") - if (opts.unknownFormats !== undefined) this.logger.error("NOT SUPPORTED: option unknownFormats") - if (opts.jsPropertySyntax !== undefined) this.logger.warn("DEPRECATED: option jsPropertySyntax") - if (opts.unicode !== undefined) this.logger.warn("DEPRECATED: option unicode") +function checkOptions( + this: Ajv, + checkOpts: OptionsInfo, + options: Options & RemovedOptions, + msg: string, + log: "warn" | "error" = "error" +): void { + for (const key in checkOpts) { + const opt = key as keyof typeof checkOpts + if (opt in options) this.logger[log](`${msg}: option ${key}. ${checkOpts[opt]}`) + } } function defaultMeta(this: Ajv): string | AnySchemaObject | undefined { diff --git a/lib/types/index.ts b/lib/types/index.ts index c278d5666..7afa4917b 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -35,12 +35,13 @@ export type LoadSchemaFunction = ( cb?: (err: Error | null, schema?: AnySchemaObject) => void ) => Promise -export interface Options { +export type Options = CurrentOptions & DeprecatedOptions + +export interface CurrentOptions { strict?: boolean | "log" $data?: boolean allErrors?: boolean verbose?: boolean - format?: boolean formats?: {[name: string]: Format} keywords?: Vocabulary | {[x: string]: KeywordDefinition} // map is deprecated schemas?: AnySchema[] | {[key: string]: AnySchema} @@ -72,20 +73,27 @@ export interface Options { | true | ((comment: string, schemaPath?: string, rootSchema?: AnySchemaObject) => unknown) allowMatchingProperties?: boolean // disables a strict mode restriction - // deprecated: - jsPropertySyntax?: boolean // added instead of jsonPointers - unicode?: boolean + validateFormats?: boolean } export interface CodeOptions { formats?: Code // code to require (or construct) map of available formats - for standalone code } +export interface DeprecatedOptions { + jsPropertySyntax?: boolean // added instead of jsonPointers + unicode?: boolean +} + export interface RemovedOptions { - // removed: + format?: boolean errorDataPath?: "object" | "property" nullable?: boolean // "nullable" keyword is supported by default + jsonPointers?: boolean schemaId?: string + strictDefaults?: boolean + strictKeywords?: boolean + strictNumbers?: boolean uniqueItems?: boolean unknownFormats?: true | string[] | "ignore" } @@ -98,6 +106,7 @@ export interface InstanceOptions extends Options { serialize: (schema: AnySchema) => unknown addUsedSchema: boolean validateSchema: boolean | "log" + validateFormats: boolean } export interface Logger { diff --git a/lib/vocabularies/format/format.ts b/lib/vocabularies/format/format.ts index 579f1198e..c0c5f0455 100644 --- a/lib/vocabularies/format/format.ts +++ b/lib/vocabularies/format/format.ts @@ -34,7 +34,7 @@ const def: CodeKeywordDefinition = { code(cxt: KeywordCxt, ruleType?: string) { const {gen, data, $data, schema, schemaCode, it} = cxt const {opts, errSchemaPath, schemaEnv, self} = it - if (opts.format === false) return + if (!opts.validateFormats) return if ($data) validate$DataFormat() else validateFormat() diff --git a/spec/options/options_validation.spec.ts b/spec/options/options_validation.spec.ts index f8c8aeac0..2b72769db 100644 --- a/spec/options/options_validation.spec.ts +++ b/spec/options/options_validation.spec.ts @@ -6,7 +6,7 @@ describe("validation options", () => { describe("format", () => { it("should not validate formats if option format == false", () => { const ajv = new _Ajv({formats: {date: DATE_FORMAT}}), - ajvFF = new _Ajv({formats: {date: DATE_FORMAT}, format: false}) + ajvFF = new _Ajv({formats: {date: DATE_FORMAT}, validateFormats: false}) const schema = {format: "date"} const invalideDateTime = "06/19/1963" // expects hyphens