Spaces:
Running
Running
File size: 4,649 Bytes
1df763a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
import type {AnySchema, AnySchemaObject, UriResolver} from "../types"
import type Ajv from "../ajv"
import type {URIComponents} from "uri-js"
import {eachItem} from "./util"
import * as equal from "fast-deep-equal"
import * as traverse from "json-schema-traverse"
// the hash of local references inside the schema (created by getSchemaRefs), used for inline resolution
export type LocalRefs = {[Ref in string]?: AnySchemaObject}
// TODO refactor to use keyword definitions
const SIMPLE_INLINED = new Set([
"type",
"format",
"pattern",
"maxLength",
"minLength",
"maxProperties",
"minProperties",
"maxItems",
"minItems",
"maximum",
"minimum",
"uniqueItems",
"multipleOf",
"required",
"enum",
"const",
])
export function inlineRef(schema: AnySchema, limit: boolean | number = true): boolean {
if (typeof schema == "boolean") return true
if (limit === true) return !hasRef(schema)
if (!limit) return false
return countKeys(schema) <= limit
}
const REF_KEYWORDS = new Set([
"$ref",
"$recursiveRef",
"$recursiveAnchor",
"$dynamicRef",
"$dynamicAnchor",
])
function hasRef(schema: AnySchemaObject): boolean {
for (const key in schema) {
if (REF_KEYWORDS.has(key)) return true
const sch = schema[key]
if (Array.isArray(sch) && sch.some(hasRef)) return true
if (typeof sch == "object" && hasRef(sch)) return true
}
return false
}
function countKeys(schema: AnySchemaObject): number {
let count = 0
for (const key in schema) {
if (key === "$ref") return Infinity
count++
if (SIMPLE_INLINED.has(key)) continue
if (typeof schema[key] == "object") {
eachItem(schema[key], (sch) => (count += countKeys(sch)))
}
if (count === Infinity) return Infinity
}
return count
}
export function getFullPath(resolver: UriResolver, id = "", normalize?: boolean): string {
if (normalize !== false) id = normalizeId(id)
const p = resolver.parse(id)
return _getFullPath(resolver, p)
}
export function _getFullPath(resolver: UriResolver, p: URIComponents): string {
const serialized = resolver.serialize(p)
return serialized.split("#")[0] + "#"
}
const TRAILING_SLASH_HASH = /#\/?$/
export function normalizeId(id: string | undefined): string {
return id ? id.replace(TRAILING_SLASH_HASH, "") : ""
}
export function resolveUrl(resolver: UriResolver, baseId: string, id: string): string {
id = normalizeId(id)
return resolver.resolve(baseId, id)
}
const ANCHOR = /^[a-z_][-a-z0-9._]*$/i
export function getSchemaRefs(this: Ajv, schema: AnySchema, baseId: string): LocalRefs {
if (typeof schema == "boolean") return {}
const {schemaId, uriResolver} = this.opts
const schId = normalizeId(schema[schemaId] || baseId)
const baseIds: {[JsonPtr in string]?: string} = {"": schId}
const pathPrefix = getFullPath(uriResolver, 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[schemaId] == "string") baseId = addRef.call(this, sch[schemaId])
addAnchor.call(this, sch.$anchor)
addAnchor.call(this, sch.$dynamicAnchor)
baseIds[jsonPtr] = baseId
function addRef(this: Ajv, ref: string): string {
// eslint-disable-next-line @typescript-eslint/unbound-method
const _resolve = this.opts.uriResolver.resolve
ref = normalizeId(baseId ? _resolve(baseId, ref) : ref)
if (schemaRefs.has(ref)) throw ambiguos(ref)
schemaRefs.add(ref)
let schOrRef = this.refs[ref]
if (typeof schOrRef == "string") schOrRef = this.refs[schOrRef]
if (typeof schOrRef == "object") {
checkAmbiguosRef(sch, schOrRef.schema, ref)
} else if (ref !== normalizeId(fullPath)) {
if (ref[0] === "#") {
checkAmbiguosRef(sch, localRefs[ref], ref)
localRefs[ref] = sch
} else {
this.refs[ref] = fullPath
}
}
return ref
}
function addAnchor(this: Ajv, anchor: unknown): void {
if (typeof anchor == "string") {
if (!ANCHOR.test(anchor)) throw new Error(`invalid anchor "${anchor}"`)
addRef.call(this, `#${anchor}`)
}
}
})
return localRefs
function checkAmbiguosRef(sch1: AnySchema, sch2: AnySchema | undefined, ref: string): void {
if (sch2 !== undefined && !equal(sch1, sch2)) throw ambiguos(ref)
}
function ambiguos(ref: string): Error {
return new Error(`reference "${ref}" resolves to more than one schema`)
}
}
|