Improve error rendering, add support for subErrors

parse-stacktrace-fix
Sven Slootweg 4 years ago
parent 2f4434dd1a
commit 8214fb3bc5

@ -27,7 +27,9 @@
"create-error": "^0.3.1", "create-error": "^0.3.1",
"default-value": "^1.0.0", "default-value": "^1.0.0",
"flatten": "^1.0.3", "flatten": "^1.0.3",
"is-arguments": "^1.0.4" "indent-string": "^4.0.0",
"is-arguments": "^1.0.4",
"supports-color": "^7.1.0"
}, },
"devDependencies": { "devDependencies": {
"@joepie91/eslint-config": "^1.1.0", "@joepie91/eslint-config": "^1.1.0",

@ -1,5 +1,7 @@
"use strict"; "use strict";
const indentString = require("indent-string");
const supportsColor = require("supports-color");
const matchVirtualProperty = require("@validatem/match-virtual-property"); const matchVirtualProperty = require("@validatem/match-virtual-property");
const AggregrateValidationError = require("./aggregrate-validation-error"); const AggregrateValidationError = require("./aggregrate-validation-error");
@ -7,38 +9,69 @@ 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: 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 // TODO: Move out the path generating logic into a separate module, to better support custom error formatting code
module.exports = function aggregrateAndThrowErrors(errors) { function joinPathSegments(segments) {
let rephrasedErrors = errors.map((error) => { return (segments.length > 0)
let stringifiedPathSegments = error.path.map((segment) => { ? 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, basePath = []) {
let rephrasedErrors = errors.map((error, i) => {
let pathSegments = error.path.map((segment) => {
if (segment == null) { if (segment == null) {
throw new Error(`Unexpected empty path segment encountered; this is a bug, please report it!`); throw new Error(`Unexpected empty path segment encountered; this is a bug, please report it!`);
} else if (typeof segment === "string") { } else if (typeof segment === "string" || typeof segment === "number") {
return segment; return openHighlight + String(segment) + closeColor;
} else if (typeof segment === "number") {
return String(segment);
} else if (matchVirtualProperty(segment)) { } else if (matchVirtualProperty(segment)) {
return `(${segment.name})`; return openDim + `(${segment.name})` + closeColor;
} else { } else {
throw new Error(`Unexpected path segment encountered: ${segment}; this is a bug, please report it!`); throw new Error(`Unexpected path segment encountered: ${segment}; this is a bug, please report it!`);
} }
}); });
/* TODO: Make immutable */ let lineCharacter = (i < errors.length - 1)
let path = (stringifiedPathSegments.length > 0) ? "├─"
? stringifiedPathSegments.join(" -> ") : "└─";
: "(root)";
error.message = `At ${path}: ${error.message}`; let mainLine = (basePath.length > 0)
return error; // ? `... -> ${joinPathSegments(pathSegments)}: ${error.message}`
? ` ${lineCharacter} ${joinPathSegments(pathSegments)}: ${error.message}`
: ` - At ${joinPathSegments(pathSegments)}: ${error.message}`;
if (error.subErrors != null && error.subErrors.length > 0) {
let renderedSubErrors = renderErrorList(error.subErrors, error.path);
return mainLine + "\n" + indentString(renderedSubErrors, 2);
} else {
return mainLine;
}
}); });
let detailLines = rephrasedErrors.map((error) => { return rephrasedErrors.map((error) => {
return ` - ${error.message}`; return `${error}`;
}).join("\n"); }).join("\n");
}
module.exports = function aggregrateAndThrowErrors(errors) {
let detailLines = renderErrorList(errors);
if (errors.length > 0) { if (errors.length > 0) {
return new AggregrateValidationError(`One or more validation errors occurred:\n${detailLines}`, { return new AggregrateValidationError(`One or more validation errors occurred:\n${detailLines}`, {
errors: rephrasedErrors errors: errors
}); });
} }
}; };

@ -697,6 +697,11 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
indent-string@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
inflight@^1.0.4: inflight@^1.0.4:
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"

Loading…
Cancel
Save