176 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			176 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| '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;
 | |
|     };
 | |
| };
 |