|
|
|
"use strict";
|
|
|
|
|
|
|
|
const indentString = require("indent-string");
|
|
|
|
const supportsColor = require("supports-color");
|
|
|
|
const matchVirtualProperty = require("@validatem/match-virtual-property");
|
|
|
|
const asExpression = require("as-expression");
|
|
|
|
const syncpipe = require("syncpipe");
|
|
|
|
|
|
|
|
const AggregrateValidationError = require("./aggregrate-validation-error");
|
|
|
|
|
|
|
|
// TODO: Omit the "At (root)" for path-less errors, to avoid confusion when singular values are being compared?
|
|
|
|
// TODO: Move out the path generating logic into a separate module, to better support custom error formatting code
|
|
|
|
|
|
|
|
function joinPathSegments(segments) {
|
|
|
|
return (segments.length > 0)
|
|
|
|
? segments.join(" -> ")
|
|
|
|
: "(root)";
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: We do some manual ANSI escape code stuff here for now, because using `chalk` would significantly inflate the bundle size of the core.
|
|
|
|
// TODO: Find a better solution for this.
|
|
|
|
let openHighlight, openDim, closeColor;
|
|
|
|
|
|
|
|
if (supportsColor.stderr) {
|
|
|
|
openHighlight = `\u001b[36m`; // cyan
|
|
|
|
openDim = `\u001b[90m`; // gray
|
|
|
|
closeColor = `\u001b[39m`;
|
|
|
|
} else {
|
|
|
|
openHighlight = "";
|
|
|
|
openDim = "";
|
|
|
|
closeColor = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderErrorList(errors, subErrorLevels = 0) {
|
|
|
|
let rephrasedErrors = errors.map((error, i) => {
|
|
|
|
let pathSegments = error.path.map((segment) => {
|
|
|
|
if (segment == null) {
|
|
|
|
throw new Error(`Unexpected empty path segment encountered; this is a bug, please report it!`);
|
|
|
|
} else if (typeof segment === "string" || typeof segment === "number") {
|
|
|
|
return openHighlight + String(segment) + closeColor;
|
|
|
|
} else if (matchVirtualProperty(segment)) {
|
|
|
|
return openDim + `(${segment.name})` + closeColor;
|
|
|
|
} else {
|
|
|
|
throw new Error(`Unexpected path segment encountered: ${segment}; this is a bug, please report it!`);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let lineCharacter = (i < errors.length - 1)
|
|
|
|
? "├─"
|
|
|
|
: "└─";
|
|
|
|
|
|
|
|
let mainLine = asExpression(() => {
|
|
|
|
if (subErrorLevels > 0) {
|
|
|
|
let message = (pathSegments.length > 0)
|
|
|
|
? `${lineCharacter} ${joinPathSegments(pathSegments)}: ${error.message}`
|
|
|
|
: `${lineCharacter} ${error.message}`;
|
|
|
|
|
|
|
|
return message;
|
|
|
|
} else {
|
|
|
|
return (pathSegments.length > 0)
|
|
|
|
? ` - At ${joinPathSegments(pathSegments)}: ${error.message}`
|
|
|
|
: ` - ${error.message}`;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (error.subErrors != null && error.subErrors.length > 0) {
|
|
|
|
let renderedSubErrors = renderErrorList(error.subErrors, subErrorLevels + 1);
|
|
|
|
let isLastError = (i === errors.length - 1);
|
|
|
|
|
|
|
|
if (subErrorLevels > 0 && !isLastError) {
|
|
|
|
return syncpipe(renderedSubErrors, [
|
|
|
|
(_) => indentString(_, 3),
|
|
|
|
(_) => indentString(_, 1, { indent: "│" }),
|
|
|
|
(_) => mainLine + "\n" + _
|
|
|
|
]);
|
|
|
|
} else {
|
|
|
|
return mainLine + "\n" + indentString(renderedSubErrors, 4);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return mainLine;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return rephrasedErrors.map((error) => {
|
|
|
|
return `${error}`;
|
|
|
|
}).join("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = function aggregrateAndThrowErrors(errors) {
|
|
|
|
let detailLines = renderErrorList(errors);
|
|
|
|
|
|
|
|
if (errors.length > 0) {
|
|
|
|
return new AggregrateValidationError(`One or more validation errors occurred:\n${detailLines}`, {
|
|
|
|
errors: errors
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|