Skip to content
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1310,7 +1310,7 @@ api.use(errorHandler1,errorHandler2)

### Error Types

Lambda API provides several different types of errors that can be used by your application. `RouteError`, `MethodError`, `ResponseError`, and `FileError` will all be passed to your error middleware. `ConfigurationError`s will throw an exception when you attempt to `.run()` your route and can be caught in a `try/catch` block. Most error types contain additional properties that further detail the issue.
Lambda API provides several different types of errors that can be used by your application. `ApiError`, `RouteError`, `MethodError`, `ResponseError`, and `FileError` will all be passed to your error middleware. `ConfigurationError`s will throw an exception when you attempt to `.run()` your route and can be caught in a `try/catch` block. Most error types contain additional properties that further detail the issue.

```javascript
const errorHandler = (err,req,res,next) => {
Expand Down
312 changes: 179 additions & 133 deletions __tests__/errorHandling.unit.js

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,13 @@ export declare class ResponseError extends Error {
constructor(message: string, code: number);
}

export declare class ApiError extends Error {
constructor(message: string, code?: number, detail?: any);
name: 'ApiError';
code?: number;
detail?: any;
}

export declare class FileError extends Error {
constructor(message: string, err: object);
}
Expand Down
15 changes: 6 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const RESPONSE = require('./lib/response');
const UTILS = require('./lib/utils');
const LOGGER = require('./lib/logger');
const S3 = () => require('./lib/s3-service');
const { ConfigurationError, ApiError } = require('./lib/errors');
const prettyPrint = require('./lib/prettyPrint');
const { ConfigurationError } = require('./lib/errors');

class API {
constructor(props) {
Expand Down Expand Up @@ -328,26 +328,21 @@ class API {

// Catch all async/sync errors
async catchErrors(e, response, code, detail) {
// Error messages should respect the app's base64 configuration
response._isBase64 = this._isBase64;

// Strip the headers, keep whitelist
const strippedHeaders = Object.entries(response._headers).reduce(
(acc, [headerName, value]) => {
if (!this._errorHeaderWhitelist.includes(headerName.toLowerCase())) {
return acc;
}

return Object.assign(acc, { [headerName]: value });
},
{}
);

response._headers = Object.assign(strippedHeaders, this._headers);

let message;

// Set the status code
response.status(code ? code : this._errorStatus);

let info = {
Expand All @@ -357,13 +352,15 @@ class API {
stack: (this._logger.stack && e.stack) || undefined,
};

if (e instanceof Error) {
const isApiError = e instanceof ApiError;

if (e instanceof Error && !isApiError) {
message = e.message;
if (this._logger.errorLogging) {
this.log.fatal(message, info);
}
} else {
message = e;
message = e instanceof Error ? e.message : e;
if (this._logger.errorLogging) {
this.log.error(message, info);
}
Expand All @@ -387,7 +384,7 @@ class API {
if (rtn) response.send(rtn);
r();
});
} // end for
}
}

// Throw standard error unless callback has already been executed
Expand Down
10 changes: 10 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
ConfigurationError,
ResponseError,
FileError,
ApiError,
} from './index';
import {
APIGatewayProxyEvent,
Expand Down Expand Up @@ -255,3 +256,12 @@ const fileError = new FileError('File not found', {
syscall: 'open',
});
expectType<FileError>(fileError);
expectType<string>(fileError.message);
expectType<string>(fileError.name);
expectType<string | undefined>(fileError.stack);

const apiError = new ApiError('Api error', 500);
expectType<ApiError>(apiError);
expectType<string>(apiError.message);
expectType<number | undefined>(apiError.code);
expectType<any>(apiError.detail);
12 changes: 12 additions & 0 deletions lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ class ResponseError extends Error {
}
}

class ApiError extends Error {
constructor(message, code, detail) {
super(message);
this.name = 'ApiError';
this.code = typeof code === 'number' ? code : 500;
if (detail !== undefined) {
this.detail = detail;
}
}
}

class FileError extends Error {
constructor(message, err) {
super(message);
Expand All @@ -55,5 +66,6 @@ module.exports = {
MethodError,
ConfigurationError,
ResponseError,
ApiError,
FileError,
};
19 changes: 13 additions & 6 deletions lib/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const UTILS = require('./utils.js');
const fs = require('fs'); // Require Node.js file system
const path = require('path'); // Require Node.js path
const compression = require('./compression'); // Require compression lib
const { ResponseError, FileError } = require('./errors'); // Require custom errors
const { ResponseError, FileError, ApiError } = require('./errors'); // Require custom errors

// Lazy load AWS S3 service
const S3 = () => require('./s3-service');
Expand Down Expand Up @@ -248,7 +248,7 @@ class RESPONSE {
// secure (Boolean): Marks the cookie to be used with HTTPS only
cookieString += opts.secure && opts.secure === true ? '; Secure' : '';

// sameSite (Boolean or String) Value of the SameSite Set-Cookie attribute
// sameSite (Boolean or String) Value of the "SameSite" Set-Cookie attribute
// see https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00#section-4.1.1.
cookieString +=
opts.sameSite !== undefined
Expand Down Expand Up @@ -594,10 +594,17 @@ class RESPONSE {

// Trigger API error
error(code, e, detail) {
detail = typeof code !== 'number' && e !== undefined ? e : detail;
e = typeof code !== 'number' ? code : e;
code = typeof code === 'number' ? code : undefined;
this.app.catchErrors(e, this, code, detail);
const message = typeof code !== 'number' ? code : e;
const statusCode = typeof code === 'number' ? code : undefined;
const errorDetail =
typeof code !== 'number' && e !== undefined ? e : detail;

const errorToSend =
typeof message === 'string'
? new ApiError(message, statusCode, errorDetail)
: message;

this.app.catchErrors(errorToSend, this, statusCode, errorDetail);
} // end error
} // end Response class

Expand Down