Skip to content

Commit 6de4177

Browse files
authored
fix(error-handling-middleware): handle str error as ApiError
1 parent 94d04d8 commit 6de4177

File tree

7 files changed

+228
-149
lines changed

7 files changed

+228
-149
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1310,7 +1310,7 @@ api.use(errorHandler1,errorHandler2)
13101310

13111311
### Error Types
13121312

1313-
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.
1313+
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.
13141314

13151315
```javascript
13161316
const errorHandler = (err,req,res,next) => {

__tests__/errorHandling.unit.js

+179-133
Large diffs are not rendered by default.

index.d.ts

+7
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,13 @@ export declare class ResponseError extends Error {
369369
constructor(message: string, code: number);
370370
}
371371

372+
export declare class ApiError extends Error {
373+
constructor(message: string, code?: number, detail?: any);
374+
name: 'ApiError';
375+
code?: number;
376+
detail?: any;
377+
}
378+
372379
export declare class FileError extends Error {
373380
constructor(message: string, err: object);
374381
}

index.js

+6-9
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ const RESPONSE = require('./lib/response');
1010
const UTILS = require('./lib/utils');
1111
const LOGGER = require('./lib/logger');
1212
const S3 = () => require('./lib/s3-service');
13+
const { ConfigurationError, ApiError } = require('./lib/errors');
1314
const prettyPrint = require('./lib/prettyPrint');
14-
const { ConfigurationError } = require('./lib/errors');
1515

1616
class API {
1717
constructor(props) {
@@ -328,26 +328,21 @@ class API {
328328

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

334-
// Strip the headers, keep whitelist
335333
const strippedHeaders = Object.entries(response._headers).reduce(
336334
(acc, [headerName, value]) => {
337335
if (!this._errorHeaderWhitelist.includes(headerName.toLowerCase())) {
338336
return acc;
339337
}
340-
341338
return Object.assign(acc, { [headerName]: value });
342339
},
343340
{}
344341
);
345342

346343
response._headers = Object.assign(strippedHeaders, this._headers);
347-
348344
let message;
349345

350-
// Set the status code
351346
response.status(code ? code : this._errorStatus);
352347

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

360-
if (e instanceof Error) {
355+
const isApiError = e instanceof ApiError;
356+
357+
if (e instanceof Error && !isApiError) {
361358
message = e.message;
362359
if (this._logger.errorLogging) {
363360
this.log.fatal(message, info);
364361
}
365362
} else {
366-
message = e;
363+
message = e instanceof Error ? e.message : e;
367364
if (this._logger.errorLogging) {
368365
this.log.error(message, info);
369366
}
@@ -387,7 +384,7 @@ class API {
387384
if (rtn) response.send(rtn);
388385
r();
389386
});
390-
} // end for
387+
}
391388
}
392389

393390
// Throw standard error unless callback has already been executed

index.test-d.ts

+10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
ConfigurationError,
1818
ResponseError,
1919
FileError,
20+
ApiError,
2021
} from './index';
2122
import {
2223
APIGatewayProxyEvent,
@@ -255,3 +256,12 @@ const fileError = new FileError('File not found', {
255256
syscall: 'open',
256257
});
257258
expectType<FileError>(fileError);
259+
expectType<string>(fileError.message);
260+
expectType<string>(fileError.name);
261+
expectType<string | undefined>(fileError.stack);
262+
263+
const apiError = new ApiError('Api error', 500);
264+
expectType<ApiError>(apiError);
265+
expectType<string>(apiError.message);
266+
expectType<number | undefined>(apiError.code);
267+
expectType<any>(apiError.detail);

lib/errors.js

+12
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ class ResponseError extends Error {
4141
}
4242
}
4343

44+
class ApiError extends Error {
45+
constructor(message, code, detail) {
46+
super(message);
47+
this.name = 'ApiError';
48+
this.code = typeof code === 'number' ? code : 500;
49+
if (detail !== undefined) {
50+
this.detail = detail;
51+
}
52+
}
53+
}
54+
4455
class FileError extends Error {
4556
constructor(message, err) {
4657
super(message);
@@ -55,5 +66,6 @@ module.exports = {
5566
MethodError,
5667
ConfigurationError,
5768
ResponseError,
69+
ApiError,
5870
FileError,
5971
};

lib/response.js

+13-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const UTILS = require('./utils.js');
1010
const fs = require('fs'); // Require Node.js file system
1111
const path = require('path'); // Require Node.js path
1212
const compression = require('./compression'); // Require compression lib
13-
const { ResponseError, FileError } = require('./errors'); // Require custom errors
13+
const { ResponseError, FileError, ApiError } = require('./errors'); // Require custom errors
1414

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

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

595595
// Trigger API error
596596
error(code, e, detail) {
597-
detail = typeof code !== 'number' && e !== undefined ? e : detail;
598-
e = typeof code !== 'number' ? code : e;
599-
code = typeof code === 'number' ? code : undefined;
600-
this.app.catchErrors(e, this, code, detail);
597+
const message = typeof code !== 'number' ? code : e;
598+
const statusCode = typeof code === 'number' ? code : undefined;
599+
const errorDetail =
600+
typeof code !== 'number' && e !== undefined ? e : detail;
601+
602+
const errorToSend =
603+
typeof message === 'string'
604+
? new ApiError(message, statusCode, errorDetail)
605+
: message;
606+
607+
this.app.catchErrors(errorToSend, this, statusCode, errorDetail);
601608
} // end error
602609
} // end Response class
603610

0 commit comments

Comments
 (0)