@hyperjump/json-schema
Version:
A JSON Schema validator with support for custom keywords, vocabularies, and dialects
100 lines (83 loc) • 3.71 kB
JavaScript
import curry from "just-curry-it";
import { resolveIri, toAbsoluteIri } from "@hyperjump/uri";
import { subscribe } from "./pubsub.js";
import {
setMetaSchemaOutputFormat,
getShouldValidateSchema,
getMetaSchemaOutputFormat
} from "./configuration.js";
import * as Instance from "./instance.js";
import { InvalidSchemaError } from "./invalid-schema-error.js";
import { getSchema, registerSchema, unregisterSchema as schemaUnregister } from "./schema.js";
import { getKeywordName } from "./keywords.js";
import Validation from "./keywords/validation.js";
import { BasicOutputPlugin } from "./evaluation-plugins/basic-output.js";
import { DetailedOutputPlugin } from "./evaluation-plugins/detailed-output.js";
export const FLAG = "FLAG", BASIC = "BASIC", DETAILED = "DETAILED";
setMetaSchemaOutputFormat(FLAG);
export const validate = async (url, value = undefined, options = undefined) => {
const schema = await getSchema(url);
const compiled = await compile(schema);
const interpretAst = (value, options) => interpret(compiled, Instance.fromJs(value), options);
return value === undefined ? interpretAst : interpretAst(value, options);
};
export const compile = async (schema) => {
const ast = { metaData: {}, plugins: new Set() };
const schemaUri = await Validation.compile(schema, ast);
return { ast, schemaUri };
};
export const interpret = curry(({ ast, schemaUri }, instance, options = FLAG) => {
const outputFormat = typeof options === "string" ? options : options.outputFormat ?? FLAG;
const plugins = options.plugins ?? [];
const context = { ast, plugins: [...ast.plugins, ...plugins] };
let outputPlugin;
switch (outputFormat) {
case FLAG:
break;
case BASIC:
outputPlugin = new BasicOutputPlugin();
context.plugins.push(outputPlugin);
break;
case DETAILED:
outputPlugin = new DetailedOutputPlugin();
context.plugins.push(outputPlugin);
break;
default:
throw Error(`Unsupported output format '${outputFormat}'`);
}
const valid = Validation.interpret(schemaUri, instance, context);
return !valid && outputPlugin ? { valid, errors: outputPlugin.errors } : { valid };
});
const metaValidators = {};
subscribe("validate.metaValidate", async (_message, schema) => {
if (getShouldValidateSchema() && !schema.document.validated) {
schema.document.validated = true;
// Compile
if (!(schema.document.dialectId in metaValidators)) {
const metaSchema = await getSchema(schema.document.dialectId, schema);
const compiledSchema = await compile(metaSchema);
metaValidators[schema.document.dialectId] = interpret(compiledSchema);
}
// Interpret
const schemaInstance = Instance.fromJs(schema.document.root, schema.document.baseUri);
const metaResults = metaValidators[schema.document.dialectId](schemaInstance, getMetaSchemaOutputFormat());
if (!metaResults.valid) {
throw new InvalidSchemaError(metaResults);
}
}
});
/**
* @deprecated since 1.7.0. Use registerSchema instead.
*/
export const addSchema = (schema, retrievalUri = undefined, contextDialectId = undefined) => {
const dialectId = typeof schema.$schema === "string" ? toAbsoluteIri(schema.$schema) : contextDialectId;
const idToken = getKeywordName(dialectId, "https://json-schema.org/keyword/id")
|| getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/id");
const id = typeof schema[idToken] === "string" ? resolveIri(schema[idToken], retrievalUri) : retrievalUri;
unregisterSchema(id);
registerSchema(schema, retrievalUri, contextDialectId);
};
export const unregisterSchema = (uri) => {
schemaUnregister(uri);
delete metaValidators[uri];
};