Skip to content

Commit

Permalink
Make error response paring safe
Browse files Browse the repository at this point in the history
  • Loading branch information
razor-x committed Nov 8, 2023
1 parent cc97e56 commit 9c3dc7c
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 24 deletions.
57 changes: 38 additions & 19 deletions src/lib/seam/connect/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,35 +49,54 @@ export const createClient = (options: ClientOptions): AxiosInstance => {
return client
}

const errorInterceptor = async (error: unknown): Promise<void> => {
if (!isAxiosError(error)) {
throw error
const errorInterceptor = async (err: unknown): Promise<void> => {
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<ApiErrorResponse>
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<ApiErrorResponse> => {
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
}
10 changes: 5 additions & 5 deletions test/seam/connect/http-error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down Expand Up @@ -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, {
Expand All @@ -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) => {
Expand All @@ -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'))
})

0 comments on commit 9c3dc7c

Please sign in to comment.