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