exception/index.js

/**
 * @module exception
 * @exports ApiException
 * @exports AuthorizationException
 * @exports Exception
 * @exports InternalException
 * @exports InvalidRequestException
 * @exports ServiceUnavailableException
 */
import {
  resolveCode,
  resolveMessage,
  resolveStatus,
} from 'treehouse/exception/utils';
import * as codes from 'treehouse/exception/codes';

/**
 * <code>ApiException</code> provides a <code>message</code> and
 * <code>stack trace</code> as <code>Exception</code> does, but also provides a
 * response <code>status</code> and an error </code>code</code> for use in
 * production debugging/support.
 *
 * @class
 * @extends Error
 */
export default class Exception extends Error {
  code: number;

  error: Object;

  details: Any;

  /**
   * Supports both <code>Error</code>-compliant and Treehouse-specific
   * paradigms of instantating a new <code>Exception</code>.
   * <code>Error</code>-compliant instantion of an Exception:
   * ```
   * new Exception("some message", 1);
   * ```
   *
   * Non-standard (or Treehouse-specific) instantation expects a payload
   * similar to those found in <code>codes.js</code> and extracts the
   * <code>error code</code>, and the <code>message</code>.
   * Optionally accepts <code>Error</code> for additional meta information as
   * the second or third argument.
   *
   * @constructor
   * @param  {string|Object}      payload  Either the error message or payload.
   * @param  {number|Error|null}  code     Optional. The error status.
   *                                       Error can be supplied instead.
   * @param  {Error|null}         e        Optional. The originating Error.
   * @return {void}
   */
  constructor(payload: String | Object, code: ?Number | ?Error, e: ?Error): void {
    super(resolveMessage(payload));
    this.code = (typeof code === 'number') ? code : resolveCode(payload);
    this.error = e;
    this.details = (typeof payload === 'object') && payload.details;

    if (typeof Error.captureStackTrace === 'function') {
      Error.captureStackTrace(this, this.constructor);
    } else {
      this.stack = (new Error(this.message)).stack;
    }
  }
}

/**
 * Base <code>Exception</code> class for all Treehouse-specifc API
 * <code>Exception</code>.
 * It is recommended to either use this error to wrap generic
 * <code>Exception</code>s or to create application/context-specific Exception
 * classes that extend <code>ApiException</code> in order to better/more easily
 * consume and  alter your application's behavior when <code>Exception</code>
 * are thrown.
 *
 * @class
 * @extends Exception
 */
export class ApiException extends Exception {
  status: number;

  /**
   * Follows the same pattern of instantation as <code>Exception</code>, except
   * constructor accepts the additional, optional parameter.
   * <code>status</code> after <code>payload</code>. The remaining signature
   * remains unaltered.
   *
   * @constructor
   * @param  {string|Object}      payload Either the error message or payload.
   * @param  {number|Error|null}  status  Optional. The error status.
   *                                      Only Error can be supplied instead.
   * @param  {number|Error|null}  code    Optional. The error status.
   *                                      Error can be supplied instead.
   * @param  {Error|null}         e       Optional. The originating Error.
   * @return {void}
   */
  constructor(payload: String | Object, status: ?Number | ?Error, code: ?Number | ?Error, e: ?Error): void {
    super(payload, code, ((status instanceof Error) ? status : e));

    this.status = (typeof status === 'number') ? resolveStatus(status) : resolveStatus(payload);
  }
}

/**
 * Exception used when a request fails security or authorization checks to
 * prompt the User or Client to refresh their session, or to indicate to other
 * services to act similarly.
 *
 * @class
 * @extends ApiException
 */
export class AuthorizationException extends ApiException {
  constructor(e: ?Error) {
    super(codes.NOT_ALLOWED, e);
  }
}

/**
 * Thrown when an unhandled or unrecoverable exception is encountered.
 * Optionally accepts a custom message when instantating.
 *
 * @class
 * @extends ApiException
 */
export class InternalException extends ApiException {
  constructor(message: String, e: ?Error) {
    super(codes.FATAL_ERROR(message), e);
  }
}

/**
 * Generic exception used to signal to a User/Client that their request was
 * invalid. Optionally accepts a custom message when instantating.
 *
 * @class
 * @extends ApiError
 */
export class InvalidRequestException extends ApiException {
  constructor(message: String, e: ?Error) {
    super(codes.INVALID_REQUEST(message), e);
  }
}

/**
 * Exception indicating that an external or internal service is unreachable or
 * unavailable. Optionally accepts a custom message when instantating.
 *
 * @class
 * @extends ApiError
 */
export class ServiceUnavailableException extends ApiException {
  constructor(message: String, e: ?Error) {
    super(codes.SERVICE_UNAVAILABLE(message), e);
  }
}