Skip to content

Instantly share code, notes, and snippets.

@fiznool
Last active September 16, 2016 09:43
Show Gist options
  • Save fiznool/27fcd362250ce668986a29ad79b59b03 to your computer and use it in GitHub Desktop.
Save fiznool/27fcd362250ce668986a29ad79b59b03 to your computer and use it in GitHub Desktop.

As discussed on gitter, the plan is to implement the error handling as a two-stage process.

  1. Add an additionalCodes property to the error object passed in as the third argument to the api handler. This will specify the additional error responses that should be setup by API Gateway, and the associated regexes.
  2. Create a new claudia-api-errors module, which will allow developers to throw a predefined set of Error objects, which will correspond to response codes.

Only 1. needs to be implemented to allow multiple error responses, 2. is more of a nice-to-have.

Background

API Gateway allows you to parse the error message returned from a lambda function, and apply a response code according to a regex that you configure. Here's a good primer on how this works.

For example, if you return the following error from a lambda function:

new Error('Bad Request: username is required.');

In API Gateway, you can setup the following configuration:

Selection pattern: ^Bad Request: .*

Method response: 400

Now, whenever you return an Error from your lambda function as above, API Gateway will transform this into a 400 response.

Currently, claudia only supports returning a single error code, but we can use the idea above to create multiple error responses.

1. Regex Codes

The plan is to allow API handlers to be setup in the following way:

const ApiBuilder = require('claudia-api-builder');

const api = new ApiBuilder();

api.post('users', function(req) {
  if(!req.body.username) {
    throw new Error('Bad Request: username is required.');
  }

  // Otherwise, we can process the request successfully.
}, {
  success: 201,
  error: {
    defaultCode: 500,  // optional, defaults to `500`
    additionalCodes: [{
      code: 400,
      pattern: '^Bad Request:.*',
      template: '$input.path(\'$.errorMessage\')'  // Optional, defaults to the value shown here
    }]
  }
});

The idea is to enumerate all of the error responses that you expect in your handler as items in the additionalCodes array. Each item includes:

  • the response code to issue
  • the pattern to match in the message passed into the error
  • the (optional) mapping template to apply to the error message

When setting up your routes, claudia will enumerate this array and apply each in turn as API gateway calls. An example of this can be seen here.

Claudia will also need to be modified so that the success response code is setup as the default code in API Gateway. Currently the error code is set up as the default. The error code should be setup as the regex .+ which will act as a 'catch-all' for all errors which do not match any of the others.

2. API Error objects

The above, while functionally complete, can be a bit of a pain to manage, especially if you want to return a JSON object in the response body. The second part of the process is building a set of API Errors which simplify this process.

The idea is to modify the above API handler code so it resembles:

const ApiBuilder = require('claudia-api-builder');
const ApiErrors = require('claudia-api-errors');

const api = new ApiBuilder();

api.post('users', function(req) {
  if(!req.body.username) {
    throw new ApiErrors.BadRequestError({ message: 'username is required' });
  }

  // Otherwise, we can process the request successfully.
}, {
  success: 201,
  error: {
    defaultCode: 500,  // optional, defaults to `500`
    additionalErrors: [
      ApiErrors.BadRequestError
    ]
  }
});

An ApiError will inherit from a base class:

class ApiBuilderError extends Error {
  constructor(code, data) {
    const serializedData = JSON.stringify(data);
    super(`{"code":${code},"data":${serializedData}}`);
  }

  toConfig() {
    return {
      code: this.code,
      pattern: `^{"code":${this.code}.*`,
      template: '$util.parseJson($input.path(\'$.errorMessage\')).data'
    };
  }
}

class BadRequestError extends ApiBuilderError {
  constructor(data) {
    super(400, data);
  }
}

Under the hood, if claudia finds an additionalErrors property on the error object, it will assume that you are passing in a class inherited from ApiError. It will instantiate this and call toConfig to generate the config needed to apply to API Gateway. You'll notice that toConfig returns the same properties as the Regex-based approach, with a couple of extras:

  • Notice that the error message property is set to a JSON string, which begins with the code and follows with the data passed in.
  • The pattern is always set to begin with the code, this allows us to automatically detect the errors that we have create with the error builder. The ^ ensures that we only match on messages which begin with the code, preventing false positives on error messages including code.
  • The template automatically unwraps the data that you passed in to the error, and returns it as the response body.

So, for the API handler above, if the username is not present, we'll receive the following response:

< 400
{
  message: 'username is required'
}

Hence, the error builder is a convenience for returning standard errors.

@phips28
Copy link

phips28 commented Sep 16, 2016

@fiznool my implementation is ready 🎉
claudiajs/claudia-api-builder#5 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment