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} from "./types/jtd-schema"
34 export {_, str, stringify, nil, Name, Code, CodeGen, CodeGenOptions} from "./compile/codegen"
44 AddedKeywordDefinition,
47 AsyncValidateFunction,
52 import type {JSONSchemaType} from "./types/json-schema"
53 import type {JTDSchemaType} from "./types/jtd-schema"
54 import ValidationError from "./runtime/validation_error"
55 import MissingRefError from "./compile/ref_error"
56 import {getRules, ValidationRules, Rule, RuleGroup, JSONType} from "./compile/rules"
57 import {SchemaEnv, compileSchema, resolveSchema} from "./compile"
58 import {Code, ValueScope} from "./compile/codegen"
59 import {normalizeId, getSchemaRefs} from "./compile/resolve"
60 import {getJSONTypes} from "./compile/validate/dataType"
61 import {eachItem} from "./compile/util"
63 import * as $dataRefSchema from "./refs/data.json"
65 const META_IGNORE_OPTIONS: (keyof Options)[] = ["removeAdditional", "useDefaults", "coerceTypes"]
66 const EXT_SCOPE_NAMES = new Set([
82 export type Options = CurrentOptions & DeprecatedOptions
84 export interface CurrentOptions {
85 // strict mode options (NEW)
86 strict?: boolean | "log"
87 strictSchema?: boolean | "log"
88 strictNumbers?: boolean | "log"
89 strictTypes?: boolean | "log"
90 strictTuples?: boolean | "log"
91 strictRequired?: boolean | "log"
92 allowMatchingProperties?: boolean // disables a strict mode restriction
93 allowUnionTypes?: boolean
94 validateFormats?: boolean
95 // validation and reporting options:
99 discriminator?: boolean
102 | ((comment: string, schemaPath?: string, rootSchema?: AnySchemaObject) => unknown)
103 formats?: {[Name in string]?: Format}
104 keywords?: Vocabulary
105 schemas?: AnySchema[] | {[Key in string]?: AnySchema}
106 logger?: Logger | false
107 loadSchema?: (uri: string) => Promise<AnySchemaObject>
108 // options to modify validated data:
109 removeAdditional?: boolean | "all" | "failing"
110 useDefaults?: boolean | "empty"
111 coerceTypes?: boolean | "array"
113 next?: boolean // NEW
114 unevaluated?: boolean // NEW
115 dynamicRef?: boolean // NEW
117 meta?: SchemaObject | boolean
118 defaultMeta?: string | AnySchemaObject
119 validateSchema?: boolean | "log"
120 addUsedSchema?: boolean
121 inlineRefs?: boolean | number
122 passContext?: boolean
123 loopRequired?: number
124 loopEnum?: number // NEW
125 ownProperties?: boolean
126 multipleOfPrecision?: number
128 code?: CodeOptions // NEW
131 export interface CodeOptions {
134 optimize?: boolean | number
135 formats?: Code // code to require (or construct) map of available formats - for standalone code
137 process?: (code: string, schema?: SchemaEnv) => string
140 interface InstanceCodeOptions extends CodeOptions {
144 interface DeprecatedOptions {
146 ignoreKeywordsWithRef?: boolean
148 jsPropertySyntax?: boolean // added instead of jsonPointers
153 interface RemovedOptions {
155 errorDataPath?: "object" | "property"
156 nullable?: boolean // "nullable" keyword is supported by default
157 jsonPointers?: boolean
158 extendRefs?: true | "ignore" | "fail"
159 missingRefs?: true | "ignore" | "fail"
160 processCode?: (code: string, schema?: SchemaEnv) => string
163 strictDefaults?: boolean
164 strictKeywords?: boolean
165 uniqueItems?: boolean
166 unknownFormats?: true | string[] | "ignore"
168 serialize?: (schema: AnySchema) => unknown
172 type OptionsInfo<T extends RemovedOptions | DeprecatedOptions> = {
173 [K in keyof T]-?: string | undefined
176 const removedOptions: OptionsInfo<RemovedOptions> = {
178 format: "`validateFormats: false` can be used instead.",
179 nullable: '"nullable" keyword is supported by default.',
180 jsonPointers: "Deprecated jsPropertySyntax can be used instead.",
181 extendRefs: "Deprecated ignoreKeywordsWithRef can be used instead.",
182 missingRefs: "Pass empty schema with $id that should be ignored to ajv.addSchema.",
183 processCode: "Use option `code: {process: (code, schemaEnv: object) => string}`",
184 sourceCode: "Use option `code: {source: true}`",
185 schemaId: "JSON Schema draft-04 is not supported in Ajv v7/8.",
186 strictDefaults: "It is default now, see option `strict`.",
187 strictKeywords: "It is default now, see option `strict`.",
188 uniqueItems: '"uniqueItems" keyword is always validated.',
189 unknownFormats: "Disable strict mode or pass `true` to `ajv.addFormat` (or `formats` option).",
190 cache: "Map is used as cache, schema object as key.",
191 serialize: "Map is used as cache, schema object as key.",
192 ajvErrors: "It is default now, see option `strict`.",
195 const deprecatedOptions: OptionsInfo<DeprecatedOptions> = {
196 ignoreKeywordsWithRef: "",
197 jsPropertySyntax: "",
198 unicode: '"minLength"/"maxLength" account for unicode characters by default.',
201 type RequiredInstanceOptions = {
215 | "validateFormats"]: NonNullable<Options[K]>
216 } & {code: InstanceCodeOptions}
218 export type InstanceOptions = Options & RequiredInstanceOptions
220 const MAX_EXPRESSION = 200
222 // eslint-disable-next-line complexity
223 function requiredOptions(o: Options): RequiredInstanceOptions {
225 const _optz = o.code?.optimize
226 const optimize = _optz === true || _optz === undefined ? 1 : _optz || 0
228 strictSchema: o.strictSchema ?? s ?? true,
229 strictNumbers: o.strictNumbers ?? s ?? true,
230 strictTypes: o.strictTypes ?? s ?? "log",
231 strictTuples: o.strictTuples ?? s ?? "log",
232 strictRequired: o.strictRequired ?? s ?? false,
233 code: o.code ? {...o.code, optimize} : {optimize},
234 loopRequired: o.loopRequired ?? MAX_EXPRESSION,
235 loopEnum: o.loopEnum ?? MAX_EXPRESSION,
236 meta: o.meta ?? true,
237 messages: o.messages ?? true,
238 inlineRefs: o.inlineRefs ?? true,
239 addUsedSchema: o.addUsedSchema ?? true,
240 validateSchema: o.validateSchema ?? true,
241 validateFormats: o.validateFormats ?? true,
245 export interface Logger {
246 log(...args: unknown[]): unknown
247 warn(...args: unknown[]): unknown
248 error(...args: unknown[]): unknown
251 export default class Ajv {
252 opts: InstanceOptions
253 errors?: ErrorObject[] | null // errors from the last validation
255 // shared external scope values for compiled functions
256 readonly scope: ValueScope
257 readonly schemas: {[Key in string]?: SchemaEnv} = {}
258 readonly refs: {[Ref in string]?: SchemaEnv | string} = {}
259 readonly formats: {[Name in string]?: AddedFormat} = {}
260 readonly RULES: ValidationRules
261 readonly _compilations: Set<SchemaEnv> = new Set()
262 private readonly _loading: {[Ref in string]?: Promise<AnySchemaObject>} = {}
263 private readonly _cache: Map<AnySchema, SchemaEnv> = new Map()
264 private readonly _metaOpts: InstanceOptions
266 static ValidationError = ValidationError
267 static MissingRefError = MissingRefError
269 constructor(opts: Options = {}) {
270 opts = this.opts = {...opts, ...requiredOptions(opts)}
271 const {es5, lines} = this.opts.code
272 this.scope = new ValueScope({scope: {}, prefixes: EXT_SCOPE_NAMES, es5, lines})
273 this.logger = getLogger(opts.logger)
274 const formatOpt = opts.validateFormats
275 opts.validateFormats = false
277 this.RULES = getRules()
278 checkOptions.call(this, removedOptions, opts, "NOT SUPPORTED")
279 checkOptions.call(this, deprecatedOptions, opts, "DEPRECATED", "warn")
280 this._metaOpts = getMetaSchemaOptions.call(this)
282 if (opts.formats) addInitialFormats.call(this)
283 this._addVocabularies()
284 this._addDefaultMetaSchema()
285 if (opts.keywords) addInitialKeywords.call(this, opts.keywords)
286 if (typeof opts.meta == "object") this.addMetaSchema(opts.meta)
287 addInitialSchemas.call(this)
288 opts.validateFormats = formatOpt
291 _addVocabularies(): void {
292 this.addKeyword("$async")
295 _addDefaultMetaSchema(): void {
296 const {$data, meta} = this.opts
297 if (meta && $data) this.addMetaSchema($dataRefSchema, $dataRefSchema.$id, false)
300 defaultMeta(): string | AnySchemaObject | undefined {
301 const {meta} = this.opts
302 return (this.opts.defaultMeta = typeof meta == "object" ? meta.$id || meta : undefined)
305 // Validate data using schema
306 // AnySchema will be compiled and cached using schema itself as a key for Map
307 validate(schema: Schema | string, data: unknown): boolean
308 validate(schemaKeyRef: AnySchema | string, data: unknown): boolean | Promise<unknown>
309 validate<T>(schema: Schema | JSONSchemaType<T> | string, data: unknown): data is T
310 // Separated for type inference to work
311 // eslint-disable-next-line @typescript-eslint/unified-signatures
312 validate<T>(schema: JTDSchemaType<T>, data: unknown): data is T
313 validate<T>(schema: AsyncSchema, data: unknown | T): Promise<T>
314 validate<T>(schemaKeyRef: AnySchema | string, data: unknown): data is T | Promise<T>
316 schemaKeyRef: AnySchema | string, // key, ref or schema object
317 data: unknown | T // to be validated
318 ): boolean | Promise<T> {
319 let v: AnyValidateFunction | undefined
320 if (typeof schemaKeyRef == "string") {
321 v = this.getSchema<T>(schemaKeyRef)
322 if (!v) throw new Error(`no schema with key or ref "${schemaKeyRef}"`)
324 v = this.compile<T>(schemaKeyRef)
327 const valid = v(data)
328 if (!("$async" in v)) this.errors = v.errors
332 // Create validation function for passed schema
333 // _meta: true if schema is a meta-schema. Used internally to compile meta schemas of user-defined keywords.
334 compile<T = unknown>(schema: Schema | JSONSchemaType<T>, _meta?: boolean): ValidateFunction<T>
335 // Separated for type inference to work
336 // eslint-disable-next-line @typescript-eslint/unified-signatures
337 compile<T = unknown>(schema: JTDSchemaType<T>, _meta?: boolean): ValidateFunction<T>
338 compile<T = unknown>(schema: AsyncSchema, _meta?: boolean): AsyncValidateFunction<T>
339 compile<T = unknown>(schema: AnySchema, _meta?: boolean): AnyValidateFunction<T>
340 compile<T = unknown>(schema: AnySchema, _meta?: boolean): AnyValidateFunction<T> {
341 const sch = this._addSchema(schema, _meta)
342 return (sch.validate || this._compileSchemaEnv(sch)) as AnyValidateFunction<T>
345 // Creates validating function for passed schema with asynchronous loading of missing schemas.
346 // `loadSchema` option should be a function that accepts schema uri and returns promise that resolves with the schema.
347 // TODO allow passing schema URI
348 // meta - optional true to compile meta-schema
349 compileAsync<T = unknown>(
350 schema: SchemaObject | JSONSchemaType<T>,
352 ): Promise<ValidateFunction<T>>
353 // Separated for type inference to work
354 // eslint-disable-next-line @typescript-eslint/unified-signatures
355 compileAsync<T = unknown>(schema: JTDSchemaType<T>, _meta?: boolean): Promise<ValidateFunction<T>>
356 compileAsync<T = unknown>(schema: AsyncSchema, meta?: boolean): Promise<AsyncValidateFunction<T>>
357 // eslint-disable-next-line @typescript-eslint/unified-signatures
358 compileAsync<T = unknown>(
359 schema: AnySchemaObject,
361 ): Promise<AnyValidateFunction<T>>
362 compileAsync<T = unknown>(
363 schema: AnySchemaObject,
365 ): Promise<AnyValidateFunction<T>> {
366 if (typeof this.opts.loadSchema != "function") {
367 throw new Error("options.loadSchema should be a function")
369 const {loadSchema} = this.opts
370 return runCompileAsync.call(this, schema, meta)
372 async function runCompileAsync(
374 _schema: AnySchemaObject,
376 ): Promise<AnyValidateFunction> {
377 await loadMetaSchema.call(this, _schema.$schema)
378 const sch = this._addSchema(_schema, _meta)
379 return sch.validate || _compileAsync.call(this, sch)
382 async function loadMetaSchema(this: Ajv, $ref?: string): Promise<void> {
383 if ($ref && !this.getSchema($ref)) {
384 await runCompileAsync.call(this, {$ref}, true)
388 async function _compileAsync(this: Ajv, sch: SchemaEnv): Promise<AnyValidateFunction> {
390 return this._compileSchemaEnv(sch)
392 if (!(e instanceof MissingRefError)) throw e
393 checkLoaded.call(this, e)
394 await loadMissingSchema.call(this, e.missingSchema)
395 return _compileAsync.call(this, sch)
399 function checkLoaded(this: Ajv, {missingSchema: ref, missingRef}: MissingRefError): void {
400 if (this.refs[ref]) {
401 throw new Error(`AnySchema ${ref} is loaded but ${missingRef} cannot be resolved`)
405 async function loadMissingSchema(this: Ajv, ref: string): Promise<void> {
406 const _schema = await _loadSchema.call(this, ref)
407 if (!this.refs[ref]) await loadMetaSchema.call(this, _schema.$schema)
408 if (!this.refs[ref]) this.addSchema(_schema, ref, meta)
411 async function _loadSchema(this: Ajv, ref: string): Promise<AnySchemaObject> {
412 const p = this._loading[ref]
415 return await (this._loading[ref] = loadSchema(ref))
417 delete this._loading[ref]
422 // Adds schema to the instance
424 schema: AnySchema | AnySchema[], // If array is passed, `key` will be ignored
425 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`.
426 _meta?: boolean, // true if schema is a meta-schema. Used internally, addMetaSchema should be used instead.
427 _validateSchema = this.opts.validateSchema // false to skip schema validation. Used internally, option validateSchema should be used instead.
429 if (Array.isArray(schema)) {
430 for (const sch of schema) this.addSchema(sch, undefined, _meta, _validateSchema)
433 let id: string | undefined
434 if (typeof schema === "object") {
436 if (id !== undefined && typeof id != "string") throw new Error("schema $id must be string")
438 key = normalizeId(key || id)
439 this._checkUnique(key)
440 this.schemas[key] = this._addSchema(schema, _meta, key, _validateSchema, true)
444 // Add schema that will be used to validate other schemas
445 // options in META_IGNORE_OPTIONS are alway set to false
447 schema: AnySchemaObject,
448 key?: string, // schema key
449 _validateSchema = this.opts.validateSchema // false to skip schema validation, can be used to override validateSchema option for meta-schema
451 this.addSchema(schema, key, true, _validateSchema)
455 // Validate schema against its meta-schema
456 validateSchema(schema: AnySchema, throwOrLogError?: boolean): boolean | Promise<unknown> {
457 if (typeof schema == "boolean") return true
458 let $schema: string | AnySchemaObject | undefined
459 $schema = schema.$schema
460 if ($schema !== undefined && typeof $schema != "string") {
461 throw new Error("$schema must be a string")
463 $schema = $schema || this.opts.defaultMeta || this.defaultMeta()
465 this.logger.warn("meta-schema not available")
469 const valid = this.validate($schema, schema)
470 if (!valid && throwOrLogError) {
471 const message = "schema is invalid: " + this.errorsText()
472 if (this.opts.validateSchema === "log") this.logger.error(message)
473 else throw new Error(message)
478 // Get compiled schema by `key` or `ref`.
479 // (`key` that was passed to `addSchema` or full schema reference - `schema.$id` or resolved id)
480 getSchema<T = unknown>(keyRef: string): AnyValidateFunction<T> | undefined {
482 while (typeof (sch = getSchEnv.call(this, keyRef)) == "string") keyRef = sch
483 if (sch === undefined) {
484 const root = new SchemaEnv({schema: {}})
485 sch = resolveSchema.call(this, root, keyRef)
487 this.refs[keyRef] = sch
489 return (sch.validate || this._compileSchemaEnv(sch)) as AnyValidateFunction<T> | undefined
492 // Remove cached schema(s).
493 // If no parameter is passed all schemas but meta-schemas are removed.
494 // If RegExp is passed all schemas with key/id matching pattern but meta-schemas are removed.
495 // Even if schema is referenced by other schemas it still can be removed as other schemas have local references.
496 removeSchema(schemaKeyRef?: AnySchema | string | RegExp): Ajv {
497 if (schemaKeyRef instanceof RegExp) {
498 this._removeAllSchemas(this.schemas, schemaKeyRef)
499 this._removeAllSchemas(this.refs, schemaKeyRef)
502 switch (typeof schemaKeyRef) {
504 this._removeAllSchemas(this.schemas)
505 this._removeAllSchemas(this.refs)
509 const sch = getSchEnv.call(this, schemaKeyRef)
510 if (typeof sch == "object") this._cache.delete(sch.schema)
511 delete this.schemas[schemaKeyRef]
512 delete this.refs[schemaKeyRef]
516 const cacheKey = schemaKeyRef
517 this._cache.delete(cacheKey)
518 let id = schemaKeyRef.$id
521 delete this.schemas[id]
527 throw new Error("ajv.removeSchema: invalid parameter")
531 // add "vocabulary" - a collection of keywords
532 addVocabulary(definitions: Vocabulary): Ajv {
533 for (const def of definitions) this.addKeyword(def)
538 kwdOrDef: string | KeywordDefinition,
539 def?: KeywordDefinition // deprecated
541 let keyword: string | string[]
542 if (typeof kwdOrDef == "string") {
544 if (typeof def == "object") {
545 this.logger.warn("these parameters are deprecated, see docs for addKeyword")
546 def.keyword = keyword
548 } else if (typeof kwdOrDef == "object" && def === undefined) {
550 keyword = def.keyword
551 if (Array.isArray(keyword) && !keyword.length) {
552 throw new Error("addKeywords: keyword must be string or non-empty array")
555 throw new Error("invalid addKeywords parameters")
558 checkKeyword.call(this, keyword, def)
560 eachItem(keyword, (kwd) => addRule.call(this, kwd))
563 keywordMetaschema.call(this, def)
564 const definition: AddedKeywordDefinition = {
566 type: getJSONTypes(def.type),
567 schemaType: getJSONTypes(def.schemaType),
571 definition.type.length === 0
572 ? (k) => addRule.call(this, k, definition)
573 : (k) => definition.type.forEach((t) => addRule.call(this, k, definition, t))
578 getKeyword(keyword: string): AddedKeywordDefinition | boolean {
579 const rule = this.RULES.all[keyword]
580 return typeof rule == "object" ? rule.definition : !!rule
584 removeKeyword(keyword: string): Ajv {
585 // TODO return type should be Ajv
587 delete RULES.keywords[keyword]
588 delete RULES.all[keyword]
589 for (const group of RULES.rules) {
590 const i = group.rules.findIndex((rule) => rule.keyword === keyword)
591 if (i >= 0) group.rules.splice(i, 1)
597 addFormat(name: string, format: Format): Ajv {
598 if (typeof format == "string") format = new RegExp(format)
599 this.formats[name] = format
604 errors: ErrorObject[] | null | undefined = this.errors, // optional array of validation errors
605 {separator = ", ", dataVar = "data"}: ErrorsTextOptions = {} // optional options with properties `separator` and `dataVar`
607 if (!errors || errors.length === 0) return "No errors"
609 .map((e) => `${dataVar}${e.instancePath} ${e.message}`)
610 .reduce((text, msg) => text + separator + msg)
613 $dataMetaSchema(metaSchema: AnySchemaObject, keywordsJsonPointers: string[]): AnySchemaObject {
614 const rules = this.RULES.all
615 metaSchema = JSON.parse(JSON.stringify(metaSchema))
616 for (const jsonPointer of keywordsJsonPointers) {
617 const segments = jsonPointer.split("/").slice(1) // first segment is an empty string
618 let keywords = metaSchema
619 for (const seg of segments) keywords = keywords[seg] as AnySchemaObject
621 for (const key in rules) {
622 const rule = rules[key]
623 if (typeof rule != "object") continue
624 const {$data} = rule.definition
625 const schema = keywords[key] as AnySchemaObject | undefined
626 if ($data && schema) keywords[key] = schemaOrData(schema)
633 private _removeAllSchemas(schemas: {[Ref in string]?: SchemaEnv | string}, regex?: RegExp): void {
634 for (const keyRef in schemas) {
635 const sch = schemas[keyRef]
636 if (!regex || regex.test(keyRef)) {
637 if (typeof sch == "string") {
638 delete schemas[keyRef]
639 } else if (sch && !sch.meta) {
640 this._cache.delete(sch.schema)
641 delete schemas[keyRef]
651 validateSchema = this.opts.validateSchema,
652 addSchema = this.opts.addUsedSchema
654 let id: string | undefined
655 if (typeof schema == "object") {
658 if (this.opts.jtd) throw new Error("schema must be object")
659 else if (typeof schema != "boolean") throw new Error("schema must be object or boolean")
661 let sch = this._cache.get(schema)
662 if (sch !== undefined) return sch
664 const localRefs = getSchemaRefs.call(this, schema)
665 baseId = normalizeId(id || baseId)
666 sch = new SchemaEnv({schema, meta, baseId, localRefs})
667 this._cache.set(sch.schema, sch)
668 if (addSchema && !baseId.startsWith("#")) {
669 // TODO atm it is allowed to overwrite schemas without id (instead of not adding them)
670 if (baseId) this._checkUnique(baseId)
671 this.refs[baseId] = sch
673 if (validateSchema) this.validateSchema(schema, true)
677 private _checkUnique(id: string): void {
678 if (this.schemas[id] || this.refs[id]) {
679 throw new Error(`schema with key or id "${id}" already exists`)
683 private _compileSchemaEnv(sch: SchemaEnv): AnyValidateFunction {
684 if (sch.meta) this._compileMetaSchema(sch)
685 else compileSchema.call(this, sch)
687 /* istanbul ignore if */
688 if (!sch.validate) throw new Error("ajv implementation error")
692 private _compileMetaSchema(sch: SchemaEnv): void {
693 const currentOpts = this.opts
694 this.opts = this._metaOpts
696 compileSchema.call(this, sch)
698 this.opts = currentOpts
703 export interface ErrorsTextOptions {
708 function checkOptions(
710 checkOpts: OptionsInfo<RemovedOptions | DeprecatedOptions>,
711 options: Options & RemovedOptions,
713 log: "warn" | "error" = "error"
715 for (const key in checkOpts) {
716 const opt = key as keyof typeof checkOpts
717 if (opt in options) this.logger[log](`${msg}: option ${key}. ${checkOpts[opt]}`)
721 function getSchEnv(this: Ajv, keyRef: string): SchemaEnv | string | undefined {
722 keyRef = normalizeId(keyRef) // TODO tests fail without this line
723 return this.schemas[keyRef] || this.refs[keyRef]
726 function addInitialSchemas(this: Ajv): void {
727 const optsSchemas = this.opts.schemas
728 if (!optsSchemas) return
729 if (Array.isArray(optsSchemas)) this.addSchema(optsSchemas)
730 else for (const key in optsSchemas) this.addSchema(optsSchemas[key] as AnySchema, key)
733 function addInitialFormats(this: Ajv): void {
734 for (const name in this.opts.formats) {
735 const format = this.opts.formats[name]
736 if (format) this.addFormat(name, format)
740 function addInitialKeywords(
742 defs: Vocabulary | {[K in string]?: KeywordDefinition}
744 if (Array.isArray(defs)) {
745 this.addVocabulary(defs)
748 this.logger.warn("keywords option as map is deprecated, pass array")
749 for (const keyword in defs) {
750 const def = defs[keyword] as KeywordDefinition
751 if (!def.keyword) def.keyword = keyword
756 function getMetaSchemaOptions(this: Ajv): InstanceOptions {
757 const metaOpts = {...this.opts}
758 for (const opt of META_IGNORE_OPTIONS) delete metaOpts[opt]
762 const noLogs = {log() {}, warn() {}, error() {}}
764 function getLogger(logger?: Partial<Logger> | false): Logger {
765 if (logger === false) return noLogs
766 if (logger === undefined) return console
767 if (logger.log && logger.warn && logger.error) return logger as Logger
768 throw new Error("logger must implement log, warn and error methods")
771 const KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i
773 function checkKeyword(this: Ajv, keyword: string | string[], def?: KeywordDefinition): void {
775 eachItem(keyword, (kwd) => {
776 if (RULES.keywords[kwd]) throw new Error(`Keyword ${kwd} is already defined`)
777 if (!KEYWORD_NAME.test(kwd)) throw new Error(`Keyword ${kwd} has invalid name`)
780 if (def.$data && !("code" in def || "validate" in def)) {
781 throw new Error('$data keyword must have "code" or "validate" function')
788 definition?: AddedKeywordDefinition,
791 const post = definition?.post
792 if (dataType && post) throw new Error('keyword with "post" flag cannot have "type"')
794 let ruleGroup = post ? RULES.post : RULES.rules.find(({type: t}) => t === dataType)
796 ruleGroup = {type: dataType, rules: []}
797 RULES.rules.push(ruleGroup)
799 RULES.keywords[keyword] = true
800 if (!definition) return
806 type: getJSONTypes(definition.type),
807 schemaType: getJSONTypes(definition.schemaType),
810 if (definition.before) addBeforeRule.call(this, ruleGroup, rule, definition.before)
811 else ruleGroup.rules.push(rule)
812 RULES.all[keyword] = rule
813 definition.implements?.forEach((kwd) => this.addKeyword(kwd))
816 function addBeforeRule(this: Ajv, ruleGroup: RuleGroup, rule: Rule, before: string): void {
817 const i = ruleGroup.rules.findIndex((_rule) => _rule.keyword === before)
819 ruleGroup.rules.splice(i, 0, rule)
821 ruleGroup.rules.push(rule)
822 this.logger.warn(`rule ${before} is not defined`)
826 function keywordMetaschema(this: Ajv, def: KeywordDefinition): void {
827 let {metaSchema} = def
828 if (metaSchema === undefined) return
829 if (def.$data && this.opts.$data) metaSchema = schemaOrData(metaSchema)
830 def.validateSchema = this.compile(metaSchema, true)
834 $ref: "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#",
837 function schemaOrData(schema: AnySchema): AnySchemaObject {
838 return {anyOf: [schema, $dataRef]}