export {DefinedError} from "./vocabularies/errors"
export {JSONType} from "./compile/rules"
export {JSONSchemaType} from "./types/json-schema"
-export {JTDSchemaType} from "./types/jtd-schema"
+export {JTDSchemaType, SomeJTDSchemaType, JTDDataType} from "./types/jtd-schema"
export {_, str, stringify, nil, Name, Code, CodeGen, CodeGenOptions} from "./compile/codegen"
import type {
ErrorObject,
Format,
AddedFormat,
+ RegExpEngine,
} from "./types"
import type {JSONSchemaType} from "./types/json-schema"
-import type {JTDSchemaType} from "./types/jtd-schema"
+import type {JTDSchemaType, SomeJTDSchemaType, JTDDataType} from "./types/jtd-schema"
import ValidationError from "./runtime/validation_error"
import MissingRefError from "./compile/ref_error"
import {getRules, ValidationRules, Rule, RuleGroup, JSONType} from "./compile/rules"
import * as $dataRefSchema from "./refs/data.json"
+const defaultRegExp: RegExpEngine = (str, flags) => new RegExp(str, flags)
+defaultRegExp.code = "new RegExp"
+
const META_IGNORE_OPTIONS: (keyof Options)[] = ["removeAdditional", "useDefaults", "coerceTypes"]
const EXT_SCOPE_NAMES = new Set([
"validate",
allErrors?: boolean
verbose?: boolean
discriminator?: boolean
+ unicodeRegExp?: boolean
+ timestamp?: "string" | "date" // JTD only
+ parseDate?: boolean // JTD only
+ allowDate?: boolean // JTD only
$comment?:
| true
| ((comment: string, schemaPath?: string, rootSchema?: AnySchemaObject) => unknown)
next?: boolean // NEW
unevaluated?: boolean // NEW
dynamicRef?: boolean // NEW
+ schemaId?: "id" | "$id"
jtd?: boolean // NEW
meta?: SchemaObject | boolean
defaultMeta?: string | AnySchemaObject
loopEnum?: number // NEW
ownProperties?: boolean
multipleOfPrecision?: number
+ int32range?: boolean // JTD only
messages?: boolean
code?: CodeOptions // NEW
}
formats?: Code // code to require (or construct) map of available formats - for standalone code
source?: boolean
process?: (code: string, schema?: SchemaEnv) => string
+ regExp?: RegExpEngine
}
interface InstanceCodeOptions extends CodeOptions {
+ regExp: RegExpEngine
optimize: number
}
missingRefs?: true | "ignore" | "fail"
processCode?: (code: string, schema?: SchemaEnv) => string
sourceCode?: boolean
- schemaId?: string
strictDefaults?: boolean
strictKeywords?: boolean
uniqueItems?: boolean
missingRefs: "Pass empty schema with $id that should be ignored to ajv.addSchema.",
processCode: "Use option `code: {process: (code, schemaEnv: object) => string}`",
sourceCode: "Use option `code: {source: true}`",
- schemaId: "JSON Schema draft-04 is not supported in Ajv v7/8.",
strictDefaults: "It is default now, see option `strict`.",
strictKeywords: "It is default now, see option `strict`.",
uniqueItems: '"uniqueItems" keyword is always validated.',
unknownFormats: "Disable strict mode or pass `true` to `ajv.addFormat` (or `formats` option).",
cache: "Map is used as cache, schema object as key.",
serialize: "Map is used as cache, schema object as key.",
- ajvErrors: "It is default now, see option `strict`.",
+ ajvErrors: "It is default now.",
}
const deprecatedOptions: OptionsInfo<DeprecatedOptions> = {
| "loopEnum"
| "meta"
| "messages"
+ | "schemaId"
| "addUsedSchema"
| "validateSchema"
- | "validateFormats"]: NonNullable<Options[K]>
+ | "validateFormats"
+ | "int32range"
+ | "unicodeRegExp"]: NonNullable<Options[K]>
} & {code: InstanceCodeOptions}
export type InstanceOptions = Options & RequiredInstanceOptions
const s = o.strict
const _optz = o.code?.optimize
const optimize = _optz === true || _optz === undefined ? 1 : _optz || 0
+ const regExp = o.code?.regExp ?? defaultRegExp
return {
strictSchema: o.strictSchema ?? s ?? true,
strictNumbers: o.strictNumbers ?? s ?? true,
strictTypes: o.strictTypes ?? s ?? "log",
strictTuples: o.strictTuples ?? s ?? "log",
strictRequired: o.strictRequired ?? s ?? false,
- code: o.code ? {...o.code, optimize} : {optimize},
+ code: o.code ? {...o.code, optimize, regExp} : {optimize, regExp},
loopRequired: o.loopRequired ?? MAX_EXPRESSION,
loopEnum: o.loopEnum ?? MAX_EXPRESSION,
meta: o.meta ?? true,
messages: o.messages ?? true,
inlineRefs: o.inlineRefs ?? true,
+ schemaId: o.schemaId ?? "$id",
addUsedSchema: o.addUsedSchema ?? true,
validateSchema: o.validateSchema ?? true,
validateFormats: o.validateFormats ?? true,
+ unicodeRegExp: o.unicodeRegExp ?? true,
+ int32range: o.int32range ?? true,
}
}
constructor(opts: Options = {}) {
opts = this.opts = {...opts, ...requiredOptions(opts)}
const {es5, lines} = this.opts.code
+
this.scope = new ValueScope({scope: {}, prefixes: EXT_SCOPE_NAMES, es5, lines})
this.logger = getLogger(opts.logger)
const formatOpt = opts.validateFormats
}
_addDefaultMetaSchema(): void {
- const {$data, meta} = this.opts
- if (meta && $data) this.addMetaSchema($dataRefSchema, $dataRefSchema.$id, false)
+ const {$data, meta, schemaId} = this.opts
+ let _dataRefSchema: SchemaObject = $dataRefSchema
+ if (schemaId === "id") {
+ _dataRefSchema = {...$dataRefSchema}
+ _dataRefSchema.id = _dataRefSchema.$id
+ delete _dataRefSchema.$id
+ }
+ if (meta && $data) this.addMetaSchema(_dataRefSchema, _dataRefSchema[schemaId], false)
}
defaultMeta(): string | AnySchemaObject | undefined {
- const {meta} = this.opts
- return (this.opts.defaultMeta = typeof meta == "object" ? meta.$id || meta : undefined)
+ const {meta, schemaId} = this.opts
+ return (this.opts.defaultMeta = typeof meta == "object" ? meta[schemaId] || meta : undefined)
}
// Validate data using schema
// Separated for type inference to work
// eslint-disable-next-line @typescript-eslint/unified-signatures
validate<T>(schema: JTDSchemaType<T>, data: unknown): data is T
+ // This overload is only intended for typescript inference, the first
+ // argument prevents manual type annotation from matching this overload
+ validate<N extends never, T extends SomeJTDSchemaType>(
+ schema: T,
+ data: unknown
+ ): data is JTDDataType<T>
validate<T>(schema: AsyncSchema, data: unknown | T): Promise<T>
validate<T>(schemaKeyRef: AnySchema | string, data: unknown): data is T | Promise<T>
validate<T>(
// Separated for type inference to work
// eslint-disable-next-line @typescript-eslint/unified-signatures
compile<T = unknown>(schema: JTDSchemaType<T>, _meta?: boolean): ValidateFunction<T>
+ // This overload is only intended for typescript inference, the first
+ // argument prevents manual type annotation from matching this overload
+ compile<N extends never, T extends SomeJTDSchemaType>(
+ schema: T,
+ _meta?: boolean
+ ): ValidateFunction<JTDDataType<T>>
compile<T = unknown>(schema: AsyncSchema, _meta?: boolean): AsyncValidateFunction<T>
compile<T = unknown>(schema: AnySchema, _meta?: boolean): AnyValidateFunction<T>
compile<T = unknown>(schema: AnySchema, _meta?: boolean): AnyValidateFunction<T> {
}
let id: string | undefined
if (typeof schema === "object") {
- id = schema.$id
- if (id !== undefined && typeof id != "string") throw new Error("schema $id must be string")
+ const {schemaId} = this.opts
+ id = schema[schemaId]
+ if (id !== undefined && typeof id != "string") {
+ throw new Error(`schema ${schemaId} must be string`)
+ }
}
key = normalizeId(key || id)
this._checkUnique(key)
let sch
while (typeof (sch = getSchEnv.call(this, keyRef)) == "string") keyRef = sch
if (sch === undefined) {
- const root = new SchemaEnv({schema: {}})
+ const {schemaId} = this.opts
+ const root = new SchemaEnv({schema: {}, schemaId})
sch = resolveSchema.call(this, root, keyRef)
if (!sch) return
this.refs[keyRef] = sch
case "object": {
const cacheKey = schemaKeyRef
this._cache.delete(cacheKey)
- let id = schemaKeyRef.$id
+ let id = schemaKeyRef[this.opts.schemaId]
if (id) {
id = normalizeId(id)
delete this.schemas[id]
addSchema = this.opts.addUsedSchema
): SchemaEnv {
let id: string | undefined
+ const {schemaId} = this.opts
if (typeof schema == "object") {
- id = schema.$id
+ id = schema[schemaId]
} else {
if (this.opts.jtd) throw new Error("schema must be object")
else if (typeof schema != "boolean") throw new Error("schema must be object or boolean")
let sch = this._cache.get(schema)
if (sch !== undefined) return sch
- const localRefs = getSchemaRefs.call(this, schema)
baseId = normalizeId(id || baseId)
- sch = new SchemaEnv({schema, meta, baseId, localRefs})
+ const localRefs = getSchemaRefs.call(this, schema, baseId)
+ sch = new SchemaEnv({schema, schemaId, meta, baseId, localRefs})
this._cache.set(sch.schema, sch)
if (addSchema && !baseId.startsWith("#")) {
// TODO atm it is allowed to overwrite schemas without id (instead of not adding them)