272 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			272 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| '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 (!prefs.errors.label) {
 | |
|         return '';
 | |
|     }
 | |
| 
 | |
|     if (flags.label) {
 | |
|         return flags.label;
 | |
|     }
 | |
| 
 | |
|     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;
 |