diff --git a/package.json b/package.json index 95a6cba..cf8311e 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@types/http-errors": "^1.8.2", "@voiceflow/logger": "1.6.1", "@voiceflow/verror": "^1.1.0", + "axios": "0.27.2", "chai": "^4.3.4", "dotenv": "^10.0.0", "gaxios": "4.3.2", diff --git a/src/guards/error.ts b/src/guards/error.ts index 10ffd79..b451b5f 100644 --- a/src/guards/error.ts +++ b/src/guards/error.ts @@ -1,4 +1,6 @@ +import { Utils } from '@voiceflow/common'; import VError from '@voiceflow/verror'; +import { AxiosError } from 'axios'; import { GaxiosError } from 'gaxios'; import { isHttpError } from 'http-errors'; import { types } from 'util'; @@ -7,6 +9,8 @@ export const isJavascriptError = types.isNativeError; export const isGaxiosError = (err: unknown): err is GaxiosError => err instanceof GaxiosError; +export const isAxiosError = (err: unknown): err is AxiosError => err != null && Utils.object.hasProperty(err, 'isAxiosError') && err.isAxiosError === true; + export const isVError = (err: unknown): err is VError => err != null && err instanceof VError; export { isHttpError }; diff --git a/src/middlewares/exception/formatters/axiosError.ts b/src/middlewares/exception/formatters/axiosError.ts new file mode 100644 index 0000000..5973739 --- /dev/null +++ b/src/middlewares/exception/formatters/axiosError.ts @@ -0,0 +1,15 @@ +import { AxiosError } from 'axios'; + +import type { ExceptionFormatter } from '../types'; + +export const formatAxiosError: ExceptionFormatter = (err) => { + return { + statusCode: err.response?.status, + name: 'AxiosError', + message: err.message, + details: { + code: err.code, + statusText: err.response?.statusText, + }, + }; +}; diff --git a/src/middlewares/exception/formatters/index.ts b/src/middlewares/exception/formatters/index.ts index b7204f2..c74ad8f 100644 --- a/src/middlewares/exception/formatters/index.ts +++ b/src/middlewares/exception/formatters/index.ts @@ -1,7 +1,9 @@ import VError from '@voiceflow/verror'; +import { merge } from 'lodash'; import * as Guards from '../../../guards'; import type { ExceptionFormat } from '../types'; +import { formatAxiosError } from './axiosError'; import { formatGaxiosError } from './gaxiosError'; import { formatHttpError } from './httpError'; import { formatJavascriptError } from './jsError'; @@ -16,13 +18,11 @@ export const formatError = (err: unknown): ExceptionFormat => { if (Guards.isVError(err)) exception = mergeExceptionResult(exception, formatVError(err)); else if (Guards.isHttpError(err)) exception = mergeExceptionResult(exception, formatHttpError(err)); + else if (Guards.isAxiosError(err)) exception = mergeExceptionResult(exception, formatAxiosError(err)); else if (Guards.isGaxiosError(err)) exception = mergeExceptionResult(exception, formatGaxiosError(err)); else if (Guards.isJavascriptError(err)) exception = mergeExceptionResult(exception, formatJavascriptError(err)); return exception; }; -export const mergeExceptionResult = (baseException: ExceptionFormat, exception: Partial) => ({ - ...baseException, - ...exception, -}); +export const mergeExceptionResult = (baseException: ExceptionFormat, exception: Partial) => merge(baseException, exception); diff --git a/src/middlewares/exception/formatters/jsError.ts b/src/middlewares/exception/formatters/jsError.ts index c9adea8..4ee2204 100644 --- a/src/middlewares/exception/formatters/jsError.ts +++ b/src/middlewares/exception/formatters/jsError.ts @@ -1,7 +1,26 @@ +import { Utils } from '@voiceflow/common'; + import type { ExceptionFormatter } from '../types'; +const hasPropertyAsNumber = (obj: T, key: K): obj is T & Record => + Utils.object.hasProperty(obj, key) && typeof obj[key] === 'number'; + +const buildStatusGuard = (prop: K) => (err: T): err is T & Record => + hasPropertyAsNumber(err, prop) && err[prop] >= 100 && err[prop] <= 599; + +const hasStatusCode = buildStatusGuard('statusCode'); + +const hasStatus = buildStatusGuard('status'); + +export const extractStatusCodeFromError = (err: Error): number | undefined => { + if (hasStatusCode(err)) return err.statusCode; + if (hasStatus(err)) return err.status; + return undefined; +}; + export const formatJavascriptError: ExceptionFormatter = (err) => { return { + statusCode: extractStatusCodeFromError(err), name: err.name, message: err.message, }; diff --git a/yarn.lock b/yarn.lock index f92e8b0..cf3a334 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1534,6 +1534,14 @@ axios@*: dependencies: follow-redirects "^1.14.0" +axios@0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -3515,6 +3523,11 @@ follow-redirects@^1.14.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== +follow-redirects@^1.14.9: + version "1.15.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"