Home Intro Source Mebo GitHub

src/MeboErrors/ValidationFail.js

const assert = require('assert');
const TypeCheck = require('js-typecheck');
const MeboError = require('../MeboError');
const Settings = require('../Settings');

// symbols used for private instance variables to avoid any potential clashing
// caused by re-implementations
const _inputName = Symbol('inputName');
const _code = Symbol('code');
const _message = Symbol('message');


/**
 * Exception raised by {@link Input} validations.
 *
 * It carries additional information about the context of the error that can be used when
 * reporting/handling it. For this reason when this exception handled by a {@link Handler}
 * it gets encoded into json {@link ValidationFail.toJSON}.
 *
 * ```javascript
 * throw new ValidationFail('File does not exit!')
 * ```
 * @see {@link Writer._errorOutput}
 */
class ValidationFail extends MeboError{

  /**
   * Initialize the exception
   *
   * @param {string} message - error message
   * @param {string} [code] - unique code based on uuid v4 that can be used to identify the error
   * @param {string} [inputName] - name of the input about where the exception was generated
   * type
   */
  constructor(message, code=null, inputName=null){
    assert(TypeCheck.isString(message) && message.length, 'message needs to defined as valid string (cannot be empty)');

    super(message);

    this.code = code;
    this.inputName = inputName;

    /**
     * Status code used by the {@link Handler} when this error is raised from inside of a top
     * level action (an action that has not been created from another action).
     *
     * Value driven by:
     * `Settings.get('error/validationFail/status')`
     * (default: `400`)
     *
     * @type {number}
     */
    this.status = Settings.get('error/validationFail/status');

    /**
     * Boolean telling if this error is not allowed as output ({@link Handler.output})
     * when it has been raised from a nested action (an action created from another
     * action ({@link Action.createAction})). When output is disabled the error
     * will not be handled by the {@link Writer}, therefore the error will be
     * emitted by the signal {@link Handler.onErrorDuringOutput}.
     *
     * Value driven by:
     * `Settings.get('error/validationFail/disableOutputInNested')`
     * (default: `true`)
     *
     * @type {boolean}
     */
    this.disableOutputInNested = Settings.get('error/validationFail/disableOutputInNested');

    // storing the original message
    this[_message] = message;

    this._updateMessage();
  }

  /**
   * Sets the input name related with the validation
   *
   * @param {string} [inputName] - name of the input
   */
  set inputName(inputName){
    assert(inputName === null || (TypeCheck.isString(inputName) && inputName.length), 'inputName needs to defined as valid string');

    this[_inputName] = inputName;

    this._updateMessage();
  }

  /**
   * Returns the input name related with the validation
   *
   * @type {string}
   */
  get inputName(){
    return this[_inputName];
  }

  /**
   * Sets an unique error code specifically related with that has failed validation, this
   * information can be used later to identify the origin of the error, instead of trying
   * to parse the message to figure out that information.
   *
   * @param {string} [errorCode] - unique code based on uuid v4 that can be used to identify the error
   */
  set code(errorCode){
    assert(errorCode === null || /^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/.test(errorCode), 'errorCode needs to defined as uuid or null');

    this[_code] = errorCode;
  }

  /**
   * Returns the code related with the validation itself
   *
   * @type {string}
   */
  get code(){
    return this[_code];
  }

  /**
   * Bakes the exception into a json string
   *
   * @return {string} json string containing the serialized version of the exception
   */
  toJSON(){
    return JSON.stringify({
      message: this[_message],
      code: this.code,
      inputName: this.inputName,
    });
  }

  /**
   * Creates a ValidationFail instance based on the input json string
   *
   * @param {string} json - string containing the serialized json version of the exception
   * @return {ValidationFail}
   */
  static fromJSON(json){
    assert(TypeCheck.isString(json) && json.length, 'json needs to be defined as valid string');

    const data = JSON.parse(json);
    return new ValidationFail(data.message, data.code, data.inputName);
  }

  /**
   * Auxiliary method that updates the validation fail message
   * @private
   */
  _updateMessage(){

    if (this.inputName){
      this.message = `${this.inputName}: ${this[_message]}`;
    }
    else{
      this.message = this[_message];
    }
  }
}

// default settings
Settings.set('error/validationFail/status', 400);
Settings.set('error/validationFail/disableOutputInNested', true);

module.exports = ValidationFail;