Skip to content

Commit

Permalink
refactor: ref resolution resolve and getJsonPointer
Browse files Browse the repository at this point in the history
  • Loading branch information
epoberezkin committed Sep 10, 2020
1 parent 17ee1c0 commit 33ffbc0
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 72 deletions.
27 changes: 11 additions & 16 deletions lib/ajv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export default class Ajv {
_fragments: {[key: string]: StoredSchema} = {}
formats: {[name: string]: AddedFormat} = {}
_compilations: Set<StoredSchema> = new Set()
_compileQueue: StoredSchema[] = []
_loadingSchemas: {[ref: string]: Promise<SchemaObject>} = {}
_metaOpts: InstanceOptions
RULES: ValidationRules
Expand Down Expand Up @@ -176,7 +177,7 @@ export default class Ajv {
e: MissingRefError
): Promise<ValidateFunction> {
const ref = e.missingSchema
if (added(ref)) {
if (self._refs[ref]) {
throw new Error(`Schema ${ref} is loaded but ${e.missingRef} cannot be resolved`)
}
let schPromise = self._loadingSchemas[ref]
Expand All @@ -186,20 +187,14 @@ export default class Ajv {
}

const sch = await schPromise
if (!added(ref)) {
await loadMetaSchemaOf(sch)
if (!added(ref)) self.addSchema(sch, ref, undefined, meta)
}
if (!self._refs[ref]) await loadMetaSchemaOf(sch)
if (!self._refs[ref]) self.addSchema(sch, ref, undefined, meta)
return _compileAsync(schemaObj)

function removePromise(): void {
delete self._loadingSchemas[ref]
}
}

function added(ref: string): string | StoredSchema {
return self._refs[ref] || self._schemas[ref]
}
}

// Adds schema to the instance
Expand Down Expand Up @@ -404,7 +399,7 @@ export interface ErrorsTextOptions {
dataVar?: string
}

function checkDeprecatedOptions(this: Ajv, opts: Options) {
function checkDeprecatedOptions(this: Ajv, opts: Options): void {
if (opts.errorDataPath !== undefined) this.logger.error("NOT SUPPORTED: option errorDataPath")
if (opts.schemaId !== undefined) this.logger.error("NOT SUPPORTED: option schemaId")
if (opts.uniqueItems !== undefined) this.logger.error("NOT SUPPORTED: option uniqueItems")
Expand Down Expand Up @@ -432,7 +427,7 @@ function _removeAllSchemas(
this: Ajv,
schemas: {[ref: string]: StoredSchema | string},
regex?: RegExp
) {
): void {
for (const keyRef in schemas) {
const schemaObj = schemas[keyRef]
if (!regex || regex.test(keyRef)) {
Expand All @@ -452,7 +447,7 @@ function _addSchema(
skipValidation?: boolean,
meta?: boolean,
shouldAddSchema?: boolean
) {
): StoredSchema {
if (typeof schema != "object" && typeof schema != "boolean") {
throw new Error("schema must be object or boolean")
}
Expand Down Expand Up @@ -518,7 +513,7 @@ function addInitialFormats(this: Ajv): void {
}
}

function addInitialKeywords(this: Ajv, defs: Vocabulary | {[x: string]: KeywordDefinition}) {
function addInitialKeywords(this: Ajv, defs: Vocabulary | {[x: string]: KeywordDefinition}): void {
if (Array.isArray(defs)) {
this.addVocabulary(defs)
return
Expand All @@ -531,13 +526,13 @@ function addInitialKeywords(this: Ajv, defs: Vocabulary | {[x: string]: KeywordD
}
}

function checkUnique(this: Ajv, id: string) {
function checkUnique(this: Ajv, id: string): void {
if (this._schemas[id] || this._refs[id]) {
throw new Error('schema with key or id "' + id + '" already exists')
}
}

function getMetaSchemaOptions(this: Ajv) {
function getMetaSchemaOptions(this: Ajv): InstanceOptions {
const metaOpts = {...this._opts}
for (const opt of META_IGNORE_OPTIONS) delete metaOpts[opt]
return metaOpts
Expand All @@ -556,7 +551,7 @@ function getLogger(logger?: Logger | false): Logger {

const KEYWORD_NAME = /^[a-z_$][a-z0-9_$-]*$/i

function checkKeyword(this: Ajv, keyword: string | string[], def?: KeywordDefinition) {
function checkKeyword(this: Ajv, keyword: string | string[], def?: KeywordDefinition): void {
/* eslint no-shadow: 0 */
const RULES: ValidationRules = this.RULES
eachItem(keyword, (kwd) => {
Expand Down
89 changes: 33 additions & 56 deletions lib/compile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export function compileStoredSchema(this: Ajv, schObj: StoredSchema): ValidateFu
return (schObj.meta ? compileMetaSchema : compileSchema).call(this, schObj)
}

function schemaScopeCode(this: Ajv, sch: StoredSchema): Name {
return this._scope.value("validate", {ref: sch}, "schema", "validate")
}

function compileMetaSchema(this: Ajv, schObj: StoredSchema): ValidateFunction {
const currentOpts = this._opts
this._opts = this._metaOpts
Expand Down Expand Up @@ -127,7 +131,7 @@ function compileSchema(this: Ajv, schObj: StoredSchema): ValidateFunction {
}
}
const root = schObj.root
return localCompile(schObj as SchemaEnv)
return localCompile(schObj)

function localCompile(sch: StoredSchema): ValidateFunction {
// TODO refactor - remove compilations
Expand All @@ -138,7 +142,7 @@ function compileSchema(this: Ajv, schObj: StoredSchema): ValidateFunction {
if (sch.root === undefined || sch.root !== root) {
return compileSchema.call(self, sch)
}
const isRoot = isRootEnv(sch as SchemaEnv)
const isRoot = sch.schema === sch.root.schema
const $async = typeof schema == "object" && schema.$async === true
const rootId = getFullPath(sch.root.schema.$id)

Expand All @@ -151,8 +155,7 @@ function compileSchema(this: Ajv, schObj: StoredSchema): ValidateFunction {
})
}

const validateName = gen.scopeValue("validate", {ref: sch}, "schema", "validate", false)
sch.validateName = validateName
const validateName = schemaScopeCode.call(self, sch)

const schemaCxt = {
gen,
Expand All @@ -163,7 +166,7 @@ function compileSchema(this: Ajv, schObj: StoredSchema): ValidateFunction {
dataNames: [N.data],
dataPathArr: [nil],
dataLevel: 0,
topSchemaRef: _`${validateName}.schema`,
topSchemaRef: _`${validateName}.schema`, // TODO maybe use validateName instead of topSchemaRef?
async: $async,
validateName,
ValidationError: _ValidationError,
Expand Down Expand Up @@ -231,6 +234,7 @@ function compileSchema(this: Ajv, schObj: StoredSchema): ValidateFunction {
if (res) return res

const refCode = localRefCode(ref)

let schOrFunc = resolve.call(self, localCompile, root, ref)
if (schOrFunc === undefined) {
const localSchema = localRefs && localRefs[ref]
Expand Down Expand Up @@ -317,43 +321,25 @@ function vars(
// TODO returns SchemaObject (if the schema can be inlined) or validation function
function resolve(
this: Ajv,
localCompile: (env: SchemaEnv) => ValidateFunction, // reference to schema compilation function (localCompile)
localCompile: (env: StoredSchema) => ValidateFunction, // reference to schema compilation function (localCompile)
root: SchemaRoot, // information about the root schema for the current schema
ref: string // reference to resolve
): RefVal | undefined {
): RefVal | undefined | Name | StoredSchema {
let schOrRef = this._refs[ref]
if (typeof schOrRef == "string") {
if (this._refs[schOrRef]) schOrRef = this._refs[schOrRef]
else return resolve.call(this, localCompile, root, schOrRef)
}

schOrRef = schOrRef || this._schemas[ref]
if (schOrRef instanceof StoredSchema) {
return inlineRef(schOrRef.schema, this._opts.inlineRefs)
? schOrRef.schema
: schOrRef.validate || compileSchema.call(this, schOrRef)
}

const env = resolveSchema.call(this, root, ref)
let schema, baseId
if (env) {
schema = env.schema
root = env.root
baseId = env.baseId
while (typeof schOrRef == "string") {
ref = schOrRef
schOrRef = this._refs[ref]
}
if (schOrRef instanceof StoredSchema) return inlineOrCompile.call(this, schOrRef)
const env = resolveSchema.call(this, root, ref) // TODO why env.schema can be StoredSchema?
if (env) return inlineOrCompile.call(this, env.schema instanceof StoredSchema ? env.schema : env)
return undefined

if (schema instanceof StoredSchema) {
if (!schema.validate) {
schema.validate = localCompile.call(this, schema as SchemaEnv)
}
return schema.validate
function inlineOrCompile(this: Ajv, sch: StoredSchema): RefVal {
return inlineRef(sch.schema, this._opts.inlineRefs)
? sch.schema
: sch.validate || localCompile.call(this, sch)
}
if (schema !== undefined) {
return inlineRef(schema, this._opts.inlineRefs)
? schema
: localCompile.call(this, {schema, root, baseId})
}
return undefined
}

// Resolve schema, its root and baseId
Expand Down Expand Up @@ -420,33 +406,24 @@ function getJsonPointer(
parsedRef: URI.URIComponents,
{baseId, schema, root}: SchemaEnv
): SchemaEnv | undefined {
if (typeof schema == "boolean") return
parsedRef.fragment = parsedRef.fragment || ""
if (parsedRef.fragment.slice(0, 1) !== "/") return
const parts = parsedRef.fragment.split("/")

for (let part of parts) {
if (!part) continue
if (parsedRef.fragment?.[0] !== "/") return
for (const part of parsedRef.fragment.slice(1).split("/")) {
if (typeof schema == "boolean") return
part = unescapeFragment(part)
schema = schema[part]
schema = schema[unescapeFragment(part)]
if (schema === undefined) return
if (PREVENT_SCOPE_CHANGE[part]) continue
if (typeof schema == "object" && schema.$id) {
// TODO PREVENT_SCOPE_CHANGE could be defined in keyword def?
if (!PREVENT_SCOPE_CHANGE[part] && typeof schema == "object" && schema.$id) {
baseId = resolveUrl(baseId, schema.$id)
}
}
if (schema === undefined) return
let env: SchemaEnv | undefined
if (typeof schema != "boolean" && schema.$ref && !schemaHasRulesButRef(schema, this.RULES)) {
const $ref = resolveUrl(baseId, schema.$ref)
const _env = resolveSchema.call(this, root, $ref)
if (_env && !isRootEnv(_env)) return _env
env = resolveSchema.call(this, root, $ref)
}
const env = {schema, root, baseId}
if (!isRootEnv(env)) return env
// even though resolution failed we need to return SchemaEnv to throw exception
// so that compileAsync loads missing schema.
env = env || {schema, root, baseId}
if (env.schema !== env.root.schema) return env
return undefined
}

function isRootEnv({schema, root}: SchemaEnv): boolean {
return schema === root.schema
}

0 comments on commit 33ffbc0

Please sign in to comment.