Skip to content

Commit

Permalink
feat(xrpc): keep reference to original cause when generatin new XRPCE…
Browse files Browse the repository at this point in the history
…rrors
  • Loading branch information
matthieusieben committed May 2, 2024
1 parent 734f785 commit 742cd7f
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 45 deletions.
48 changes: 47 additions & 1 deletion packages/xrpc/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ export enum ResponseType {
UpstreamTimeout = 504,
}

export function httpResponseCodeToEnum(status: number): ResponseType {
if (status in ResponseType) {
return status
} else if (status >= 100 && status < 200) {
return ResponseType.XRPCNotSupported
} else if (status >= 200 && status < 300) {
return ResponseType.Success
} else if (status >= 300 && status < 400) {
return ResponseType.XRPCNotSupported
} else if (status >= 400 && status < 500) {
return ResponseType.InvalidRequest
} else {
return ResponseType.InternalServerError
}
}

export const ResponseTypeNames = {
[ResponseType.InvalidResponse]: 'InvalidResponse',
[ResponseType.Success]: 'Success',
Expand Down Expand Up @@ -87,12 +103,40 @@ export class XRPCError extends Error {
public error?: string,
message?: string,
headers?: Headers,
options?: ErrorOptions,
) {
super(message || error || ResponseTypeStrings[status])
super(message || error || ResponseTypeStrings[status], options)
if (!this.error) {
this.error = ResponseTypeNames[status]
}
this.headers = headers

// Pre 2022 runtimes won't handle the "options" constructor argument
if (!this.cause && options?.cause) {
this.cause = options.cause
}
}

static from(cause: unknown, fallbackStatus?: ResponseType): XRPCError {
if (cause instanceof XRPCError) {
return cause
}

// Extract status code from "http-errors" like errors
const statusCode: unknown =
cause instanceof Error
? ('statusCode' in cause ? cause.statusCode : undefined) ??
('status' in cause ? cause.status : undefined)
: undefined

const status: ResponseType =
typeof statusCode === 'number'
? httpResponseCodeToEnum(statusCode)
: fallbackStatus ?? ResponseType.Unknown

const message = cause instanceof Error ? cause.message : String(cause)

return new XRPCError(status, undefined, message, undefined, { cause })
}
}

Expand All @@ -106,6 +150,8 @@ export class XRPCInvalidResponseError extends XRPCError {
ResponseType.InvalidResponse,
ResponseTypeStrings[ResponseType.InvalidResponse],
`The server gave an invalid response and may be out of date.`,
undefined,
{ cause: validationError },
)
}
}
54 changes: 16 additions & 38 deletions packages/xrpc/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,53 +381,31 @@ const toUint8Array: (value: unknown) => Uint8Array | undefined = Buffer
throw new TypeError(`Unsupported value type: ${typeof value}`)
}

export function httpResponseCodeToEnum(status: number): ResponseType {
let resCode: ResponseType
if (status in ResponseType) {
resCode = status
} else if (status >= 100 && status < 200) {
resCode = ResponseType.XRPCNotSupported
} else if (status >= 200 && status < 300) {
resCode = ResponseType.Success
} else if (status >= 300 && status < 400) {
resCode = ResponseType.XRPCNotSupported
} else if (status >= 400 && status < 500) {
resCode = ResponseType.InvalidRequest
} else {
resCode = ResponseType.InternalServerError
}
return resCode
}

export function httpResponseBodyParse(
mimeType: string | null,
data: ArrayBuffer | undefined,
): any {
if (mimeType) {
if (mimeType.includes('application/json') && data?.byteLength) {
try {
try {
if (mimeType) {
if (mimeType.includes('application/json')) {
const str = new TextDecoder().decode(data)
return jsonStringToLex(str)
} catch (e) {
throw new XRPCError(
ResponseType.InvalidResponse,
`Failed to parse response body: ${String(e)}`,
)
}
}
if (mimeType.startsWith('text/') && data?.byteLength) {
try {
if (mimeType.startsWith('text/')) {
return new TextDecoder().decode(data)
} catch (e) {
throw new XRPCError(
ResponseType.InvalidResponse,
`Failed to parse response body: ${String(e)}`,
)
}
}
if (data instanceof ArrayBuffer) {
return new Uint8Array(data)
}
return data
} catch (cause) {
throw new XRPCError(
ResponseType.InvalidResponse,
undefined,
`Failed to parse response body: ${String(cause)}`,
undefined,
{ cause },
)
}
if (data instanceof ArrayBuffer) {
return new Uint8Array(data)
}
return data
}
9 changes: 3 additions & 6 deletions packages/xrpc/src/xrpc-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import {
XRPCError,
XRPCInvalidResponseError,
XRPCResponse,
httpResponseCodeToEnum,
} from './types'
import {
constructMethodCallHeaders,
constructMethodCallUrl,
encodeMethodCallBody,
getMethodSchemaHTTPMethod,
httpResponseBodyParse,
httpResponseCodeToEnum,
isErrorResponseBody,
} from './util'
import { XrpcDispatcher, XrpcDispatcherOptions } from './xrpc-dispatcher'
Expand Down Expand Up @@ -114,11 +114,8 @@ export class XrpcClient {
throw new XRPCError(resCode)
}
}
} catch (cause) {
if (cause instanceof XRPCError) throw cause
const error = new XRPCError(ResponseType.Unknown, String(cause))
error.cause = cause
throw error
} catch (err) {
throw XRPCError.from(err)
}
}
}

0 comments on commit 742cd7f

Please sign in to comment.