You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
5.1 KiB
175 lines
5.1 KiB
'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; |
|
}; |
|
};
|
|
|