[6a3a178] | 1 | export {
|
---|
| 2 | Format,
|
---|
| 3 | FormatDefinition,
|
---|
| 4 | AsyncFormatDefinition,
|
---|
| 5 | KeywordDefinition,
|
---|
| 6 | KeywordErrorDefinition,
|
---|
| 7 | CodeKeywordDefinition,
|
---|
| 8 | MacroKeywordDefinition,
|
---|
| 9 | FuncKeywordDefinition,
|
---|
| 10 | Vocabulary,
|
---|
| 11 | Schema,
|
---|
| 12 | SchemaObject,
|
---|
| 13 | AnySchemaObject,
|
---|
| 14 | AsyncSchema,
|
---|
| 15 | AnySchema,
|
---|
| 16 | ValidateFunction,
|
---|
| 17 | AsyncValidateFunction,
|
---|
| 18 | AnyValidateFunction,
|
---|
| 19 | ErrorObject,
|
---|
| 20 | ErrorNoParams,
|
---|
| 21 | } from "./types"
|
---|
| 22 |
|
---|
| 23 | export {SchemaCxt, SchemaObjCxt} from "./compile"
|
---|
| 24 | export interface Plugin<Opts> {
|
---|
| 25 | (ajv: Ajv, options?: Opts): Ajv
|
---|
| 26 | [prop: string]: any
|
---|
| 27 | }
|
---|
| 28 |
|
---|
| 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"
|
---|
| 35 |
|
---|
| 36 | import type {
|
---|
| 37 | Schema,
|
---|
| 38 | AnySchema,
|
---|
| 39 | AnySchemaObject,
|
---|
| 40 | SchemaObject,
|
---|
| 41 | AsyncSchema,
|
---|
| 42 | Vocabulary,
|
---|
| 43 | KeywordDefinition,
|
---|
| 44 | AddedKeywordDefinition,
|
---|
| 45 | AnyValidateFunction,
|
---|
| 46 | ValidateFunction,
|
---|
| 47 | AsyncValidateFunction,
|
---|
| 48 | ErrorObject,
|
---|
| 49 | Format,
|
---|
| 50 | AddedFormat,
|
---|
| 51 | } from "./types"
|
---|
| 52 | import type {JSONSchemaType} from "./types/json-schema"
|
---|
| 53 | import type {JTDSchemaType, SomeJTDSchemaType, JTDDataType} 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"
|
---|
| 62 |
|
---|
| 63 | import * as $dataRefSchema from "./refs/data.json"
|
---|
| 64 |
|
---|
| 65 | const META_IGNORE_OPTIONS: (keyof Options)[] = ["removeAdditional", "useDefaults", "coerceTypes"]
|
---|
| 66 | const EXT_SCOPE_NAMES = new Set([
|
---|
| 67 | "validate",
|
---|
| 68 | "serialize",
|
---|
| 69 | "parse",
|
---|
| 70 | "wrapper",
|
---|
| 71 | "root",
|
---|
| 72 | "schema",
|
---|
| 73 | "keyword",
|
---|
| 74 | "pattern",
|
---|
| 75 | "formats",
|
---|
| 76 | "validate$data",
|
---|
| 77 | "func",
|
---|
| 78 | "obj",
|
---|
| 79 | "Error",
|
---|
| 80 | ])
|
---|
| 81 |
|
---|
| 82 | export type Options = CurrentOptions & DeprecatedOptions
|
---|
| 83 |
|
---|
| 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:
|
---|
| 96 | $data?: boolean
|
---|
| 97 | allErrors?: boolean
|
---|
| 98 | verbose?: boolean
|
---|
| 99 | discriminator?: boolean
|
---|
| 100 | unicodeRegExp?: boolean
|
---|
| 101 | timestamp?: "string" | "date" // JTD only
|
---|
| 102 | parseDate?: boolean // JTD only
|
---|
| 103 | allowDate?: boolean // JTD only
|
---|
| 104 | $comment?:
|
---|
| 105 | | true
|
---|
| 106 | | ((comment: string, schemaPath?: string, rootSchema?: AnySchemaObject) => unknown)
|
---|
| 107 | formats?: {[Name in string]?: Format}
|
---|
| 108 | keywords?: Vocabulary
|
---|
| 109 | schemas?: AnySchema[] | {[Key in string]?: AnySchema}
|
---|
| 110 | logger?: Logger | false
|
---|
| 111 | loadSchema?: (uri: string) => Promise<AnySchemaObject>
|
---|
| 112 | // options to modify validated data:
|
---|
| 113 | removeAdditional?: boolean | "all" | "failing"
|
---|
| 114 | useDefaults?: boolean | "empty"
|
---|
| 115 | coerceTypes?: boolean | "array"
|
---|
| 116 | // advanced options:
|
---|
| 117 | next?: boolean // NEW
|
---|
| 118 | unevaluated?: boolean // NEW
|
---|
| 119 | dynamicRef?: boolean // NEW
|
---|
| 120 | schemaId?: "id" | "$id"
|
---|
| 121 | jtd?: boolean // NEW
|
---|
| 122 | meta?: SchemaObject | boolean
|
---|
| 123 | defaultMeta?: string | AnySchemaObject
|
---|
| 124 | validateSchema?: boolean | "log"
|
---|
| 125 | addUsedSchema?: boolean
|
---|
| 126 | inlineRefs?: boolean | number
|
---|
| 127 | passContext?: boolean
|
---|
| 128 | loopRequired?: number
|
---|
| 129 | loopEnum?: number // NEW
|
---|
| 130 | ownProperties?: boolean
|
---|
| 131 | multipleOfPrecision?: number
|
---|
| 132 | int32range?: boolean // JTD only
|
---|
| 133 | messages?: boolean
|
---|
| 134 | code?: CodeOptions // NEW
|
---|
| 135 | }
|
---|
| 136 |
|
---|
| 137 | export interface CodeOptions {
|
---|
| 138 | es5?: boolean
|
---|
| 139 | lines?: boolean
|
---|
| 140 | optimize?: boolean | number
|
---|
| 141 | formats?: Code // code to require (or construct) map of available formats - for standalone code
|
---|
| 142 | source?: boolean
|
---|
| 143 | process?: (code: string, schema?: SchemaEnv) => string
|
---|
| 144 | }
|
---|
| 145 |
|
---|
| 146 | interface InstanceCodeOptions extends CodeOptions {
|
---|
| 147 | optimize: number
|
---|
| 148 | }
|
---|
| 149 |
|
---|
| 150 | interface DeprecatedOptions {
|
---|
| 151 | /** @deprecated */
|
---|
| 152 | ignoreKeywordsWithRef?: boolean
|
---|
| 153 | /** @deprecated */
|
---|
| 154 | jsPropertySyntax?: boolean // added instead of jsonPointers
|
---|
| 155 | /** @deprecated */
|
---|
| 156 | unicode?: boolean
|
---|
| 157 | }
|
---|
| 158 |
|
---|
| 159 | interface RemovedOptions {
|
---|
| 160 | format?: boolean
|
---|
| 161 | errorDataPath?: "object" | "property"
|
---|
| 162 | nullable?: boolean // "nullable" keyword is supported by default
|
---|
| 163 | jsonPointers?: boolean
|
---|
| 164 | extendRefs?: true | "ignore" | "fail"
|
---|
| 165 | missingRefs?: true | "ignore" | "fail"
|
---|
| 166 | processCode?: (code: string, schema?: SchemaEnv) => string
|
---|
| 167 | sourceCode?: boolean
|
---|
| 168 | strictDefaults?: boolean
|
---|
| 169 | strictKeywords?: boolean
|
---|
| 170 | uniqueItems?: boolean
|
---|
| 171 | unknownFormats?: true | string[] | "ignore"
|
---|
| 172 | cache?: any
|
---|
| 173 | serialize?: (schema: AnySchema) => unknown
|
---|
| 174 | ajvErrors?: boolean
|
---|
| 175 | }
|
---|
| 176 |
|
---|
| 177 | type OptionsInfo<T extends RemovedOptions | DeprecatedOptions> = {
|
---|
| 178 | [K in keyof T]-?: string | undefined
|
---|
| 179 | }
|
---|
| 180 |
|
---|
| 181 | const removedOptions: OptionsInfo<RemovedOptions> = {
|
---|
| 182 | errorDataPath: "",
|
---|
| 183 | format: "`validateFormats: false` can be used instead.",
|
---|
| 184 | nullable: '"nullable" keyword is supported by default.',
|
---|
| 185 | jsonPointers: "Deprecated jsPropertySyntax can be used instead.",
|
---|
| 186 | extendRefs: "Deprecated ignoreKeywordsWithRef can be used instead.",
|
---|
| 187 | missingRefs: "Pass empty schema with $id that should be ignored to ajv.addSchema.",
|
---|
| 188 | processCode: "Use option `code: {process: (code, schemaEnv: object) => string}`",
|
---|
| 189 | sourceCode: "Use option `code: {source: true}`",
|
---|
| 190 | strictDefaults: "It is default now, see option `strict`.",
|
---|
| 191 | strictKeywords: "It is default now, see option `strict`.",
|
---|
| 192 | uniqueItems: '"uniqueItems" keyword is always validated.',
|
---|
| 193 | unknownFormats: "Disable strict mode or pass `true` to `ajv.addFormat` (or `formats` option).",
|
---|
| 194 | cache: "Map is used as cache, schema object as key.",
|
---|
| 195 | serialize: "Map is used as cache, schema object as key.",
|
---|
| 196 | ajvErrors: "It is default now.",
|
---|
| 197 | }
|
---|
| 198 |
|
---|
| 199 | const deprecatedOptions: OptionsInfo<DeprecatedOptions> = {
|
---|
| 200 | ignoreKeywordsWithRef: "",
|
---|
| 201 | jsPropertySyntax: "",
|
---|
| 202 | unicode: '"minLength"/"maxLength" account for unicode characters by default.',
|
---|
| 203 | }
|
---|
| 204 |
|
---|
| 205 | type RequiredInstanceOptions = {
|
---|
| 206 | [K in
|
---|
| 207 | | "strictSchema"
|
---|
| 208 | | "strictNumbers"
|
---|
| 209 | | "strictTypes"
|
---|
| 210 | | "strictTuples"
|
---|
| 211 | | "strictRequired"
|
---|
| 212 | | "inlineRefs"
|
---|
| 213 | | "loopRequired"
|
---|
| 214 | | "loopEnum"
|
---|
| 215 | | "meta"
|
---|
| 216 | | "messages"
|
---|
| 217 | | "schemaId"
|
---|
| 218 | | "addUsedSchema"
|
---|
| 219 | | "validateSchema"
|
---|
| 220 | | "validateFormats"
|
---|
| 221 | | "int32range"
|
---|
| 222 | | "unicodeRegExp"]: NonNullable<Options[K]>
|
---|
| 223 | } & {code: InstanceCodeOptions}
|
---|
| 224 |
|
---|
| 225 | export type InstanceOptions = Options & RequiredInstanceOptions
|
---|
| 226 |
|
---|
| 227 | const MAX_EXPRESSION = 200
|
---|
| 228 |
|
---|
| 229 | // eslint-disable-next-line complexity
|
---|
| 230 | function requiredOptions(o: Options): RequiredInstanceOptions {
|
---|
| 231 | const s = o.strict
|
---|
| 232 | const _optz = o.code?.optimize
|
---|
| 233 | const optimize = _optz === true || _optz === undefined ? 1 : _optz || 0
|
---|
| 234 | return {
|
---|
| 235 | strictSchema: o.strictSchema ?? s ?? true,
|
---|
| 236 | strictNumbers: o.strictNumbers ?? s ?? true,
|
---|
| 237 | strictTypes: o.strictTypes ?? s ?? "log",
|
---|
| 238 | strictTuples: o.strictTuples ?? s ?? "log",
|
---|
| 239 | strictRequired: o.strictRequired ?? s ?? false,
|
---|
| 240 | code: o.code ? {...o.code, optimize} : {optimize},
|
---|
| 241 | loopRequired: o.loopRequired ?? MAX_EXPRESSION,
|
---|
| 242 | loopEnum: o.loopEnum ?? MAX_EXPRESSION,
|
---|
| 243 | meta: o.meta ?? true,
|
---|
| 244 | messages: o.messages ?? true,
|
---|
| 245 | inlineRefs: o.inlineRefs ?? true,
|
---|
| 246 | schemaId: o.schemaId ?? "$id",
|
---|
| 247 | addUsedSchema: o.addUsedSchema ?? true,
|
---|
| 248 | validateSchema: o.validateSchema ?? true,
|
---|
| 249 | validateFormats: o.validateFormats ?? true,
|
---|
| 250 | unicodeRegExp: o.unicodeRegExp ?? true,
|
---|
| 251 | int32range: o.int32range ?? true,
|
---|
| 252 | }
|
---|
| 253 | }
|
---|
| 254 |
|
---|
| 255 | export interface Logger {
|
---|
| 256 | log(...args: unknown[]): unknown
|
---|
| 257 | warn(...args: unknown[]): unknown
|
---|
| 258 | error(...args: unknown[]): unknown
|
---|
| 259 | }
|
---|
| 260 |
|
---|
| 261 | export default class Ajv {
|
---|
| 262 | opts: InstanceOptions
|
---|
| 263 | errors?: ErrorObject[] | null // errors from the last validation
|
---|
| 264 | logger: Logger
|
---|
| 265 | // shared external scope values for compiled functions
|
---|
| 266 | readonly scope: ValueScope
|
---|
| 267 | readonly schemas: {[Key in string]?: SchemaEnv} = {}
|
---|
| 268 | readonly refs: {[Ref in string]?: SchemaEnv | string} = {}
|
---|
| 269 | readonly formats: {[Name in string]?: AddedFormat} = {}
|
---|
| 270 | readonly RULES: ValidationRules
|
---|
| 271 | readonly _compilations: Set<SchemaEnv> = new Set()
|
---|
| 272 | private readonly _loading: {[Ref in string]?: Promise<AnySchemaObject>} = {}
|
---|
| 273 | private readonly _cache: Map<AnySchema, SchemaEnv> = new Map()
|
---|
| 274 | private readonly _metaOpts: InstanceOptions
|
---|
| 275 |
|
---|
| 276 | static ValidationError = ValidationError
|
---|
| 277 | static MissingRefError = MissingRefError
|
---|
| 278 |
|
---|
| 279 | constructor(opts: Options = {}) {
|
---|
| 280 | opts = this.opts = {...opts, ...requiredOptions(opts)}
|
---|
| 281 | const {es5, lines} = this.opts.code
|
---|
| 282 | this.scope = new ValueScope({scope: {}, prefixes: EXT_SCOPE_NAMES, es5, lines})
|
---|
| 283 | this.logger = getLogger(opts.logger)
|
---|
| 284 | const formatOpt = opts.validateFormats
|
---|
| 285 | opts.validateFormats = false
|
---|
| 286 |
|
---|
| 287 | this.RULES = getRules()
|
---|
| 288 | checkOptions.call(this, removedOptions, opts, "NOT SUPPORTED")
|
---|
| 289 | checkOptions.call(this, deprecatedOptions, opts, "DEPRECATED", "warn")
|
---|
| 290 | this._metaOpts = getMetaSchemaOptions.call(this)
|
---|
| 291 |
|
---|
| 292 | if (opts.formats) addInitialFormats.call(this)
|
---|
| 293 | this._addVocabularies()
|
---|
| 294 | this._addDefaultMetaSchema()
|
---|
| 295 | if (opts.keywords) addInitialKeywords.call(this, opts.keywords)
|
---|
| 296 | if (typeof opts.meta == "object") this.addMetaSchema(opts.meta)
|
---|
| 297 | addInitialSchemas.call(this)
|
---|
| 298 | opts.validateFormats = formatOpt
|
---|
| 299 | }
|
---|
| 300 |
|
---|
| 301 | _addVocabularies(): void {
|
---|
| 302 | this.addKeyword("$async")
|
---|
| 303 | }
|
---|
| 304 |
|
---|
| 305 | _addDefaultMetaSchema(): void {
|
---|
| 306 | const {$data, meta, schemaId} = this.opts
|
---|
| 307 | let _dataRefSchema: SchemaObject = $dataRefSchema
|
---|
| 308 | if (schemaId === "id") {
|
---|
| 309 | _dataRefSchema = {...$dataRefSchema}
|
---|
| 310 | _dataRefSchema.id = _dataRefSchema.$id
|
---|
| 311 | delete _dataRefSchema.$id
|
---|
| 312 | }
|
---|
| 313 | if (meta && $data) this.addMetaSchema(_dataRefSchema, _dataRefSchema[schemaId], false)
|
---|
| 314 | }
|
---|
| 315 |
|
---|
| 316 | defaultMeta(): string | AnySchemaObject | undefined {
|
---|
| 317 | const {meta, schemaId} = this.opts
|
---|
| 318 | return (this.opts.defaultMeta = typeof meta == "object" ? meta[schemaId] || meta : undefined)
|
---|
| 319 | }
|
---|
| 320 |
|
---|
| 321 | // Validate data using schema
|
---|
| 322 | // AnySchema will be compiled and cached using schema itself as a key for Map
|
---|
| 323 | validate(schema: Schema | string, data: unknown): boolean
|
---|
| 324 | validate(schemaKeyRef: AnySchema | string, data: unknown): boolean | Promise<unknown>
|
---|
| 325 | validate<T>(schema: Schema | JSONSchemaType<T> | string, data: unknown): data is T
|
---|
| 326 | // Separated for type inference to work
|
---|
| 327 | // eslint-disable-next-line @typescript-eslint/unified-signatures
|
---|
| 328 | validate<T>(schema: JTDSchemaType<T>, data: unknown): data is T
|
---|
| 329 | // This overload is only intended for typescript inference, the first
|
---|
| 330 | // argument prevents manual type annotation from matching this overload
|
---|
| 331 | validate<N extends never, T extends SomeJTDSchemaType>(
|
---|
| 332 | schema: T,
|
---|
| 333 | data: unknown
|
---|
| 334 | ): data is JTDDataType<T>
|
---|
| 335 | validate<T>(schema: AsyncSchema, data: unknown | T): Promise<T>
|
---|
| 336 | validate<T>(schemaKeyRef: AnySchema | string, data: unknown): data is T | Promise<T>
|
---|
| 337 | validate<T>(
|
---|
| 338 | schemaKeyRef: AnySchema | string, // key, ref or schema object
|
---|
| 339 | data: unknown | T // to be validated
|
---|
| 340 | ): boolean | Promise<T> {
|
---|
| 341 | let v: AnyValidateFunction | undefined
|
---|
| 342 | if (typeof schemaKeyRef == "string") {
|
---|
| 343 | v = this.getSchema<T>(schemaKeyRef)
|
---|
| 344 | if (!v) throw new Error(`no schema with key or ref "${schemaKeyRef}"`)
|
---|
| 345 | } else {
|
---|
| 346 | v = this.compile<T>(schemaKeyRef)
|
---|
| 347 | }
|
---|
| 348 |
|
---|
| 349 | const valid = v(data)
|
---|
| 350 | if (!("$async" in v)) this.errors = v.errors
|
---|
| 351 | return valid
|
---|
| 352 | }
|
---|
| 353 |
|
---|
| 354 | // Create validation function for passed schema
|
---|
| 355 | // _meta: true if schema is a meta-schema. Used internally to compile meta schemas of user-defined keywords.
|
---|
| 356 | compile<T = unknown>(schema: Schema | JSONSchemaType<T>, _meta?: boolean): ValidateFunction<T>
|
---|
| 357 | // Separated for type inference to work
|
---|
| 358 | // eslint-disable-next-line @typescript-eslint/unified-signatures
|
---|
| 359 | compile<T = unknown>(schema: JTDSchemaType<T>, _meta?: boolean): ValidateFunction<T>
|
---|
| 360 | // This overload is only intended for typescript inference, the first
|
---|
| 361 | // argument prevents manual type annotation from matching this overload
|
---|
| 362 | compile<N extends never, T extends SomeJTDSchemaType>(
|
---|
| 363 | schema: T,
|
---|
| 364 | _meta?: boolean
|
---|
| 365 | ): ValidateFunction<JTDDataType<T>>
|
---|
| 366 | compile<T = unknown>(schema: AsyncSchema, _meta?: boolean): AsyncValidateFunction<T>
|
---|
| 367 | compile<T = unknown>(schema: AnySchema, _meta?: boolean): AnyValidateFunction<T>
|
---|
| 368 | compile<T = unknown>(schema: AnySchema, _meta?: boolean): AnyValidateFunction<T> {
|
---|
| 369 | const sch = this._addSchema(schema, _meta)
|
---|
| 370 | return (sch.validate || this._compileSchemaEnv(sch)) as AnyValidateFunction<T>
|
---|
| 371 | }
|
---|
| 372 |
|
---|
| 373 | // Creates validating function for passed schema with asynchronous loading of missing schemas.
|
---|
| 374 | // `loadSchema` option should be a function that accepts schema uri and returns promise that resolves with the schema.
|
---|
| 375 | // TODO allow passing schema URI
|
---|
| 376 | // meta - optional true to compile meta-schema
|
---|
| 377 | compileAsync<T = unknown>(
|
---|
| 378 | schema: SchemaObject | JSONSchemaType<T>,
|
---|
| 379 | _meta?: boolean
|
---|
| 380 | ): Promise<ValidateFunction<T>>
|
---|
| 381 | // Separated for type inference to work
|
---|
| 382 | // eslint-disable-next-line @typescript-eslint/unified-signatures
|
---|
| 383 | compileAsync<T = unknown>(schema: JTDSchemaType<T>, _meta?: boolean): Promise<ValidateFunction<T>>
|
---|
| 384 | compileAsync<T = unknown>(schema: AsyncSchema, meta?: boolean): Promise<AsyncValidateFunction<T>>
|
---|
| 385 | // eslint-disable-next-line @typescript-eslint/unified-signatures
|
---|
| 386 | compileAsync<T = unknown>(
|
---|
| 387 | schema: AnySchemaObject,
|
---|
| 388 | meta?: boolean
|
---|
| 389 | ): Promise<AnyValidateFunction<T>>
|
---|
| 390 | compileAsync<T = unknown>(
|
---|
| 391 | schema: AnySchemaObject,
|
---|
| 392 | meta?: boolean
|
---|
| 393 | ): Promise<AnyValidateFunction<T>> {
|
---|
| 394 | if (typeof this.opts.loadSchema != "function") {
|
---|
| 395 | throw new Error("options.loadSchema should be a function")
|
---|
| 396 | }
|
---|
| 397 | const {loadSchema} = this.opts
|
---|
| 398 | return runCompileAsync.call(this, schema, meta)
|
---|
| 399 |
|
---|
| 400 | async function runCompileAsync(
|
---|
| 401 | this: Ajv,
|
---|
| 402 | _schema: AnySchemaObject,
|
---|
| 403 | _meta?: boolean
|
---|
| 404 | ): Promise<AnyValidateFunction> {
|
---|
| 405 | await loadMetaSchema.call(this, _schema.$schema)
|
---|
| 406 | const sch = this._addSchema(_schema, _meta)
|
---|
| 407 | return sch.validate || _compileAsync.call(this, sch)
|
---|
| 408 | }
|
---|
| 409 |
|
---|
| 410 | async function loadMetaSchema(this: Ajv, $ref?: string): Promise<void> {
|
---|
| 411 | if ($ref && !this.getSchema($ref)) {
|
---|
| 412 | await runCompileAsync.call(this, {$ref}, true)
|
---|
| 413 | }
|
---|
| 414 | }
|
---|
| 415 |
|
---|
| 416 | async function _compileAsync(this: Ajv, sch: SchemaEnv): Promise<AnyValidateFunction> {
|
---|
| 417 | try {
|
---|
| 418 | return this._compileSchemaEnv(sch)
|
---|
| 419 | } catch (e) {
|
---|
| 420 | if (!(e instanceof MissingRefError)) throw e
|
---|
| 421 | checkLoaded.call(this, e)
|
---|
| 422 | await loadMissingSchema.call(this, e.missingSchema)
|
---|
| 423 | return _compileAsync.call(this, sch)
|
---|
| 424 | }
|
---|
| 425 | }
|
---|
| 426 |
|
---|
| 427 | function checkLoaded(this: Ajv, {missingSchema: ref, missingRef}: MissingRefError): void {
|
---|
| 428 | if (this.refs[ref]) {
|
---|
| 429 | throw new Error(`AnySchema ${ref} is loaded but ${missingRef} cannot be resolved`)
|
---|
| 430 | }
|
---|
| 431 | }
|
---|
| 432 |
|
---|
| 433 | async function loadMissingSchema(this: Ajv, ref: string): Promise<void> {
|
---|
| 434 | const _schema = await _loadSchema.call(this, ref)
|
---|
| 435 | if (!this.refs[ref]) await loadMetaSchema.call(this, _schema.$schema)
|
---|
| 436 | if (!this.refs[ref]) this.addSchema(_schema, ref, meta)
|
---|
| 437 | }
|
---|
| 438 |
|
---|
| 439 | async function _loadSchema(this: Ajv, ref: string): Promise<AnySchemaObject> {
|
---|
| 440 | const p = this._loading[ref]
|
---|
| 441 | if (p) return p
|
---|
| 442 | try {
|
---|
| 443 | return await (this._loading[ref] = loadSchema(ref))
|
---|
| 444 | } finally {
|
---|
| 445 | delete this._loading[ref]
|
---|
| 446 | }
|
---|
| 447 | }
|
---|
| 448 | }
|
---|
| 449 |
|
---|
| 450 | // Adds schema to the instance
|
---|
| 451 | addSchema(
|
---|
| 452 | schema: AnySchema | AnySchema[], // If array is passed, `key` will be ignored
|
---|
| 453 | 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`.
|
---|
| 454 | _meta?: boolean, // true if schema is a meta-schema. Used internally, addMetaSchema should be used instead.
|
---|
| 455 | _validateSchema = this.opts.validateSchema // false to skip schema validation. Used internally, option validateSchema should be used instead.
|
---|
| 456 | ): Ajv {
|
---|
| 457 | if (Array.isArray(schema)) {
|
---|
| 458 | for (const sch of schema) this.addSchema(sch, undefined, _meta, _validateSchema)
|
---|
| 459 | return this
|
---|
| 460 | }
|
---|
| 461 | let id: string | undefined
|
---|
| 462 | if (typeof schema === "object") {
|
---|
| 463 | const {schemaId} = this.opts
|
---|
| 464 | id = schema[schemaId]
|
---|
| 465 | if (id !== undefined && typeof id != "string") {
|
---|
| 466 | throw new Error(`schema ${schemaId} must be string`)
|
---|
| 467 | }
|
---|
| 468 | }
|
---|
| 469 | key = normalizeId(key || id)
|
---|
| 470 | this._checkUnique(key)
|
---|
| 471 | this.schemas[key] = this._addSchema(schema, _meta, key, _validateSchema, true)
|
---|
| 472 | return this
|
---|
| 473 | }
|
---|
| 474 |
|
---|
| 475 | // Add schema that will be used to validate other schemas
|
---|
| 476 | // options in META_IGNORE_OPTIONS are alway set to false
|
---|
| 477 | addMetaSchema(
|
---|
| 478 | schema: AnySchemaObject,
|
---|
| 479 | key?: string, // schema key
|
---|
| 480 | _validateSchema = this.opts.validateSchema // false to skip schema validation, can be used to override validateSchema option for meta-schema
|
---|
| 481 | ): Ajv {
|
---|
| 482 | this.addSchema(schema, key, true, _validateSchema)
|
---|
| 483 | return this
|
---|
| 484 | }
|
---|
| 485 |
|
---|
| 486 | // Validate schema against its meta-schema
|
---|
| 487 | validateSchema(schema: AnySchema, throwOrLogError?: boolean): boolean | Promise<unknown> {
|
---|
| 488 | if (typeof schema == "boolean") return true
|
---|
| 489 | let $schema: string | AnySchemaObject | undefined
|
---|
| 490 | $schema = schema.$schema
|
---|
| 491 | if ($schema !== undefined && typeof $schema != "string") {
|
---|
| 492 | throw new Error("$schema must be a string")
|
---|
| 493 | }
|
---|
| 494 | $schema = $schema || this.opts.defaultMeta || this.defaultMeta()
|
---|
| 495 | if (!$schema) {
|
---|
| 496 | this.logger.warn("meta-schema not available")
|
---|
| 497 | this.errors = null
|
---|
| 498 | return true
|
---|
| 499 | }
|
---|
| 500 | const valid = this.validate($schema, schema)
|
---|
| 501 | if (!valid && throwOrLogError) {
|
---|
| 502 | const message = "schema is invalid: " + this.errorsText()
|
---|
| 503 | if (this.opts.validateSchema === "log") this.logger.error(message)
|
---|
| 504 | else throw new Error(message)
|
---|
| 505 | }
|
---|
| 506 | return valid
|
---|
| 507 | }
|
---|
| 508 |
|
---|
| 509 | // Get compiled schema by `key` or `ref`.
|
---|
| 510 | // (`key` that was passed to `addSchema` or full schema reference - `schema.$id` or resolved id)
|
---|
| 511 | getSchema<T = unknown>(keyRef: string): AnyValidateFunction<T> | undefined {
|
---|
| 512 | let sch
|
---|
| 513 | while (typeof (sch = getSchEnv.call(this, keyRef)) == "string") keyRef = sch
|
---|
| 514 | if (sch === undefined) {
|
---|
| 515 | const {schemaId} = this.opts
|
---|
| 516 | const root = new SchemaEnv({schema: {}, schemaId})
|
---|
| 517 | sch = resolveSchema.call(this, root, keyRef)
|
---|
| 518 | if (!sch) return
|
---|
| 519 | this.refs[keyRef] = sch
|
---|
| 520 | }
|
---|
| 521 | return (sch.validate || this._compileSchemaEnv(sch)) as AnyValidateFunction<T> | undefined
|
---|
| 522 | }
|
---|
| 523 |
|
---|
| 524 | // Remove cached schema(s).
|
---|
| 525 | // If no parameter is passed all schemas but meta-schemas are removed.
|
---|
| 526 | // If RegExp is passed all schemas with key/id matching pattern but meta-schemas are removed.
|
---|
| 527 | // Even if schema is referenced by other schemas it still can be removed as other schemas have local references.
|
---|
| 528 | removeSchema(schemaKeyRef?: AnySchema | string | RegExp): Ajv {
|
---|
| 529 | if (schemaKeyRef instanceof RegExp) {
|
---|
| 530 | this._removeAllSchemas(this.schemas, schemaKeyRef)
|
---|
| 531 | this._removeAllSchemas(this.refs, schemaKeyRef)
|
---|
| 532 | return this
|
---|
| 533 | }
|
---|
| 534 | switch (typeof schemaKeyRef) {
|
---|
| 535 | case "undefined":
|
---|
| 536 | this._removeAllSchemas(this.schemas)
|
---|
| 537 | this._removeAllSchemas(this.refs)
|
---|
| 538 | this._cache.clear()
|
---|
| 539 | return this
|
---|
| 540 | case "string": {
|
---|
| 541 | const sch = getSchEnv.call(this, schemaKeyRef)
|
---|
| 542 | if (typeof sch == "object") this._cache.delete(sch.schema)
|
---|
| 543 | delete this.schemas[schemaKeyRef]
|
---|
| 544 | delete this.refs[schemaKeyRef]
|
---|
| 545 | return this
|
---|
| 546 | }
|
---|
| 547 | case "object": {
|
---|
| 548 | const cacheKey = schemaKeyRef
|
---|
| 549 | this._cache.delete(cacheKey)
|
---|
| 550 | let id = schemaKeyRef[this.opts.schemaId]
|
---|
| 551 | if (id) {
|
---|
| 552 | id = normalizeId(id)
|
---|
| 553 | delete this.schemas[id]
|
---|
| 554 | delete this.refs[id]
|
---|
| 555 | }
|
---|
| 556 | return this
|
---|
| 557 | }
|
---|
| 558 | default:
|
---|
| 559 | throw new Error("ajv.removeSchema: invalid parameter")
|
---|
| 560 | }
|
---|
| 561 | }
|
---|
| 562 |
|
---|
| 563 | // add "vocabulary" - a collection of keywords
|
---|
| 564 | addVocabulary(definitions: Vocabulary): Ajv {
|
---|
| 565 | for (const def of definitions) this.addKeyword(def)
|
---|
| 566 | return this
|
---|
| 567 | }
|
---|
| 568 |
|
---|
| 569 | addKeyword(
|
---|
| 570 | kwdOrDef: string | KeywordDefinition,
|
---|
| 571 | def?: KeywordDefinition // deprecated
|
---|
| 572 | ): Ajv {
|
---|
| 573 | let keyword: string | string[]
|
---|
| 574 | if (typeof kwdOrDef == "string") {
|
---|
| 575 | keyword = kwdOrDef
|
---|
| 576 | if (typeof def == "object") {
|
---|
| 577 | this.logger.warn("these parameters are deprecated, see docs for addKeyword")
|
---|
| 578 | def.keyword = keyword
|
---|
| 579 | }
|
---|
| 580 | } else if (typeof kwdOrDef == "object" && def === undefined) {
|
---|
| 581 | def = kwdOrDef
|
---|
| 582 | keyword = def.keyword
|
---|
| 583 | if (Array.isArray(keyword) && !keyword.length) {
|
---|
| 584 | throw new Error("addKeywords: keyword must be string or non-empty array")
|
---|
| 585 | }
|
---|
| 586 | } else {
|
---|
| 587 | throw new Error("invalid addKeywords parameters")
|
---|
| 588 | }
|
---|
| 589 |
|
---|
| 590 | checkKeyword.call(this, keyword, def)
|
---|
| 591 | if (!def) {
|
---|
| 592 | eachItem(keyword, (kwd) => addRule.call(this, kwd))
|
---|
| 593 | return this
|
---|
| 594 | }
|
---|
| 595 | keywordMetaschema.call(this, def)
|
---|
| 596 | const definition: AddedKeywordDefinition = {
|
---|
| 597 | ...def,
|
---|
| 598 | type: getJSONTypes(def.type),
|
---|
| 599 | schemaType: getJSONTypes(def.schemaType),
|
---|
| 600 | }
|
---|
| 601 | eachItem(
|
---|
| 602 | keyword,
|
---|
| 603 | definition.type.length === 0
|
---|
| 604 | ? (k) => addRule.call(this, k, definition)
|
---|
| 605 | : (k) => definition.type.forEach((t) => addRule.call(this, k, definition, t))
|
---|
| 606 | )
|
---|
| 607 | return this
|
---|
| 608 | }
|
---|
| 609 |
|
---|
| 610 | getKeyword(keyword: string): AddedKeywordDefinition | boolean {
|
---|
| 611 | const rule = this.RULES.all[keyword]
|
---|
| 612 | return typeof rule == "object" ? rule.definition : !!rule
|
---|
| 613 | }
|
---|
| 614 |
|
---|
| 615 | // Remove keyword
|
---|
| 616 | removeKeyword(keyword: string): Ajv {
|
---|
| 617 | // TODO return type should be Ajv
|
---|
| 618 | const {RULES} = this
|
---|
| 619 | delete RULES.keywords[keyword]
|
---|
| 620 | delete RULES.all[keyword]
|
---|
| 621 | for (const group of RULES.rules) {
|
---|
| 622 | const i = group.rules.findIndex((rule) => rule.keyword === keyword)
|
---|
| 623 | if (i >= 0) group.rules.splice(i, 1)
|
---|
| 624 | }
|
---|
| 625 | return this
|
---|
| 626 | }
|
---|
| 627 |
|
---|
| 628 | // Add format
|
---|
| 629 | addFormat(name: string, format: Format): Ajv {
|
---|
| 630 | if (typeof format == "string") format = new RegExp(format)
|
---|
| 631 | this.formats[name] = format
|
---|
| 632 | return this
|
---|
| 633 | }
|
---|
| 634 |
|
---|
| 635 | errorsText(
|
---|
| 636 | errors: ErrorObject[] | null | undefined = this.errors, // optional array of validation errors
|
---|
| 637 | {separator = ", ", dataVar = "data"}: ErrorsTextOptions = {} // optional options with properties `separator` and `dataVar`
|
---|
| 638 | ): string {
|
---|
| 639 | if (!errors || errors.length === 0) return "No errors"
|
---|
| 640 | return errors
|
---|
| 641 | .map((e) => `${dataVar}${e.instancePath} ${e.message}`)
|
---|
| 642 | .reduce((text, msg) => text + separator + msg)
|
---|
| 643 | }
|
---|
| 644 |
|
---|
| 645 | $dataMetaSchema(metaSchema: AnySchemaObject, keywordsJsonPointers: string[]): AnySchemaObject {
|
---|
| 646 | const rules = this.RULES.all
|
---|
| 647 | metaSchema = JSON.parse(JSON.stringify(metaSchema))
|
---|
| 648 | for (const jsonPointer of keywordsJsonPointers) {
|
---|
| 649 | const segments = jsonPointer.split("/").slice(1) // first segment is an empty string
|
---|
| 650 | let keywords = metaSchema
|
---|
| 651 | for (const seg of segments) keywords = keywords[seg] as AnySchemaObject
|
---|
| 652 |
|
---|
| 653 | for (const key in rules) {
|
---|
| 654 | const rule = rules[key]
|
---|
| 655 | if (typeof rule != "object") continue
|
---|
| 656 | const {$data} = rule.definition
|
---|
| 657 | const schema = keywords[key] as AnySchemaObject | undefined
|
---|
| 658 | if ($data && schema) keywords[key] = schemaOrData(schema)
|
---|
| 659 | }
|
---|
| 660 | }
|
---|
| 661 |
|
---|
| 662 | return metaSchema
|
---|
| 663 | }
|
---|
| 664 |
|
---|
| 665 | private _removeAllSchemas(schemas: {[Ref in string]?: SchemaEnv | string}, regex?: RegExp): void {
|
---|
| 666 | for (const keyRef in schemas) {
|
---|
| 667 | const sch = schemas[keyRef]
|
---|
| 668 | if (!regex || regex.test(keyRef)) {
|
---|
| 669 | if (typeof sch == "string") {
|
---|
| 670 | delete schemas[keyRef]
|
---|
| 671 | } else if (sch && !sch.meta) {
|
---|
| 672 | this._cache.delete(sch.schema)
|
---|
| 673 | delete schemas[keyRef]
|
---|
| 674 | }
|
---|
| 675 | }
|
---|
| 676 | }
|
---|
| 677 | }
|
---|
| 678 |
|
---|
| 679 | _addSchema(
|
---|
| 680 | schema: AnySchema,
|
---|
| 681 | meta?: boolean,
|
---|
| 682 | baseId?: string,
|
---|
| 683 | validateSchema = this.opts.validateSchema,
|
---|
| 684 | addSchema = this.opts.addUsedSchema
|
---|
| 685 | ): SchemaEnv {
|
---|
| 686 | let id: string | undefined
|
---|
| 687 | const {schemaId} = this.opts
|
---|
| 688 | if (typeof schema == "object") {
|
---|
| 689 | id = schema[schemaId]
|
---|
| 690 | } else {
|
---|
| 691 | if (this.opts.jtd) throw new Error("schema must be object")
|
---|
| 692 | else if (typeof schema != "boolean") throw new Error("schema must be object or boolean")
|
---|
| 693 | }
|
---|
| 694 | let sch = this._cache.get(schema)
|
---|
| 695 | if (sch !== undefined) return sch
|
---|
| 696 |
|
---|
| 697 | const localRefs = getSchemaRefs.call(this, schema)
|
---|
| 698 | baseId = normalizeId(id || baseId)
|
---|
| 699 | sch = new SchemaEnv({schema, schemaId, meta, baseId, localRefs})
|
---|
| 700 | this._cache.set(sch.schema, sch)
|
---|
| 701 | if (addSchema && !baseId.startsWith("#")) {
|
---|
| 702 | // TODO atm it is allowed to overwrite schemas without id (instead of not adding them)
|
---|
| 703 | if (baseId) this._checkUnique(baseId)
|
---|
| 704 | this.refs[baseId] = sch
|
---|
| 705 | }
|
---|
| 706 | if (validateSchema) this.validateSchema(schema, true)
|
---|
| 707 | return sch
|
---|
| 708 | }
|
---|
| 709 |
|
---|
| 710 | private _checkUnique(id: string): void {
|
---|
| 711 | if (this.schemas[id] || this.refs[id]) {
|
---|
| 712 | throw new Error(`schema with key or id "${id}" already exists`)
|
---|
| 713 | }
|
---|
| 714 | }
|
---|
| 715 |
|
---|
| 716 | private _compileSchemaEnv(sch: SchemaEnv): AnyValidateFunction {
|
---|
| 717 | if (sch.meta) this._compileMetaSchema(sch)
|
---|
| 718 | else compileSchema.call(this, sch)
|
---|
| 719 |
|
---|
| 720 | /* istanbul ignore if */
|
---|
| 721 | if (!sch.validate) throw new Error("ajv implementation error")
|
---|
| 722 | return sch.validate
|
---|
| 723 | }
|
---|
| 724 |
|
---|
| 725 | private _compileMetaSchema(sch: SchemaEnv): void {
|
---|
| 726 | const currentOpts = this.opts
|
---|
| 727 | this.opts = this._metaOpts
|
---|
| 728 | try {
|
---|
| 729 | compileSchema.call(this, sch)
|
---|
| 730 | } finally {
|
---|
| 731 | this.opts = currentOpts
|
---|
| 732 | }
|
---|
| 733 | }
|
---|
| 734 | }
|
---|
| 735 |
|
---|
| 736 | export interface ErrorsTextOptions {
|
---|
| 737 | separator?: string
|
---|
| 738 | dataVar?: string
|
---|
| 739 | }
|
---|
| 740 |
|
---|
| 741 | function checkOptions(
|
---|
| 742 | this: Ajv,
|
---|
| 743 | checkOpts: OptionsInfo<RemovedOptions | DeprecatedOptions>,
|
---|
| 744 | options: Options & RemovedOptions,
|
---|
| 745 | msg: string,
|
---|
| 746 | log: "warn" | "error" = "error"
|
---|
| 747 | ): void {
|
---|
| 748 | for (const key in checkOpts) {
|
---|
| 749 | const opt = key as keyof typeof checkOpts
|
---|
| 750 | if (opt in options) this.logger[log](`${msg}: option ${key}. ${checkOpts[opt]}`)
|
---|
| 751 | }
|
---|
| 752 | }
|
---|
| 753 |
|
---|
| 754 | function getSchEnv(this: Ajv, keyRef: string): SchemaEnv | string | undefined {
|
---|
| 755 | keyRef = normalizeId(keyRef) // TODO tests fail without this line
|
---|
| 756 | return this.schemas[keyRef] || this.refs[keyRef]
|
---|
| 757 | }
|
---|
| 758 |
|
---|
| 759 | function addInitialSchemas(this: Ajv): void {
|
---|
| 760 | const optsSchemas = this.opts.schemas
|
---|
| 761 | if (!optsSchemas) return
|
---|
| 762 | if (Array.isArray(optsSchemas)) this.addSchema(optsSchemas)
|
---|
| 763 | else for (const key in optsSchemas) this.addSchema(optsSchemas[key] as AnySchema, key)
|
---|
| 764 | }
|
---|
| 765 |
|
---|
| 766 | function addInitialFormats(this: Ajv): void {
|
---|
| 767 | for (const name in this.opts.formats) {
|
---|
| 768 | const format = this.opts.formats[name]
|
---|
| 769 | if (format) this.addFormat(name, format)
|
---|
| 770 | }
|
---|
| 771 | }
|
---|
| 772 |
|
---|
| 773 | function addInitialKeywords(
|
---|
| 774 | this: Ajv,
|
---|
| 775 | defs: Vocabulary | {[K in string]?: KeywordDefinition}
|
---|
| 776 | ): void {
|
---|
| 777 | if (Array.isArray(defs)) {
|
---|
| 778 | this.addVocabulary(defs)
|
---|
| 779 | return
|
---|
| 780 | }
|
---|
| 781 | this.logger.warn("keywords option as map is deprecated, pass array")
|
---|
| 782 | for (const keyword in defs) {
|
---|
| 783 | const def = defs[keyword] as KeywordDefinition
|
---|
| 784 | if (!def.keyword) def.keyword = keyword
|
---|
| 785 | this.addKeyword(def)
|
---|
| 786 | }
|
---|
| 787 | }
|
---|
| 788 |
|
---|
| 789 | function getMetaSchemaOptions(this: Ajv): InstanceOptions {
|
---|
| 790 | const metaOpts = {...this.opts}
|
---|
| 791 | for (const opt of META_IGNORE_OPTIONS) delete metaOpts[opt]
|
---|
| 792 | return metaOpts
|
---|
| 793 | }
|
---|
| 794 |
|
---|
| 795 | const noLogs = {log() {}, warn() {}, error() {}}
|
---|
| 796 |
|
---|
| 797 | function getLogger(logger?: Partial<Logger> | false): Logger {
|
---|
| 798 | if (logger === false) return noLogs
|
---|
| 799 | if (logger === undefined) return console
|
---|
| 800 | if (logger.log && logger.warn && logger.error) return logger as Logger
|
---|
| 801 | throw new Error("logger must implement log, warn and error methods")
|
---|
| 802 | }
|
---|
| 803 |
|
---|
| 804 | const KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i
|
---|
| 805 |
|
---|
| 806 | function checkKeyword(this: Ajv, keyword: string | string[], def?: KeywordDefinition): void {
|
---|
| 807 | const {RULES} = this
|
---|
| 808 | eachItem(keyword, (kwd) => {
|
---|
| 809 | if (RULES.keywords[kwd]) throw new Error(`Keyword ${kwd} is already defined`)
|
---|
| 810 | if (!KEYWORD_NAME.test(kwd)) throw new Error(`Keyword ${kwd} has invalid name`)
|
---|
| 811 | })
|
---|
| 812 | if (!def) return
|
---|
| 813 | if (def.$data && !("code" in def || "validate" in def)) {
|
---|
| 814 | throw new Error('$data keyword must have "code" or "validate" function')
|
---|
| 815 | }
|
---|
| 816 | }
|
---|
| 817 |
|
---|
| 818 | function addRule(
|
---|
| 819 | this: Ajv,
|
---|
| 820 | keyword: string,
|
---|
| 821 | definition?: AddedKeywordDefinition,
|
---|
| 822 | dataType?: JSONType
|
---|
| 823 | ): void {
|
---|
| 824 | const post = definition?.post
|
---|
| 825 | if (dataType && post) throw new Error('keyword with "post" flag cannot have "type"')
|
---|
| 826 | const {RULES} = this
|
---|
| 827 | let ruleGroup = post ? RULES.post : RULES.rules.find(({type: t}) => t === dataType)
|
---|
| 828 | if (!ruleGroup) {
|
---|
| 829 | ruleGroup = {type: dataType, rules: []}
|
---|
| 830 | RULES.rules.push(ruleGroup)
|
---|
| 831 | }
|
---|
| 832 | RULES.keywords[keyword] = true
|
---|
| 833 | if (!definition) return
|
---|
| 834 |
|
---|
| 835 | const rule: Rule = {
|
---|
| 836 | keyword,
|
---|
| 837 | definition: {
|
---|
| 838 | ...definition,
|
---|
| 839 | type: getJSONTypes(definition.type),
|
---|
| 840 | schemaType: getJSONTypes(definition.schemaType),
|
---|
| 841 | },
|
---|
| 842 | }
|
---|
| 843 | if (definition.before) addBeforeRule.call(this, ruleGroup, rule, definition.before)
|
---|
| 844 | else ruleGroup.rules.push(rule)
|
---|
| 845 | RULES.all[keyword] = rule
|
---|
| 846 | definition.implements?.forEach((kwd) => this.addKeyword(kwd))
|
---|
| 847 | }
|
---|
| 848 |
|
---|
| 849 | function addBeforeRule(this: Ajv, ruleGroup: RuleGroup, rule: Rule, before: string): void {
|
---|
| 850 | const i = ruleGroup.rules.findIndex((_rule) => _rule.keyword === before)
|
---|
| 851 | if (i >= 0) {
|
---|
| 852 | ruleGroup.rules.splice(i, 0, rule)
|
---|
| 853 | } else {
|
---|
| 854 | ruleGroup.rules.push(rule)
|
---|
| 855 | this.logger.warn(`rule ${before} is not defined`)
|
---|
| 856 | }
|
---|
| 857 | }
|
---|
| 858 |
|
---|
| 859 | function keywordMetaschema(this: Ajv, def: KeywordDefinition): void {
|
---|
| 860 | let {metaSchema} = def
|
---|
| 861 | if (metaSchema === undefined) return
|
---|
| 862 | if (def.$data && this.opts.$data) metaSchema = schemaOrData(metaSchema)
|
---|
| 863 | def.validateSchema = this.compile(metaSchema, true)
|
---|
| 864 | }
|
---|
| 865 |
|
---|
| 866 | const $dataRef = {
|
---|
| 867 | $ref: "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#",
|
---|
| 868 | }
|
---|
| 869 |
|
---|
| 870 | function schemaOrData(schema: AnySchema): AnySchemaObject {
|
---|
| 871 | return {anyOf: [schema, $dataRef]}
|
---|
| 872 | }
|
---|