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;
 |