@apidevtools/json-schema-ref-parser
Version:
Parse, Resolve, and Dereference JSON Schema $ref pointers
115 lines (95 loc) • 3.36 kB
text/typescript
import * as url from "./url.js";
import type { ParserOptions } from "../options.js";
import type { JSONSchema } from "../index.js";
import type $Refs from "../refs.js";
export function getSchemaBasePath(basePath: string, value: unknown, legacyId = false) {
const schemaId = getSchemaId(value, legacyId);
return schemaId ? url.resolve(basePath, schemaId) : basePath;
}
export function usesDynamicIdScope(value: unknown) {
if (!value || typeof value !== "object" || ArrayBuffer.isView(value)) {
return false;
}
// A schema can omit `$schema`, but an explicit identifier still establishes
// a base URI for references inside that resource.
if (getSchemaId(value)) {
return true;
}
const schema = (value as { $schema?: unknown }).$schema;
if (
typeof schema === "string" &&
(schema.includes("draft-04/") ||
schema.includes("draft-06/") ||
schema.includes("draft-07/") ||
schema.includes("draft/2019-09/") ||
schema.includes("draft/2020-12/") ||
schema.includes("oas/3.1/"))
) {
return true;
}
const openapi = (value as { openapi?: unknown }).openapi;
return typeof openapi === "string" && /^3\.1(?:\.|$)/.test(openapi);
}
export function registerSchemaResources<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>(
$refs: $Refs<S, O>,
basePath: string,
value: unknown,
pathType?: string | unknown,
dynamicIdScope = false,
) {
if (!dynamicIdScope) {
return;
}
const seen = new Set<object>();
const visit = (node: unknown, scopeBase: string, inheritedLegacyId: boolean) => {
if (!node || typeof node !== "object" || ArrayBuffer.isView(node) || seen.has(node)) {
return;
}
seen.add(node);
const legacyId = getSchemaIdMode(node, inheritedLegacyId);
const nextScopeBase = getSchemaBasePath(scopeBase, node, legacyId);
if (nextScopeBase !== scopeBase) {
$refs._addAlias(nextScopeBase, node as S, pathType, dynamicIdScope, legacyId);
}
for (const key of Object.keys(node)) {
visit((node as Record<string, unknown>)[key], nextScopeBase, legacyId);
}
};
visit(value, basePath, false);
}
export function getSchemaId(value: unknown, inheritedLegacyId = false): string | undefined {
if (!value || typeof value !== "object") {
return undefined;
}
const legacyId = getSchemaIdMode(value, inheritedLegacyId);
const schemaId = legacyId ? (value as { id?: unknown }).id : (value as { $id?: unknown }).$id;
if (typeof schemaId === "string" && schemaId.length > 0) {
return schemaId;
}
return undefined;
}
export function getSchemaIdMode(value: unknown, inheritedLegacyId = false): boolean {
if (!value || typeof value !== "object") {
return inheritedLegacyId;
}
const schema = (value as { $schema?: unknown }).$schema;
if (typeof schema === "string") {
if (schema.includes("draft-04/")) {
return true;
}
if (
schema.includes("draft-06/") ||
schema.includes("draft-07/") ||
schema.includes("draft/2019-09/") ||
schema.includes("draft/2020-12/") ||
schema.includes("oas/3.1/")
) {
return false;
}
}
const openapi = (value as { openapi?: unknown }).openapi;
if (typeof openapi === "string" && /^3\.1(?:\.|$)/.test(openapi)) {
return false;
}
return inheritedLegacyId;
}