graphql-http 1.17.1 → 1.19.0
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/README.md +17 -1
- package/lib/audits/common.d.mts +1 -1
- package/lib/audits/common.d.ts +1 -1
- package/lib/audits/render.js +15 -0
- package/lib/audits/render.mjs +15 -0
- package/lib/audits/server.js +42 -172
- package/lib/audits/server.mjs +42 -172
- package/lib/audits/utils.js +6 -3
- package/lib/audits/utils.mjs +6 -3
- package/lib/handler.d.mts +20 -3
- package/lib/handler.d.ts +20 -3
- package/lib/handler.js +36 -14
- package/lib/handler.mjs +37 -15
- package/lib/use/express.js +1 -18
- package/lib/use/express.mjs +1 -18
- package/lib/use/fastify.js +1 -19
- package/lib/use/fastify.mjs +1 -19
- package/lib/use/fetch.d.mts +1 -1
- package/lib/use/fetch.d.ts +1 -1
- package/lib/use/fetch.js +2 -22
- package/lib/use/fetch.mjs +2 -22
- package/lib/use/http.d.mts +1 -1
- package/lib/use/http.d.ts +1 -1
- package/lib/use/http.js +2 -19
- package/lib/use/http.mjs +2 -19
- package/lib/use/http2.d.mts +1 -1
- package/lib/use/http2.d.ts +1 -1
- package/lib/use/http2.js +2 -19
- package/lib/use/http2.mjs +2 -19
- package/lib/use/koa.js +0 -14
- package/lib/use/koa.mjs +0 -14
- package/lib/use/node.d.mts +1 -1
- package/lib/use/node.d.ts +1 -1
- package/lib/use/node.js +1 -1
- package/lib/use/node.mjs +1 -1
- package/lib/use/uWebSockets.d.mts +36 -0
- package/lib/use/uWebSockets.d.ts +36 -0
- package/lib/use/uWebSockets.js +84 -0
- package/lib/use/uWebSockets.mjs +80 -0
- package/lib/utils.d.mts +2 -0
- package/lib/utils.d.ts +2 -0
- package/lib/utils.js +15 -1
- package/lib/utils.mjs +13 -0
- package/package.json +30 -25
- package/umd/graphql-http-audits.js +63 -175
- package/umd/graphql-http-audits.min.js +1 -1
- package/umd/graphql-http-audits.min.js.gz +0 -0
package/lib/audits/server.mjs
CHANGED
|
@@ -54,9 +54,13 @@ export function serverAudits(opts) {
|
|
|
54
54
|
ressert(res).header('content-type').toContain('application/json');
|
|
55
55
|
}),
|
|
56
56
|
audit('80D8', 'SHOULD assume application/json content-type when accept is missing', async () => {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: {
|
|
60
|
+
'content-type': 'application/json',
|
|
61
|
+
},
|
|
62
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
63
|
+
});
|
|
60
64
|
ressert(res).status.toBe(200);
|
|
61
65
|
ressert(res).header('content-type').toContain('application/json');
|
|
62
66
|
}),
|
|
@@ -127,7 +131,7 @@ export function serverAudits(opts) {
|
|
|
127
131
|
ressert(res).status.toBeBetween(400, 499);
|
|
128
132
|
}),
|
|
129
133
|
// Request POST
|
|
130
|
-
audit('9ABE', '
|
|
134
|
+
audit('9ABE', 'MAY respond with 4xx status code if content-type is not supplied on POST requests', async () => {
|
|
131
135
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
132
136
|
method: 'POST',
|
|
133
137
|
});
|
|
@@ -141,23 +145,15 @@ export function serverAudits(opts) {
|
|
|
141
145
|
});
|
|
142
146
|
ressert(res).status.toBe(200);
|
|
143
147
|
}),
|
|
144
|
-
audit('
|
|
145
|
-
var _a;
|
|
148
|
+
audit('A5BF', 'MAY use 400 status code when request body is missing on POST', async () => {
|
|
146
149
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
147
150
|
method: 'POST',
|
|
148
151
|
headers: { 'content-type': 'application/json' },
|
|
149
152
|
});
|
|
150
|
-
|
|
151
|
-
await ressert(res).bodyAsExecutionResult.toHaveProperty('errors');
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
ressert(res).status.toBe(400);
|
|
155
|
-
}
|
|
153
|
+
ressert(res).status.toBe(400);
|
|
156
154
|
}),
|
|
157
155
|
// Request Parameters
|
|
158
|
-
audit(
|
|
159
|
-
// TODO: convert to MUST after watershed
|
|
160
|
-
'6610', 'SHOULD use 400 status code on missing {query} parameter when accepting application/graphql-response+json', async () => {
|
|
156
|
+
audit('423L', 'MAY use 400 status code on missing {query} parameter', async () => {
|
|
161
157
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
162
158
|
method: 'POST',
|
|
163
159
|
headers: {
|
|
@@ -168,26 +164,11 @@ export function serverAudits(opts) {
|
|
|
168
164
|
});
|
|
169
165
|
ressert(res).status.toBe(400);
|
|
170
166
|
}),
|
|
171
|
-
|
|
167
|
+
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid, index) => audit(`LKJ${index}`, `MAY use 400 status code on ${extendedTypeof(invalid)} {query} parameter`, async () => {
|
|
172
168
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
173
169
|
method: 'POST',
|
|
174
170
|
headers: {
|
|
175
171
|
'content-type': 'application/json',
|
|
176
|
-
accept: 'application/json',
|
|
177
|
-
},
|
|
178
|
-
body: JSON.stringify({ notquery: '{ __typename }' }),
|
|
179
|
-
});
|
|
180
|
-
ressert(res).status.toBe(200);
|
|
181
|
-
await ressert(res).bodyAsExecutionResult.toHaveProperty('errors');
|
|
182
|
-
}),
|
|
183
|
-
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid, index) => audit(`4F5${index}`,
|
|
184
|
-
// TODO: convert to MUST after watershed
|
|
185
|
-
`SHOULD use 400 status code on ${extendedTypeof(invalid)} {query} parameter when accepting application/graphql-response+json`, async () => {
|
|
186
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
187
|
-
method: 'POST',
|
|
188
|
-
headers: {
|
|
189
|
-
'content-type': 'application/json',
|
|
190
|
-
accept: 'application/graphql-response+json',
|
|
191
172
|
},
|
|
192
173
|
body: JSON.stringify({
|
|
193
174
|
query: invalid,
|
|
@@ -195,20 +176,6 @@ export function serverAudits(opts) {
|
|
|
195
176
|
});
|
|
196
177
|
ressert(res).status.toBe(400);
|
|
197
178
|
})),
|
|
198
|
-
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid, index) => audit(`9FE${index}`, `SHOULD use 200 status code with errors field on ${extendedTypeof(invalid)} {query} parameter when accepting application/json`, async () => {
|
|
199
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
200
|
-
method: 'POST',
|
|
201
|
-
headers: {
|
|
202
|
-
'content-type': 'application/json',
|
|
203
|
-
accept: 'application/json',
|
|
204
|
-
},
|
|
205
|
-
body: JSON.stringify({
|
|
206
|
-
query: invalid,
|
|
207
|
-
}),
|
|
208
|
-
});
|
|
209
|
-
ressert(res).status.toBe(200);
|
|
210
|
-
await ressert(res).bodyAsExecutionResult.toHaveProperty('errors');
|
|
211
|
-
})),
|
|
212
179
|
audit(
|
|
213
180
|
// TODO: convert to MUST after watershed
|
|
214
181
|
'34A2', 'SHOULD allow string {query} parameter when accepting application/graphql-response+json', async () => {
|
|
@@ -238,14 +205,11 @@ export function serverAudits(opts) {
|
|
|
238
205
|
ressert(res).status.toBe(200);
|
|
239
206
|
await ressert(res).bodyAsExecutionResult.notToHaveProperty('errors');
|
|
240
207
|
}),
|
|
241
|
-
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid, index) => audit(`
|
|
242
|
-
// TODO: convert to MUST after watershed
|
|
243
|
-
`SHOULD use 400 status code on ${extendedTypeof(invalid)} {operationName} parameter when accepting application/graphql-response+json`, async () => {
|
|
208
|
+
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid, index) => audit(`6C0${index}`, `MAY use 400 status code on ${extendedTypeof(invalid)} {operationName} parameter`, async () => {
|
|
244
209
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
245
210
|
method: 'POST',
|
|
246
211
|
headers: {
|
|
247
212
|
'content-type': 'application/json',
|
|
248
|
-
accept: 'application/graphql-response+json',
|
|
249
213
|
},
|
|
250
214
|
body: JSON.stringify({
|
|
251
215
|
operationName: invalid,
|
|
@@ -254,21 +218,6 @@ export function serverAudits(opts) {
|
|
|
254
218
|
});
|
|
255
219
|
ressert(res).status.toBe(400);
|
|
256
220
|
})),
|
|
257
|
-
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid, index) => audit(`FB9${index}`, `SHOULD use 200 status code with errors field on ${extendedTypeof(invalid)} {operationName} parameter when accepting application/json`, async () => {
|
|
258
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
259
|
-
method: 'POST',
|
|
260
|
-
headers: {
|
|
261
|
-
'content-type': 'application/json',
|
|
262
|
-
accept: 'application/json',
|
|
263
|
-
},
|
|
264
|
-
body: JSON.stringify({
|
|
265
|
-
operationName: invalid,
|
|
266
|
-
query: '{ __typename }',
|
|
267
|
-
}),
|
|
268
|
-
});
|
|
269
|
-
ressert(res).status.toBe(200);
|
|
270
|
-
await ressert(res).bodyAsExecutionResult.toHaveProperty('errors');
|
|
271
|
-
})),
|
|
272
221
|
audit(
|
|
273
222
|
// TODO: convert to MUST after watershed
|
|
274
223
|
'8161', 'SHOULD allow string {operationName} parameter when accepting application/graphql-response+json', async () => {
|
|
@@ -334,14 +283,11 @@ export function serverAudits(opts) {
|
|
|
334
283
|
await ressert(res).bodyAsExecutionResult.notToHaveProperty('errors');
|
|
335
284
|
}),
|
|
336
285
|
]),
|
|
337
|
-
...['string', 0, false, ['array']].map((invalid, index) => audit(`
|
|
338
|
-
// TODO: convert to MUST after watershed
|
|
339
|
-
`SHOULD use 400 status code on ${extendedTypeof(invalid)} {variables} parameter when accepting application/graphql-response+json`, async () => {
|
|
286
|
+
...['string', 0, false, ['array']].map((invalid, index) => audit(`476${index}`, `MAY use 400 status code on ${extendedTypeof(invalid)} {variables} parameter`, async () => {
|
|
340
287
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
341
288
|
method: 'POST',
|
|
342
289
|
headers: {
|
|
343
290
|
'content-type': 'application/json',
|
|
344
|
-
accept: 'application/graphql-response+json',
|
|
345
291
|
},
|
|
346
292
|
body: JSON.stringify({
|
|
347
293
|
query: '{ __typename }',
|
|
@@ -350,21 +296,6 @@ export function serverAudits(opts) {
|
|
|
350
296
|
});
|
|
351
297
|
ressert(res).status.toBe(400);
|
|
352
298
|
})),
|
|
353
|
-
...['string', 0, false, ['array']].map((invalid, index) => audit(`F05${index}`, `SHOULD use 200 status code with errors field on ${extendedTypeof(invalid)} {variables} parameter when accepting application/json`, async () => {
|
|
354
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
355
|
-
method: 'POST',
|
|
356
|
-
headers: {
|
|
357
|
-
'content-type': 'application/json',
|
|
358
|
-
accept: 'application/json',
|
|
359
|
-
},
|
|
360
|
-
body: JSON.stringify({
|
|
361
|
-
query: '{ __typename }',
|
|
362
|
-
variables: invalid,
|
|
363
|
-
}),
|
|
364
|
-
});
|
|
365
|
-
ressert(res).status.toBe(200);
|
|
366
|
-
await ressert(res).bodyAsExecutionResult.toHaveProperty('errors');
|
|
367
|
-
})),
|
|
368
299
|
audit(
|
|
369
300
|
// TODO: convert to MUST after watershed
|
|
370
301
|
'2EA1', 'SHOULD allow map {variables} parameter when accepting application/graphql-response+json', async () => {
|
|
@@ -421,14 +352,13 @@ export function serverAudits(opts) {
|
|
|
421
352
|
ressert(res).status.toBe(200);
|
|
422
353
|
await ressert(res).bodyAsExecutionResult.notToHaveProperty('errors');
|
|
423
354
|
}),
|
|
424
|
-
...['string', 0, false, ['array']].map((invalid, index) => audit(`
|
|
355
|
+
...['string', 0, false, ['array']].map((invalid, index) => audit(`58B${index}`,
|
|
425
356
|
// TODO: convert to MUST after watershed
|
|
426
|
-
`
|
|
357
|
+
`MAY use 400 status code on ${extendedTypeof(invalid)} {extensions} parameter`, async () => {
|
|
427
358
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
428
359
|
method: 'POST',
|
|
429
360
|
headers: {
|
|
430
361
|
'content-type': 'application/json',
|
|
431
|
-
accept: 'application/graphql-response+json',
|
|
432
362
|
},
|
|
433
363
|
body: JSON.stringify({
|
|
434
364
|
query: '{ __typename }',
|
|
@@ -437,21 +367,6 @@ export function serverAudits(opts) {
|
|
|
437
367
|
});
|
|
438
368
|
ressert(res).status.toBe(400);
|
|
439
369
|
})),
|
|
440
|
-
...['string', 0, false, ['array']].map((invalid, index) => audit(`368${index}`, `SHOULD use 200 status code with errors field on ${extendedTypeof(invalid)} {extensions} parameter when accepting application/json`, async () => {
|
|
441
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
442
|
-
method: 'POST',
|
|
443
|
-
headers: {
|
|
444
|
-
'content-type': 'application/json',
|
|
445
|
-
accept: 'application/json',
|
|
446
|
-
},
|
|
447
|
-
body: JSON.stringify({
|
|
448
|
-
query: '{ __typename }',
|
|
449
|
-
extensions: invalid,
|
|
450
|
-
}),
|
|
451
|
-
});
|
|
452
|
-
ressert(res).status.toBe(200);
|
|
453
|
-
await ressert(res).bodyAsExecutionResult.toHaveProperty('errors');
|
|
454
|
-
})),
|
|
455
370
|
audit(
|
|
456
371
|
// TODO: convert to MUST after watershed
|
|
457
372
|
'428F', 'SHOULD allow map {extensions} parameter when accepting application/graphql-response+json', async () => {
|
|
@@ -483,136 +398,91 @@ export function serverAudits(opts) {
|
|
|
483
398
|
ressert(res).status.toBe(200);
|
|
484
399
|
await ressert(res).bodyAsExecutionResult.notToHaveProperty('errors');
|
|
485
400
|
}),
|
|
486
|
-
|
|
487
|
-
// Response application/json
|
|
488
|
-
audit('D477', 'SHOULD use 200 status code on JSON parsing failure when accepting application/json', async () => {
|
|
401
|
+
audit('B6DC', 'MAY use 4xx or 5xx status codes on JSON parsing failure', async () => {
|
|
489
402
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
490
403
|
method: 'POST',
|
|
491
404
|
headers: {
|
|
492
405
|
'content-type': 'application/json',
|
|
493
|
-
accept: 'application/json',
|
|
494
406
|
},
|
|
495
407
|
body: '{ "not a JSON',
|
|
496
408
|
});
|
|
497
|
-
ressert(res).status.
|
|
498
|
-
}),
|
|
499
|
-
audit('F5AF', 'SHOULD use 200 status code if parameters are invalid when accepting application/json', async () => {
|
|
500
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
501
|
-
method: 'POST',
|
|
502
|
-
headers: {
|
|
503
|
-
'content-type': 'application/json',
|
|
504
|
-
accept: 'application/json',
|
|
505
|
-
},
|
|
506
|
-
body: JSON.stringify({
|
|
507
|
-
qeury: /* typo */ '{ __typename }',
|
|
508
|
-
}),
|
|
509
|
-
});
|
|
510
|
-
ressert(res).status.toBe(200);
|
|
409
|
+
ressert(res).status.toBeBetween(400, 499);
|
|
511
410
|
}),
|
|
512
|
-
audit('
|
|
411
|
+
audit('BCF8', 'MAY use 400 status code on JSON parsing failure', async () => {
|
|
513
412
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
514
413
|
method: 'POST',
|
|
515
414
|
headers: {
|
|
516
415
|
'content-type': 'application/json',
|
|
517
|
-
accept: 'application/json',
|
|
518
416
|
},
|
|
519
|
-
body:
|
|
417
|
+
body: '{ "not a JSON',
|
|
520
418
|
});
|
|
521
|
-
ressert(res).status.toBe(
|
|
419
|
+
ressert(res).status.toBe(400);
|
|
522
420
|
}),
|
|
523
|
-
audit('
|
|
421
|
+
audit('8764', 'MAY use 4xx or 5xx status codes if parameters are invalid', async () => {
|
|
524
422
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
525
423
|
method: 'POST',
|
|
526
424
|
headers: {
|
|
527
425
|
'content-type': 'application/json',
|
|
528
|
-
accept: 'application/json',
|
|
529
426
|
},
|
|
530
427
|
body: JSON.stringify({
|
|
531
|
-
|
|
428
|
+
qeury /* typo */: '{ __typename }',
|
|
532
429
|
}),
|
|
533
430
|
});
|
|
534
|
-
ressert(res).status.
|
|
431
|
+
ressert(res).status.toBeBetween(400, 599);
|
|
535
432
|
}),
|
|
536
|
-
audit('
|
|
433
|
+
audit('3E3A', 'MAY use 400 status code if parameters are invalid', async () => {
|
|
537
434
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
538
435
|
method: 'POST',
|
|
539
436
|
headers: {
|
|
540
437
|
'content-type': 'application/json',
|
|
541
|
-
accept: 'application/json',
|
|
542
438
|
},
|
|
543
439
|
body: JSON.stringify({
|
|
544
|
-
|
|
545
|
-
variables: { id: null },
|
|
440
|
+
qeury: /* typo */ '{ __typename }',
|
|
546
441
|
}),
|
|
547
442
|
});
|
|
548
|
-
ressert(res).status.toBe(200);
|
|
549
|
-
}),
|
|
550
|
-
// Response application/graphql-response+json
|
|
551
|
-
audit(
|
|
552
|
-
// TODO: convert to MUST after watershed
|
|
553
|
-
'60AA', 'SHOULD use 4xx or 5xx status codes on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
554
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
555
|
-
method: 'POST',
|
|
556
|
-
headers: {
|
|
557
|
-
'content-type': 'application/json',
|
|
558
|
-
accept: 'application/graphql-response+json',
|
|
559
|
-
},
|
|
560
|
-
body: '{ "not a JSON',
|
|
561
|
-
});
|
|
562
|
-
ressert(res).status.toBeBetween(400, 499);
|
|
563
|
-
}),
|
|
564
|
-
audit('2163', 'SHOULD use 400 status code on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
565
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
566
|
-
method: 'POST',
|
|
567
|
-
headers: {
|
|
568
|
-
'content-type': 'application/json',
|
|
569
|
-
accept: 'application/graphql-response+json',
|
|
570
|
-
},
|
|
571
|
-
body: '{ "not a JSON',
|
|
572
|
-
});
|
|
573
443
|
ressert(res).status.toBe(400);
|
|
574
444
|
}),
|
|
575
|
-
audit(
|
|
576
|
-
//
|
|
577
|
-
'
|
|
445
|
+
// TODO: audit('39AA', 'MUST accept a map for the {extensions} parameter'),
|
|
446
|
+
// Response application/json
|
|
447
|
+
audit('572B', 'SHOULD use 200 status code on document parsing failure when accepting application/json', async () => {
|
|
578
448
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
579
449
|
method: 'POST',
|
|
580
450
|
headers: {
|
|
581
451
|
'content-type': 'application/json',
|
|
582
|
-
accept: 'application/
|
|
452
|
+
accept: 'application/json',
|
|
583
453
|
},
|
|
584
|
-
body: JSON.stringify({
|
|
585
|
-
qeury /* typo */: '{ __typename }',
|
|
586
|
-
}),
|
|
454
|
+
body: JSON.stringify({ query: '{' }),
|
|
587
455
|
});
|
|
588
|
-
ressert(res).status.
|
|
456
|
+
ressert(res).status.toBe(200);
|
|
589
457
|
}),
|
|
590
|
-
audit('
|
|
458
|
+
audit('FDE2', 'SHOULD use 200 status code on document validation failure when accepting application/json', async () => {
|
|
591
459
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
592
460
|
method: 'POST',
|
|
593
461
|
headers: {
|
|
594
462
|
'content-type': 'application/json',
|
|
595
|
-
accept: 'application/
|
|
463
|
+
accept: 'application/json',
|
|
596
464
|
},
|
|
597
465
|
body: JSON.stringify({
|
|
598
|
-
|
|
466
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
599
467
|
}),
|
|
600
468
|
});
|
|
601
|
-
ressert(res).status.toBe(
|
|
469
|
+
ressert(res).status.toBe(200);
|
|
602
470
|
}),
|
|
603
|
-
audit('
|
|
471
|
+
audit('7B9B', 'SHOULD use a status code of 200 on variable coercion failure when accepting application/json', async () => {
|
|
604
472
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
605
473
|
method: 'POST',
|
|
606
474
|
headers: {
|
|
607
475
|
'content-type': 'application/json',
|
|
608
|
-
accept: 'application/
|
|
476
|
+
accept: 'application/json',
|
|
609
477
|
},
|
|
610
478
|
body: JSON.stringify({
|
|
611
|
-
|
|
479
|
+
query: 'query CoerceFailure($id: ID!){ __typename }',
|
|
480
|
+
variables: { id: null },
|
|
612
481
|
}),
|
|
613
482
|
});
|
|
614
|
-
|
|
483
|
+
ressert(res).status.toBe(200);
|
|
615
484
|
}),
|
|
485
|
+
// Response application/graphql-response+json
|
|
616
486
|
audit(
|
|
617
487
|
// TODO: convert to MUST after watershed
|
|
618
488
|
'865D', 'SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => {
|
package/lib/audits/utils.js
CHANGED
|
@@ -48,10 +48,13 @@ function audit(id, name, fn) {
|
|
|
48
48
|
id,
|
|
49
49
|
name,
|
|
50
50
|
status: name.startsWith('MUST')
|
|
51
|
-
? //
|
|
51
|
+
? // failing MUSTs are considered errors
|
|
52
52
|
'error'
|
|
53
|
-
:
|
|
54
|
-
|
|
53
|
+
: name.startsWith('SHOULD')
|
|
54
|
+
? // recommendations are warnings
|
|
55
|
+
'warn'
|
|
56
|
+
: // everything else is truly optional
|
|
57
|
+
'notice',
|
|
55
58
|
reason: err.reason,
|
|
56
59
|
response: err.response,
|
|
57
60
|
};
|
package/lib/audits/utils.mjs
CHANGED
|
@@ -31,10 +31,13 @@ export function audit(id, name, fn) {
|
|
|
31
31
|
id,
|
|
32
32
|
name,
|
|
33
33
|
status: name.startsWith('MUST')
|
|
34
|
-
? //
|
|
34
|
+
? // failing MUSTs are considered errors
|
|
35
35
|
'error'
|
|
36
|
-
:
|
|
37
|
-
|
|
36
|
+
: name.startsWith('SHOULD')
|
|
37
|
+
? // recommendations are warnings
|
|
38
|
+
'warn'
|
|
39
|
+
: // everything else is truly optional
|
|
40
|
+
'notice',
|
|
38
41
|
reason: err.reason,
|
|
39
42
|
response: err.response,
|
|
40
43
|
};
|
package/lib/handler.d.mts
CHANGED
|
@@ -101,6 +101,12 @@ export declare function isResponse(val: unknown): val is Response;
|
|
|
101
101
|
* @category Server
|
|
102
102
|
*/
|
|
103
103
|
export type OperationContext = Record<PropertyKey, unknown> | symbol | number | string | boolean | undefined | null;
|
|
104
|
+
/**
|
|
105
|
+
* The (GraphQL) error formatter function.
|
|
106
|
+
*
|
|
107
|
+
* @category Server
|
|
108
|
+
*/
|
|
109
|
+
export type FormatError = (err: Readonly<GraphQLError | Error>) => GraphQLError | Error;
|
|
104
110
|
/** @category Server */
|
|
105
111
|
export type OperationArgs<Context extends OperationContext = undefined> = ExecutionArgs & {
|
|
106
112
|
contextValue?: Context;
|
|
@@ -215,6 +221,14 @@ export interface HandlerOptions<RequestRaw = unknown, RequestContext = unknown,
|
|
|
215
221
|
* further execution.
|
|
216
222
|
*/
|
|
217
223
|
onOperation?: (req: Request<RequestRaw, RequestContext>, args: OperationArgs<Context>, result: ExecutionResult) => Promise<ExecutionResult | Response | void> | ExecutionResult | Response | void;
|
|
224
|
+
/**
|
|
225
|
+
* Format handled errors to your satisfaction. Either GraphQL errors
|
|
226
|
+
* or safe request processing errors are meant by "handleded errors".
|
|
227
|
+
*
|
|
228
|
+
* If multiple errors have occured, all of them will be mapped using
|
|
229
|
+
* this formatter.
|
|
230
|
+
*/
|
|
231
|
+
formatError?: FormatError;
|
|
218
232
|
}
|
|
219
233
|
/**
|
|
220
234
|
* The ready-to-use handler. Simply plug it in your favourite HTTP framework
|
|
@@ -301,9 +315,12 @@ export declare function getAcceptableMediaType(acceptHeader: string | null | und
|
|
|
301
315
|
*
|
|
302
316
|
* If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
|
|
303
317
|
*
|
|
304
|
-
* If the first argument is
|
|
305
|
-
*
|
|
318
|
+
* If the first argument is (an array of) `GraphQLError`, or an `ExecutionResult` without the `data` field, it will be treated
|
|
319
|
+
* the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP spec.
|
|
320
|
+
*
|
|
321
|
+
* If the first argument is an `Error`, the operation will be treated as a bad request responding with `400: Bad Request` and the
|
|
322
|
+
* error will be present in the `ExecutionResult` style.
|
|
306
323
|
*
|
|
307
324
|
* @category Server
|
|
308
325
|
*/
|
|
309
|
-
export declare function makeResponse(resultOrErrors: Readonly<ExecutionResult> | Readonly<GraphQLError[]> | Readonly<GraphQLError>, acceptedMediaType: AcceptableMediaType): Response;
|
|
326
|
+
export declare function makeResponse(resultOrErrors: Readonly<ExecutionResult> | Readonly<GraphQLError[]> | Readonly<GraphQLError> | Readonly<Error>, acceptedMediaType: AcceptableMediaType, formatError: FormatError): Response;
|
package/lib/handler.d.ts
CHANGED
|
@@ -101,6 +101,12 @@ export declare function isResponse(val: unknown): val is Response;
|
|
|
101
101
|
* @category Server
|
|
102
102
|
*/
|
|
103
103
|
export type OperationContext = Record<PropertyKey, unknown> | symbol | number | string | boolean | undefined | null;
|
|
104
|
+
/**
|
|
105
|
+
* The (GraphQL) error formatter function.
|
|
106
|
+
*
|
|
107
|
+
* @category Server
|
|
108
|
+
*/
|
|
109
|
+
export type FormatError = (err: Readonly<GraphQLError | Error>) => GraphQLError | Error;
|
|
104
110
|
/** @category Server */
|
|
105
111
|
export type OperationArgs<Context extends OperationContext = undefined> = ExecutionArgs & {
|
|
106
112
|
contextValue?: Context;
|
|
@@ -215,6 +221,14 @@ export interface HandlerOptions<RequestRaw = unknown, RequestContext = unknown,
|
|
|
215
221
|
* further execution.
|
|
216
222
|
*/
|
|
217
223
|
onOperation?: (req: Request<RequestRaw, RequestContext>, args: OperationArgs<Context>, result: ExecutionResult) => Promise<ExecutionResult | Response | void> | ExecutionResult | Response | void;
|
|
224
|
+
/**
|
|
225
|
+
* Format handled errors to your satisfaction. Either GraphQL errors
|
|
226
|
+
* or safe request processing errors are meant by "handleded errors".
|
|
227
|
+
*
|
|
228
|
+
* If multiple errors have occured, all of them will be mapped using
|
|
229
|
+
* this formatter.
|
|
230
|
+
*/
|
|
231
|
+
formatError?: FormatError;
|
|
218
232
|
}
|
|
219
233
|
/**
|
|
220
234
|
* The ready-to-use handler. Simply plug it in your favourite HTTP framework
|
|
@@ -301,9 +315,12 @@ export declare function getAcceptableMediaType(acceptHeader: string | null | und
|
|
|
301
315
|
*
|
|
302
316
|
* If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
|
|
303
317
|
*
|
|
304
|
-
* If the first argument is
|
|
305
|
-
*
|
|
318
|
+
* If the first argument is (an array of) `GraphQLError`, or an `ExecutionResult` without the `data` field, it will be treated
|
|
319
|
+
* the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP spec.
|
|
320
|
+
*
|
|
321
|
+
* If the first argument is an `Error`, the operation will be treated as a bad request responding with `400: Bad Request` and the
|
|
322
|
+
* error will be present in the `ExecutionResult` style.
|
|
306
323
|
*
|
|
307
324
|
* @category Server
|
|
308
325
|
*/
|
|
309
|
-
export declare function makeResponse(resultOrErrors: Readonly<ExecutionResult> | Readonly<GraphQLError[]> | Readonly<GraphQLError>, acceptedMediaType: AcceptableMediaType): Response;
|
|
326
|
+
export declare function makeResponse(resultOrErrors: Readonly<ExecutionResult> | Readonly<GraphQLError[]> | Readonly<GraphQLError> | Readonly<Error>, acceptedMediaType: AcceptableMediaType, formatError: FormatError): Response;
|
package/lib/handler.js
CHANGED
|
@@ -76,7 +76,7 @@ exports.isResponse = isResponse;
|
|
|
76
76
|
* @category Server
|
|
77
77
|
*/
|
|
78
78
|
function createHandler(options) {
|
|
79
|
-
const { schema, context, validate = graphql_1.validate, validationRules = [], execute = graphql_1.execute, parse = graphql_1.parse, getOperationAST = graphql_1.getOperationAST, rootValue, onSubscribe, onOperation, } = options;
|
|
79
|
+
const { schema, context, validate = graphql_1.validate, validationRules = [], execute = graphql_1.execute, parse = graphql_1.parse, getOperationAST = graphql_1.getOperationAST, rootValue, onSubscribe, onOperation, formatError = (err) => err, } = options;
|
|
80
80
|
return async function handler(req) {
|
|
81
81
|
var _a, _b;
|
|
82
82
|
const method = req.method;
|
|
@@ -177,6 +177,10 @@ function createHandler(options) {
|
|
|
177
177
|
Array.isArray(partParams.variables))) {
|
|
178
178
|
throw new Error('Invalid variables');
|
|
179
179
|
}
|
|
180
|
+
if (partParams.operationName != null &&
|
|
181
|
+
typeof partParams.operationName !== 'string') {
|
|
182
|
+
throw new Error('Invalid operationName');
|
|
183
|
+
}
|
|
180
184
|
if (partParams.extensions != null &&
|
|
181
185
|
(typeof partParams.extensions !== 'object' ||
|
|
182
186
|
Array.isArray(partParams.extensions))) {
|
|
@@ -186,7 +190,7 @@ function createHandler(options) {
|
|
|
186
190
|
params = partParams;
|
|
187
191
|
}
|
|
188
192
|
catch (err) {
|
|
189
|
-
return makeResponse(
|
|
193
|
+
return makeResponse(err, acceptedMediaType, formatError);
|
|
190
194
|
}
|
|
191
195
|
let args;
|
|
192
196
|
const maybeResErrsOrArgs = await (onSubscribe === null || onSubscribe === void 0 ? void 0 : onSubscribe(req, params));
|
|
@@ -194,7 +198,7 @@ function createHandler(options) {
|
|
|
194
198
|
return maybeResErrsOrArgs;
|
|
195
199
|
else if ((0, utils_1.isExecutionResult)(maybeResErrsOrArgs) ||
|
|
196
200
|
(0, utils_1.areGraphQLErrors)(maybeResErrsOrArgs))
|
|
197
|
-
return makeResponse(maybeResErrsOrArgs, acceptedMediaType);
|
|
201
|
+
return makeResponse(maybeResErrsOrArgs, acceptedMediaType, formatError);
|
|
198
202
|
else if (maybeResErrsOrArgs)
|
|
199
203
|
args = maybeResErrsOrArgs;
|
|
200
204
|
else {
|
|
@@ -206,7 +210,7 @@ function createHandler(options) {
|
|
|
206
210
|
document = parse(query);
|
|
207
211
|
}
|
|
208
212
|
catch (err) {
|
|
209
|
-
return makeResponse(err, acceptedMediaType);
|
|
213
|
+
return makeResponse(err, acceptedMediaType, formatError);
|
|
210
214
|
}
|
|
211
215
|
const resOrContext = typeof context === 'function' ? await context(req, params) : context;
|
|
212
216
|
if (isResponse(resOrContext))
|
|
@@ -235,7 +239,7 @@ function createHandler(options) {
|
|
|
235
239
|
}
|
|
236
240
|
const validationErrs = validate(args.schema, args.document, rules);
|
|
237
241
|
if (validationErrs.length) {
|
|
238
|
-
return makeResponse(validationErrs, acceptedMediaType);
|
|
242
|
+
return makeResponse(validationErrs, acceptedMediaType, formatError);
|
|
239
243
|
}
|
|
240
244
|
}
|
|
241
245
|
let operation;
|
|
@@ -246,10 +250,10 @@ function createHandler(options) {
|
|
|
246
250
|
operation = ast.operation;
|
|
247
251
|
}
|
|
248
252
|
catch (_d) {
|
|
249
|
-
return makeResponse(new graphql_1.GraphQLError('Unable to detect operation AST'), acceptedMediaType);
|
|
253
|
+
return makeResponse(new graphql_1.GraphQLError('Unable to detect operation AST'), acceptedMediaType, formatError);
|
|
250
254
|
}
|
|
251
255
|
if (operation === 'subscription') {
|
|
252
|
-
return makeResponse(new graphql_1.GraphQLError('Subscriptions are not supported'), acceptedMediaType);
|
|
256
|
+
return makeResponse(new graphql_1.GraphQLError('Subscriptions are not supported'), acceptedMediaType, formatError);
|
|
253
257
|
}
|
|
254
258
|
// mutations cannot happen over GETs
|
|
255
259
|
// https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
|
|
@@ -283,9 +287,9 @@ function createHandler(options) {
|
|
|
283
287
|
else if (maybeResponseOrResult)
|
|
284
288
|
result = maybeResponseOrResult;
|
|
285
289
|
if ((0, utils_1.isAsyncIterable)(result)) {
|
|
286
|
-
return makeResponse(new graphql_1.GraphQLError('Subscriptions are not supported'), acceptedMediaType);
|
|
290
|
+
return makeResponse(new graphql_1.GraphQLError('Subscriptions are not supported'), acceptedMediaType, formatError);
|
|
287
291
|
}
|
|
288
|
-
return makeResponse(result, acceptedMediaType);
|
|
292
|
+
return makeResponse(result, acceptedMediaType, formatError);
|
|
289
293
|
};
|
|
290
294
|
}
|
|
291
295
|
exports.createHandler = createHandler;
|
|
@@ -327,12 +331,29 @@ exports.getAcceptableMediaType = getAcceptableMediaType;
|
|
|
327
331
|
*
|
|
328
332
|
* If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
|
|
329
333
|
*
|
|
330
|
-
* If the first argument is
|
|
331
|
-
*
|
|
334
|
+
* If the first argument is (an array of) `GraphQLError`, or an `ExecutionResult` without the `data` field, it will be treated
|
|
335
|
+
* the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP spec.
|
|
336
|
+
*
|
|
337
|
+
* If the first argument is an `Error`, the operation will be treated as a bad request responding with `400: Bad Request` and the
|
|
338
|
+
* error will be present in the `ExecutionResult` style.
|
|
332
339
|
*
|
|
333
340
|
* @category Server
|
|
334
341
|
*/
|
|
335
|
-
function makeResponse(resultOrErrors, acceptedMediaType) {
|
|
342
|
+
function makeResponse(resultOrErrors, acceptedMediaType, formatError) {
|
|
343
|
+
if (resultOrErrors instanceof Error &&
|
|
344
|
+
// because GraphQLError extends the Error class
|
|
345
|
+
!(0, utils_1.isGraphQLError)(resultOrErrors)) {
|
|
346
|
+
return [
|
|
347
|
+
JSON.stringify({ errors: [formatError(resultOrErrors)] }, utils_1.jsonErrorReplacer),
|
|
348
|
+
{
|
|
349
|
+
status: 400,
|
|
350
|
+
statusText: 'Bad Request',
|
|
351
|
+
headers: {
|
|
352
|
+
'content-type': 'application/json; charset=utf-8',
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
];
|
|
356
|
+
}
|
|
336
357
|
const errors = (0, utils_1.isGraphQLError)(resultOrErrors)
|
|
337
358
|
? [resultOrErrors]
|
|
338
359
|
: (0, utils_1.areGraphQLErrors)(resultOrErrors)
|
|
@@ -340,7 +361,7 @@ function makeResponse(resultOrErrors, acceptedMediaType) {
|
|
|
340
361
|
: null;
|
|
341
362
|
if (errors) {
|
|
342
363
|
return [
|
|
343
|
-
JSON.stringify({ errors }),
|
|
364
|
+
JSON.stringify({ errors: errors.map(formatError) }, utils_1.jsonErrorReplacer),
|
|
344
365
|
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
345
366
|
? {
|
|
346
367
|
status: 200,
|
|
@@ -357,7 +378,8 @@ function makeResponse(resultOrErrors, acceptedMediaType) {
|
|
|
357
378
|
];
|
|
358
379
|
}
|
|
359
380
|
return [
|
|
360
|
-
JSON.stringify(resultOrErrors
|
|
381
|
+
JSON.stringify('errors' in resultOrErrors && resultOrErrors.errors
|
|
382
|
+
? Object.assign(Object.assign({}, resultOrErrors), { errors: resultOrErrors.errors.map(formatError) }) : resultOrErrors, utils_1.jsonErrorReplacer),
|
|
361
383
|
{
|
|
362
384
|
status: 200,
|
|
363
385
|
statusText: 'OK',
|