graphql-http 1.17.0 → 1.18.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 +1 -0
- package/lib/audits/common.d.mts +1 -1
- package/lib/audits/common.d.ts +1 -1
- package/lib/audits/index.d.mts +3 -3
- package/lib/audits/render.d.mts +1 -1
- package/lib/audits/render.js +15 -0
- package/lib/audits/render.mjs +15 -0
- package/lib/audits/server.d.mts +1 -1
- package/lib/audits/server.js +61 -161
- package/lib/audits/server.mjs +61 -161
- package/lib/audits/utils.d.mts +2 -2
- package/lib/audits/utils.js +6 -3
- package/lib/audits/utils.mjs +6 -3
- package/lib/client.d.mts +2 -2
- package/lib/handler.d.mts +22 -5
- package/lib/handler.d.ts +20 -3
- package/lib/handler.js +36 -14
- package/lib/handler.mjs +37 -15
- package/lib/index.d.mts +4 -4
- package/lib/use/express.d.mts +2 -2
- package/lib/use/fastify.d.mts +2 -2
- package/lib/use/fetch.d.mts +2 -2
- package/lib/use/http.d.mts +2 -2
- package/lib/use/http2.d.mts +2 -2
- package/lib/use/koa.d.mts +2 -2
- package/lib/use/node.d.mts +3 -3
- 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 +16 -15
- package/umd/graphql-http-audits.js +82 -164
- 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
|
}),
|
|
@@ -83,7 +87,9 @@ export function serverAudits(opts) {
|
|
|
83
87
|
headers: {
|
|
84
88
|
'content-type': 'application/json; charset=utf-8',
|
|
85
89
|
},
|
|
86
|
-
body: JSON.stringify({
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
query: '{ __type(name: "Run🏃Swim🏊") { name } }',
|
|
92
|
+
}),
|
|
87
93
|
});
|
|
88
94
|
ressert(res).status.toBe(200);
|
|
89
95
|
}),
|
|
@@ -125,7 +131,7 @@ export function serverAudits(opts) {
|
|
|
125
131
|
ressert(res).status.toBeBetween(400, 499);
|
|
126
132
|
}),
|
|
127
133
|
// Request POST
|
|
128
|
-
audit('9ABE', '
|
|
134
|
+
audit('9ABE', 'MAY respond with 4xx status code if content-type is not supplied on POST requests', async () => {
|
|
129
135
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
130
136
|
method: 'POST',
|
|
131
137
|
});
|
|
@@ -139,23 +145,15 @@ export function serverAudits(opts) {
|
|
|
139
145
|
});
|
|
140
146
|
ressert(res).status.toBe(200);
|
|
141
147
|
}),
|
|
142
|
-
audit('
|
|
143
|
-
var _a;
|
|
148
|
+
audit('A5BF', 'MAY use 400 status code when request body is missing on POST', async () => {
|
|
144
149
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
145
150
|
method: 'POST',
|
|
146
151
|
headers: { 'content-type': 'application/json' },
|
|
147
152
|
});
|
|
148
|
-
|
|
149
|
-
await ressert(res).bodyAsExecutionResult.toHaveProperty('errors');
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
ressert(res).status.toBe(400);
|
|
153
|
-
}
|
|
153
|
+
ressert(res).status.toBe(400);
|
|
154
154
|
}),
|
|
155
155
|
// Request Parameters
|
|
156
|
-
audit(
|
|
157
|
-
// TODO: convert to MUST after watershed
|
|
158
|
-
'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 () => {
|
|
159
157
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
160
158
|
method: 'POST',
|
|
161
159
|
headers: {
|
|
@@ -166,26 +164,11 @@ export function serverAudits(opts) {
|
|
|
166
164
|
});
|
|
167
165
|
ressert(res).status.toBe(400);
|
|
168
166
|
}),
|
|
169
|
-
|
|
167
|
+
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid, index) => audit(`LKJ${index}`, `MAY use 400 status code on ${extendedTypeof(invalid)} {query} parameter`, async () => {
|
|
170
168
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
171
169
|
method: 'POST',
|
|
172
170
|
headers: {
|
|
173
171
|
'content-type': 'application/json',
|
|
174
|
-
accept: 'application/json',
|
|
175
|
-
},
|
|
176
|
-
body: JSON.stringify({ notquery: '{ __typename }' }),
|
|
177
|
-
});
|
|
178
|
-
ressert(res).status.toBe(200);
|
|
179
|
-
await ressert(res).bodyAsExecutionResult.toHaveProperty('errors');
|
|
180
|
-
}),
|
|
181
|
-
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid, index) => audit(`4F5${index}`,
|
|
182
|
-
// TODO: convert to MUST after watershed
|
|
183
|
-
`SHOULD use 400 status code on ${extendedTypeof(invalid)} {query} parameter when accepting application/graphql-response+json`, async () => {
|
|
184
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
185
|
-
method: 'POST',
|
|
186
|
-
headers: {
|
|
187
|
-
'content-type': 'application/json',
|
|
188
|
-
accept: 'application/graphql-response+json',
|
|
189
172
|
},
|
|
190
173
|
body: JSON.stringify({
|
|
191
174
|
query: invalid,
|
|
@@ -193,20 +176,6 @@ export function serverAudits(opts) {
|
|
|
193
176
|
});
|
|
194
177
|
ressert(res).status.toBe(400);
|
|
195
178
|
})),
|
|
196
|
-
...[{ 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 () => {
|
|
197
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
198
|
-
method: 'POST',
|
|
199
|
-
headers: {
|
|
200
|
-
'content-type': 'application/json',
|
|
201
|
-
accept: 'application/json',
|
|
202
|
-
},
|
|
203
|
-
body: JSON.stringify({
|
|
204
|
-
query: invalid,
|
|
205
|
-
}),
|
|
206
|
-
});
|
|
207
|
-
ressert(res).status.toBe(200);
|
|
208
|
-
await ressert(res).bodyAsExecutionResult.toHaveProperty('errors');
|
|
209
|
-
})),
|
|
210
179
|
audit(
|
|
211
180
|
// TODO: convert to MUST after watershed
|
|
212
181
|
'34A2', 'SHOULD allow string {query} parameter when accepting application/graphql-response+json', async () => {
|
|
@@ -236,14 +205,11 @@ export function serverAudits(opts) {
|
|
|
236
205
|
ressert(res).status.toBe(200);
|
|
237
206
|
await ressert(res).bodyAsExecutionResult.notToHaveProperty('errors');
|
|
238
207
|
}),
|
|
239
|
-
...[{ obj: 'ect' }, 0, false, ['array']].map((invalid, index) => audit(`
|
|
240
|
-
// TODO: convert to MUST after watershed
|
|
241
|
-
`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 () => {
|
|
242
209
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
243
210
|
method: 'POST',
|
|
244
211
|
headers: {
|
|
245
212
|
'content-type': 'application/json',
|
|
246
|
-
accept: 'application/graphql-response+json',
|
|
247
213
|
},
|
|
248
214
|
body: JSON.stringify({
|
|
249
215
|
operationName: invalid,
|
|
@@ -252,21 +218,6 @@ export function serverAudits(opts) {
|
|
|
252
218
|
});
|
|
253
219
|
ressert(res).status.toBe(400);
|
|
254
220
|
})),
|
|
255
|
-
...[{ 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 () => {
|
|
256
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
257
|
-
method: 'POST',
|
|
258
|
-
headers: {
|
|
259
|
-
'content-type': 'application/json',
|
|
260
|
-
accept: 'application/json',
|
|
261
|
-
},
|
|
262
|
-
body: JSON.stringify({
|
|
263
|
-
operationName: invalid,
|
|
264
|
-
query: '{ __typename }',
|
|
265
|
-
}),
|
|
266
|
-
});
|
|
267
|
-
ressert(res).status.toBe(200);
|
|
268
|
-
await ressert(res).bodyAsExecutionResult.toHaveProperty('errors');
|
|
269
|
-
})),
|
|
270
221
|
audit(
|
|
271
222
|
// TODO: convert to MUST after watershed
|
|
272
223
|
'8161', 'SHOULD allow string {operationName} parameter when accepting application/graphql-response+json', async () => {
|
|
@@ -332,14 +283,11 @@ export function serverAudits(opts) {
|
|
|
332
283
|
await ressert(res).bodyAsExecutionResult.notToHaveProperty('errors');
|
|
333
284
|
}),
|
|
334
285
|
]),
|
|
335
|
-
...['string', 0, false, ['array']].map((invalid, index) => audit(`
|
|
336
|
-
// TODO: convert to MUST after watershed
|
|
337
|
-
`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 () => {
|
|
338
287
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
339
288
|
method: 'POST',
|
|
340
289
|
headers: {
|
|
341
290
|
'content-type': 'application/json',
|
|
342
|
-
accept: 'application/graphql-response+json',
|
|
343
291
|
},
|
|
344
292
|
body: JSON.stringify({
|
|
345
293
|
query: '{ __typename }',
|
|
@@ -348,21 +296,6 @@ export function serverAudits(opts) {
|
|
|
348
296
|
});
|
|
349
297
|
ressert(res).status.toBe(400);
|
|
350
298
|
})),
|
|
351
|
-
...['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 () => {
|
|
352
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
353
|
-
method: 'POST',
|
|
354
|
-
headers: {
|
|
355
|
-
'content-type': 'application/json',
|
|
356
|
-
accept: 'application/json',
|
|
357
|
-
},
|
|
358
|
-
body: JSON.stringify({
|
|
359
|
-
query: '{ __typename }',
|
|
360
|
-
variables: invalid,
|
|
361
|
-
}),
|
|
362
|
-
});
|
|
363
|
-
ressert(res).status.toBe(200);
|
|
364
|
-
await ressert(res).bodyAsExecutionResult.toHaveProperty('errors');
|
|
365
|
-
})),
|
|
366
299
|
audit(
|
|
367
300
|
// TODO: convert to MUST after watershed
|
|
368
301
|
'2EA1', 'SHOULD allow map {variables} parameter when accepting application/graphql-response+json', async () => {
|
|
@@ -419,14 +352,13 @@ export function serverAudits(opts) {
|
|
|
419
352
|
ressert(res).status.toBe(200);
|
|
420
353
|
await ressert(res).bodyAsExecutionResult.notToHaveProperty('errors');
|
|
421
354
|
}),
|
|
422
|
-
...['string', 0, false, ['array']].map((invalid, index) => audit(`
|
|
355
|
+
...['string', 0, false, ['array']].map((invalid, index) => audit(`58B${index}`,
|
|
423
356
|
// TODO: convert to MUST after watershed
|
|
424
|
-
`
|
|
357
|
+
`MAY use 400 status code on ${extendedTypeof(invalid)} {extensions} parameter`, async () => {
|
|
425
358
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
426
359
|
method: 'POST',
|
|
427
360
|
headers: {
|
|
428
361
|
'content-type': 'application/json',
|
|
429
|
-
accept: 'application/graphql-response+json',
|
|
430
362
|
},
|
|
431
363
|
body: JSON.stringify({
|
|
432
364
|
query: '{ __typename }',
|
|
@@ -435,21 +367,6 @@ export function serverAudits(opts) {
|
|
|
435
367
|
});
|
|
436
368
|
ressert(res).status.toBe(400);
|
|
437
369
|
})),
|
|
438
|
-
...['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 () => {
|
|
439
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
440
|
-
method: 'POST',
|
|
441
|
-
headers: {
|
|
442
|
-
'content-type': 'application/json',
|
|
443
|
-
accept: 'application/json',
|
|
444
|
-
},
|
|
445
|
-
body: JSON.stringify({
|
|
446
|
-
query: '{ __typename }',
|
|
447
|
-
extensions: invalid,
|
|
448
|
-
}),
|
|
449
|
-
});
|
|
450
|
-
ressert(res).status.toBe(200);
|
|
451
|
-
await ressert(res).bodyAsExecutionResult.toHaveProperty('errors');
|
|
452
|
-
})),
|
|
453
370
|
audit(
|
|
454
371
|
// TODO: convert to MUST after watershed
|
|
455
372
|
'428F', 'SHOULD allow map {extensions} parameter when accepting application/graphql-response+json', async () => {
|
|
@@ -481,122 +398,91 @@ export function serverAudits(opts) {
|
|
|
481
398
|
ressert(res).status.toBe(200);
|
|
482
399
|
await ressert(res).bodyAsExecutionResult.notToHaveProperty('errors');
|
|
483
400
|
}),
|
|
484
|
-
|
|
485
|
-
// Response application/json
|
|
486
|
-
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 () => {
|
|
487
402
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
488
403
|
method: 'POST',
|
|
489
404
|
headers: {
|
|
490
405
|
'content-type': 'application/json',
|
|
491
|
-
accept: 'application/json',
|
|
492
406
|
},
|
|
493
407
|
body: '{ "not a JSON',
|
|
494
408
|
});
|
|
495
|
-
ressert(res).status.
|
|
496
|
-
}),
|
|
497
|
-
audit('F5AF', 'SHOULD use 200 status code if parameters are invalid when accepting application/json', async () => {
|
|
498
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
499
|
-
method: 'POST',
|
|
500
|
-
headers: {
|
|
501
|
-
'content-type': 'application/json',
|
|
502
|
-
accept: 'application/json',
|
|
503
|
-
},
|
|
504
|
-
body: JSON.stringify({
|
|
505
|
-
qeury: /* typo */ '{ __typename }',
|
|
506
|
-
}),
|
|
507
|
-
});
|
|
508
|
-
ressert(res).status.toBe(200);
|
|
409
|
+
ressert(res).status.toBeBetween(400, 499);
|
|
509
410
|
}),
|
|
510
|
-
audit('
|
|
411
|
+
audit('BCF8', 'MAY use 400 status code on JSON parsing failure', async () => {
|
|
511
412
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
512
413
|
method: 'POST',
|
|
513
414
|
headers: {
|
|
514
415
|
'content-type': 'application/json',
|
|
515
|
-
accept: 'application/json',
|
|
516
416
|
},
|
|
517
|
-
body:
|
|
417
|
+
body: '{ "not a JSON',
|
|
518
418
|
});
|
|
519
|
-
ressert(res).status.toBe(
|
|
419
|
+
ressert(res).status.toBe(400);
|
|
520
420
|
}),
|
|
521
|
-
audit('
|
|
421
|
+
audit('8764', 'MAY use 4xx or 5xx status codes if parameters are invalid', async () => {
|
|
522
422
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
523
423
|
method: 'POST',
|
|
524
424
|
headers: {
|
|
525
425
|
'content-type': 'application/json',
|
|
526
|
-
accept: 'application/json',
|
|
527
426
|
},
|
|
528
427
|
body: JSON.stringify({
|
|
529
|
-
|
|
428
|
+
qeury /* typo */: '{ __typename }',
|
|
530
429
|
}),
|
|
531
430
|
});
|
|
532
|
-
ressert(res).status.
|
|
431
|
+
ressert(res).status.toBeBetween(400, 599);
|
|
533
432
|
}),
|
|
534
|
-
|
|
535
|
-
audit(
|
|
536
|
-
// TODO: convert to MUST after watershed
|
|
537
|
-
'60AA', 'SHOULD use 4xx or 5xx status codes on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
433
|
+
audit('3E3A', 'MAY use 400 status code if parameters are invalid', async () => {
|
|
538
434
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
539
435
|
method: 'POST',
|
|
540
436
|
headers: {
|
|
541
437
|
'content-type': 'application/json',
|
|
542
|
-
accept: 'application/graphql-response+json',
|
|
543
438
|
},
|
|
544
|
-
body:
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}),
|
|
548
|
-
audit('2163', 'SHOULD use 400 status code on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
549
|
-
const res = await fetchFn(await getUrl(opts.url), {
|
|
550
|
-
method: 'POST',
|
|
551
|
-
headers: {
|
|
552
|
-
'content-type': 'application/json',
|
|
553
|
-
accept: 'application/graphql-response+json',
|
|
554
|
-
},
|
|
555
|
-
body: '{ "not a JSON',
|
|
439
|
+
body: JSON.stringify({
|
|
440
|
+
qeury: /* typo */ '{ __typename }',
|
|
441
|
+
}),
|
|
556
442
|
});
|
|
557
443
|
ressert(res).status.toBe(400);
|
|
558
444
|
}),
|
|
559
|
-
audit(
|
|
560
|
-
//
|
|
561
|
-
'
|
|
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 () => {
|
|
562
448
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
563
449
|
method: 'POST',
|
|
564
450
|
headers: {
|
|
565
451
|
'content-type': 'application/json',
|
|
566
|
-
accept: 'application/
|
|
452
|
+
accept: 'application/json',
|
|
567
453
|
},
|
|
568
|
-
body: JSON.stringify({
|
|
569
|
-
qeury /* typo */: '{ __typename }',
|
|
570
|
-
}),
|
|
454
|
+
body: JSON.stringify({ query: '{' }),
|
|
571
455
|
});
|
|
572
|
-
ressert(res).status.
|
|
456
|
+
ressert(res).status.toBe(200);
|
|
573
457
|
}),
|
|
574
|
-
audit('
|
|
458
|
+
audit('FDE2', 'SHOULD use 200 status code on document validation failure when accepting application/json', async () => {
|
|
575
459
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
576
460
|
method: 'POST',
|
|
577
461
|
headers: {
|
|
578
462
|
'content-type': 'application/json',
|
|
579
|
-
accept: 'application/
|
|
463
|
+
accept: 'application/json',
|
|
580
464
|
},
|
|
581
465
|
body: JSON.stringify({
|
|
582
|
-
|
|
466
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
583
467
|
}),
|
|
584
468
|
});
|
|
585
|
-
ressert(res).status.toBe(
|
|
469
|
+
ressert(res).status.toBe(200);
|
|
586
470
|
}),
|
|
587
|
-
audit('
|
|
471
|
+
audit('7B9B', 'SHOULD use a status code of 200 on variable coercion failure when accepting application/json', async () => {
|
|
588
472
|
const res = await fetchFn(await getUrl(opts.url), {
|
|
589
473
|
method: 'POST',
|
|
590
474
|
headers: {
|
|
591
475
|
'content-type': 'application/json',
|
|
592
|
-
accept: 'application/
|
|
476
|
+
accept: 'application/json',
|
|
593
477
|
},
|
|
594
478
|
body: JSON.stringify({
|
|
595
|
-
|
|
479
|
+
query: 'query CoerceFailure($id: ID!){ __typename }',
|
|
480
|
+
variables: { id: null },
|
|
596
481
|
}),
|
|
597
482
|
});
|
|
598
|
-
|
|
483
|
+
ressert(res).status.toBe(200);
|
|
599
484
|
}),
|
|
485
|
+
// Response application/graphql-response+json
|
|
600
486
|
audit(
|
|
601
487
|
// TODO: convert to MUST after watershed
|
|
602
488
|
'865D', 'SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
@@ -679,6 +565,20 @@ export function serverAudits(opts) {
|
|
|
679
565
|
});
|
|
680
566
|
await ressert(res).bodyAsExecutionResult.data.toBe(undefined);
|
|
681
567
|
}),
|
|
568
|
+
audit('86EE', 'SHOULD use a status code of 400 on variable coercion failure when accepting application/graphql-response+json', async () => {
|
|
569
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
570
|
+
method: 'POST',
|
|
571
|
+
headers: {
|
|
572
|
+
'content-type': 'application/json',
|
|
573
|
+
accept: 'application/graphql-response+json',
|
|
574
|
+
},
|
|
575
|
+
body: JSON.stringify({
|
|
576
|
+
query: 'query CoerceFailure($id: ID!){ __typename }',
|
|
577
|
+
variables: { id: null },
|
|
578
|
+
}),
|
|
579
|
+
});
|
|
580
|
+
ressert(res).status.toBe(400);
|
|
581
|
+
}),
|
|
682
582
|
// TODO: how to fail and have the data entry?
|
|
683
583
|
// audit('EE52', 'MUST use 2xx status code if response contains the data entry and it is not null when accepting application/graphql-response+json'),
|
|
684
584
|
// TODO: how to make an unauthorized request?
|
package/lib/audits/utils.d.mts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
import type { ExecutionResult } from 'graphql';
|
|
7
|
-
import { Audit, AuditName } from './common';
|
|
8
|
-
export * from '../utils';
|
|
7
|
+
import { Audit, AuditName } from './common.mjs';
|
|
8
|
+
export * from '../utils.mjs';
|
|
9
9
|
/**
|
|
10
10
|
* Wrap and prepare an audit for testing.
|
|
11
11
|
*
|
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/client.d.mts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
import type { ExecutionResult } from 'graphql';
|
|
7
|
-
import { RequestParams, Sink } from './common';
|
|
7
|
+
import { RequestParams, Sink } from './common.mjs';
|
|
8
8
|
/** This file is the entry point for browsers, re-export common elements. */
|
|
9
|
-
export * from './common';
|
|
9
|
+
export * from './common.mjs';
|
|
10
10
|
/** @category Client */
|
|
11
11
|
export interface ClientOptions {
|
|
12
12
|
/**
|
package/lib/handler.d.mts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
import { ExecutionArgs, ExecutionResult, GraphQLSchema, validate as graphqlValidate, ValidationRule, execute as graphqlExecute, parse as graphqlParse, getOperationAST as graphqlGetOperationAST, GraphQLError } from 'graphql';
|
|
7
|
-
import { RequestParams } from './common';
|
|
7
|
+
import { RequestParams } from './common.mjs';
|
|
8
8
|
/**
|
|
9
9
|
* The incoming request headers the implementing server should provide.
|
|
10
10
|
*
|
|
@@ -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
|
|
@@ -246,7 +260,7 @@ export type Handler<RequestRaw = unknown, RequestContext = unknown> = (req: Requ
|
|
|
246
260
|
* ```js
|
|
247
261
|
* import http from 'http';
|
|
248
262
|
* import { createHandler } from 'graphql-http';
|
|
249
|
-
* import { schema } from './my-graphql-schema';
|
|
263
|
+
* import { schema } from './my-graphql-schema/index.mjs';
|
|
250
264
|
*
|
|
251
265
|
* // Create the GraphQL over HTTP handler
|
|
252
266
|
* const handler = createHandler({ schema });
|
|
@@ -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;
|