Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
epoberezkin committed May 20, 2021
2 parents dc28feb + f54f1b7 commit 6e7e5a1
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 28 deletions.
22 changes: 15 additions & 7 deletions lib/compile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export interface SchemaObjCxt extends SchemaCxt {
}
interface SchemaEnvArgs {
readonly schema: AnySchema
readonly schemaId?: "$id" | "id"
readonly root?: SchemaEnv
readonly baseId?: string
readonly schemaPath?: string
Expand All @@ -72,6 +73,7 @@ interface SchemaEnvArgs {

export class SchemaEnv implements SchemaEnvArgs {
readonly schema: AnySchema
readonly schemaId?: "$id" | "id"
readonly root: SchemaEnv
baseId: string // TODO possibly, it should be readonly
schemaPath?: string
Expand All @@ -91,8 +93,9 @@ export class SchemaEnv implements SchemaEnvArgs {
let schema: AnySchemaObject | undefined
if (typeof env.schema == "object") schema = env.schema
this.schema = env.schema
this.schemaId = env.schemaId
this.root = env.root || this
this.baseId = env.baseId ?? normalizeId(schema?.$id)
this.baseId = env.baseId ?? normalizeId(schema?.[env.schemaId || "$id"])
this.schemaPath = env.schemaPath
this.localRefs = env.localRefs
this.meta = env.meta
Expand Down Expand Up @@ -212,7 +215,8 @@ export function resolveRef(
let _sch = resolve.call(this, root, ref)
if (_sch === undefined) {
const schema = root.localRefs?.[ref] // TODO maybe localRefs should hold SchemaEnv
if (schema) _sch = new SchemaEnv({schema, root, baseId})
const {schemaId} = this.opts
if (schema) _sch = new SchemaEnv({schema, schemaId, root, baseId})
}

if (_sch === undefined) return
Expand Down Expand Up @@ -273,8 +277,10 @@ export function resolveSchema(
if (!schOrRef.validate) compileSchema.call(this, schOrRef)
if (id === normalizeId(ref)) {
const {schema} = schOrRef
if (schema.$id) baseId = resolveUrl(baseId, schema.$id)
return new SchemaEnv({schema, root, baseId})
const {schemaId} = this.opts
const schId = schema[schemaId]
if (schId) baseId = resolveUrl(baseId, schId)
return new SchemaEnv({schema, schemaId, root, baseId})
}
return getJsonPointer.call(this, p, schOrRef)
}
Expand All @@ -298,8 +304,9 @@ function getJsonPointer(
schema = schema[unescapeFragment(part)]
if (schema === undefined) return
// TODO PREVENT_SCOPE_CHANGE could be defined in keyword def?
if (!PREVENT_SCOPE_CHANGE.has(part) && typeof schema == "object" && schema.$id) {
baseId = resolveUrl(baseId, schema.$id)
const schId = typeof schema == "object" && schema[this.opts.schemaId]
if (!PREVENT_SCOPE_CHANGE.has(part) && schId) {
baseId = resolveUrl(baseId, schId)
}
}
let env: SchemaEnv | undefined
Expand All @@ -309,7 +316,8 @@ function getJsonPointer(
}
// even though resolution failed we need to return SchemaEnv to throw exception
// so that compileAsync loads missing schema.
env = env || new SchemaEnv({schema, root, baseId})
const {schemaId} = this.opts
env = env || new SchemaEnv({schema, schemaId, root, baseId})
if (env.schema !== env.root.schema) return env
return undefined
}
9 changes: 5 additions & 4 deletions lib/compile/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,18 @@ const ANCHOR = /^[a-z_][-a-z0-9._]*$/i

export function getSchemaRefs(this: Ajv, schema: AnySchema): LocalRefs {
if (typeof schema == "boolean") return {}
const schemaId = normalizeId(schema.$id)
const baseIds: {[JsonPtr in string]?: string} = {"": schemaId}
const pathPrefix = getFullPath(schemaId, false)
const {schemaId} = this.opts
const schId = normalizeId(schema[schemaId])
const baseIds: {[JsonPtr in string]?: string} = {"": schId}
const pathPrefix = getFullPath(schId, false)
const localRefs: LocalRefs = {}
const schemaRefs: Set<string> = new Set()

traverse(schema, {allKeys: true}, (sch, jsonPtr, _, parentJsonPtr) => {
if (parentJsonPtr === undefined) return
const fullPath = pathPrefix + jsonPtr
let baseId = baseIds[parentJsonPtr]
if (typeof sch.$id == "string") baseId = addRef.call(this, sch.$id)
if (typeof sch[schemaId] == "string") baseId = addRef.call(this, sch[schemaId])
addAnchor.call(this, sch.$anchor)
addAnchor.call(this, sch.$dynamicAnchor)
baseIds[jsonPtr] = baseId
Expand Down
8 changes: 4 additions & 4 deletions lib/compile/validate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,8 @@ function resetEvaluated(it: SchemaObjCxt): void {
}

function funcSourceUrl(schema: AnySchema, opts: InstanceOptions): Code {
return typeof schema == "object" && schema.$id && (opts.code.source || opts.code.process)
? _`/*# sourceURL=${schema.$id} */`
: nil
const schId = typeof schema == "object" && schema[opts.schemaId]
return schId && (opts.code.source || opts.code.process) ? _`/*# sourceURL=${schId} */` : nil
}

// schema compilation - this function is used recursively to generate code for sub-schemas
Expand Down Expand Up @@ -177,7 +176,8 @@ function checkNoDefault(it: SchemaObjCxt): void {
}

function updateContext(it: SchemaObjCxt): void {
if (it.schema.$id) it.baseId = resolveUrl(it.baseId, it.schema.$id)
const schId = it.schema[it.opts.schemaId]
if (schId) it.baseId = resolveUrl(it.baseId, schId)
}

function checkAsyncSchema(it: SchemaObjCxt): void {
Expand Down
36 changes: 24 additions & 12 deletions lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export interface CurrentOptions {
next?: boolean // NEW
unevaluated?: boolean // NEW
dynamicRef?: boolean // NEW
schemaId?: "id" | "$id"
jtd?: boolean // NEW
meta?: SchemaObject | boolean
defaultMeta?: string | AnySchemaObject
Expand Down Expand Up @@ -163,7 +164,6 @@ interface RemovedOptions {
missingRefs?: true | "ignore" | "fail"
processCode?: (code: string, schema?: SchemaEnv) => string
sourceCode?: boolean
schemaId?: string
strictDefaults?: boolean
strictKeywords?: boolean
uniqueItems?: boolean
Expand All @@ -186,7 +186,6 @@ const removedOptions: OptionsInfo<RemovedOptions> = {
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.',
Expand Down Expand Up @@ -214,6 +213,7 @@ type RequiredInstanceOptions = {
| "loopEnum"
| "meta"
| "messages"
| "schemaId"
| "addUsedSchema"
| "validateSchema"
| "validateFormats"
Expand Down Expand Up @@ -241,6 +241,7 @@ function requiredOptions(o: Options): RequiredInstanceOptions {
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,
Expand Down Expand Up @@ -299,13 +300,19 @@ export default class Ajv {
}

_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
Expand Down Expand Up @@ -450,8 +457,11 @@ export default class Ajv {
}
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)
Expand Down Expand Up @@ -499,7 +509,8 @@ export default class Ajv {
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
Expand Down Expand Up @@ -533,7 +544,7 @@ export default class Ajv {
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]
Expand Down Expand Up @@ -670,8 +681,9 @@ export default class Ajv {
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")
Expand All @@ -681,7 +693,7 @@ export default class Ajv {

const localRefs = getSchemaRefs.call(this, schema)
baseId = normalizeId(id || baseId)
sch = new SchemaEnv({schema, meta, baseId, localRefs})
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)
Expand Down
2 changes: 2 additions & 0 deletions lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import type {KeywordCxt} from "../compile/validate"
import type Ajv from "../core"

interface _SchemaObject {
id?: string
$id?: string
$schema?: string
[x: string]: any // TODO
}

export interface SchemaObject extends _SchemaObject {
id?: string
$id?: string
$schema?: string
$async?: false
Expand Down
3 changes: 2 additions & 1 deletion lib/vocabularies/dynamic/dynamicAnchor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export function dynamicAnchor(cxt: KeywordCxt, anchor: string): void {
function _getValidate(cxt: KeywordCxt): Code {
const {schemaEnv, schema, self} = cxt.it
const {root, baseId, localRefs, meta} = schemaEnv.root
const sch = new SchemaEnv({schema, root, baseId, localRefs, meta})
const {schemaId} = self.opts
const sch = new SchemaEnv({schema, schemaId, root, baseId, localRefs, meta})
compileSchema.call(self, sch)
return getValidate(cxt, sch)
}
Expand Down

0 comments on commit 6e7e5a1

Please sign in to comment.