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.
271 lines
5.9 KiB
271 lines
5.9 KiB
'use strict'; |
|
|
|
const Annotate = require('./annotate'); |
|
const Common = require('./common'); |
|
const Template = require('./template'); |
|
|
|
|
|
const internals = {}; |
|
|
|
|
|
exports.Report = class { |
|
|
|
constructor(code, value, local, flags, messages, state, prefs) { |
|
|
|
this.code = code; |
|
this.flags = flags; |
|
this.messages = messages; |
|
this.path = state.path; |
|
this.prefs = prefs; |
|
this.state = state; |
|
this.value = value; |
|
|
|
this.message = null; |
|
this.template = null; |
|
|
|
this.local = local || {}; |
|
this.local.label = exports.label(this.flags, this.state, this.prefs, this.messages); |
|
|
|
if (this.value !== undefined && |
|
!this.local.hasOwnProperty('value')) { |
|
|
|
this.local.value = this.value; |
|
} |
|
|
|
if (this.path.length) { |
|
const key = this.path[this.path.length - 1]; |
|
if (typeof key !== 'object') { |
|
this.local.key = key; |
|
} |
|
} |
|
} |
|
|
|
_setTemplate(template) { |
|
|
|
this.template = template; |
|
|
|
if (!this.flags.label && |
|
this.path.length === 0) { |
|
|
|
const localized = this._template(this.template, 'root'); |
|
if (localized) { |
|
this.local.label = localized; |
|
} |
|
} |
|
} |
|
|
|
toString() { |
|
|
|
if (this.message) { |
|
return this.message; |
|
} |
|
|
|
const code = this.code; |
|
|
|
if (!this.prefs.errors.render) { |
|
return this.code; |
|
} |
|
|
|
const template = this._template(this.template) || |
|
this._template(this.prefs.messages) || |
|
this._template(this.messages); |
|
|
|
if (template === undefined) { |
|
return `Error code "${code}" is not defined, your custom type is missing the correct messages definition`; |
|
} |
|
|
|
// Render and cache result |
|
|
|
this.message = template.render(this.value, this.state, this.prefs, this.local, { errors: this.prefs.errors, messages: [this.prefs.messages, this.messages] }); |
|
if (!this.prefs.errors.label) { |
|
this.message = this.message.replace(/^"" /, '').trim(); |
|
} |
|
|
|
return this.message; |
|
} |
|
|
|
_template(messages, code) { |
|
|
|
return exports.template(this.value, messages, code || this.code, this.state, this.prefs); |
|
} |
|
}; |
|
|
|
|
|
exports.path = function (path) { |
|
|
|
let label = ''; |
|
for (const segment of path) { |
|
if (typeof segment === 'object') { // Exclude array single path segment |
|
continue; |
|
} |
|
|
|
if (typeof segment === 'string') { |
|
if (label) { |
|
label += '.'; |
|
} |
|
|
|
label += segment; |
|
} |
|
else { |
|
label += `[${segment}]`; |
|
} |
|
} |
|
|
|
return label; |
|
}; |
|
|
|
|
|
exports.template = function (value, messages, code, state, prefs) { |
|
|
|
if (!messages) { |
|
return; |
|
} |
|
|
|
if (Template.isTemplate(messages)) { |
|
return code !== 'root' ? messages : null; |
|
} |
|
|
|
let lang = prefs.errors.language; |
|
if (Common.isResolvable(lang)) { |
|
lang = lang.resolve(value, state, prefs); |
|
} |
|
|
|
if (lang && |
|
messages[lang]) { |
|
|
|
if (messages[lang][code] !== undefined) { |
|
return messages[lang][code]; |
|
} |
|
|
|
if (messages[lang]['*'] !== undefined) { |
|
return messages[lang]['*']; |
|
} |
|
} |
|
|
|
if (!messages[code]) { |
|
return messages['*']; |
|
} |
|
|
|
return messages[code]; |
|
}; |
|
|
|
|
|
exports.label = function (flags, state, prefs, messages) { |
|
|
|
if (flags.label) { |
|
return flags.label; |
|
} |
|
|
|
if (!prefs.errors.label) { |
|
return ''; |
|
} |
|
|
|
let path = state.path; |
|
if (prefs.errors.label === 'key' && |
|
state.path.length > 1) { |
|
|
|
path = state.path.slice(-1); |
|
} |
|
|
|
const normalized = exports.path(path); |
|
if (normalized) { |
|
return normalized; |
|
} |
|
|
|
return exports.template(null, prefs.messages, 'root', state, prefs) || |
|
messages && exports.template(null, messages, 'root', state, prefs) || |
|
'value'; |
|
}; |
|
|
|
|
|
exports.process = function (errors, original, prefs) { |
|
|
|
if (!errors) { |
|
return null; |
|
} |
|
|
|
const { override, message, details } = exports.details(errors); |
|
if (override) { |
|
return override; |
|
} |
|
|
|
if (prefs.errors.stack) { |
|
return new exports.ValidationError(message, details, original); |
|
} |
|
|
|
const limit = Error.stackTraceLimit; |
|
Error.stackTraceLimit = 0; |
|
const validationError = new exports.ValidationError(message, details, original); |
|
Error.stackTraceLimit = limit; |
|
return validationError; |
|
}; |
|
|
|
|
|
exports.details = function (errors, options = {}) { |
|
|
|
let messages = []; |
|
const details = []; |
|
|
|
for (const item of errors) { |
|
|
|
// Override |
|
|
|
if (item instanceof Error) { |
|
if (options.override !== false) { |
|
return { override: item }; |
|
} |
|
|
|
const message = item.toString(); |
|
messages.push(message); |
|
|
|
details.push({ |
|
message, |
|
type: 'override', |
|
context: { error: item } |
|
}); |
|
|
|
continue; |
|
} |
|
|
|
// Report |
|
|
|
const message = item.toString(); |
|
messages.push(message); |
|
|
|
details.push({ |
|
message, |
|
path: item.path.filter((v) => typeof v !== 'object'), |
|
type: item.code, |
|
context: item.local |
|
}); |
|
} |
|
|
|
if (messages.length > 1) { |
|
messages = [...new Set(messages)]; |
|
} |
|
|
|
return { message: messages.join('. '), details }; |
|
}; |
|
|
|
|
|
exports.ValidationError = class extends Error { |
|
|
|
constructor(message, details, original) { |
|
|
|
super(message); |
|
this._original = original; |
|
this.details = details; |
|
} |
|
|
|
static isError(err) { |
|
|
|
return err instanceof exports.ValidationError; |
|
} |
|
}; |
|
|
|
|
|
exports.ValidationError.prototype.isJoi = true; |
|
|
|
exports.ValidationError.prototype.name = 'ValidationError'; |
|
|
|
exports.ValidationError.prototype.annotate = Annotate.error;
|
|
|