json-schema-library
Version:
Customizable and hackable json-validator and json-schema utilities for traversal, data generation and validation
85 lines (75 loc) • 3.26 kB
text/typescript
import copy from "fast-copy";
import { getRef } from "./keywords/$ref";
import { draft04 } from "./draft04";
import { draft06 } from "./draft06";
import { draft07 } from "./draft07";
import { draft2019 } from "./draft2019";
import { draft2020 } from "./draft2020";
import { pick } from "./utils/pick";
import { JsonSchema, Draft, BooleanSchema } from "./types";
import { TemplateOptions } from "./methods/getData";
import { SchemaNode, SchemaNodeMethods, addKeywords, isSchemaNode } from "./SchemaNode";
export type CompileOptions = {
drafts?: Draft[];
remote?: SchemaNode;
formatAssertion?: boolean | "meta-schema";
getDataDefaultOptions?: TemplateOptions;
};
const defaultDrafts: Draft[] = [draft04, draft06, draft07, draft2019, draft2020];
function getDraft(drafts: Draft[], $schema: string) {
return drafts.find((d) => new RegExp(d.$schemaRegEx).test($schema)) ?? drafts[drafts.length - 1];
}
/**
* With compileSchema we replace the schema and all sub-schemas with a schemaNode,
* wrapping each schema with utilities and as much preevaluation is possible. Each
* node will be reused for each task, but will create a compiledNode for bound data.
*/
export function compileSchema(schema: JsonSchema, options: CompileOptions = {}) {
let formatAssertion = options.formatAssertion ?? true;
const drafts = options.drafts ?? defaultDrafts;
const draft = getDraft(drafts, schema?.$schema);
const node: SchemaNode = {
evaluationPath: "#",
lastIdPointer: "#",
schemaLocation: "#",
dynamicId: "",
reducers: [],
resolvers: [],
validators: [],
schema,
// @ts-expect-error self-reference add edlater
context: {
remotes: {},
dynamicAnchors: {},
...(options.remote?.context ?? {}),
anchors: {},
refs: {},
...copy(pick(draft, "methods", "keywords", "version", "formats", "errors")),
getDataDefaultOptions: options.getDataDefaultOptions,
drafts
},
...SchemaNodeMethods
};
node.context.rootNode = node;
node.context.remotes[schema?.$id ?? "#"] = node;
if (options.remote) {
const metaSchema = getRef(node, node.schema.$schema);
if (isSchemaNode(metaSchema) && metaSchema.schema.$vocabulary) {
const vocabs = Object.keys(metaSchema.schema.$vocabulary);
// const withAnnotations = vocabs.find((vocab) => vocab.includes("vocab/format-annotation"));
const formatAssertionString = vocabs.find((vocab) => vocab.includes("vocab/format-assertion"));
if (formatAssertion === "meta-schema") {
formatAssertion = metaSchema.schema.$vocabulary[formatAssertionString] === true;
}
const validKeywords = Object.keys(metaSchema.getData({}, { addOptionalProps: true }));
if (validKeywords.length > 0) {
node.context.keywords = node.context.keywords.filter((f) => validKeywords.includes(f.keyword));
}
}
}
if (formatAssertion === false) {
node.context.keywords = node.context.keywords.filter((f) => f.keyword !== "format");
}
addKeywords(node);
return node;
}