-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: export error handling function
- Loading branch information
Showing
5 changed files
with
210 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { isPlainObject } from 'lodash-es' | ||
import { AxiosError } from 'axios' | ||
|
||
/** | ||
* Handles errors received from the server. Parses the error into a more useful | ||
* format, places it in an exception and throws it. | ||
* See https://www.contentful.com/developers/docs/references/errors/ | ||
* for more details on the data received on the errorResponse.data property | ||
* and the expected error codes. | ||
* @private | ||
*/ | ||
export default function errorHandler(errorResponse: AxiosError): never { | ||
const { config, response } = errorResponse | ||
let errorName | ||
|
||
// Obscure the Management token | ||
if (config && config.headers && config.headers['Authorization']) { | ||
const token = `...${config.headers['Authorization'].substr(-5)}` | ||
config.headers['Authorization'] = `Bearer ${token}` | ||
} | ||
|
||
if (!isPlainObject(response) || !isPlainObject(config)) { | ||
throw errorResponse | ||
} | ||
|
||
const data = response?.data | ||
|
||
const errorData: { | ||
status?: number | ||
statusText?: string | ||
requestId?: string | ||
message: string | ||
details: Record<string, unknown> | ||
request?: Record<string, unknown> | ||
} = { | ||
status: response?.status, | ||
statusText: response?.statusText, | ||
message: '', | ||
details: {}, | ||
} | ||
|
||
if (isPlainObject(config)) { | ||
errorData.request = { | ||
url: config.url, | ||
headers: config.headers, | ||
method: config.method, | ||
payloadData: config.data, | ||
} | ||
} | ||
if (data && isPlainObject(data)) { | ||
if ('requestId' in data) { | ||
errorData.requestId = data.requestId || 'UNKNOWN' | ||
} | ||
if ('message' in data) { | ||
errorData.message = data.message || '' | ||
} | ||
if ('details' in data) { | ||
errorData.details = data.details || {} | ||
} | ||
if ('sys' in data) { | ||
if ('id' in data.sys) { | ||
errorName = data.sys.id | ||
} | ||
} | ||
} | ||
|
||
const error = new Error() | ||
error.name = | ||
errorName && errorName !== 'Unknown' ? errorName : `${response?.status} ${response?.statusText}` | ||
|
||
try { | ||
error.message = JSON.stringify(errorData, null, ' ') | ||
} catch { | ||
error.message = errorData?.message ?? '' | ||
} | ||
throw error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import errorHandler from '../../src/error-handler' | ||
import { errorMock } from './mocks' | ||
import { expect } from 'chai' | ||
import cloneDeep from 'lodash/cloneDeep' | ||
|
||
const error: any = cloneDeep(errorMock) | ||
|
||
describe('A errorHandler', () => { | ||
// Best case scenario where an error is a known and expected situation and the | ||
// server returns an error with a JSON payload with all the information possible | ||
it('Throws well formed error with details from server', async () => { | ||
error.response.data = { | ||
sys: { | ||
id: 'SpecificError', | ||
type: 'Error', | ||
}, | ||
message: 'datamessage', | ||
requestId: 'requestid', | ||
details: 'errordetails', | ||
} | ||
|
||
try { | ||
errorHandler(error) | ||
} catch (err) { | ||
const parsedMessage = JSON.parse(err.message) | ||
expect(err.name).equals('SpecificError', 'error name') | ||
expect(parsedMessage.request.url).equals('requesturl', 'request url') | ||
expect(parsedMessage.message).equals('datamessage', 'error payload message') | ||
expect(parsedMessage.requestId).equals('requestid', 'request id') | ||
expect(parsedMessage.details).equals('errordetails', 'error payload details') | ||
} | ||
}) | ||
|
||
// Second best case scenario, where we'll still get a JSON payload from the server | ||
// but only with an Unknown error type and no additional details | ||
it('Throws unknown error received from server', async () => { | ||
error.response.data = { | ||
sys: { | ||
id: 'Unknown', | ||
type: 'Error', | ||
}, | ||
requestId: 'requestid', | ||
} | ||
error.response.status = 500 | ||
error.response.statusText = 'Internal' | ||
|
||
try { | ||
errorHandler(error) | ||
} catch (err) { | ||
const parsedMessage = JSON.parse(err.message) | ||
expect(err.name).equals('500 Internal', 'error name defaults to status code and text') | ||
expect(parsedMessage.request.url).equals('requesturl', 'request url') | ||
expect(parsedMessage.requestId).equals('requestid', 'request id') | ||
} | ||
}) | ||
|
||
// Wurst case scenario, where we have no JSON payload and only HTTP status information | ||
it('Throws error without additional detail', async () => { | ||
error.response.status = 500 | ||
error.response.statusText = 'Everything is on fire' | ||
|
||
try { | ||
errorHandler(error) | ||
} catch (err) { | ||
const parsedMessage = JSON.parse(err.message) | ||
expect(err.name).equals( | ||
'500 Everything is on fire', | ||
'error name defaults to status code and text' | ||
) | ||
expect(parsedMessage.request.url).equals('requesturl', 'request url') | ||
} | ||
}) | ||
|
||
it('Obscures management token in any error message', async () => { | ||
const responseError: any = cloneDeep(errorMock) | ||
responseError.config.headers = { | ||
Authorization: 'Bearer secret-management-token', | ||
} | ||
|
||
try { | ||
errorHandler(responseError) | ||
} catch (err) { | ||
const parsedMessage = JSON.parse(err.message) | ||
expect(parsedMessage.request.headers.Authorization).equals( | ||
'Bearer ...token', | ||
'Obscures management token' | ||
) | ||
} | ||
|
||
const requestError: any = { | ||
config: { | ||
url: 'requesturl', | ||
headers: {}, | ||
}, | ||
data: {}, | ||
request: { | ||
status: 404, | ||
statusText: 'Not Found', | ||
}, | ||
} | ||
|
||
requestError.config.headers = { | ||
Authorization: 'Bearer secret-management-token', | ||
} | ||
|
||
try { | ||
errorHandler(requestError) | ||
} catch (err) { | ||
expect(err.config.headers.Authorization).equals( | ||
'Bearer ...token', | ||
'Obscures management token' | ||
) | ||
} | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters