1.2.0 • Published 7 years ago

iris-error-handler v1.2.0

Weekly downloads
2
License
MIT
Repository
github
Last release
7 years ago

Iris Error Handler

Error handling middleware for IrisVR microservices.

  1. Getting Started
  2. Error Table
  3. API
  4. Testing
  5. Contribution

Compatible with Node.js + Express apps, with an optional MongoDB backend.

CircleCI Codecov

Getting started

Installation

$ npm install iris-error-handler --save

Initialization

const errorHandler = require('iris-error-handler');

Error Table

You may conjure the list of internal Iris error codes and their respective messages like this:

const errorTable = require('iris-error-handler').errorTable;

The error code acts as the key, while the value contains the meta payload to be returned to the client. The meta object contains the following fields:

  • code: An integer value that categorizes the type of response according to our internal status code library. This number is unrelated to a standard HTTP status code.
  • error_type: A brief description of the error.
  • error_message: A string that describes error in full detail.

Organization

  • -1: Unknown error. This is the default error returned by the middleware if no error code is specified.
  • 1 - 99: Third-party errors
  • 100 - 149: Request header errors
  • 150 - 199: MongoDB errors
  • 200 - 299: User / Password errors
  • 300 - 399: Library errors
  • 400 - 499: Team errors
  • 500 - 599: Billing errors
  • 600 - 699: Notification errors

Example

{
  ...
  202: {
    meta: {
      code: 202,
      error_type: 'UsernameTaken',
      error_message: 'The username is already registered'
    }
  },
  ...
}

API

const errorUtils = require('iris-error-handler').utils;

General

handleError(res)(err)

Currying function that accepts an Express response object and an error argument. This method internally triggers sendError for ultimately sending the response back to the client.

The error passed in must be one of two types:

Example
/**
 * route.js
 */
// Arbitrary error thrower
function calculate() {
  return Promise.reject(Error(101));
};

// Route handler
const calculateNumber = (req, res) => 
  calculate(req.body)
    .then(n => res.send(n))
    .catch(errorUtils.handleError(res));
  • A Mongoose Error triggered by validators defined in the schema. There are two types of mongoose errors that we support:
Required Field Error

The required field validator comes out of the box when you define your schema. The actual error code you want to throw for specific missing fields can be configured here.

Example
/**
 * userModel.js
 */
const User = new mongoose.Schema({
  username: {
    type: String
  },
  password: {
    type: String,
    required: true // built-in validator
  }
});

module.exports = mongoose.model('User', User);

/**
 * route.js
 */
const User = mongoose.model('User');
const createUser = (req, res) => {
  const userWithoutPassword = { username: 'cookies@gmail.gov' };
  User.create(userWithoutPassword)
    .then(u => res.send(u))
    .catch(errorUtils.handleError(res)); // Sends `205: PasswordMissing`
}
Custom Validation Error

You can set up custom validation on your schema using .validate(callback, [message]). If you do so, make sure to pass in the appropriate error code as the second argument.

Custom Validation Example
/**
 * userModel.js
 */
const User = new mongoose.Schema({
  username: String
});

// Custom validator. Note that the second argument is an Iris error code.
User.path('username').validate(callback, '201');

function callback(value, respond) {
  return validator.isEmail(value)
    ? respond(true) : respond(false);
}

module.exports = mongoose.model('User', User);

/**
 * route.js
 */
const User = mongoose.model('User');
const createUser = (req, res) =>
  User.create({ username: 'INVALID_EMAIL_FORMAT' })
    .then(u => res.send(u))
    .catch(errorUtils.handleError(res)); // Sends `201: UsernameInvalid`

sendError(res, code)

Accepts an Express response object and an Iris error code. The code is referenced against the error table and the appropriate response payload is sent to the client. If no code is passed, the method will default to -1: UnknownError.

The client should always expect a successful HTTP 200 status for errored responses, as more detailed information about the error will be provided in the response body. Refer to the error table for a breakdown of the payload.

This method should not be called directly as it is invoked by the wrapper method handleError. However, it may be convenient to use in development.

Example
const responder = (req, res) =>
  errorUtils.sendError(res, 101);

