110 lines
4.2 KiB
TypeScript
110 lines
4.2 KiB
TypeScript
/* eslint max-statements-per-line: ["error", { "max": 2 }] */
|
|
import { eachSchema } from "../eachSchema";
|
|
import { Draft } from "../draft";
|
|
import joinScope from "./joinScope";
|
|
import getRef from "./getRef";
|
|
import { JsonSchema } from "../types";
|
|
import { Context } from "./types";
|
|
import { get } from "@sagold/json-pointer";
|
|
|
|
const COMPILED = "__compiled";
|
|
const COMPILED_REF = "__ref";
|
|
const GET_REF = "getRef";
|
|
const GET_ROOT = "getRoot";
|
|
const suffixes = /(#|\/)+$/g;
|
|
|
|
/**
|
|
* compiles the input root schema for `$ref` resolution and returns it again
|
|
* @attention this modifies input schema but maintains data-structure and thus returns
|
|
* the same object with JSON.stringify
|
|
*
|
|
* for a compiled json-schema you can call getRef on any contained schema (location of type).
|
|
* this resolves a $ref target to a valid schema (for a valid $ref)
|
|
*
|
|
* @param draft
|
|
* @param schemaToCompile - json-schema to compile
|
|
* @param [rootSchema] - compiled root json-schema to use for definitions resolution
|
|
* @param [force] = false - force compile json-schema
|
|
* @return compiled input json-schema
|
|
*/
|
|
export default function compileSchema(
|
|
draft: Draft,
|
|
schemaToCompile: JsonSchema,
|
|
rootSchema: JsonSchema = schemaToCompile,
|
|
force = false
|
|
): JsonSchema {
|
|
if (!schemaToCompile || schemaToCompile[COMPILED] !== undefined) {
|
|
return schemaToCompile;
|
|
}
|
|
|
|
const context: Context = { ids: {}, remotes: draft.remotes };
|
|
const rootSchemaAsString = JSON.stringify(schemaToCompile);
|
|
const compiledSchema: JsonSchema = JSON.parse(rootSchemaAsString);
|
|
|
|
// flag this schema as compiled
|
|
Object.defineProperty(compiledSchema, COMPILED, { enumerable: false, value: true });
|
|
// add getRef-helper to this object
|
|
Object.defineProperty(compiledSchema, GET_REF, {
|
|
enumerable: false,
|
|
value: getRef.bind(null, context, compiledSchema)
|
|
});
|
|
|
|
// bail early, when no $refs are defined
|
|
if (force === false && rootSchemaAsString.includes("$ref") === false) {
|
|
return compiledSchema;
|
|
}
|
|
|
|
// compile this schema under rootSchema, making definitions available to $ref-resolution
|
|
if (schemaToCompile !== rootSchema) {
|
|
Object.defineProperty(compiledSchema, "definitions", {
|
|
enumerable: false,
|
|
value: Object.assign(
|
|
{},
|
|
rootSchema.definitions,
|
|
rootSchema.$defs,
|
|
schemaToCompile.definitions,
|
|
schemaToCompile.$defs
|
|
)
|
|
});
|
|
}
|
|
|
|
const scopes: Record<string, string> = {};
|
|
const getRoot = () => compiledSchema;
|
|
|
|
eachSchema(compiledSchema, (schema, pointer) => {
|
|
if (schema.id) {
|
|
// if this is a schema being merged on root object, we cannot override
|
|
// parents locations, but must reuse it
|
|
if (schema.id.startsWith("http") && /(allOf|anyOf|oneOf)\/\d+$/.test(pointer)) {
|
|
const parentPointer = pointer.replace(/\/(allOf|anyOf|oneOf)\/\d+$/, "");
|
|
const parentSchema = get(compiledSchema, parentPointer);
|
|
schema.id = parentSchema.id ?? schema.id;
|
|
}
|
|
context.ids[schema.id.replace(suffixes, "")] = pointer;
|
|
}
|
|
|
|
// build up scopes and add them to $ref-resolution map
|
|
pointer = `#${pointer}`.replace(/##+/, "#");
|
|
const previousPointer = pointer.replace(/\/[^/]+$/, "");
|
|
const parentPointer = pointer.replace(/\/[^/]+\/[^/]+$/, "");
|
|
const previousScope = scopes[previousPointer] || scopes[parentPointer];
|
|
const scope = joinScope(previousScope, schema.id);
|
|
scopes[pointer] = scope;
|
|
if (context.ids[scope] == null) {
|
|
context.ids[scope] = pointer;
|
|
}
|
|
|
|
if (schema.$ref && !schema[COMPILED_REF]) {
|
|
Object.defineProperty(schema, COMPILED_REF, {
|
|
enumerable: false,
|
|
value: joinScope(scope, schema.$ref)
|
|
});
|
|
// @todo currently not used:
|
|
Object.defineProperty(schema, GET_ROOT, { enumerable: false, value: getRoot });
|
|
// console.log("compiled ref", scope, schema.$ref, "=>", joinScope(scope, schema.$ref));
|
|
}
|
|
});
|
|
|
|
// console.log(JSON.stringify(context.ids, null, 2));
|
|
return compiledSchema;
|
|
}
|