From 9c3dc7cfc7f40ba11c7d526abfb64e2af80123ad Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Tue, 7 Nov 2023 16:20:38 -0800 Subject: [PATCH] Make error response paring safe --- src/lib/seam/connect/client.ts | 57 ++++++++++++++++++---------- test/seam/connect/http-error.test.ts | 10 ++--- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/lib/seam/connect/client.ts b/src/lib/seam/connect/client.ts index 33a01821..7b07000c 100644 --- a/src/lib/seam/connect/client.ts +++ b/src/lib/seam/connect/client.ts @@ -49,35 +49,54 @@ export const createClient = (options: ClientOptions): AxiosInstance => { return client } -const errorInterceptor = async (error: unknown): Promise => { - if (!isAxiosError(error)) { - throw error +const errorInterceptor = async (err: unknown): Promise => { + if (!isAxiosError(err)) { + throw err } - const headers = error.response?.headers + const status = err.response?.status + const headers = err.response?.headers const requestId = headers?.['seam-request-id'] ?? '' - const contentType = headers?.['Content-Type']?.toString() ?? '' - if (error.status === 401) { + if (status == null) throw err + + if (status === 401) { throw new SeamHttpUnauthorizedError(requestId) } - if (contentType.startsWith('application/json')) { - throw error - } + if (!isApiErrorResponse(err)) throw err - try { - const err = error as AxiosError - const { response } = err - if (response == null) throw err + const { response } = err + if (response == null) throw err - const { type } = response.data.error + const { type } = response.data.error - const args = [response.data.error, response.status, requestId] as const + const args = [response.data.error, status, requestId] as const + + if (type === 'invalid_input') throw new SeamHttpInvalidInputError(...args) + throw new SeamHttpApiError(...args) +} - if (type === 'invalid_input') throw new SeamHttpInvalidInputError(...args) - throw new SeamHttpApiError(...args) - } catch { - throw error +const isApiErrorResponse = ( + err: AxiosError, +): err is AxiosError => { + const headers = err.response?.headers + if (headers == null) return false + + const contentType = headers['content-type'] + if ( + typeof contentType === 'string' && + !contentType.startsWith('application/json') + ) { + return false } + + const data = err.response?.data as any + if (typeof data === 'object') { + return ( + 'error' in data && typeof data.error === 'object' && 'type' in data.error + ) + } + + return false } diff --git a/test/seam/connect/http-error.test.ts b/test/seam/connect/http-error.test.ts index ee491069..05920bcd 100644 --- a/test/seam/connect/http-error.test.ts +++ b/test/seam/connect/http-error.test.ts @@ -9,7 +9,7 @@ import { SeamHttpUnauthorizedError, } from '@seamapi/http/connect' -test('SeamHttp: throws AxiosError on non-json response', async (t) => { +test('SeamHttp: throws AxiosError on non-standard response', async (t) => { const { seed, endpoint, db } = await getTestServer(t) db.simulateWorkspaceOutage(seed.seed_workspace_1, { @@ -46,10 +46,10 @@ test('SeamHttp: throws SeamHttpUnauthorizedError if unauthorized', async (t) => t.is(err?.statusCode, 401) t.is(err?.code, 'unauthorized') - t.is(err?.requestId, 'request1') + t.true(err?.requestId?.startsWith('request')) }) -test('SeamHttp: throws SeamHttpApiError on json response', async (t) => { +test('SeamHttp: throws SeamHttpApiError on standard error response', async (t) => { const { seed, endpoint } = await getTestServer(t) const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { @@ -68,7 +68,7 @@ test('SeamHttp: throws SeamHttpApiError on json response', async (t) => { t.is(err?.statusCode, 404) t.is(err?.code, 'device_not_found') - t.is(err?.requestId, 'request1') + t.true(err?.requestId?.startsWith('request')) }) test('SeamHttp: throws SeamHttpInvalidInputError on invalid input', async (t) => { @@ -91,5 +91,5 @@ test('SeamHttp: throws SeamHttpInvalidInputError on invalid input', async (t) => t.is(err?.statusCode, 400) t.is(err?.code, 'invalid_input') - t.is(err?.requestId, 'request1') + t.true(err?.requestId?.startsWith('request')) })