forked from olcxjas-softworks/LarpixClient
154 lines
No EOL
5.8 KiB
JavaScript
154 lines
No EOL
5.8 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.validateSchema = validateSchema;
|
|
const ajv_1 = require("ajv");
|
|
const ajv = new ajv_1.default({
|
|
allErrors: true,
|
|
verbose: true,
|
|
coerceTypes: true,
|
|
strict: false,
|
|
});
|
|
// Cache the compiled validator for the canonical scheme.json so it is only
|
|
// compiled once per process lifetime.
|
|
let _cachedValidate;
|
|
function validateSchema(schema, data, config = {}) {
|
|
var _a, _b;
|
|
if (_cachedValidate == null || _cachedValidate.schema !== schema) {
|
|
_cachedValidate = ajv.compile(schema);
|
|
}
|
|
const validate = _cachedValidate;
|
|
if (validate(data)) {
|
|
return;
|
|
}
|
|
const errors = (_a = validate.errors) !== null && _a !== void 0 ? _a : [];
|
|
const baseDataPath = "configuration";
|
|
const name = (_b = config.name) !== null && _b !== void 0 ? _b : "Object";
|
|
const relevant = filterRelevantErrors(errors);
|
|
const formatted = relevant.map(error => {
|
|
let msg = formatError(error, baseDataPath);
|
|
if (config.postFormatter != null) {
|
|
msg = config.postFormatter(msg, error);
|
|
}
|
|
return ` - ${indentNewlines(msg, " ")}`;
|
|
});
|
|
const header = `Invalid configuration object. ${name} has been initialized using a configuration object that does not match the API schema.`;
|
|
throw new Error(`${header}\n${formatted.join("\n")}`);
|
|
}
|
|
/**
|
|
* Filters the flat list of ajv errors to the most actionable subset.
|
|
* Composite keywords (anyOf/oneOf/if) are suppressed when more specific
|
|
* errors exist at the same or deeper paths. When all errors share the
|
|
* same instancePath and a composite parent for that path exists, the
|
|
* parent is kept instead so the user sees a single "should be one of"
|
|
* message rather than every failed branch listed separately.
|
|
*/
|
|
function filterRelevantErrors(errors) {
|
|
const compositeKeywords = new Set(["anyOf", "oneOf", "if"]);
|
|
const specific = errors.filter(e => !compositeKeywords.has(e.keyword));
|
|
if (specific.length === 0) {
|
|
return errors.filter(e => compositeKeywords.has(e.keyword));
|
|
}
|
|
// Group specific errors by instancePath
|
|
const byPath = new Map();
|
|
for (const e of specific) {
|
|
const list = byPath.get(e.instancePath);
|
|
if (list != null) {
|
|
list.push(e);
|
|
}
|
|
else {
|
|
byPath.set(e.instancePath, [e]);
|
|
}
|
|
}
|
|
const result = [];
|
|
for (const [path, group] of byPath) {
|
|
// When multiple branch-failures share the same path, prefer the anyOf
|
|
// parent error (one concise "should be one of these" message).
|
|
if (group.length > 1) {
|
|
const parent = errors.find(e => e.instancePath === path && compositeKeywords.has(e.keyword));
|
|
if (parent != null) {
|
|
result.push(parent);
|
|
continue;
|
|
}
|
|
}
|
|
result.push(...group);
|
|
}
|
|
return result;
|
|
}
|
|
/** Decodes a single RFC 6901 JSON Pointer segment (`~1` → `/`, `~0` → `~`). */
|
|
function decodePointerSegment(segment) {
|
|
return segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
}
|
|
function instancePathToLabel(instancePath, baseDataPath) {
|
|
if (!instancePath) {
|
|
return baseDataPath;
|
|
}
|
|
const segments = instancePath.split("/").filter(Boolean).map(decodePointerSegment);
|
|
return [baseDataPath, ...segments].join(".");
|
|
}
|
|
function formatError(error, baseDataPath) {
|
|
var _a;
|
|
const label = instancePathToLabel(error.instancePath, baseDataPath);
|
|
const params = error.params;
|
|
const parentSchema = error.parentSchema;
|
|
switch (error.keyword) {
|
|
case "additionalProperties":
|
|
return `${label} has an unknown property '${params.additionalProperty}'`;
|
|
case "required": {
|
|
const missingProp = String(params.missingProperty).replace(/^\./, "");
|
|
return `${label} misses the property '${missingProp}'`;
|
|
}
|
|
case "type": {
|
|
const type = params.type;
|
|
if (Array.isArray(type)) {
|
|
const typeList = type.join(" | ");
|
|
const desc = descriptionSuffix(parentSchema);
|
|
return `${label} should be:\n${typeList}${desc}`;
|
|
}
|
|
return `${label} should be a ${type}`;
|
|
}
|
|
case "enum": {
|
|
const enumVals = (_a = parentSchema === null || parentSchema === void 0 ? void 0 : parentSchema.enum) !== null && _a !== void 0 ? _a : [];
|
|
if (enumVals.length === 1) {
|
|
return `${label} should be ${JSON.stringify(enumVals[0])}`;
|
|
}
|
|
return `${label} should be one of these:\n${enumVals.map(v => JSON.stringify(v)).join(" | ")}`;
|
|
}
|
|
case "anyOf":
|
|
case "oneOf": {
|
|
const schemaText = formatSchemaType(parentSchema);
|
|
return `${label} should be one of these:\n${schemaText}`;
|
|
}
|
|
default:
|
|
return `${label} ${error.message}`;
|
|
}
|
|
}
|
|
function descriptionSuffix(schema) {
|
|
return typeof (schema === null || schema === void 0 ? void 0 : schema.description) === "string" ? `\n-> ${schema.description}` : "";
|
|
}
|
|
function formatSchemaType(schema) {
|
|
if (schema == null) {
|
|
return "";
|
|
}
|
|
if (Array.isArray(schema.type)) {
|
|
return schema.type.join(" | ");
|
|
}
|
|
if (typeof schema.type === "string") {
|
|
return schema.type;
|
|
}
|
|
if (Array.isArray(schema.anyOf)) {
|
|
return schema.anyOf
|
|
.map(s => {
|
|
if (Array.isArray(s.type)) {
|
|
return s.type.join(" | ");
|
|
}
|
|
return typeof s.type === "string" ? s.type : "";
|
|
})
|
|
.filter(Boolean)
|
|
.join(" | ");
|
|
}
|
|
return JSON.stringify(schema);
|
|
}
|
|
function indentNewlines(str, prefix) {
|
|
return str.replace(/\n(?!$)/g, `\n${prefix}`);
|
|
}
|
|
//# sourceMappingURL=schemaValidator.js.map
|