6 KeywordErrorDefinition,
8 MacroKeywordDefinition,
17 AsyncValidateFunction,
23 export {SchemaCxt, SchemaObjCxt} from "./compile"
24 export interface Plugin<Opts> {
25 (ajv: Ajv, options?: Opts): Ajv
29 export {KeywordCxt} from "./compile/validate"
30 export {DefinedError} from "./vocabularies/errors"
31 export {JSONType} from "./compile/rules"
32 export {JSONSchemaType} from "./types/json-schema"
33 export {JTDSchemaType, SomeJTDSchemaType, JTDDataType} from "./types/jtd-schema"
34 export {_, str, stringify, nil, Name, Code, CodeGen, CodeGenOptions} from "./compile/codegen"
44 AddedKeywordDefinition,
47 AsyncValidateFunction,
53 import type {JSONSchemaType} from "./types/json-schema"
54 import type {JTDSchemaType, SomeJTDSchemaType, JTDDataType} from "./types/jtd-schema"
55 import ValidationError from "./runtime/validation_error"
56 import MissingRefError from "./compile/ref_error"
57 import {getRules, ValidationRules, Rule, RuleGroup, JSONType} from "./compile/rules"
58 import {SchemaEnv, compileSchema, resolveSchema} from "./compile"
59 import {Code, ValueScope} from "./compile/codegen"
60 import {normalizeId, getSchemaRefs} from "./compile/resolve"
61 import {getJSONTypes} from "./compile/validate/dataType"
62 import {eachItem} from "./compile/util"
64 import * as $dataRefSchema from "./refs/data.json"
66 const defaultRegExp: RegExpEngine = (str, flags) => new RegExp(str, flags)
67 defaultRegExp.code = "new RegExp"
69 const META_IGNORE_OPTIONS: (keyof Options)[] = ["removeAdditional", "useDefaults", "coerceTypes"]
70 const EXT_SCOPE_NAMES = new Set([
86 export type Options = CurrentOptions & DeprecatedOptions
88 export interface CurrentOptions {
89 // strict mode options (NEW)
90 strict?: boolean | "log"
91 strictSchema?: boolean | "log"
92 strictNumbers?: boolean | "log"
93 strictTypes?: boolean | "log"
94 strictTuples?: boolean | "log"
95 strictRequired?: boolean | "log"
96 allowMatchingProperties?: boolean // disables a strict mode restriction
97 allowUnionTypes?: boolean
98 validateFormats?: boolean
99 // validation and reporting options:
103 discriminator?: boolean
104 unicodeRegExp?: boolean
105 timestamp?: "string" | "date" // JTD only
106 parseDate?: boolean // JTD only
107 allowDate?: boolean // JTD only
110 | ((comment: string, schemaPath?: string, rootSchema?: AnySchemaObject) => unknown)
111 formats?: {[Name in string]?: Format}
112 keywords?: Vocabulary
113 schemas?: AnySchema[] | {[Key in string]?: AnySchema}
114 logger?: Logger | false
115 loadSchema?: (uri: string) => Promise<AnySchemaObject>
116 // options to modify validated data:
117 removeAdditional?: boolean | "all" | "failing"
118 useDefaults?: boolean | "empty"
119 coerceTypes?: boolean | "array"
121 next?: boolean // NEW
122 unevaluated?: boolean // NEW
123 dynamicRef?: boolean // NEW
124 schemaId?: "id" | "$id"
126 meta?: SchemaObject | boolean
127 defaultMeta?: string | AnySchemaObject
128 validateSchema?: boolean | "log"
129 addUsedSchema?: boolean
130 inlineRefs?: boolean | number
131 passContext?: boolean
132 loopRequired?: number
133 loopEnum?: number // NEW
134 ownProperties?: boolean
135 multipleOfPrecision?: number
136 int32range?: boolean // JTD only
138 code?: CodeOptions // NEW
141 export interface CodeOptions {
144 optimize?: boolean | number
145 formats?: Code // code to require (or construct) map of available formats - for standalone code
147 process?: (code: string, schema?: SchemaEnv) => string
148 regExp?: RegExpEngine
151 interface InstanceCodeOptions extends CodeOptions {
156 interface DeprecatedOptions {
158 ignoreKeywordsWithRef?: boolean
160 jsPropertySyntax?: boolean // added instead of jsonPointers
165 interface RemovedOptions {
167 errorDataPath?: "object" | "property"
168 nullable?: boolean // "nullable" keyword is supported by default
169 jsonPointers?: boolean
170 extendRefs?: true | "ignore" | "fail"
171 missingRefs?: true | "ignore" | "fail"
172 processCode?: (code: string, schema?: SchemaEnv) => string
174 strictDefaults?: boolean
175 strictKeywords?: boolean
176 uniqueItems?: boolean
177 unknownFormats?: true | string[] | "ignore"
179 serialize?: (schema: AnySchema) => unknown
183 type OptionsInfo<T extends RemovedOptions | DeprecatedOptions> = {
184 [K in keyof T]-?: string | undefined
187 const removedOptions: OptionsInfo<RemovedOptions> = {
189 format: "`validateFormats: false` can be used instead.",
190 nullable: '"nullable" keyword is supported by default.',
191 jsonPointers: "Deprecated jsPropertySyntax can be used instead.",
192 extendRefs: "Deprecated ignoreKeywordsWithRef can be used instead.",
193 missingRefs: "Pass empty schema with $id that should be ignored to ajv.addSchema.",
194 processCode: "Use option `code: {process: (code, schemaEnv: object) => string}`",
195 sourceCode: "Use option `code: {source: true}`",
196 strictDefaults: "It is default now, see option `strict`.",
197 strictKeywords: "It is default now, see option `strict`.",
198 uniqueItems: '"uniqueItems" keyword is always validated.',
199 unknownFormats: "Disable strict mode or pass `true` to `ajv.addFormat` (or `formats` option).",
200 cache: "Map is used as cache, schema object as key.",
201 serialize: "Map is used as cache, schema object as key.",
202 ajvErrors: "It is default now.",
205 const deprecatedOptions: OptionsInfo<DeprecatedOptions> = {
206 ignoreKeywordsWithRef: "",
207 jsPropertySyntax: "",
208 unicode: '"minLength"/"maxLength" account for unicode characters by default.',
211 type RequiredInstanceOptions = {
228 | "unicodeRegExp"]: NonNullable<Options[K]>
229 } & {code: InstanceCodeOptions}
231 export type InstanceOptions = Options & RequiredInstanceOptions
233 const MAX_EXPRESSION = 200
235 // eslint-disable-next-line complexity
236 function requiredOptions(o: Options): RequiredInstanceOptions {
238 const _optz = o.code?.optimize
239 const optimize = _optz === true || _optz === undefined ? 1 : _optz || 0
240 const regExp = o.code?.regExp ?? defaultRegExp
242 strictSchema: o.strictSchema ?? s ?? true,
243 strictNumbers: o.strictNumbers ?? s ?? true,
244 strictTypes: o.strictTypes ?? s ?? "log",
245 strictTuples: o.strictTuples ?? s ?? "log",
246 strictRequired: o.strictRequired ?? s ?? false,
247 code: o.code ? {...o.code, optimize, regExp} : {optimize, regExp},
248 loopRequired: o.loopRequired ?? MAX_EXPRESSION,
249 loopEnum: o.loopEnum ?? MAX_EXPRESSION,
250 meta: o.meta ?? true,
251 messages: o.messages ?? true,
252 inlineRefs: o.inlineRefs ?? true,
253 schemaId: o.schemaId ?? "$id",
254 addUsedSchema: o.addUsedSchema ?? true,
255 validateSchema: o.validateSchema ?? true,
256 validateFormats: o.validateFormats ?? true,
257 unicodeRegExp: o.unicodeRegExp ?? true,
258 int32range: o.int32range ?? true,
262 export interface Logger {
263 log(...args: unknown[]): unknown
264 warn(...args: unknown[]): unknown
265 error(...args: unknown[]): unknown
268 export default class Ajv {
269 opts: InstanceOptions
270 errors?: ErrorObject[] | null // errors from the last validation
272 // shared external scope values for compiled functions
273 readonly scope: ValueScope
274 readonly schemas: {[Key in string]?: SchemaEnv} = {}
275 readonly refs: {[Ref in string]?: SchemaEnv | string} = {}
276 readonly formats: {[Name in string]?: AddedFormat} = {}
277 readonly RULES: ValidationRules
278 readonly _compilations: Set<SchemaEnv> = new Set()
279 private readonly _loading: {[Ref in string]?: Promise<AnySchemaObject>} = {}
280 private readonly _cache: Map<AnySchema, SchemaEnv> = new Map()
281 private readonly _metaOpts: InstanceOptions
283 static ValidationError = ValidationError
284 static MissingRefError = MissingRefError
286 constructor(opts: Options = {}) {
287 opts = this.opts = {...opts, ...requiredOptions(opts)}
288 const {es5, lines} = this.opts.code
290 this.scope = new ValueScope({scope: {}, prefixes: EXT_SCOPE_NAMES, es5, lines})
291 this.logger = getLogger(opts.logger)
292 const formatOpt = opts.validateFormats
293 opts.validateFormats = false
295 this.RULES = getRules()
296 checkOptions.call(this, removedOptions, opts, "NOT SUPPORTED")
297 checkOptions.call(this, deprecatedOptions, opts, "DEPRECATED", "warn")
298 this._metaOpts = getMetaSchemaOptions.call(this)
300 if (opts.formats) addInitialFormats.call(this)
301 this._addVocabularies()
302 this._addDefaultMetaSchema()
303 if (opts.keywords) addInitialKeywords.call(this, opts.keywords)
304 if (typeof opts.meta == "object") this.addMetaSchema(opts.meta)
305 addInitialSchemas.call(this)
306 opts.validateFormats = formatOpt
309 _addVocabularies(): void {
310 this.addKeyword("$async")
313 _addDefaultMetaSchema(): void {
314 const {$data, meta, schemaId} = this.opts
315 let _dataRefSchema: SchemaObject = $dataRefSchema
316 if (schemaId === "id") {
317 _dataRefSchema = {...$dataRefSchema}
318 _dataRefSchema.id = _dataRefSchema.$id
319 delete _dataRefSchema.$id
321 if (meta && $data) this.addMetaSchema(_dataRefSchema, _dataRefSchema[schemaId], false)
324 defaultMeta(): string | AnySchemaObject | undefined {
325 const {meta, schemaId} = this.opts
326 return (this.opts.defaultMeta = typeof meta == "object" ? meta[schemaId] || meta : undefined)
329 // Validate data using schema
330 // AnySchema will be compiled and cached using schema itself as a key for Map
331 validate(schema: Schema | string, data: unknown): boolean
332 validate(schemaKeyRef: AnySchema | string, data: unknown): boolean | Promise<unknown>
333 validate<T>(schema: Schema | JSONSchemaType<T> | string, data: unknown): data is T
334 // Separated for type inference to work
335 // eslint-disable-next-line @typescript-eslint/unified-signatures
336 validate<T>(schema: JTDSchemaType<T>, data: unknown): data is T
337 // This overload is only intended for typescript inference, the first
338 // argument prevents manual type annotation from matching this overload
339 validate<N extends never, T extends SomeJTDSchemaType>(
342 ): data is JTDDataType<T>
343 validate<T>(schema: AsyncSchema, data: unknown | T): Promise<T>
344 validate<T>(schemaKeyRef: AnySchema | string, data: unknown): data is T | Promise<T>
346 schemaKeyRef: AnySchema | string, // key, ref or schema object
347 data: unknown | T // to be validated
348 ): boolean | Promise<T> {
349 let v: AnyValidateFunction | undefined
350 if (typeof schemaKeyRef == "string") {
351 v = this.getSchema<T>(schemaKeyRef)
352 if (!v) throw new Error(`no schema with key or ref "${schemaKeyRef}"`)
354 v = this.compile<T>(schemaKeyRef)
357 const valid = v(data)
358 if (!("$async" in v)) this.errors = v.errors
362 // Create validation function for passed schema
363 // _meta: true if schema is a meta-schema. Used internally to compile meta schemas of user-defined keywords.
364 compile<T = unknown>(schema: Schema | JSONSchemaType<T>, _meta?: boolean): ValidateFunction<T>
365 // Separated for type inference to work
366 // eslint-disable-next-line @typescript-eslint/unified-signatures
367 compile<T = unknown>(schema: JTDSchemaType<T>, _meta?: boolean): ValidateFunction<T>
368 // This overload is only intended for typescript inference, the first
369 // argument prevents manual type annotation from matching this overload
370 compile<N extends never, T extends SomeJTDSchemaType>(
373 ): ValidateFunction<JTDDataType<T>>
374 compile<T = unknown>(schema: AsyncSchema, _meta?: boolean): AsyncValidateFunction<T>
375 compile<T = unknown>(schema: AnySchema, _meta?: boolean): AnyValidateFunction<T>
376 compile<T = unknown>(schema: AnySchema, _meta?: boolean): AnyValidateFunction<T> {
377 const sch = this._addSchema(schema, _meta)
378 return (sch.validate || this._compileSchemaEnv(sch)) as AnyValidateFunction<T>
381 // Creates validating function for passed schema with asynchronous loading of missing schemas.
382 // `loadSchema` option should be a function that accepts schema uri and returns promise that resolves with the schema.
383 // TODO allow passing schema URI
384 // meta - optional true to compile meta-schema
385 compileAsync<T = unknown>(
386 schema: SchemaObject | JSONSchemaType<T>,
388 ): Promise<ValidateFunction<T>>
389 // Separated for type inference to work
390 // eslint-disable-next-line @typescript-eslint/unified-signatures
391 compileAsync<T = unknown>(schema: JTDSchemaType<T>, _meta?: boolean): Promise<ValidateFunction<T>>
392 compileAsync<T = unknown>(schema: AsyncSchema, meta?: boolean): Promise<AsyncValidateFunction<T>>
393 // eslint-disable-next-line @typescript-eslint/unified-signatures
394 compileAsync<T = unknown>(
395 schema: AnySchemaObject,
397 ): Promise<AnyValidateFunction<T>>
398 compileAsync<T = unknown>(
399 schema: AnySchemaObject,
401 ): Promise<AnyValidateFunction<T>> {
402 if (typeof this.opts.loadSchema != "function") {
403 throw new Error("options.loadSchema should be a function")
405 const {loadSchema} = this.opts
406 return runCompileAsync.call(this, schema, meta)
408 async function runCompileAsync(
410 _schema: AnySchemaObject,
412 ): Promise<AnyValidateFunction> {
413 await loadMetaSchema.call(this, _schema.$schema)
414 const sch = this._addSchema(_schema, _meta)
415 return sch.validate || _compileAsync.call(this, sch)
418 async function loadMetaSchema(this: Ajv, $ref?: string): Promise<void> {
419 if ($ref && !this.getSchema($ref)) {
420 await runCompileAsync.call(this, {$ref}, true)
424 async function _compileAsync(this: Ajv, sch: SchemaEnv): Promise<AnyValidateFunction> {
426 return this._compileSchemaEnv(sch)
428 if (!(e instanceof MissingRefError)) throw e
429 checkLoaded.call(this, e)
430 await loadMissingSchema.call(this, e.missingSchema)
431 return _compileAsync.call(this, sch)
435 function checkLoaded(this: Ajv, {missingSchema: ref, missingRef}: MissingRefError): void {
436 if (this.refs[ref]) {
437 throw new Error(`AnySchema ${ref} is loaded but ${missingRef} cannot be resolved`)
441 async function loadMissingSchema(this: Ajv, ref: string): Promise<void> {
442 const _schema = await _loadSchema.call(this, ref)
443 if (!this.refs[ref]) await loadMetaSchema.call(this, _schema.$schema)
444 if (!this.refs[ref]) this.addSchema(_schema, ref, meta)
447 async function _loadSchema(this: Ajv, ref: string): Promise<AnySchemaObject> {
448 const p = this._loading[ref]
451 return await (this._loading[ref] = loadSchema(ref))
453 delete this._loading[ref]
458 // Adds schema to the instance
460 schema: AnySchema | AnySchema[], // If array is passed, `key` will be ignored
461 key?: string, // Optional schema key. Can be passed to `validate` method instead of schema object or id/ref. One schema per instance can have empty `id` and `key`.
462 _meta?: boolean, // true if schema is a meta-schema. Used internally, addMetaSchema should be used instead.
463 _validateSchema = this.opts.validateSchema // false to skip schema validation. Used internally, option validateSchema should be used instead.
465 if (Array.isArray(schema)) {
466 for (const sch of schema) this.addSchema(sch, undefined, _meta, _validateSchema)
469 let id: string | undefined
470 if (typeof schema === "object") {
471 const {schemaId} = this.opts
472 id = schema[schemaId]
473 if (id !== undefined && typeof id != "string") {
474 throw new Error(`schema ${schemaId} must be string`)
477 key = normalizeId(key || id)
478 this._checkUnique(key)
479 this.schemas[key] = this._addSchema(schema, _meta, key, _validateSchema, true)
483 // Add schema that will be used to validate other schemas
484 // options in META_IGNORE_OPTIONS are alway set to false
486 schema: AnySchemaObject,
487 key?: string, // schema key
488 _validateSchema = this.opts.validateSchema // false to skip schema validation, can be used to override validateSchema option for meta-schema
490 this.addSchema(schema, key, true, _validateSchema)
494 // Validate schema against its meta-schema
495 validateSchema(schema: AnySchema, throwOrLogError?: boolean): boolean | Promise<unknown> {
496 if (typeof schema == "boolean") return true
497 let $schema: string | AnySchemaObject | undefined
498 $schema = schema.$schema
499 if ($schema !== undefined && typeof $schema != "string") {
500 throw new Error("$schema must be a string")
502 $schema = $schema || this.opts.defaultMeta || this.defaultMeta()
504 this.logger.warn("meta-schema not available")
508 const valid = this.validate($schema, schema)
509 if (!valid && throwOrLogError) {
510 const message = "schema is invalid: " + this.errorsText()
511 if (this.opts.validateSchema === "log") this.logger.error(message)
512 else throw new Error(message)
517 // Get compiled schema by `key` or `ref`.
518 // (`key` that was passed to `addSchema` or full schema reference - `schema.$id` or resolved id)
519 getSchema<T = unknown>(keyRef: string): AnyValidateFunction<T> | undefined {
521 while (typeof (sch = getSchEnv.call(this, keyRef)) == "string") keyRef = sch
522 if (sch === undefined) {
523 const {schemaId} = this.opts
524 const root = new SchemaEnv({schema: {}, schemaId})
525 sch = resolveSchema.call(this, root, keyRef)
527 this.refs[keyRef] = sch
529 return (sch.validate || this._compileSchemaEnv(sch)) as AnyValidateFunction<T> | undefined
532 // Remove cached schema(s).
533 // If no parameter is passed all schemas but meta-schemas are removed.
534 // If RegExp is passed all schemas with key/id matching pattern but meta-schemas are removed.
535 // Even if schema is referenced by other schemas it still can be removed as other schemas have local references.
536 removeSchema(schemaKeyRef?: AnySchema | string | RegExp): Ajv {
537 if (schemaKeyRef instanceof RegExp) {
538 this._removeAllSchemas(this.schemas, schemaKeyRef)
539 this._removeAllSchemas(this.refs, schemaKeyRef)
542 switch (typeof schemaKeyRef) {
544 this._removeAllSchemas(this.schemas)
545 this._removeAllSchemas(this.refs)
549 const sch = getSchEnv.call(this, schemaKeyRef)
550 if (typeof sch == "object") this._cache.delete(sch.schema)
551 delete this.schemas[schemaKeyRef]
552 delete this.refs[schemaKeyRef]
556 const cacheKey = schemaKeyRef
557 this._cache.delete(cacheKey)
558 let id = schemaKeyRef[this.opts.schemaId]
561 delete this.schemas[id]
567 throw new Error("ajv.removeSchema: invalid parameter")
571 // add "vocabulary" - a collection of keywords
572 addVocabulary(definitions: Vocabulary): Ajv {
573 for (const def of definitions) this.addKeyword(def)
578 kwdOrDef: string | KeywordDefinition,
579 def?: KeywordDefinition // deprecated
581 let keyword: string | string[]
582 if (typeof kwdOrDef == "string") {
584 if (typeof def == "object") {
585 this.logger.warn("these parameters are deprecated, see docs for addKeyword")
586 def.keyword = keyword
588 } else if (typeof kwdOrDef == "object" && def === undefined) {
590 keyword = def.keyword
591 if (Array.isArray(keyword) && !keyword.length) {
592 throw new Error("addKeywords: keyword must be string or non-empty array")
595 throw new Error("invalid addKeywords parameters")
598 checkKeyword.call(this, keyword, def)
600 eachItem(keyword, (kwd) => addRule.call(this, kwd))
603 keywordMetaschema.call(this, def)
604 const definition: AddedKeywordDefinition = {
606 type: getJSONTypes(def.type),
607 schemaType: getJSONTypes(def.schemaType),
611 definition.type.length === 0
612 ? (k) => addRule.call(this, k, definition)
613 : (k) => definition.type.forEach((t) => addRule.call(this, k, definition, t))
618 getKeyword(keyword: string): AddedKeywordDefinition | boolean {
619 const rule = this.RULES.all[keyword]
620 return typeof rule == "object" ? rule.definition : !!rule
624 removeKeyword(keyword: string): Ajv {
625 // TODO return type should be Ajv
627 delete RULES.keywords[keyword]
628 delete RULES.all[keyword]
629 for (const group of RULES.rules) {
630 const i = group.rules.findIndex((rule) => rule.keyword === keyword)
631 if (i >= 0) group.rules.splice(i, 1)
637 addFormat(name: string, format: Format): Ajv {
638 if (typeof format == "string") format = new RegExp(format)
639 this.formats[name] = format
644 errors: ErrorObject[] | null | undefined = this.errors, // optional array of validation errors
645 {separator = ", ", dataVar = "data"}: ErrorsTextOptions = {} // optional options with properties `separator` and `dataVar`
647 if (!errors || errors.length === 0) return "No errors"
649 .map((e) => `${dataVar}${e.instancePath} ${e.message}`)
650 .reduce((text, msg) => text + separator + msg)
653 $dataMetaSchema(metaSchema: AnySchemaObject, keywordsJsonPointers: string[]): AnySchemaObject {
654 const rules = this.RULES.all
655 metaSchema = JSON.parse(JSON.stringify(metaSchema))
656 for (const jsonPointer of keywordsJsonPointers) {
657 const segments = jsonPointer.split("/").slice(1) // first segment is an empty string
658 let keywords = metaSchema
659 for (const seg of segments) keywords = keywords[seg] as AnySchemaObject
661 for (const key in rules) {
662 const rule = rules[key]
663 if (typeof rule != "object") continue
664 const {$data} = rule.definition
665 const schema = keywords[key] as AnySchemaObject | undefined
666 if ($data && schema) keywords[key] = schemaOrData(schema)
673 private _removeAllSchemas(schemas: {[Ref in string]?: SchemaEnv | string}, regex?: RegExp): void {
674 for (const keyRef in schemas) {
675 const sch = schemas[keyRef]
676 if (!regex || regex.test(keyRef)) {
677 if (typeof sch == "string") {
678 delete schemas[keyRef]
679 } else if (sch && !sch.meta) {
680 this._cache.delete(sch.schema)
681 delete schemas[keyRef]
691 validateSchema = this.opts.validateSchema,
692 addSchema = this.opts.addUsedSchema
694 let id: string | undefined
695 const {schemaId} = this.opts
696 if (typeof schema == "object") {
697 id = schema[schemaId]
699 if (this.opts.jtd) throw new Error("schema must be object")
700 else if (typeof schema != "boolean") throw new Error("schema must be object or boolean")
702 let sch = this._cache.get(schema)
703 if (sch !== undefined) return sch
705 baseId = normalizeId(id || baseId)
706 const localRefs = getSchemaRefs.call(this, schema, baseId)
707 sch = new SchemaEnv({schema, schemaId, meta, baseId, localRefs})
708 this._cache.set(sch.schema, sch)
709 if (addSchema && !baseId.startsWith("#")) {
710 // TODO atm it is allowed to overwrite schemas without id (instead of not adding them)
711 if (baseId) this._checkUnique(baseId)
712 this.refs[baseId] = sch
714 if (validateSchema) this.validateSchema(schema, true)
718 private _checkUnique(id: string): void {
719 if (this.schemas[id] || this.refs[id]) {
720 throw new Error(`schema with key or id "${id}" already exists`)
724 private _compileSchemaEnv(sch: SchemaEnv): AnyValidateFunction {
725 if (sch.meta) this._compileMetaSchema(sch)
726 else compileSchema.call(this, sch)
728 /* istanbul ignore if */
729 if (!sch.validate) throw new Error("ajv implementation error")
733 private _compileMetaSchema(sch: SchemaEnv): void {
734 const currentOpts = this.opts
735 this.opts = this._metaOpts
737 compileSchema.call(this, sch)
739 this.opts = currentOpts
744 export interface ErrorsTextOptions {
749 function checkOptions(
751 checkOpts: OptionsInfo<RemovedOptions | DeprecatedOptions>,
752 options: Options & RemovedOptions,
754 log: "warn" | "error" = "error"
756 for (const key in checkOpts) {
757 const opt = key as keyof typeof checkOpts
758 if (opt in options) this.logger[log](`${msg}: option ${key}. ${checkOpts[opt]}`)
762 function getSchEnv(this: Ajv, keyRef: string): SchemaEnv | string | undefined {
763 keyRef = normalizeId(keyRef) // TODO tests fail without this line
764 return this.schemas[keyRef] || this.refs[keyRef]
767 function addInitialSchemas(this: Ajv): void {
768 const optsSchemas = this.opts.schemas
769 if (!optsSchemas) return
770 if (Array.isArray(optsSchemas)) this.addSchema(optsSchemas)
771 else for (const key in optsSchemas) this.addSchema(optsSchemas[key] as AnySchema, key)
774 function addInitialFormats(this: Ajv): void {
775 for (const name in this.opts.formats) {
776 const format = this.opts.formats[name]
777 if (format) this.addFormat(name, format)
781 function addInitialKeywords(
783 defs: Vocabulary | {[K in string]?: KeywordDefinition}
785 if (Array.isArray(defs)) {
786 this.addVocabulary(defs)
789 this.logger.warn("keywords option as map is deprecated, pass array")
790 for (const keyword in defs) {
791 const def = defs[keyword] as KeywordDefinition
792 if (!def.keyword) def.keyword = keyword
797 function getMetaSchemaOptions(this: Ajv): InstanceOptions {
798 const metaOpts = {...this.opts}
799 for (const opt of META_IGNORE_OPTIONS) delete metaOpts[opt]
803 const noLogs = {log() {}, warn() {}, error() {}}
805 function getLogger(logger?: Partial<Logger> | false): Logger {
806 if (logger === false) return noLogs
807 if (logger === undefined) return console
808 if (logger.log && logger.warn && logger.error) return logger as Logger
809 throw new Error("logger must implement log, warn and error methods")
812 const KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i
814 function checkKeyword(this: Ajv, keyword: string | string[], def?: KeywordDefinition): void {
816 eachItem(keyword, (kwd) => {
817 if (RULES.keywords[kwd]) throw new Error(`Keyword ${kwd} is already defined`)
818 if (!KEYWORD_NAME.test(kwd)) throw new Error(`Keyword ${kwd} has invalid name`)
821 if (def.$data && !("code" in def || "validate" in def)) {
822 throw new Error('$data keyword must have "code" or "validate" function')
829 definition?: AddedKeywordDefinition,
832 const post = definition?.post
833 if (dataType && post) throw new Error('keyword with "post" flag cannot have "type"')
835 let ruleGroup = post ? RULES.post : RULES.rules.find(({type: t}) => t === dataType)
837 ruleGroup = {type: dataType, rules: []}
838 RULES.rules.push(ruleGroup)
840 RULES.keywords[keyword] = true
841 if (!definition) return
847 type: getJSONTypes(definition.type),
848 schemaType: getJSONTypes(definition.schemaType),
851 if (definition.before) addBeforeRule.call(this, ruleGroup, rule, definition.before)
852 else ruleGroup.rules.push(rule)
853 RULES.all[keyword] = rule
854 definition.implements?.forEach((kwd) => this.addKeyword(kwd))
857 function addBeforeRule(this: Ajv, ruleGroup: RuleGroup, rule: Rule, before: string): void {
858 const i = ruleGroup.rules.findIndex((_rule) => _rule.keyword === before)
860 ruleGroup.rules.splice(i, 0, rule)
862 ruleGroup.rules.push(rule)
863 this.logger.warn(`rule ${before} is not defined`)
867 function keywordMetaschema(this: Ajv, def: KeywordDefinition): void {
868 let {metaSchema} = def
869 if (metaSchema === undefined) return
870 if (def.$data && this.opts.$data) metaSchema = schemaOrData(metaSchema)
871 def.validateSchema = this.compile(metaSchema, true)
875 $ref: "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#",
878 function schemaOrData(schema: AnySchema): AnySchemaObject {
879 return {anyOf: [schema, $dataRef]}