| 'use strict'; | 
|   | 
| const Hoek = require('@hapi/hoek'); | 
|   | 
| const Language = require('./language'); | 
|   | 
|   | 
| const internals = { | 
|     annotations: Symbol('joi-annotations') | 
| }; | 
|   | 
|   | 
| internals.stringify = function (value, wrapArrays) { | 
|   | 
|     const type = typeof value; | 
|   | 
|     if (value === null) { | 
|         return 'null'; | 
|     } | 
|   | 
|     if (type === 'string') { | 
|         return value; | 
|     } | 
|   | 
|     if (value instanceof exports.Err || type === 'function' || type === 'symbol') { | 
|         return value.toString(); | 
|     } | 
|   | 
|     if (type === 'object') { | 
|         if (Array.isArray(value)) { | 
|             let partial = ''; | 
|   | 
|             for (let i = 0; i < value.length; ++i) { | 
|                 partial = partial + (partial.length ? ', ' : '') + internals.stringify(value[i], wrapArrays); | 
|             } | 
|   | 
|             return wrapArrays ? '[' + partial + ']' : partial; | 
|         } | 
|   | 
|         return value.toString(); | 
|     } | 
|   | 
|     return JSON.stringify(value); | 
| }; | 
|   | 
|   | 
| exports.Err = class { | 
|   | 
|     constructor(type, context, state, options, flags, message, template) { | 
|   | 
|         this.isJoi = true; | 
|         this.type = type; | 
|         this.context = context || {}; | 
|         this.context.key = state.path[state.path.length - 1]; | 
|         this.context.label = state.key; | 
|         this.path = state.path; | 
|         this.options = options; | 
|         this.flags = flags; | 
|         this.message = message; | 
|         this.template = template; | 
|   | 
|         const localized = this.options.language; | 
|   | 
|         if (this.flags.label) { | 
|             this.context.label = this.flags.label; | 
|         } | 
|         else if (localized &&                   // language can be null for arrays exclusion check | 
|             (this.context.label === '' || | 
|             this.context.label === null)) { | 
|             this.context.label = localized.root || Language.errors.root; | 
|         } | 
|     } | 
|   | 
|     toString() { | 
|   | 
|         if (this.message) { | 
|             return this.message; | 
|         } | 
|   | 
|         let format; | 
|   | 
|         if (this.template) { | 
|             format = this.template; | 
|         } | 
|   | 
|         const localized = this.options.language; | 
|   | 
|         format = format || Hoek.reach(localized, this.type) || Hoek.reach(Language.errors, this.type); | 
|   | 
|         if (format === undefined) { | 
|             return `Error code "${this.type}" is not defined, your custom type is missing the correct language definition`; | 
|         } | 
|   | 
|         let wrapArrays = Hoek.reach(localized, 'messages.wrapArrays'); | 
|         if (typeof wrapArrays !== 'boolean') { | 
|             wrapArrays = Language.errors.messages.wrapArrays; | 
|         } | 
|   | 
|         if (format === null) { | 
|             const childrenString = internals.stringify(this.context.reason, wrapArrays); | 
|             if (wrapArrays) { | 
|                 return childrenString.slice(1, -1); | 
|             } | 
|   | 
|             return childrenString; | 
|         } | 
|   | 
|         const hasKey = /{{!?label}}/.test(format); | 
|         const skipKey = format.length > 2 && format[0] === '!' && format[1] === '!'; | 
|   | 
|         if (skipKey) { | 
|             format = format.slice(2); | 
|         } | 
|   | 
|         if (!hasKey && !skipKey) { | 
|             const localizedKey = Hoek.reach(localized, 'key'); | 
|             if (typeof localizedKey === 'string') { | 
|                 format = localizedKey + format; | 
|             } | 
|             else { | 
|                 format = Hoek.reach(Language.errors, 'key') + format; | 
|             } | 
|         } | 
|   | 
|         const message =  format.replace(/{{(!?)([^}]+)}}/g, ($0, isSecure, name) => { | 
|   | 
|             const value = Hoek.reach(this.context, name); | 
|             const normalized = internals.stringify(value, wrapArrays); | 
|             return (isSecure && this.options.escapeHtml ? Hoek.escapeHtml(normalized) : normalized); | 
|         }); | 
|   | 
|         this.toString = () => message;  // Persist result of last toString call, it won't change | 
|   | 
|         return message; | 
|     } | 
|   | 
| }; | 
|   | 
|   | 
| exports.create = function (type, context, state, options, flags, message, template) { | 
|   | 
|     return new exports.Err(type, context, state, options, flags, message, template); | 
| }; | 
|   | 
|   | 
| exports.process = function (errors, object) { | 
|   | 
|     if (!errors) { | 
|         return null; | 
|     } | 
|   | 
|     // Construct error | 
|   | 
|     let message = ''; | 
|     const details = []; | 
|   | 
|     const processErrors = function (localErrors, parent, overrideMessage) { | 
|   | 
|         for (let i = 0; i < localErrors.length; ++i) { | 
|             const item = localErrors[i]; | 
|   | 
|             if (item instanceof Error) { | 
|                 return item; | 
|             } | 
|   | 
|             if (item.flags.error && typeof item.flags.error !== 'function') { | 
|                 if (!item.flags.selfError || !item.context.reason) { | 
|                     return item.flags.error; | 
|                 } | 
|             } | 
|   | 
|             let itemMessage; | 
|             if (parent === undefined) { | 
|                 itemMessage = item.toString(); | 
|                 message = message + (message ? '. ' : '') + itemMessage; | 
|             } | 
|   | 
|             // Do not push intermediate errors, we're only interested in leafs | 
|   | 
|             if (item.context.reason) { | 
|                 const override = processErrors(item.context.reason, item.path, item.type === 'override' ? item.message : null); | 
|                 if (override) { | 
|                     return override; | 
|                 } | 
|             } | 
|             else { | 
|                 details.push({ | 
|                     message: overrideMessage || itemMessage || item.toString(), | 
|                     path: item.path, | 
|                     type: item.type, | 
|                     context: item.context | 
|                 }); | 
|             } | 
|         } | 
|     }; | 
|   | 
|     const override = processErrors(errors); | 
|     if (override) { | 
|         return override; | 
|     } | 
|   | 
|     const error = new Error(message); | 
|     error.isJoi = true; | 
|     error.name = 'ValidationError'; | 
|     error.details = details; | 
|     error._object = object; | 
|     error.annotate = internals.annotate; | 
|     return error; | 
| }; | 
|   | 
|   | 
| // 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 { | 
|                     const errorKeys = Object.keys(annotations.errors); | 
|                     for (let i = 0; i < errorKeys.length; ++i) { | 
|                         const errorKey = errorKeys[i]; | 
|                         value[`${errorKey}_$key$_${annotations.errors[errorKey].sort().join(', ')}_$end$_`] = value[errorKey]; | 
|                         value[errorKey] = undefined; | 
|                     } | 
|   | 
|                     const missingKeys = Object.keys(annotations.missing); | 
|                     for (let i = 0; i < missingKeys.length; ++i) { | 
|                         const missingKey = missingKeys[i]; | 
|                         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; | 
|     }; | 
| }; | 
|   | 
|   | 
| internals.annotate = function (stripColorCodes) { | 
|   | 
|     const redFgEscape = stripColorCodes ? '' : '\u001b[31m'; | 
|     const redBgEscape = stripColorCodes ? '' : '\u001b[41m'; | 
|     const endColor = stripColorCodes ? '' : '\u001b[0m'; | 
|   | 
|     if (typeof this._object !== 'object') { | 
|         return this.details[0].message; | 
|     } | 
|   | 
|     const obj = Hoek.clone(this._object || {}); | 
|   | 
|     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 ref = obj; | 
|         for (let j = 0; ; ++j) { | 
|             const seg = path[j]; | 
|   | 
|             if (ref.isImmutable) { | 
|                 ref = ref.clone();                              // joi schemas are not cloned by hoek, we have to take this extra step | 
|             } | 
|   | 
|             if (j + 1 < path.length && | 
|                 ref[seg] && | 
|                 typeof ref[seg] !== 'string') { | 
|   | 
|                 ref = ref[seg]; | 
|             } | 
|             else { | 
|                 const refAnnotations = ref[internals.annotations] = ref[internals.annotations] || { errors: {}, missing: {} }; | 
|                 const value = ref[seg]; | 
|                 const cacheKey = seg || error.context.label; | 
|   | 
|                 if (value !== 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; | 
| }; |