-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresponse.ts
More file actions
212 lines (196 loc) · 5.55 KB
/
response.ts
File metadata and controls
212 lines (196 loc) · 5.55 KB
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
/**
* Set of simple shortcuts for creating standard JSON web responses
* for every HTTP status code, like respond.OK() or respond.NotFound().
* It also automatically creates corresponding error classes for HTTP errors,
* so you can easily throw a NotFoundError in your code.
* This interact with our provided server to allow errors that can be used
* by both application code and to build http response.
* @module
*/
import { STATUS_CODE, STATUS_TEXT } from '@std/http/status'
const defaultHeaderEntries: [string, string][] = [
['content-type', 'application/json'],
]
const defaultHeaders = new Headers(defaultHeaderEntries)
const json = (data?: unknown, init?: ResponseInit) => {
if (data == null) return new Response(null, init)
if (!init) {
init = { headers: defaultHeaders }
} else if (!init.headers) {
init.headers = defaultHeaders
} else {
if (!(init.headers instanceof Headers)) {
init.headers = new Headers(init.headers)
}
const h = init.headers as Headers
for (const entry of defaultHeaderEntries) {
h.set(entry[0], entry[1])
}
}
return new Response(JSON.stringify(data), init)
}
/**
* A custom error class that encapsulates a `Response` object.
* This is the base class for all HTTP errors created by the `respond` object.
*
* @example
* ```ts
* import { ResponseError, respond } from '@01edu/response';
*
* try {
* throw new respond.NotFoundError({ message: 'User not found' });
* } catch (e) {
* if (e instanceof ResponseError) {
* // e.response is a Response object
* }
* }
* ```
*/
export class ResponseError extends Error {
/** The `Response` object associated with the error. */
public response: Response
constructor(message: string, response: Response) {
super(message)
this.name = 'ResponseError'
this.response = response
}
}
type StatusCodeWithoutBody =
| 'Continue'
| 'SwitchingProtocols'
| 'Processing'
| 'EarlyHints'
| 'NoContent'
| 'ResetContent'
| 'NotModified'
const withoutBody = new Set([
100, // Continue
101, // SwitchingProtocols
102, // Processing
103, // EarlyHints
204, // NoContent
205, // ResetContent
304, // NotModified
])
type StatusNotErrors =
| 'OK'
| 'Created'
| 'Accepted'
| 'NonAuthoritativeInfo'
| 'NoContent'
| 'ResetContent'
| 'PartialContent'
| 'MultiStatus'
| 'AlreadyReported'
| 'IMUsed'
| 'MultipleChoices'
| 'MovedPermanently'
| 'Found'
| 'SeeOther'
| 'NotModified'
| 'UseProxy'
| 'TemporaryRedirect'
| 'PermanentRedirect'
const notErrors = new Set([
200, // OK
201, // Created
202, // Accepted
203, // NonAuthoritativeInfo
204, // NoContent
205, // ResetContent
206, // PartialContent
207, // MultiStatus
208, // AlreadyReported
226, // IMUsed
300, // MultipleChoices
301, // MovedPermanently
302, // Found
303, // SeeOther
304, // NotModified
305, // UseProxy
307, // TemporaryRedirect
308, // PermanentRedirect
])
type ErrorStatus = Exclude<
Exclude<keyof typeof STATUS_CODE, StatusCodeWithoutBody>,
StatusNotErrors
>
/**
* A collection of utility functions and error classes for creating standard `Response` objects.
*
* - For each HTTP status code, there is a corresponding function (e.g., `respond.OK()`, `respond.NotFound()`).
* - For HTTP error status codes, there is a corresponding error class (e.g., `respond.NotFoundError`).
*
* @example
* ```ts
* import { respond } from '@01edu/response';
*
* // Create a 200 OK response with a JSON body
* const okResponse = respond.OK({ message: 'Success!' });
*
* // Create a 404 Not Found response
* const notFoundResponse = respond.NotFound();
*
* // Throw a 400 Bad Request error
* throw new respond.BadRequestError({ error: 'Invalid input' });
* ```
*/
export const respond = Object.fromEntries([
...Object.entries(STATUS_CODE).map(([key, status]) => {
const statusText = STATUS_TEXT[status]
const defaultData = new TextEncoder().encode(
JSON.stringify({ message: statusText }) + '\n',
)
const makeResponse = withoutBody.has(status)
? (headers?: HeadersInit) =>
headers === undefined
? json(null, { headers: defaultHeaders, status, statusText })
: json(null, { headers, status, statusText })
: (data?: unknown, headers?: HeadersInit) =>
data === undefined
? new Response(defaultData, {
headers: defaultHeaders,
status,
statusText,
})
: json(data, { headers, status, statusText })
return [key, makeResponse]
}),
...Object.entries(STATUS_CODE)
.filter(([_, status]) => !withoutBody.has(status) && !notErrors.has(status))
.map(([key, status]) => {
const statusText = STATUS_TEXT[status]
const name = `${key}Error`
return [
name,
class extends ResponseError {
constructor(data?: unknown, headers?: HeadersInit) {
super(statusText, respond[key as ErrorStatus](data, headers))
this.name = name
}
},
]
}),
['ResponseError', ResponseError],
]) as (
& {
[k in Exclude<keyof typeof STATUS_CODE, StatusCodeWithoutBody>]: (
data?: unknown,
headers?: HeadersInit,
) => Response
}
& {
[k in Extract<keyof typeof STATUS_CODE, StatusCodeWithoutBody>]: (
headers?: HeadersInit,
) => Response
}
& {
[
k in `${Exclude<
Exclude<keyof typeof STATUS_CODE, StatusCodeWithoutBody>,
StatusNotErrors
>}Error`
]: new (data?: unknown, headers?: HeadersInit) => ResponseError
}
& { ResponseError: typeof ResponseError }
)