diff --git a/src/lib/seam/connect/client.ts b/src/lib/seam/connect/client.ts index 7b07000c..f03a9c36 100644 --- a/src/lib/seam/connect/client.ts +++ b/src/lib/seam/connect/client.ts @@ -1,21 +1,11 @@ -import axios, { - type AxiosError, - type AxiosInstance, - type AxiosRequestConfig, - isAxiosError, -} from 'axios' +import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios' // @ts-expect-error https://github.com/svsool/axios-better-stacktrace/issues/12 import axiosBetterStacktrace from 'axios-better-stacktrace' import axiosRetry, { type AxiosRetry, exponentialDelay } from 'axios-retry' import { paramsSerializer } from 'lib/params-serializer.js' -import type { ApiErrorResponse } from './api-error-type.js' -import { - SeamHttpApiError, - SeamHttpInvalidInputError, - SeamHttpUnauthorizedError, -} from './seam-http-error.js' +import { errorInterceptor } from './error-interceptor.js' export type Client = AxiosInstance @@ -48,55 +38,3 @@ export const createClient = (options: ClientOptions): AxiosInstance => { return client } - -const errorInterceptor = async (err: unknown): Promise => { - if (!isAxiosError(err)) { - throw err - } - - const status = err.response?.status - const headers = err.response?.headers - const requestId = headers?.['seam-request-id'] ?? '' - - if (status == null) throw err - - if (status === 401) { - throw new SeamHttpUnauthorizedError(requestId) - } - - if (!isApiErrorResponse(err)) throw err - - const { response } = err - if (response == null) throw err - - const { type } = response.data.error - - const args = [response.data.error, status, requestId] as const - - if (type === 'invalid_input') throw new SeamHttpInvalidInputError(...args) - throw new SeamHttpApiError(...args) -} - -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/src/lib/seam/connect/error-interceptor.ts b/src/lib/seam/connect/error-interceptor.ts new file mode 100644 index 00000000..0ce64459 --- /dev/null +++ b/src/lib/seam/connect/error-interceptor.ts @@ -0,0 +1,62 @@ +import { type AxiosError, isAxiosError } from 'axios' + +import type { ApiErrorResponse } from './api-error-type.js' +import { + SeamHttpApiError, + SeamHttpInvalidInputError, + SeamHttpUnauthorizedError, +} from './seam-http-error.js' + +export const errorInterceptor = async (err: unknown): Promise => { + if (!isAxiosError(err)) throw err + + const status = err.response?.status + const headers = err.response?.headers + const requestId = headers?.['seam-request-id'] ?? '' + + if (status == null) throw err + + if (status === 401) { + throw new SeamHttpUnauthorizedError(requestId) + } + + if (!isApiErrorResponse(err)) throw err + + const { response } = err + if (response == null) throw err + + const { type } = response.data.error + + const args = [response.data.error, status, requestId] as const + + if (type === 'invalid_input') throw new SeamHttpInvalidInputError(...args) + throw new SeamHttpApiError(...args) +} + +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 + if (typeof data === 'object' && data != null) { + return ( + 'error' in data && + typeof data.error === 'object' && + data.error != null && + 'type' in data.error && + typeof data.error === 'string' + ) + } + + return false +} diff --git a/src/lib/seam/connect/index.ts b/src/lib/seam/connect/index.ts index 31a5e2c1..54bd1662 100644 --- a/src/lib/seam/connect/index.ts +++ b/src/lib/seam/connect/index.ts @@ -1,3 +1,4 @@ +export * from './error-interceptor.js' export * from './options.js' export * from './routes/index.js' export * from './seam-http.js' diff --git a/src/lib/seam/connect/seam-http-error.ts b/src/lib/seam/connect/seam-http-error.ts index b848d0fe..5d45eb8e 100644 --- a/src/lib/seam/connect/seam-http-error.ts +++ b/src/lib/seam/connect/seam-http-error.ts @@ -1,23 +1,5 @@ import type { ApiError } from './api-error-type.js' -export const isSeamHttpApiError = ( - error: unknown, -): error is SeamHttpApiError => { - return error instanceof SeamHttpApiError -} - -export const isSeamHttpInvalidInputError = ( - error: unknown, -): error is SeamHttpInvalidInputError => { - return error instanceof SeamHttpInvalidInputError -} - -export const isSeamHttpUnauthorizedError = ( - error: unknown, -): error is SeamHttpUnauthorizedError => { - return error instanceof SeamHttpUnauthorizedError -} - export class SeamHttpApiError extends Error { code: string statusCode: number @@ -36,6 +18,12 @@ export class SeamHttpApiError extends Error { } } +export const isSeamHttpApiError = ( + error: unknown, +): error is SeamHttpApiError => { + return error instanceof SeamHttpApiError +} + export class SeamHttpUnauthorizedError extends SeamHttpApiError { override code: 'unauthorized' override statusCode: 401 @@ -50,6 +38,12 @@ export class SeamHttpUnauthorizedError extends SeamHttpApiError { } } +export const isSeamHttpUnauthorizedError = ( + error: unknown, +): error is SeamHttpUnauthorizedError => { + return error instanceof SeamHttpUnauthorizedError +} + export class SeamHttpInvalidInputError extends SeamHttpApiError { override code: 'invalid_input' @@ -60,3 +54,9 @@ export class SeamHttpInvalidInputError extends SeamHttpApiError { this.code = 'invalid_input' } } + +export const isSeamHttpInvalidInputError = ( + error: unknown, +): error is SeamHttpInvalidInputError => { + return error instanceof SeamHttpInvalidInputError +}