Skip to content

Commit

Permalink
replace option format with validateFormats, refactor checkDeprecatedP…
Browse files Browse the repository at this point in the history
…roperties
  • Loading branch information
epoberezkin committed Sep 18, 2020
1 parent 5f5265f commit 656a315
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 35 deletions.
32 changes: 17 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ const schema: JSONSchemaType<MyData> = {
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,
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -992,14 +992,16 @@ 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:
```javascript
const ajv = new Ajv()
```
See [Options](#options)
#### ajv.compile(schema: object): (data: any) =\> boolean | Promise\<any\>
Generate validating function and cache the compiled schema for future use.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
53 changes: 41 additions & 12 deletions lib/ajv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import type {
Options,
InstanceOptions,
RemovedOptions,
DeprecatedOptions,
AnyValidateFunction,
ValidateFunction,
AsyncValidateFunction,
Expand Down Expand Up @@ -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",
Expand All @@ -90,6 +91,28 @@ const optsDefaults = {
addUsedSchema: true,
}

type OptionsInfo<T extends RemovedOptions | DeprecatedOptions> = {
[key in keyof T]-?: string | undefined
}

const removedOptions: OptionsInfo<RemovedOptions> = {
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<DeprecatedOptions> = {
jsPropertySyntax: "",
unicode: '"minLength"/"maxLength" account for unicode characters by default.',
}

export default class Ajv {
opts: InstanceOptions
errors?: ErrorObject[] | null // errors from the last validation
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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<RemovedOptions | DeprecatedOptions>,
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 {
Expand Down
21 changes: 15 additions & 6 deletions lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ export type LoadSchemaFunction = (
cb?: (err: Error | null, schema?: AnySchemaObject) => void
) => Promise<AnySchemaObject>

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}
Expand Down Expand Up @@ -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"
}
Expand All @@ -98,6 +106,7 @@ export interface InstanceOptions extends Options {
serialize: (schema: AnySchema) => unknown
addUsedSchema: boolean
validateSchema: boolean | "log"
validateFormats: boolean
}

export interface Logger {
Expand Down
2 changes: 1 addition & 1 deletion lib/vocabularies/format/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion spec/options/options_validation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 656a315

Please sign in to comment.