|
|
'use strict';
const Clone = require('@hapi/hoek/lib/clone');
const Common = require('./common');
const internals = { annotations: Symbol('annotations') };
exports.error = function (stripColorCodes) {
if (!this._original || typeof this._original !== 'object') {
return this.details[0].message; }
const redFgEscape = stripColorCodes ? '' : '\u001b[31m'; const redBgEscape = stripColorCodes ? '' : '\u001b[41m'; const endColor = stripColorCodes ? '' : '\u001b[0m';
const obj = Clone(this._original);
for (let i = this.details.length - 1; i >= 0; --i) { // Reverse order to process deepest child first
const pos = i + 1; const error = this.details[i]; const path = error.path; let node = obj; for (let j = 0; ; ++j) { const seg = path[j];
if (Common.isSchema(node)) { node = node.clone(); // joi schemas are not cloned by hoek, we have to take this extra step
}
if (j + 1 < path.length && typeof node[seg] !== 'string') {
node = node[seg]; } else { const refAnnotations = node[internals.annotations] || { errors: {}, missing: {} }; node[internals.annotations] = refAnnotations;
const cacheKey = seg || error.context.key;
if (node[seg] !== undefined) { refAnnotations.errors[cacheKey] = refAnnotations.errors[cacheKey] || []; refAnnotations.errors[cacheKey].push(pos); } else { refAnnotations.missing[cacheKey] = pos; }
break; } } }
const replacers = { key: /_\$key\$_([, \d]+)_\$end\$_"/g, missing: /"_\$miss\$_([^|]+)\|(\d+)_\$end\$_": "__missing__"/g, arrayIndex: /\s*"_\$idx\$_([, \d]+)_\$end\$_",?\n(.*)/g, specials: /"\[(NaN|Symbol.*|-?Infinity|function.*|\(.*)]"/g };
let message = internals.safeStringify(obj, 2) .replace(replacers.key, ($0, $1) => `" ${redFgEscape}[${$1}]${endColor}`) .replace(replacers.missing, ($0, $1, $2) => `${redBgEscape}"${$1}"${endColor}${redFgEscape} [${$2}]: -- missing --${endColor}`) .replace(replacers.arrayIndex, ($0, $1, $2) => `\n${$2} ${redFgEscape}[${$1}]${endColor}`) .replace(replacers.specials, ($0, $1) => $1);
message = `${message}\n${redFgEscape}`;
for (let i = 0; i < this.details.length; ++i) { const pos = i + 1; message = `${message}\n[${pos}] ${this.details[i].message}`; }
message = message + endColor;
return message; };
// Inspired by json-stringify-safe
internals.safeStringify = function (obj, spaces) {
return JSON.stringify(obj, internals.serializer(), spaces); };
internals.serializer = function () {
const keys = []; const stack = [];
const cycleReplacer = (key, value) => {
if (stack[0] === value) { return '[Circular ~]'; }
return '[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']'; };
return function (key, value) {
if (stack.length > 0) { const thisPos = stack.indexOf(this); if (~thisPos) { stack.length = thisPos + 1; keys.length = thisPos + 1; keys[thisPos] = key; } else { stack.push(this); keys.push(key); }
if (~stack.indexOf(value)) { value = cycleReplacer.call(this, key, value); } } else { stack.push(value); }
if (value) { const annotations = value[internals.annotations]; if (annotations) { if (Array.isArray(value)) { const annotated = [];
for (let i = 0; i < value.length; ++i) { if (annotations.errors[i]) { annotated.push(`_$idx$_${annotations.errors[i].sort().join(', ')}_$end$_`); }
annotated.push(value[i]); }
value = annotated; } else { for (const errorKey in annotations.errors) { value[`${errorKey}_$key$_${annotations.errors[errorKey].sort().join(', ')}_$end$_`] = value[errorKey]; value[errorKey] = undefined; }
for (const missingKey in annotations.missing) { value[`_$miss$_${missingKey}|${annotations.missing[missingKey]}_$end$_`] = '__missing__'; } }
return value; } }
if (value === Infinity || value === -Infinity || Number.isNaN(value) || typeof value === 'function' || typeof value === 'symbol') {
return '[' + value.toString() + ']'; }
return value; }; };
|