-
Notifications
You must be signed in to change notification settings - Fork 554
/
types.ts
190 lines (163 loc) · 5.73 KB
/
types.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import { z } from 'zod'
import { ValidationError } from '@atproto/lexicon'
export type QueryParams = Record<string, any>
export type HeadersMap = Record<string, string>
export type {
/** @deprecated not to be confused with the WHATWG Headers constructor */
HeadersMap as Headers,
}
export type Gettable<T> = T | (() => T)
export interface CallOptions {
encoding?: string
signal?: AbortSignal
headers?: HeadersMap
}
export const errorResponseBody = z.object({
error: z.string().optional(),
message: z.string().optional(),
})
export type ErrorResponseBody = z.infer<typeof errorResponseBody>
export enum ResponseType {
Unknown = 1,
InvalidResponse = 2,
Success = 200,
InvalidRequest = 400,
AuthRequired = 401,
Forbidden = 403,
XRPCNotSupported = 404,
NotAcceptable = 406,
PayloadTooLarge = 413,
UnsupportedMediaType = 415,
RateLimitExceeded = 429,
InternalServerError = 500,
MethodNotImplemented = 501,
UpstreamFailure = 502,
NotEnoughResources = 503,
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.Unknown]: 'Unknown',
[ResponseType.InvalidResponse]: 'InvalidResponse',
[ResponseType.Success]: 'Success',
[ResponseType.InvalidRequest]: 'InvalidRequest',
[ResponseType.AuthRequired]: 'AuthenticationRequired',
[ResponseType.Forbidden]: 'Forbidden',
[ResponseType.XRPCNotSupported]: 'XRPCNotSupported',
[ResponseType.PayloadTooLarge]: 'PayloadTooLarge',
[ResponseType.UnsupportedMediaType]: 'UnsupportedMediaType',
[ResponseType.RateLimitExceeded]: 'RateLimitExceeded',
[ResponseType.InternalServerError]: 'InternalServerError',
[ResponseType.MethodNotImplemented]: 'MethodNotImplemented',
[ResponseType.UpstreamFailure]: 'UpstreamFailure',
[ResponseType.NotEnoughResources]: 'NotEnoughResources',
[ResponseType.UpstreamTimeout]: 'UpstreamTimeout',
}
export function httpResponseCodeToName(status: number): string {
return ResponseTypeNames[httpResponseCodeToEnum(status)]
}
export const ResponseTypeStrings = {
[ResponseType.Unknown]: 'Unknown',
[ResponseType.InvalidResponse]: 'Invalid Response',
[ResponseType.Success]: 'Success',
[ResponseType.InvalidRequest]: 'Invalid Request',
[ResponseType.AuthRequired]: 'Authentication Required',
[ResponseType.Forbidden]: 'Forbidden',
[ResponseType.XRPCNotSupported]: 'XRPC Not Supported',
[ResponseType.PayloadTooLarge]: 'Payload Too Large',
[ResponseType.UnsupportedMediaType]: 'Unsupported Media Type',
[ResponseType.RateLimitExceeded]: 'Rate Limit Exceeded',
[ResponseType.InternalServerError]: 'Internal Server Error',
[ResponseType.MethodNotImplemented]: 'Method Not Implemented',
[ResponseType.UpstreamFailure]: 'Upstream Failure',
[ResponseType.NotEnoughResources]: 'Not Enough Resources',
[ResponseType.UpstreamTimeout]: 'Upstream Timeout',
}
export function httpResponseCodeToString(status: number): string {
return ResponseTypeStrings[httpResponseCodeToEnum(status)]
}
export class XRPCResponse {
success = true
constructor(
public data: any,
public headers: HeadersMap,
) {}
}
export class XRPCError extends Error {
success = false
public status: ResponseType
constructor(
statusCode: number,
public error: string = httpResponseCodeToName(statusCode),
message?: string,
public headers?: HeadersMap,
options?: ErrorOptions,
) {
super(message || error || httpResponseCodeToString(statusCode), options)
this.status = httpResponseCodeToEnum(statusCode)
// Pre 2022 runtimes won't handle the "options" constructor argument
const cause = options?.cause
if (this.cause === undefined && cause !== undefined) {
this.cause = cause
}
}
static from(cause: unknown, fallbackStatus?: ResponseType): XRPCError {
if (cause instanceof XRPCError) {
return cause
}
// Type cast the cause to an Error if it is one
const causeErr = cause instanceof Error ? cause : undefined
// Try and find a Response object in the cause
const causeResponse: Response | undefined =
cause instanceof Response
? cause
: cause?.['response'] instanceof Response
? cause['response']
: undefined
const statusCode: unknown =
// Extract status code from "http-errors" like errors
causeErr?.['statusCode'] ??
causeErr?.['status'] ??
// Use the status code from the response object as fallback
causeResponse?.status
// Convert the status code to a ResponseType
const status: ResponseType =
typeof statusCode === 'number'
? httpResponseCodeToEnum(statusCode)
: fallbackStatus ?? ResponseType.Unknown
const message = causeErr?.message ?? String(cause)
const headers = causeResponse
? Object.fromEntries(causeResponse.headers.entries())
: undefined
return new XRPCError(status, undefined, message, headers, { cause })
}
}
export class XRPCInvalidResponseError extends XRPCError {
constructor(
public lexiconNsid: string,
public validationError: ValidationError,
public responseBody: unknown,
) {
super(
ResponseType.InvalidResponse,
ResponseTypeStrings[ResponseType.InvalidResponse],
`The server gave an invalid response and may be out of date.`,
undefined,
{ cause: validationError },
)
}
}