MongoDB

validateObjectID(id)

Confirms whether a string is a valid Mongo ObjectID; if so, the string is passed on to the next middleware.

This method should be placed directly before making database queries that involve document ID(s).

Example
const User = mongoose.model('User');
const getUser = (req, res) => {
  const id = req.user._id;
  errorUtils.validateObjectID(id)
    .then((id) => User.findById(id))
    .then(u => res.send(u))
    .catch(errorUtils.handleError(res));  // Sends `150: ObjectIDInvalid`
}

handleEntityNotFound(entity, category)

Checks whether the relevant document exists in the database; if so, the document is passed on to the next middleware.

This method should be placed directly after a database query.

An optional category argument can specify what type of document was(n't) found. If none is provided, the error will default to 160: NotFound. A dictionary of supported category strings are available here.

Example
const User = mongoose.model('User');
const getUser = (req, res) =>
  User.findById(req.user._id)
    .then(u => errorUtils.handleEntityNotFound(u, 'userId'))
    .then(u => res.send(u))
    .catch(errorUtils.handleError(res)); // Sends `204: UserIDNotFound`

validateOwner(user)(document)

Confirms whether a user has access to a document; if so, the document is passed on to the next middleware.

This is a lesser used method that only applies to documents with an owner.username field, such as Panos in the Library service.

Example
const Document = mongoose.model('Document');
const updateDocument = (req, res) => {
  const user = req.user;
  const documentId = req.body._id;

  Document.findbyId(documentId)
    .then(errorUtils.validateOwner(user))
    .then(d => res.send(d))
    .catch(errorUtils.handleError(res)); // Sends `350: PermissionsError`
}

Testing

Unit and integration tests are contained in /specs. Make sure to install all dev dependencies required for the testing environment.

$ git clone https://github.com/IrisVR/iris-error-handler.git
$ cd iris-error-handler
$ npm install

Even though this module itself does not require Express or Mongo, it provides support for services using that tech stack. As a result, a server and DB must be mocked in testing.

Linting

$ npm run lint

Running tests

Run unit tests once

$ npm test

Run tests on file change

$ npm run test:watch

Code coverage

$ npm run coverage

All of the above

$ npm run validate

This script uses npm-run-all --parallel under the hood to execute the processes simultaneously.

Contribution

If you'd like to contribute, please make a pull request to the develop branch for imminent review.

Making Changes

Error Codes

Prior to adding a new error code, ensure that a synonymous one doesn't already exist in the error table. If it's indeed a new one, either find a relevant category for it (e.g. User errors fall under 200-299) or create a new category and assign a new group of 100 integers.

If applicable, you may want to update the requiredField and/or notFound dictionary.

Methods

New methods should be placed in /utils; those specific to individual microservices should be placed in /utils/services. All new and/or updated methods should have corresponding unit and integration tests.

Committing and Releasing

To release updates, you must be part of the IrisVR NPM Organization.

Preview

To preview the contents of the npm module prior to publishing:

$ npm pack

This will create the tar zip file that would be served by npm. You can unzip it and explore its contents, which should consist of the following:

dist/
LICENSE
README.md
package.json

When the module is require'd, package.json will point the user to the relevant entry point at dist/index.js.

Commit

The module uses commitizen for making commits, which is included in the dev dependencies.

$ git add -A
$ npm run commit

This will prompt a CLI to walk you through the changes you made. Only a commit type of feat or fix will trigger an update to the published npm module; other types such as refactor and style will not be a release as they don't change anything from the user's perspective.

Committing will run a githook that triggers npm run validate, which in turn runs npm test, npm run lint and npm run coverage in parallel. If there is an error at any stage, the commit will be rejected.

Publish

Once a PR is merged into develop, CircleCI will ensure that the codebase is properly tested, linted and covered.

In the case where develop is merged into master, CirclCI will additionally create a release build, make a new tag according to the nature of the update (major, minor or patch), and auto-release the new version to npm.

1.2.0

7 years ago

1.1.1

7 years ago

1.1.0

7 years ago

1.0.2

7 years ago

1.0.1

7 years ago

1.0.0

7 years ago