@atproto/lex-client 0.0.17 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -0
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +18 -19
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +62 -37
- package/dist/errors.js.map +1 -1
- package/dist/response.d.ts +7 -3
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +37 -35
- package/dist/response.js.map +1 -1
- package/dist/xrpc.js +1 -1
- package/dist/xrpc.js.map +1 -1
- package/package.json +3 -3
- package/src/client.ts +2 -0
- package/src/errors.test.ts +243 -32
- package/src/errors.ts +86 -49
- package/src/response.ts +55 -51
- package/src/xrpc.test.ts +76 -76
- package/src/xrpc.ts +1 -3
package/src/response.ts
CHANGED
|
@@ -13,8 +13,7 @@ import {
|
|
|
13
13
|
XrpcAuthenticationError,
|
|
14
14
|
XrpcInvalidResponseError,
|
|
15
15
|
XrpcResponseError,
|
|
16
|
-
|
|
17
|
-
isXrpcErrorPayload,
|
|
16
|
+
XrpcResponseValidationError,
|
|
18
17
|
} from './errors.js'
|
|
19
18
|
import {
|
|
20
19
|
EncodingString,
|
|
@@ -58,7 +57,7 @@ export type XrpcResponseBody<M extends Procedure | Query> =
|
|
|
58
57
|
M['output'] extends Payload<infer TEncoding, infer TSchema>
|
|
59
58
|
? TEncoding extends string
|
|
60
59
|
? InferBodyType<TEncoding, TSchema>
|
|
61
|
-
: undefined
|
|
60
|
+
: undefined | LexValue | Uint8Array
|
|
62
61
|
: never
|
|
63
62
|
|
|
64
63
|
/**
|
|
@@ -75,7 +74,9 @@ export type XrpcResponsePayload<M extends Procedure | Query> =
|
|
|
75
74
|
encoding: InferEncodingType<TEncoding>
|
|
76
75
|
body: InferBodyType<TEncoding, TSchema>
|
|
77
76
|
}
|
|
78
|
-
:
|
|
77
|
+
: // If the schema does not specify an output encoding, anything could be
|
|
78
|
+
// returned, including no payload at all (undefined).
|
|
79
|
+
undefined | { body: LexValue | Uint8Array; encoding: string }
|
|
79
80
|
: never
|
|
80
81
|
|
|
81
82
|
export type XrpcResponseOptions = {
|
|
@@ -170,7 +171,7 @@ export class XrpcResponse<M extends Procedure | Query>
|
|
|
170
171
|
* {@link XrpcResponseError.matchesSchemaErrors} to narrow the error type based on
|
|
171
172
|
* the method's declared error schema. This can be narrowed further as a
|
|
172
173
|
* {@link XrpcAuthenticationError} if the error is an authentication error.
|
|
173
|
-
* @throws {
|
|
174
|
+
* @throws {XrpcInvalidResponseError} when the response is not a valid XRPC
|
|
174
175
|
* response, or if the response does not conform to the method's schema.
|
|
175
176
|
*/
|
|
176
177
|
static async fromFetchResponse<const M extends Procedure | Query>(
|
|
@@ -182,63 +183,62 @@ export class XrpcResponse<M extends Procedure | Query>
|
|
|
182
183
|
// Since nothing should cause an exception before "readPayload" is
|
|
183
184
|
// called, we can safely not use a try/finally here.
|
|
184
185
|
|
|
185
|
-
//
|
|
186
|
-
if (response.status
|
|
187
|
-
// Always parse json for error responses
|
|
186
|
+
// Always turn 4xx/5xx responses into XrpcResponseError
|
|
187
|
+
if (response.status >= 400) {
|
|
188
188
|
const payload = await readPayload(method, response, {
|
|
189
|
-
|
|
189
|
+
// Always parse errors in non-strict mode
|
|
190
|
+
parse: { strict: false },
|
|
190
191
|
})
|
|
191
192
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
throw response.status === 401
|
|
195
|
-
? new XrpcAuthenticationError<M>(method, response, payload)
|
|
196
|
-
: new XrpcResponseError<M>(method, response, payload)
|
|
193
|
+
if (response.status === 401) {
|
|
194
|
+
throw new XrpcAuthenticationError<M>(method, response, payload)
|
|
197
195
|
}
|
|
198
196
|
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
throw new XrpcResponseError<M>(method, response, payload)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// @NOTE redirect is set to 'follow', so we shouldn't get 3xx responses here
|
|
201
|
+
if (response.status < 200 || response.status >= 300) {
|
|
202
|
+
await response.body?.cancel()
|
|
203
|
+
|
|
204
|
+
throw new XrpcInvalidResponseError(
|
|
201
205
|
method,
|
|
202
206
|
response,
|
|
203
|
-
|
|
204
|
-
response.status
|
|
205
|
-
? 'Upstream server encountered an error'
|
|
206
|
-
: response.status >= 400
|
|
207
|
-
? 'Invalid response payload'
|
|
208
|
-
: 'Invalid response status code',
|
|
207
|
+
undefined,
|
|
208
|
+
`Unexpected status code ${response.status}`,
|
|
209
209
|
)
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
const payload = await readPayload(method, response, {
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
// Parse response if there is a schema, or if the encoding is
|
|
214
|
+
// "application/json"
|
|
215
|
+
parse:
|
|
216
|
+
method.output.schema || method.output.encoding === CONTENT_TYPE_JSON
|
|
217
|
+
? { strict: options?.strictResponseProcessing ?? true }
|
|
218
|
+
: // If there is no declared output encoding, we'll parse the output (in loose mode)
|
|
219
|
+
method.output.encoding == null
|
|
220
|
+
? { strict: false }
|
|
221
|
+
: false,
|
|
217
222
|
})
|
|
218
223
|
|
|
224
|
+
if (!method.output.matchesEncoding(payload?.encoding)) {
|
|
225
|
+
throw new XrpcInvalidResponseError(
|
|
226
|
+
method,
|
|
227
|
+
response,
|
|
228
|
+
payload,
|
|
229
|
+
`Expected ${stringifyEncoding(method.output.encoding)} response (got ${stringifyEncoding(payload?.encoding)})`,
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
219
233
|
// Response is successful (2xx). Validate payload (data and encoding) against schema.
|
|
220
|
-
if (method.output.encoding
|
|
221
|
-
//
|
|
222
|
-
if
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
} else {
|
|
231
|
-
// Schema expects a payload
|
|
232
|
-
if (!payload || !method.output.matchesEncoding(payload.encoding)) {
|
|
233
|
-
throw new XrpcUpstreamError(
|
|
234
|
-
method,
|
|
235
|
-
response,
|
|
236
|
-
payload,
|
|
237
|
-
payload
|
|
238
|
-
? `Expected ${method.output.encoding} response, got ${payload.encoding}`
|
|
239
|
-
: `Expected non-empty response with content-type ${method.output.encoding}`,
|
|
240
|
-
)
|
|
241
|
-
}
|
|
234
|
+
if (method.output.encoding != null) {
|
|
235
|
+
// If the schema specifies an output, verify that the response properly
|
|
236
|
+
// matches the expected format (encoding and schema, if present). If no
|
|
237
|
+
// output is specified, any payload could be returned.
|
|
238
|
+
|
|
239
|
+
// Needed for type safety. Should never happen since matchesEncoding()
|
|
240
|
+
// should return not succeed if there is a schema encoding but no payload.
|
|
241
|
+
if (!payload) throw new Error('Expected payload')
|
|
242
242
|
|
|
243
243
|
// Assert valid response body.
|
|
244
244
|
if (method.output.schema && options?.validateResponse !== false) {
|
|
@@ -247,7 +247,7 @@ export class XrpcResponse<M extends Procedure | Query>
|
|
|
247
247
|
})
|
|
248
248
|
|
|
249
249
|
if (!result.success) {
|
|
250
|
-
throw new
|
|
250
|
+
throw new XrpcResponseValidationError(
|
|
251
251
|
method,
|
|
252
252
|
response,
|
|
253
253
|
payload,
|
|
@@ -343,12 +343,16 @@ async function readPayload(
|
|
|
343
343
|
} catch (cause) {
|
|
344
344
|
const message = 'Unable to parse response payload'
|
|
345
345
|
const messageDetail = cause instanceof TypeError ? cause.message : undefined
|
|
346
|
-
throw new
|
|
346
|
+
throw new XrpcInvalidResponseError(
|
|
347
347
|
method,
|
|
348
348
|
response,
|
|
349
|
-
|
|
349
|
+
undefined,
|
|
350
350
|
messageDetail ? `${message}: ${messageDetail}` : message,
|
|
351
351
|
{ cause },
|
|
352
352
|
)
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
|
+
|
|
356
|
+
function stringifyEncoding(encoding: string | undefined) {
|
|
357
|
+
return encoding ? `"${encoding}"` : 'no payload'
|
|
358
|
+
}
|
package/src/xrpc.test.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
XrpcInternalError,
|
|
10
10
|
XrpcInvalidResponseError,
|
|
11
11
|
XrpcResponseError,
|
|
12
|
-
|
|
12
|
+
XrpcResponseValidationError,
|
|
13
13
|
} from './errors.js'
|
|
14
14
|
import { XrpcResponse } from './response.js'
|
|
15
15
|
import { xrpc, xrpcSafe } from './xrpc.js'
|
|
@@ -221,6 +221,17 @@ describe(xrpc, () => {
|
|
|
221
221
|
expect(response.success).toBe(true)
|
|
222
222
|
expect(response.body).toEqual({ value: 'ok' })
|
|
223
223
|
})
|
|
224
|
+
|
|
225
|
+
it('ignores output for no-output queries', async () => {
|
|
226
|
+
const fetchHandler = vi.fn<FetchHandler>(async () => {
|
|
227
|
+
return Response.json({ unexpected: 'data' })
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
const response = await xrpc(fetchHandler, testNoOutputQuery)
|
|
231
|
+
|
|
232
|
+
expect(response.success).toBe(true)
|
|
233
|
+
expect(response.body).toStrictEqual({ unexpected: 'data' })
|
|
234
|
+
})
|
|
224
235
|
})
|
|
225
236
|
|
|
226
237
|
describe('error handling', () => {
|
|
@@ -271,7 +282,7 @@ describe(xrpc, () => {
|
|
|
271
282
|
).rejects.toSatisfy((err) => {
|
|
272
283
|
assert(err instanceof XrpcResponseError)
|
|
273
284
|
expect(err.status).toBe(400)
|
|
274
|
-
expect(err.
|
|
285
|
+
expect(err.toJSON()).toEqual({
|
|
275
286
|
error: 'TestError',
|
|
276
287
|
message: 'bad request',
|
|
277
288
|
})
|
|
@@ -297,7 +308,7 @@ describe(xrpc, () => {
|
|
|
297
308
|
})
|
|
298
309
|
})
|
|
299
310
|
|
|
300
|
-
it('throws
|
|
311
|
+
it('throws XrpcResponseError for non-XRPC error response', async () => {
|
|
301
312
|
const fetchHandler = vi.fn<FetchHandler>(async () => {
|
|
302
313
|
return new Response('Not Found', {
|
|
303
314
|
status: 404,
|
|
@@ -308,13 +319,13 @@ describe(xrpc, () => {
|
|
|
308
319
|
await expect(
|
|
309
320
|
xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
|
|
310
321
|
).rejects.toSatisfy((err) => {
|
|
311
|
-
assert(err instanceof
|
|
312
|
-
expect(err.message).toBe('
|
|
322
|
+
assert(err instanceof XrpcResponseError)
|
|
323
|
+
expect(err.message).toBe('Upstream server responded with a 404 error')
|
|
313
324
|
return true
|
|
314
325
|
})
|
|
315
326
|
})
|
|
316
327
|
|
|
317
|
-
it('throws
|
|
328
|
+
it('throws XrpcResponseError for 500 without valid error payload', async () => {
|
|
318
329
|
const fetchHandler = vi.fn<FetchHandler>(async () => {
|
|
319
330
|
return new Response('Internal Server Error', {
|
|
320
331
|
status: 500,
|
|
@@ -325,8 +336,8 @@ describe(xrpc, () => {
|
|
|
325
336
|
await expect(
|
|
326
337
|
xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
|
|
327
338
|
).rejects.toSatisfy((err) => {
|
|
328
|
-
assert(err instanceof
|
|
329
|
-
expect(err.message).toBe('Upstream server
|
|
339
|
+
assert(err instanceof XrpcResponseError)
|
|
340
|
+
expect(err.message).toBe('Upstream server responded with a 500 error')
|
|
330
341
|
return true
|
|
331
342
|
})
|
|
332
343
|
})
|
|
@@ -344,7 +355,7 @@ describe(xrpc, () => {
|
|
|
344
355
|
).rejects.toSatisfy((err) => {
|
|
345
356
|
assert(err instanceof XrpcResponseError)
|
|
346
357
|
expect(err.status).toBe(502)
|
|
347
|
-
expect(err.
|
|
358
|
+
expect(err.toJSON()).toEqual({
|
|
348
359
|
error: 'ServerError',
|
|
349
360
|
message: 'Something went wrong',
|
|
350
361
|
})
|
|
@@ -354,7 +365,7 @@ describe(xrpc, () => {
|
|
|
354
365
|
})
|
|
355
366
|
|
|
356
367
|
describe('invalid response errors', () => {
|
|
357
|
-
it('throws
|
|
368
|
+
it('throws XrpcResponseValidationError when response body fails validation', async () => {
|
|
358
369
|
// Schema expects { value: string } but we return { value: 123 }
|
|
359
370
|
const fetchHandler = vi.fn<FetchHandler>(async () => {
|
|
360
371
|
return Response.json({ value: 123 })
|
|
@@ -363,14 +374,14 @@ describe(xrpc, () => {
|
|
|
363
374
|
await expect(
|
|
364
375
|
xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
|
|
365
376
|
).rejects.toSatisfy((err) => {
|
|
366
|
-
assert(err instanceof
|
|
367
|
-
expect(err).toBeInstanceOf(
|
|
377
|
+
assert(err instanceof XrpcResponseValidationError)
|
|
378
|
+
expect(err).toBeInstanceOf(XrpcInvalidResponseError)
|
|
368
379
|
expect(err.cause).toBeInstanceOf(Error)
|
|
369
380
|
return true
|
|
370
381
|
})
|
|
371
382
|
})
|
|
372
383
|
|
|
373
|
-
it('throws
|
|
384
|
+
it('throws XrpcInvalidResponseError when response has wrong content-type', async () => {
|
|
374
385
|
const fetchHandler = vi.fn<FetchHandler>(async () => {
|
|
375
386
|
return new Response('binary data', {
|
|
376
387
|
status: 200,
|
|
@@ -381,7 +392,7 @@ describe(xrpc, () => {
|
|
|
381
392
|
await expect(
|
|
382
393
|
xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
|
|
383
394
|
).rejects.toSatisfy((err) => {
|
|
384
|
-
assert(err instanceof
|
|
395
|
+
assert(err instanceof XrpcInvalidResponseError)
|
|
385
396
|
expect(err.message).toContain('application/json')
|
|
386
397
|
return true
|
|
387
398
|
})
|
|
@@ -408,7 +419,7 @@ describe(xrpc, () => {
|
|
|
408
419
|
})
|
|
409
420
|
|
|
410
421
|
describe('response payload parsing', () => {
|
|
411
|
-
it('throws
|
|
422
|
+
it('throws XrpcInvalidResponseError when error response body cannot be parsed', async () => {
|
|
412
423
|
const fetchHandler = vi.fn<FetchHandler>(async () => {
|
|
413
424
|
return new Response('not valid json', {
|
|
414
425
|
status: 400,
|
|
@@ -419,7 +430,7 @@ describe(xrpc, () => {
|
|
|
419
430
|
await expect(
|
|
420
431
|
xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
|
|
421
432
|
).rejects.toSatisfy((err) => {
|
|
422
|
-
assert(err instanceof
|
|
433
|
+
assert(err instanceof XrpcInvalidResponseError)
|
|
423
434
|
expect(err.message).toMatch('Unable to parse response payload')
|
|
424
435
|
assert(err.cause instanceof Error)
|
|
425
436
|
expect(err.cause.message).toContain('Unexpected token')
|
|
@@ -427,7 +438,7 @@ describe(xrpc, () => {
|
|
|
427
438
|
})
|
|
428
439
|
})
|
|
429
440
|
|
|
430
|
-
it('throws
|
|
441
|
+
it('throws XrpcInvalidResponseError when success response body cannot be parsed', async () => {
|
|
431
442
|
const fetchHandler = vi.fn<FetchHandler>(async () => {
|
|
432
443
|
return new Response('not valid json', {
|
|
433
444
|
status: 200,
|
|
@@ -438,7 +449,7 @@ describe(xrpc, () => {
|
|
|
438
449
|
await expect(
|
|
439
450
|
xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
|
|
440
451
|
).rejects.toSatisfy((err) => {
|
|
441
|
-
assert(err instanceof
|
|
452
|
+
assert(err instanceof XrpcInvalidResponseError)
|
|
442
453
|
expect(err.message).toMatch('Unable to parse response payload')
|
|
443
454
|
assert(err.cause instanceof Error)
|
|
444
455
|
expect(err.cause.message).toContain('Unexpected token')
|
|
@@ -446,21 +457,7 @@ describe(xrpc, () => {
|
|
|
446
457
|
})
|
|
447
458
|
})
|
|
448
459
|
|
|
449
|
-
it('throws
|
|
450
|
-
const fetchHandler = vi.fn<FetchHandler>(async () => {
|
|
451
|
-
return Response.json({ unexpected: 'data' })
|
|
452
|
-
})
|
|
453
|
-
|
|
454
|
-
await expect(xrpc(fetchHandler, testNoOutputQuery)).rejects.toSatisfy(
|
|
455
|
-
(err) => {
|
|
456
|
-
assert(err instanceof XrpcUpstreamError)
|
|
457
|
-
expect(err.message).toContain('no body')
|
|
458
|
-
return true
|
|
459
|
-
},
|
|
460
|
-
)
|
|
461
|
-
})
|
|
462
|
-
|
|
463
|
-
it('throws XrpcUpstreamError when schema expects payload but response is empty', async () => {
|
|
460
|
+
it('throws XrpcInvalidResponseError when schema expects payload but response is empty', async () => {
|
|
464
461
|
const fetchHandler = vi.fn<FetchHandler>(async () => {
|
|
465
462
|
return new Response(null, { status: 200 })
|
|
466
463
|
})
|
|
@@ -468,8 +465,8 @@ describe(xrpc, () => {
|
|
|
468
465
|
await expect(
|
|
469
466
|
xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
|
|
470
467
|
).rejects.toSatisfy((err) => {
|
|
471
|
-
assert(err instanceof
|
|
472
|
-
expect(err.message).toContain('
|
|
468
|
+
assert(err instanceof XrpcInvalidResponseError)
|
|
469
|
+
expect(err.message).toContain('got no payload')
|
|
473
470
|
return true
|
|
474
471
|
})
|
|
475
472
|
})
|
|
@@ -516,15 +513,15 @@ describe(xrpc, () => {
|
|
|
516
513
|
})
|
|
517
514
|
|
|
518
515
|
describe('non-2xx non-4xx/5xx responses', () => {
|
|
519
|
-
it('throws
|
|
516
|
+
it('throws XrpcInvalidResponseError for 3xx status codes', async () => {
|
|
520
517
|
const fetchHandler: FetchHandler = async () =>
|
|
521
518
|
Response.json({ value: 'redirect' }, { status: 302 })
|
|
522
519
|
|
|
523
520
|
await expect(
|
|
524
521
|
xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
|
|
525
522
|
).rejects.toSatisfy((err) => {
|
|
526
|
-
assert(err instanceof
|
|
527
|
-
expect(err.message).toBe('
|
|
523
|
+
assert(err instanceof XrpcInvalidResponseError)
|
|
524
|
+
expect(err.message).toBe('Unexpected status code 302')
|
|
528
525
|
return true
|
|
529
526
|
})
|
|
530
527
|
})
|
|
@@ -606,8 +603,8 @@ describe(xrpc, () => {
|
|
|
606
603
|
await expect(
|
|
607
604
|
xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
|
|
608
605
|
).rejects.toSatisfy((err) => {
|
|
609
|
-
assert(err instanceof
|
|
610
|
-
expect(err).toBeInstanceOf(
|
|
606
|
+
assert(err instanceof XrpcResponseValidationError)
|
|
607
|
+
expect(err).toBeInstanceOf(XrpcInvalidResponseError)
|
|
611
608
|
return true
|
|
612
609
|
})
|
|
613
610
|
})
|
|
@@ -660,7 +657,7 @@ describe(xrpc, () => {
|
|
|
660
657
|
await expect(
|
|
661
658
|
xrpc(validWithFloatHandler, testQuery, { params: { limit: 10 } }),
|
|
662
659
|
).rejects.toSatisfy((err) => {
|
|
663
|
-
assert(err instanceof
|
|
660
|
+
assert(err instanceof XrpcInvalidResponseError)
|
|
664
661
|
expect(err.message).toMatch('Unable to parse response payload')
|
|
665
662
|
expect(err.cause).toBeInstanceOf(TypeError)
|
|
666
663
|
return true
|
|
@@ -683,7 +680,7 @@ describe(xrpc, () => {
|
|
|
683
680
|
strictResponseProcessing: true,
|
|
684
681
|
}),
|
|
685
682
|
).rejects.toSatisfy((err) => {
|
|
686
|
-
assert(err instanceof
|
|
683
|
+
assert(err instanceof XrpcInvalidResponseError)
|
|
687
684
|
expect(err.message).toMatch('Unable to parse response payload')
|
|
688
685
|
expect(err.cause).toBeInstanceOf(TypeError)
|
|
689
686
|
return true
|
|
@@ -693,8 +690,8 @@ describe(xrpc, () => {
|
|
|
693
690
|
it('rejects error response with invalid lex data by default', async () => {
|
|
694
691
|
await expect(xrpc(errorWithFloatHandler, testQuery)).rejects.toSatisfy(
|
|
695
692
|
(err) => {
|
|
696
|
-
assert(err instanceof
|
|
697
|
-
expect(err.message).
|
|
693
|
+
assert(err instanceof XrpcResponseError)
|
|
694
|
+
expect(err.message).toBe('test-error-description')
|
|
698
695
|
return true
|
|
699
696
|
},
|
|
700
697
|
)
|
|
@@ -710,7 +707,7 @@ describe(xrpc, () => {
|
|
|
710
707
|
// Error response is still an error, but it should be parsed successfully
|
|
711
708
|
assert(err instanceof XrpcResponseError)
|
|
712
709
|
expect(err.status).toBe(400)
|
|
713
|
-
expect(err.payload
|
|
710
|
+
expect(err.payload?.body).toEqual({
|
|
714
711
|
error: 'TestError',
|
|
715
712
|
message: 'test-error-description',
|
|
716
713
|
extra: 1.5,
|
|
@@ -731,8 +728,8 @@ describe(xrpc, () => {
|
|
|
731
728
|
validateResponse: true,
|
|
732
729
|
}),
|
|
733
730
|
).rejects.toSatisfy((err) => {
|
|
734
|
-
assert(err instanceof
|
|
735
|
-
expect(err).toBeInstanceOf(
|
|
731
|
+
assert(err instanceof XrpcResponseValidationError)
|
|
732
|
+
expect(err).toBeInstanceOf(XrpcInvalidResponseError)
|
|
736
733
|
return true
|
|
737
734
|
})
|
|
738
735
|
})
|
|
@@ -778,7 +775,7 @@ describe(xrpc, () => {
|
|
|
778
775
|
validateResponse: false,
|
|
779
776
|
}),
|
|
780
777
|
).rejects.toSatisfy((err) => {
|
|
781
|
-
assert(err instanceof
|
|
778
|
+
assert(err instanceof XrpcInvalidResponseError)
|
|
782
779
|
expect(err.message).toMatch('Unable to parse response payload')
|
|
783
780
|
return true
|
|
784
781
|
})
|
|
@@ -925,8 +922,8 @@ describe(xrpcSafe, () => {
|
|
|
925
922
|
|
|
926
923
|
assert(!result.success)
|
|
927
924
|
assert(result instanceof XrpcResponseError)
|
|
928
|
-
expect(result.status).toBe(400)
|
|
929
|
-
expect(result.
|
|
925
|
+
expect(result.response.status).toBe(400)
|
|
926
|
+
expect(result.toJSON()).toEqual({
|
|
930
927
|
error: 'TestError',
|
|
931
928
|
message: 'bad request',
|
|
932
929
|
})
|
|
@@ -946,10 +943,10 @@ describe(xrpcSafe, () => {
|
|
|
946
943
|
assert(!result.success)
|
|
947
944
|
assert(result instanceof XrpcResponseError)
|
|
948
945
|
expect(result).toBeInstanceOf(XrpcAuthenticationError)
|
|
949
|
-
expect(result.status).toBe(401)
|
|
946
|
+
expect(result.response.status).toBe(401)
|
|
950
947
|
})
|
|
951
948
|
|
|
952
|
-
it('returns
|
|
949
|
+
it('returns XrpcResponseError for non-XRPC error response', async () => {
|
|
953
950
|
const fetchHandler: FetchHandler = async () =>
|
|
954
951
|
new Response('Not Found', {
|
|
955
952
|
status: 404,
|
|
@@ -961,10 +958,10 @@ describe(xrpcSafe, () => {
|
|
|
961
958
|
})
|
|
962
959
|
|
|
963
960
|
assert(!result.success)
|
|
964
|
-
expect(result).toBeInstanceOf(
|
|
961
|
+
expect(result).toBeInstanceOf(XrpcResponseError)
|
|
965
962
|
})
|
|
966
963
|
|
|
967
|
-
it('returns
|
|
964
|
+
it('returns XrpcResponseError for 500 without valid error payload', async () => {
|
|
968
965
|
const fetchHandler: FetchHandler = async () =>
|
|
969
966
|
new Response('Internal Server Error', {
|
|
970
967
|
status: 500,
|
|
@@ -976,12 +973,16 @@ describe(xrpcSafe, () => {
|
|
|
976
973
|
})
|
|
977
974
|
|
|
978
975
|
assert(!result.success)
|
|
979
|
-
expect(result).toBeInstanceOf(
|
|
976
|
+
expect(result).toBeInstanceOf(XrpcResponseError)
|
|
977
|
+
expect(result.error).toBe('InternalServerError')
|
|
978
|
+
expect(result.message).toMatch(
|
|
979
|
+
'Upstream server responded with a 500 error',
|
|
980
|
+
)
|
|
980
981
|
})
|
|
981
982
|
})
|
|
982
983
|
|
|
983
984
|
describe('invalid response errors', () => {
|
|
984
|
-
it('returns
|
|
985
|
+
it('returns XrpcResponseValidationError when response body fails validation', async () => {
|
|
985
986
|
const fetchHandler: FetchHandler = async () =>
|
|
986
987
|
Response.json({ value: 123 })
|
|
987
988
|
|
|
@@ -990,11 +991,11 @@ describe(xrpcSafe, () => {
|
|
|
990
991
|
})
|
|
991
992
|
|
|
992
993
|
assert(!result.success)
|
|
994
|
+
expect(result).toBeInstanceOf(XrpcResponseValidationError)
|
|
993
995
|
expect(result).toBeInstanceOf(XrpcInvalidResponseError)
|
|
994
|
-
expect(result).toBeInstanceOf(XrpcUpstreamError)
|
|
995
996
|
})
|
|
996
997
|
|
|
997
|
-
it('returns
|
|
998
|
+
it('returns XrpcInvalidResponseError when response has wrong content-type', async () => {
|
|
998
999
|
const fetchHandler: FetchHandler = async () =>
|
|
999
1000
|
new Response('binary data', {
|
|
1000
1001
|
status: 200,
|
|
@@ -1006,7 +1007,7 @@ describe(xrpcSafe, () => {
|
|
|
1006
1007
|
})
|
|
1007
1008
|
|
|
1008
1009
|
assert(!result.success)
|
|
1009
|
-
expect(result).toBeInstanceOf(
|
|
1010
|
+
expect(result).toBeInstanceOf(XrpcInvalidResponseError)
|
|
1010
1011
|
})
|
|
1011
1012
|
})
|
|
1012
1013
|
|
|
@@ -1042,6 +1043,7 @@ describe(xrpcSafe, () => {
|
|
|
1042
1043
|
assert(!result.success)
|
|
1043
1044
|
expect(result).toBeInstanceOf(XrpcInternalError)
|
|
1044
1045
|
expect(result).not.toBeInstanceOf(XrpcFetchError)
|
|
1046
|
+
expect(fetchHandler).not.toHaveBeenCalled()
|
|
1045
1047
|
})
|
|
1046
1048
|
|
|
1047
1049
|
it('returns XrpcInternalError for invalid body when enabled', async () => {
|
|
@@ -1088,7 +1090,7 @@ describe(xrpcSafe, () => {
|
|
|
1088
1090
|
})
|
|
1089
1091
|
|
|
1090
1092
|
describe('validateResponse', () => {
|
|
1091
|
-
it('returns
|
|
1093
|
+
it('returns XrpcResponseValidationError for invalid body by default', async () => {
|
|
1092
1094
|
const fetchHandler = vi.fn<FetchHandler>(async () => {
|
|
1093
1095
|
return Response.json({ value: 123 })
|
|
1094
1096
|
})
|
|
@@ -1098,8 +1100,8 @@ describe(xrpcSafe, () => {
|
|
|
1098
1100
|
})
|
|
1099
1101
|
|
|
1100
1102
|
assert(!result.success)
|
|
1103
|
+
expect(result).toBeInstanceOf(XrpcResponseValidationError)
|
|
1101
1104
|
expect(result).toBeInstanceOf(XrpcInvalidResponseError)
|
|
1102
|
-
expect(result).toBeInstanceOf(XrpcUpstreamError)
|
|
1103
1105
|
})
|
|
1104
1106
|
|
|
1105
1107
|
it('accepts invalid response body when disabled', async () => {
|
|
@@ -1164,7 +1166,7 @@ describe(xrpcSafe, () => {
|
|
|
1164
1166
|
|
|
1165
1167
|
const result = await xrpcSafe(fetchHandler, testQueryGetBlobRef)
|
|
1166
1168
|
assert(!result.success)
|
|
1167
|
-
expect(result).toBeInstanceOf(
|
|
1169
|
+
expect(result).toBeInstanceOf(XrpcInvalidResponseError)
|
|
1168
1170
|
expect(result.message).toMatch('Unable to parse response payload')
|
|
1169
1171
|
assert(result.cause instanceof TypeError)
|
|
1170
1172
|
expect(result.cause.message).toBe('Invalid blob object')
|
|
@@ -1187,7 +1189,7 @@ describe(xrpcSafe, () => {
|
|
|
1187
1189
|
|
|
1188
1190
|
const result = await xrpcSafe(fetchHandler, testQueryGetBlobRef)
|
|
1189
1191
|
assert(!result.success)
|
|
1190
|
-
expect(result).toBeInstanceOf(
|
|
1192
|
+
expect(result).toBeInstanceOf(XrpcInvalidResponseError)
|
|
1191
1193
|
expect(result.message).toMatch('Unable to parse response payload')
|
|
1192
1194
|
assert(result.cause instanceof TypeError)
|
|
1193
1195
|
expect(result.cause.message).toBe('Invalid blob object')
|
|
@@ -1209,9 +1211,9 @@ describe(xrpcSafe, () => {
|
|
|
1209
1211
|
|
|
1210
1212
|
const result = await xrpcSafe(fetchHandler, testQueryGetBlobRef)
|
|
1211
1213
|
assert(!result.success)
|
|
1212
|
-
expect(result).toBeInstanceOf(
|
|
1214
|
+
expect(result).toBeInstanceOf(XrpcInvalidResponseError)
|
|
1213
1215
|
expect(result.message).toBe(
|
|
1214
|
-
'Invalid response: Expected "image/png" (got "invalid/mime") at $.blobRef.mimeType',
|
|
1216
|
+
'Invalid response payload: Expected "image/png" (got "invalid/mime") at $.blobRef.mimeType',
|
|
1215
1217
|
)
|
|
1216
1218
|
})
|
|
1217
1219
|
|
|
@@ -1231,9 +1233,9 @@ describe(xrpcSafe, () => {
|
|
|
1231
1233
|
|
|
1232
1234
|
const result = await xrpcSafe(fetchHandler, testQueryGetBlobRef)
|
|
1233
1235
|
assert(!result.success)
|
|
1234
|
-
expect(result).toBeInstanceOf(
|
|
1236
|
+
expect(result).toBeInstanceOf(XrpcInvalidResponseError)
|
|
1235
1237
|
expect(result.message).toBe(
|
|
1236
|
-
'Invalid response: blob too big (maximum 10, got 100) at $.blobRef',
|
|
1238
|
+
'Invalid response payload: blob too big (maximum 10, got 100) at $.blobRef',
|
|
1237
1239
|
)
|
|
1238
1240
|
})
|
|
1239
1241
|
|
|
@@ -1342,7 +1344,7 @@ describe(xrpcSafe, () => {
|
|
|
1342
1344
|
})
|
|
1343
1345
|
|
|
1344
1346
|
assert(!result.success)
|
|
1345
|
-
expect(result).toBeInstanceOf(
|
|
1347
|
+
expect(result).toBeInstanceOf(XrpcInvalidResponseError)
|
|
1346
1348
|
expect(result.message).toMatch('Unable to parse response payload')
|
|
1347
1349
|
assert(result.cause instanceof TypeError)
|
|
1348
1350
|
expect(result.cause.message).toBe('Invalid non-integer number: 1.5')
|
|
@@ -1369,7 +1371,7 @@ describe(xrpcSafe, () => {
|
|
|
1369
1371
|
})
|
|
1370
1372
|
|
|
1371
1373
|
assert(!result.success)
|
|
1372
|
-
expect(result).toBeInstanceOf(
|
|
1374
|
+
expect(result).toBeInstanceOf(XrpcInvalidResponseError)
|
|
1373
1375
|
expect(result.message).toMatch('Unable to parse response payload')
|
|
1374
1376
|
assert(result.cause instanceof TypeError)
|
|
1375
1377
|
expect(result.cause.message).toBe('Invalid non-integer number: 1.5')
|
|
@@ -1383,10 +1385,8 @@ describe(xrpcSafe, () => {
|
|
|
1383
1385
|
})
|
|
1384
1386
|
|
|
1385
1387
|
assert(!result.success)
|
|
1386
|
-
expect(result).toBeInstanceOf(
|
|
1387
|
-
expect(result.message).
|
|
1388
|
-
assert(result.cause instanceof TypeError)
|
|
1389
|
-
expect(result.cause.message).toBe('Invalid non-integer number: 1.5')
|
|
1388
|
+
expect(result).toBeInstanceOf(XrpcResponseError)
|
|
1389
|
+
expect(result.message).toBe('test-error-description')
|
|
1390
1390
|
})
|
|
1391
1391
|
|
|
1392
1392
|
it('parses error response with invalid lex data when strict processing is disabled', async () => {
|
|
@@ -1399,7 +1399,7 @@ describe(xrpcSafe, () => {
|
|
|
1399
1399
|
|
|
1400
1400
|
assert(!result.success)
|
|
1401
1401
|
assert(result instanceof XrpcResponseError)
|
|
1402
|
-
expect(result.status).toBe(400)
|
|
1402
|
+
expect(result.response.status).toBe(400)
|
|
1403
1403
|
expect(result.message).toBe('test-error-description')
|
|
1404
1404
|
})
|
|
1405
1405
|
|
|
@@ -1415,8 +1415,8 @@ describe(xrpcSafe, () => {
|
|
|
1415
1415
|
})
|
|
1416
1416
|
|
|
1417
1417
|
assert(!result.success)
|
|
1418
|
+
expect(result).toBeInstanceOf(XrpcResponseValidationError)
|
|
1418
1419
|
expect(result).toBeInstanceOf(XrpcInvalidResponseError)
|
|
1419
|
-
expect(result).toBeInstanceOf(XrpcUpstreamError)
|
|
1420
1420
|
})
|
|
1421
1421
|
|
|
1422
1422
|
it('with strictResponseProcessing: false and validateResponse: false, schema validation is skipped', async () => {
|
|
@@ -1444,7 +1444,7 @@ describe(xrpcSafe, () => {
|
|
|
1444
1444
|
})
|
|
1445
1445
|
|
|
1446
1446
|
assert(!result.success)
|
|
1447
|
-
expect(result).toBeInstanceOf(
|
|
1447
|
+
expect(result).toBeInstanceOf(XrpcInvalidResponseError)
|
|
1448
1448
|
expect(result.message).toMatch('Unable to parse response payload')
|
|
1449
1449
|
assert(result.cause instanceof TypeError)
|
|
1450
1450
|
expect(result.cause.message).toBe('Invalid non-integer number: 1.5')
|
package/src/xrpc.ts
CHANGED
|
@@ -360,9 +360,7 @@ function buildPayload(
|
|
|
360
360
|
): null | { body: BodyInit; encoding: string } {
|
|
361
361
|
if (schema.encoding === undefined) {
|
|
362
362
|
if (body !== undefined) {
|
|
363
|
-
throw new TypeError(
|
|
364
|
-
`Cannot send a ${typeof body} body with undefined encoding`,
|
|
365
|
-
)
|
|
363
|
+
throw new TypeError(`Endpoint expects no payload`)
|
|
366
364
|
}
|
|
367
365
|
|
|
368
366
|
return null
|