graphql-http 1.22.0 → 1.22.2

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 CHANGED
@@ -1,5 +1,3 @@
1
- [![GraphQL Conf 2023](/GraphQLConf-2023-Banner.png)](https://graphql.org/conf/)
2
-
3
1
  <div align="center">
4
2
  <br />
5
3
 
@@ -120,7 +118,7 @@ import express from 'express'; // yarn add express
120
118
  import { createHandler } from 'graphql-http/lib/use/express';
121
119
  import { schema } from './previous-step';
122
120
 
123
- // Create a express instance serving all methods on `/graphql`
121
+ // Create an express instance serving all methods on `/graphql`
124
122
  // where the GraphQL over HTTP express request handler is
125
123
  const app = express();
126
124
  app.all('/graphql', createHandler({ schema }));
@@ -266,6 +264,16 @@ const client = createClient({
266
264
  })();
267
265
  ```
268
266
 
267
+ #### Serve [GraphiQL](https://github.com/graphql/graphiql)
268
+
269
+ Thanks to [`ruru`](https://github.com/graphile/crystal/tree/main/grafast/ruru), serving GraphiQL is as easy as running:
270
+
271
+ ```sh
272
+ npx ruru -SP -p 4001 -e http://localhost:4000/graphql
273
+ ```
274
+
275
+ Open [http://localhost:4001](http://localhost:4001) in the browser to use it.
276
+
269
277
  ## Recipes
270
278
 
271
279
  <details id="promise">
@@ -38,8 +38,7 @@ async function renderAuditResultsToHTML(results) {
38
38
  report += `<li><span style="font-family: monospace">💡</span> <b>${grouped.notice.length}</b> notices (suggestions)</li>\n`;
39
39
  }
40
40
  if (grouped.warn.length) {
41
- // TODO: warning sign is rendered as "⚠️" in markdown instead of the emoji
42
- report += `<li><span style="font-family: monospace">⚠️</span> <b>${grouped.warn.length}</b> warnings (optional)</li>\n`;
41
+ report += `<li><span style="font-family: monospace">❗️</span> <b>${grouped.warn.length}</b> warnings (optional)</li>\n`;
43
42
  }
44
43
  if (grouped.error.length) {
45
44
  report += `<li><span style="font-family: monospace">❌</span> <b>${grouped.error.length}</b> errors (required)</li>\n`;
@@ -89,6 +88,7 @@ async function renderAuditResultsToHTML(results) {
89
88
  }
90
89
  exports.renderAuditResultsToHTML = renderAuditResultsToHTML;
91
90
  async function printAuditFail(result) {
91
+ var _a;
92
92
  let report = '';
93
93
  report += `<li><code>${result.id}</code> ${result.name}\n`;
94
94
  report += '<details>\n';
@@ -101,7 +101,7 @@ async function printAuditFail(result) {
101
101
  if (key === 'date') {
102
102
  headers[key] = '<timestamp>';
103
103
  }
104
- else if (['cf-ray', 'server-timing'].includes(key)) {
104
+ else if (['cf-ray', 'server-timing', 'set-cookie'].includes(key)) {
105
105
  headers[key] = '<omitted>';
106
106
  }
107
107
  else {
@@ -112,9 +112,13 @@ async function printAuditFail(result) {
112
112
  try {
113
113
  text = await res.text();
114
114
  json = JSON.parse(text);
115
+ // is json, there shouldnt be nothing to sanitize (hopefully)
115
116
  }
116
- catch (_a) {
117
- // noop
117
+ catch (_b) {
118
+ // is not json, avoid rendering html (rest is allowed)
119
+ if ((_a = res.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('text/html')) {
120
+ text = '<html omitted>';
121
+ }
118
122
  }
119
123
  const stringified = JSON.stringify({
120
124
  status: res.status,
@@ -35,8 +35,7 @@ export async function renderAuditResultsToHTML(results) {
35
35
  report += `<li><span style="font-family: monospace">💡</span> <b>${grouped.notice.length}</b> notices (suggestions)</li>\n`;
36
36
  }
37
37
  if (grouped.warn.length) {
38
- // TODO: warning sign is rendered as "⚠️" in markdown instead of the emoji
39
- report += `<li><span style="font-family: monospace">⚠️</span> <b>${grouped.warn.length}</b> warnings (optional)</li>\n`;
38
+ report += `<li><span style="font-family: monospace">❗️</span> <b>${grouped.warn.length}</b> warnings (optional)</li>\n`;
40
39
  }
41
40
  if (grouped.error.length) {
42
41
  report += `<li><span style="font-family: monospace">❌</span> <b>${grouped.error.length}</b> errors (required)</li>\n`;
@@ -85,6 +84,7 @@ export async function renderAuditResultsToHTML(results) {
85
84
  return report;
86
85
  }
87
86
  async function printAuditFail(result) {
87
+ var _a;
88
88
  let report = '';
89
89
  report += `<li><code>${result.id}</code> ${result.name}\n`;
90
90
  report += '<details>\n';
@@ -97,7 +97,7 @@ async function printAuditFail(result) {
97
97
  if (key === 'date') {
98
98
  headers[key] = '<timestamp>';
99
99
  }
100
- else if (['cf-ray', 'server-timing'].includes(key)) {
100
+ else if (['cf-ray', 'server-timing', 'set-cookie'].includes(key)) {
101
101
  headers[key] = '<omitted>';
102
102
  }
103
103
  else {
@@ -108,9 +108,13 @@ async function printAuditFail(result) {
108
108
  try {
109
109
  text = await res.text();
110
110
  json = JSON.parse(text);
111
+ // is json, there shouldnt be nothing to sanitize (hopefully)
111
112
  }
112
- catch (_a) {
113
- // noop
113
+ catch (_b) {
114
+ // is not json, avoid rendering html (rest is allowed)
115
+ if ((_a = res.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('text/html')) {
116
+ text = '<html omitted>';
117
+ }
114
118
  }
115
119
  const stringified = JSON.stringify({
116
120
  status: res.status,
package/lib/handler.js CHANGED
@@ -10,10 +10,22 @@ const graphql_1 = require("graphql");
10
10
  const utils_1 = require("./utils");
11
11
  /** Checks whether the passed value is the `graphql-http` server agnostic response. */
12
12
  function isResponse(val) {
13
- // TODO: make sure the contents of init match ResponseInit
14
- return (Array.isArray(val) &&
15
- (typeof val[0] === 'string' || val[0] === null) &&
16
- (0, utils_1.isObject)(val[1]));
13
+ // Make sure the contents of body match string | null
14
+ if (!Array.isArray(val))
15
+ return false;
16
+ if (typeof val[0] !== 'string' && val[0] !== null)
17
+ return false;
18
+ if (!(0, utils_1.isObject)(val[1]))
19
+ return false;
20
+ // Make sure the contents of init match ResponseInit
21
+ const init = val[1];
22
+ if (typeof init.status !== 'number')
23
+ return false;
24
+ if (typeof init.statusText !== 'string')
25
+ return false;
26
+ if (init.headers && !(0, utils_1.isObject)(init.headers))
27
+ return false;
28
+ return true;
17
29
  }
18
30
  /**
19
31
  * The GraphQL over HTTP spec compliant request parser for an incoming GraphQL request.
@@ -193,9 +205,9 @@ function createHandler(options) {
193
205
  // accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
194
206
  // TODO: handle the weight parameter "q"
195
207
  const [mediaType, ...params] = accept.split(';');
196
- const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
208
+ const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf-8'; // utf-8 is assumed when not specified;
197
209
  if (mediaType === 'application/graphql-response+json' &&
198
- charset === 'charset=utf8') {
210
+ charset === 'charset=utf-8') {
199
211
  acceptedMediaType = 'application/graphql-response+json';
200
212
  break;
201
213
  }
@@ -203,7 +215,7 @@ function createHandler(options) {
203
215
  if ((mediaType === 'application/json' ||
204
216
  mediaType === 'application/*' ||
205
217
  mediaType === '*/*') &&
206
- charset === 'charset=utf8') {
218
+ (charset === 'charset=utf-8' || charset === 'charset=utf8')) {
207
219
  acceptedMediaType = 'application/json';
208
220
  break;
209
221
  }
@@ -417,10 +429,11 @@ val) {
417
429
  if (val instanceof Error &&
418
430
  // GraphQL errors implement their own stringer
419
431
  !isGraphQLError(val)) {
432
+ const error = val;
420
433
  return {
421
- // name: val.name, name is included in message
422
- message: val.message,
423
- // stack: val.stack, can leak sensitive details
434
+ // name: error.name, name is included in message
435
+ message: error.message,
436
+ // stack: error.stack, can leak sensitive details
424
437
  };
425
438
  }
426
439
  return val;
package/lib/handler.mjs CHANGED
@@ -7,10 +7,22 @@ import { validate as graphqlValidate, specifiedRules, execute as graphqlExecute,
7
7
  import { isAsyncIterable, isExecutionResult, isObject } from './utils.mjs';
8
8
  /** Checks whether the passed value is the `graphql-http` server agnostic response. */
9
9
  function isResponse(val) {
10
- // TODO: make sure the contents of init match ResponseInit
11
- return (Array.isArray(val) &&
12
- (typeof val[0] === 'string' || val[0] === null) &&
13
- isObject(val[1]));
10
+ // Make sure the contents of body match string | null
11
+ if (!Array.isArray(val))
12
+ return false;
13
+ if (typeof val[0] !== 'string' && val[0] !== null)
14
+ return false;
15
+ if (!isObject(val[1]))
16
+ return false;
17
+ // Make sure the contents of init match ResponseInit
18
+ const init = val[1];
19
+ if (typeof init.status !== 'number')
20
+ return false;
21
+ if (typeof init.statusText !== 'string')
22
+ return false;
23
+ if (init.headers && !isObject(init.headers))
24
+ return false;
25
+ return true;
14
26
  }
15
27
  /**
16
28
  * The GraphQL over HTTP spec compliant request parser for an incoming GraphQL request.
@@ -189,9 +201,9 @@ export function createHandler(options) {
189
201
  // accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
190
202
  // TODO: handle the weight parameter "q"
191
203
  const [mediaType, ...params] = accept.split(';');
192
- const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
204
+ const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf-8'; // utf-8 is assumed when not specified;
193
205
  if (mediaType === 'application/graphql-response+json' &&
194
- charset === 'charset=utf8') {
206
+ charset === 'charset=utf-8') {
195
207
  acceptedMediaType = 'application/graphql-response+json';
196
208
  break;
197
209
  }
@@ -199,7 +211,7 @@ export function createHandler(options) {
199
211
  if ((mediaType === 'application/json' ||
200
212
  mediaType === 'application/*' ||
201
213
  mediaType === '*/*') &&
202
- charset === 'charset=utf8') {
214
+ (charset === 'charset=utf-8' || charset === 'charset=utf8')) {
203
215
  acceptedMediaType = 'application/json';
204
216
  break;
205
217
  }
@@ -412,10 +424,11 @@ val) {
412
424
  if (val instanceof Error &&
413
425
  // GraphQL errors implement their own stringer
414
426
  !isGraphQLError(val)) {
427
+ const error = val;
415
428
  return {
416
- // name: val.name, name is included in message
417
- message: val.message,
418
- // stack: val.stack, can leak sensitive details
429
+ // name: error.name, name is included in message
430
+ message: error.message,
431
+ // stack: error.stack, can leak sensitive details
419
432
  };
420
433
  }
421
434
  return val;
@@ -1,4 +1,4 @@
1
- import { HandlerOptions as RawHandlerOptions, OperationContext } from '../../handler/index.mjs';
1
+ import { HandlerOptions as RawHandlerOptions, OperationContext } from '../../handler.mjs';
2
2
  import type { Handler as NetlifyHandler, HandlerEvent as NetlifyHandlerEvent, HandlerContext as NetlifyHandlerContext } from '@netlify/functions';
3
3
  /**
4
4
  * Handler options when using the netlify adapter
@@ -99,6 +99,7 @@ function toRequest(req, res) {
99
99
  }
100
100
  return new Promise((resolve) => {
101
101
  let body = '';
102
+ req.setEncoding('utf-8');
102
103
  req.on('data', (chunk) => (body += chunk));
103
104
  req.on('end', () => resolve(body));
104
105
  });
@@ -94,6 +94,7 @@ function toRequest(req, res) {
94
94
  }
95
95
  return new Promise((resolve) => {
96
96
  let body = '';
97
+ req.setEncoding('utf-8');
97
98
  req.on('data', (chunk) => (body += chunk));
98
99
  req.on('end', () => resolve(body));
99
100
  });
package/lib/use/http.js CHANGED
@@ -108,6 +108,7 @@ function toRequest(req, res) {
108
108
  headers: req.headers,
109
109
  body: () => new Promise((resolve) => {
110
110
  let body = '';
111
+ req.setEncoding('utf-8');
111
112
  req.on('data', (chunk) => (body += chunk));
112
113
  req.on('end', () => resolve(body));
113
114
  }),
package/lib/use/http.mjs CHANGED
@@ -103,6 +103,7 @@ function toRequest(req, res) {
103
103
  headers: req.headers,
104
104
  body: () => new Promise((resolve) => {
105
105
  let body = '';
106
+ req.setEncoding('utf-8');
106
107
  req.on('data', (chunk) => (body += chunk));
107
108
  req.on('end', () => resolve(body));
108
109
  }),
package/lib/use/http2.js CHANGED
@@ -144,6 +144,7 @@ function toRequest(req, res) {
144
144
  headers: req.headers,
145
145
  body: () => new Promise((resolve) => {
146
146
  let body = '';
147
+ req.setEncoding('utf-8');
147
148
  req.on('data', (chunk) => (body += chunk));
148
149
  req.on('end', () => resolve(body));
149
150
  }),
package/lib/use/http2.mjs CHANGED
@@ -139,6 +139,7 @@ function toRequest(req, res) {
139
139
  headers: req.headers,
140
140
  body: () => new Promise((resolve) => {
141
141
  let body = '';
142
+ req.setEncoding('utf-8');
142
143
  req.on('data', (chunk) => (body += chunk));
143
144
  req.on('end', () => resolve(body));
144
145
  }),
package/lib/use/koa.js CHANGED
@@ -92,12 +92,13 @@ function createHandler(options) {
92
92
  method: ctx.method,
93
93
  headers: ctx.headers,
94
94
  body: () => {
95
- if (ctx.body) {
95
+ if ('body' in ctx.request) {
96
96
  // in case koa has a body parser
97
- return ctx.body;
97
+ return ctx.request.body;
98
98
  }
99
99
  return new Promise((resolve) => {
100
100
  let body = '';
101
+ ctx.req.setEncoding('utf-8');
101
102
  ctx.req.on('data', (chunk) => (body += chunk));
102
103
  ctx.req.on('end', () => resolve(body));
103
104
  });
package/lib/use/koa.mjs CHANGED
@@ -88,12 +88,13 @@ export function createHandler(options) {
88
88
  method: ctx.method,
89
89
  headers: ctx.headers,
90
90
  body: () => {
91
- if (ctx.body) {
91
+ if ('body' in ctx.request) {
92
92
  // in case koa has a body parser
93
- return ctx.body;
93
+ return ctx.request.body;
94
94
  }
95
95
  return new Promise((resolve) => {
96
96
  let body = '';
97
+ ctx.req.setEncoding('utf-8');
97
98
  ctx.req.on('data', (chunk) => (body += chunk));
98
99
  ctx.req.on('end', () => resolve(body));
99
100
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphql-http",
3
- "version": "1.22.0",
3
+ "version": "1.22.2",
4
4
  "description": "Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.",
5
5
  "keywords": [
6
6
  "graphql",
@@ -24,7 +24,7 @@
24
24
  "engines": {
25
25
  "node": ">=12"
26
26
  },
27
- "packageManager": "yarn@3.6.1",
27
+ "packageManager": "yarn@4.0.2",
28
28
  "main": "lib/index.js",
29
29
  "module": "lib/index.mjs",
30
30
  "browser": "umd/graphql-http.js",
@@ -116,43 +116,43 @@
116
116
  "graphql": ">=0.11 <=16"
117
117
  },
118
118
  "devDependencies": {
119
- "@cspell/cspell-types": "^7.0.0",
120
- "@netlify/functions": "^1.6.0",
121
- "@rollup/plugin-terser": "^0.4.3",
122
- "@rollup/plugin-typescript": "^11.1.2",
119
+ "@cspell/cspell-types": "^8.1.2",
120
+ "@netlify/functions": "^2.4.0",
121
+ "@rollup/plugin-terser": "^0.4.4",
122
+ "@rollup/plugin-typescript": "^11.1.5",
123
123
  "@semantic-release/changelog": "^6.0.3",
124
124
  "@semantic-release/git": "^10.0.1",
125
- "@types/eslint": "^8.44.2",
126
- "@types/express": "^4.17.17",
125
+ "@types/eslint": "^8.44.8",
126
+ "@types/express": "^4.17.21",
127
127
  "@types/glob": "^8.1.0",
128
- "@types/html-validator": "^5.0.3",
129
- "@types/k6": "^0.46.0",
130
- "@types/koa": "^2.13.8",
131
- "@types/koa-mount": "^4.0.2",
132
- "@typescript-eslint/eslint-plugin": "^6.4.0",
133
- "@typescript-eslint/parser": "^6.4.0",
134
- "@whatwg-node/fetch": "^0.9.9",
135
- "cspell": "^7.0.0",
136
- "eslint": "^8.47.0",
137
- "eslint-config-prettier": "^9.0.0",
128
+ "@types/html-validator": "^5.0.6",
129
+ "@types/k6": "^0.47.3",
130
+ "@types/koa": "^2.15.0",
131
+ "@types/koa-mount": "^4.0.5",
132
+ "@typescript-eslint/eslint-plugin": "^6.13.2",
133
+ "@typescript-eslint/parser": "^6.13.2",
134
+ "@whatwg-node/fetch": "^0.9.14",
135
+ "cspell": "^8.1.2",
136
+ "eslint": "^8.55.0",
137
+ "eslint-config-prettier": "^9.1.0",
138
138
  "express": "^4.18.2",
139
- "fastify": "^4.21.0",
140
- "glob": "^10.3.3",
141
- "graphql": "^16.8.0",
139
+ "fastify": "^4.24.3",
140
+ "glob": "^10.3.10",
141
+ "graphql": "^16.8.1",
142
142
  "html-validator": "^6.0.1",
143
- "koa": "^2.14.2",
143
+ "koa": "^2.15.3",
144
144
  "koa-mount": "^4.0.0",
145
145
  "node-fetch": "^3.3.2",
146
- "prettier": "^3.0.1",
147
- "rollup": "^3.28.0",
146
+ "prettier": "^3.1.0",
147
+ "rollup": "^4.6.1",
148
148
  "rollup-plugin-gzip": "^3.1.0",
149
- "semantic-release": "^21.0.7",
150
- "tslib": "^2.6.1",
151
- "tsx": "^3.12.7",
152
- "typedoc": "^0.24.8",
153
- "typedoc-plugin-markdown": "^3.15.4",
154
- "typescript": "^5.1.6",
155
- "uWebSockets.js": "uNetworking/uWebSockets.js#v20.31.0",
156
- "vitest": "^0.34.1"
149
+ "semantic-release": "^22.0.9",
150
+ "tslib": "^2.6.2",
151
+ "tsx": "^4.6.2",
152
+ "typedoc": "^0.25.4",
153
+ "typedoc-plugin-markdown": "^3.17.1",
154
+ "typescript": "^5.3.2",
155
+ "uWebSockets.js": "uNetworking/uWebSockets.js#v20.34.0",
156
+ "vitest": "^1.0.1"
157
157
  }
158
158
  }
@@ -808,8 +808,7 @@
808
808
  report += `<li><span style="font-family: monospace">💡</span> <b>${grouped.notice.length}</b> notices (suggestions)</li>\n`;
809
809
  }
810
810
  if (grouped.warn.length) {
811
- // TODO: warning sign is rendered as "⚠️" in markdown instead of the emoji
812
- report += `<li><span style="font-family: monospace">⚠️</span> <b>${grouped.warn.length}</b> warnings (optional)</li>\n`;
811
+ report += `<li><span style="font-family: monospace">❗️</span> <b>${grouped.warn.length}</b> warnings (optional)</li>\n`;
813
812
  }
814
813
  if (grouped.error.length) {
815
814
  report += `<li><span style="font-family: monospace">❌</span> <b>${grouped.error.length}</b> errors (required)</li>\n`;
@@ -858,6 +857,7 @@
858
857
  return report;
859
858
  }
860
859
  async function printAuditFail(result) {
860
+ var _a;
861
861
  let report = '';
862
862
  report += `<li><code>${result.id}</code> ${result.name}\n`;
863
863
  report += '<details>\n';
@@ -870,7 +870,7 @@
870
870
  if (key === 'date') {
871
871
  headers[key] = '<timestamp>';
872
872
  }
873
- else if (['cf-ray', 'server-timing'].includes(key)) {
873
+ else if (['cf-ray', 'server-timing', 'set-cookie'].includes(key)) {
874
874
  headers[key] = '<omitted>';
875
875
  }
876
876
  else {
@@ -881,9 +881,13 @@
881
881
  try {
882
882
  text = await res.text();
883
883
  json = JSON.parse(text);
884
+ // is json, there shouldnt be nothing to sanitize (hopefully)
884
885
  }
885
- catch (_a) {
886
- // noop
886
+ catch (_b) {
887
+ // is not json, avoid rendering html (rest is allowed)
888
+ if ((_a = res.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('text/html')) {
889
+ text = '<html omitted>';
890
+ }
887
891
  }
888
892
  const stringified = JSON.stringify({
889
893
  status: res.status,
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).graphqlHttpAudits={})}(this,(function(e){"use strict";function t(e){return null===e?"null":Array.isArray(e)?"array":typeof e}function a(e,t,a){return{id:e,name:t,fn:async()=>{try{return await a(),{id:e,name:t,status:"ok"}}catch(a){if(!(a instanceof n))throw a;return{id:e,name:t,status:t.startsWith("MUST")?"error":t.startsWith("SHOULD")?"warn":"notice",reason:a.reason,response:a.response}}}}}class n{constructor(e,t){this.response=e,this.reason=t}}function o(e){return{status:{toBe(t){if(e.status!==t)throw new n(e,`Response status code is not ${t}`)},toBeBetween:(t,a)=>{if(!(t<=e.status&&e.status<=a))throw new n(e,`Response status is not between ${t} and ${a}`)}},header:t=>({toContain(a){var o;if(!(null===(o=e.headers.get(t))||void 0===o?void 0:o.includes(a)))throw new n(e,`Response header ${t} does not contain ${a}`)},notToContain(a){var o;if(null===(o=e.headers.get(t))||void 0===o?void 0:o.includes(a))throw new n(e,`Response header ${t} contains ${a}`)}}),bodyAsExecutionResult:{data:{async toBe(t){const a=e.clone();if((await s(e)).data!==t)throw new n(a,`Response body execution result data is not "${t}"`)}},async toHaveProperty(t){const a=e.clone();if(!(t in await s(e)))throw new n(a,`Response body execution result does not have a property "${t}"`)},async notToHaveProperty(t){const a=e.clone();if(t in await s(e))throw new n(a,`Response body execution result has a property "${t}"`)}}}}async function s(e){let t,a;try{const a=new TextDecoder("utf-8"),n=await e.arrayBuffer();t=a.decode(n)}catch(t){throw new n(e,"Response body is not UTF-8 encoded")}try{a=JSON.parse(t)}catch(t){throw new n(e,"Response body is not valid JSON")}return a}function i(e){const s=e.fetchFn||fetch;return[a("22EB","SHOULD accept application/graphql-response+json and match the content-type",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/graphql-response+json")})),a("4655","MUST accept application/json and match the content-type",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/json")})),a("47DE","SHOULD accept */* and use application/json for the content-type",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"*/*"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/json")})),a("80D8","SHOULD assume application/json content-type when accept is missing",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/json")})),a("82A3","MUST use utf-8 encoding when responding",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200);try{new TextDecoder("utf-8").decode(await t.arrayBuffer())}catch(e){throw new n(t,"Response body is not UTF-8 encoded")}})),a("BF61","MUST accept utf-8 encoded request",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify({query:'{ __type(name: "Run🏃Swim🏊") { name } }'})})).status.toBe(200)})),a("78D5","MUST assume utf-8 in request if encoding is unspecified",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("2C94","MUST accept POST requests",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("5A70","MAY accept application/x-www-form-urlencoded formatted GET requests",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","{ __typename }");o(await s(t.toString())).status.toBe(200)})),a("9C48","MAY NOT allow executing mutations on GET requests",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","mutation { __typename }");o(await s(t.toString(),{headers:{accept:"application/graphql-response+json"}})).status.toBeBetween(400,499)})),a("9ABE","MAY respond with 4xx status code if content-type is not supplied on POST requests",(async()=>{o(await s(await r(e.url),{method:"POST"})).status.toBeBetween(400,499)})),a("03D4","MUST accept application/json POST requests",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("A5BF","MAY use 400 status code when request body is missing on POST",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"}})).status.toBe(400)})),a("423L","MAY use 400 status code on missing {query} parameter",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({notquery:"{ __typename }"})})).status.toBe(400)})),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`LKJ${i}`,`MAY use 400 status code on ${t(n)} {query} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:n})})).status.toBe(400)})))),a("34A2","SHOULD allow string {query} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("13EE","MUST allow string {query} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`6C0${i}`,`MAY use 400 status code on ${t(n)} {operationName} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({operationName:n,query:"{ __typename }"})})).status.toBe(400)})))),a("8161","SHOULD allow string {operationName} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({operationName:"Query",query:"query Query { __typename }"})})).status.toBe(200)})),a("B8B3","MUST allow string {operationName} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({operationName:"Query",query:"query Query { __typename }"})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),...["variables","operationName","extensions"].flatMap(((t,n)=>[a(`94B${n}`,`SHOULD allow null {${t}} parameter when accepting application/graphql-response+json`,(async()=>{const a=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }",[t]:null})});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")})),a(`022${n}`,`MUST allow null {${t}} parameter when accepting application/json`,(async()=>{const a=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }",[t]:null})});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")}))])),...["string",0,!1,["array"]].map(((n,i)=>a(`476${i}`,`MAY use 400 status code on ${t(n)} {variables} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }",variables:n})})).status.toBe(400)})))),a("2EA1","SHOULD allow map {variables} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"query Type($name: String!) { __type(name: $name) { name } }",variables:{name:"sometype"}})})).status.toBe(200)})),a("28B9","MUST allow map {variables} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"query Type($name: String!) { __type(name: $name) { name } }",variables:{name:"sometype"}})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),a("D6D5","MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","query Type($name: String!) { __type(name: $name) { name } }"),t.searchParams.set("variables",JSON.stringify({name:"sometype"}));o(await s(t.toString(),{method:"GET",headers:{accept:"application/graphql-response+json"}})).status.toBe(200)})),a("6A70","MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","query Type($name: String!) { __type(name: $name) { name } }"),t.searchParams.set("variables",JSON.stringify({name:"sometype"}));const a=await s(t.toString(),{method:"GET",headers:{accept:"application/json"}});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")})),...["string",0,!1,["array"]].map(((n,i)=>a(`58B${i}`,`MAY use 400 status code on ${t(n)} {extensions} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }",extensions:n})})).status.toBe(400)})))),a("428F","SHOULD allow map {extensions} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }",extensions:{some:"value"}})})).status.toBe(200)})),a("1B7A","MUST allow map {extensions} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }",extensions:{some:"value"}})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),a("B6DC","MAY use 4xx or 5xx status codes on JSON parsing failure",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:'{ "not a JSON'})).status.toBeBetween(400,499)})),a("BCF8","MAY use 400 status code on JSON parsing failure",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:'{ "not a JSON'})).status.toBe(400)})),a("8764","MAY use 4xx or 5xx status codes if parameters are invalid",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({qeury:"{ __typename }"})})).status.toBeBetween(400,599)})),a("3E3A","MAY use 400 status code if parameters are invalid",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({qeury:"{ __typename }"})})).status.toBe(400)})),a("572B","SHOULD use 200 status code on document parsing failure when accepting application/json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{"})})).status.toBe(200)})),a("FDE2","SHOULD use 200 status code on document validation failure when accepting application/json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})})).status.toBe(200)})),a("7B9B","SHOULD use a status code of 200 on variable coercion failure when accepting application/json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"query CoerceFailure($id: ID!){ __typename }",variables:{id:null}})})).status.toBe(200)})),a("865D","SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{"})})).status.toBeBetween(400,599)})),a("556A","SHOULD use 400 status code on document parsing failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{"})})).status.toBe(400)})),a("D586","SHOULD not contain the data entry on document parsing failure when accepting application/graphql-response+json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{"})});await o(t).bodyAsExecutionResult.data.toBe(void 0)})),a("51FE","SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})})).status.toBeBetween(400,599)})),a("74FF","SHOULD use 400 status code on document validation failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})})).status.toBe(400)})),a("5E5B","SHOULD not contain the data entry on document validation failure when accepting application/graphql-response+json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})});await o(t).bodyAsExecutionResult.data.toBe(void 0)})),a("86EE","SHOULD use a status code of 400 on variable coercion failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"query CoerceFailure($id: ID!){ __typename }",variables:{id:null}})})).status.toBe(400)}))]}async function r(e){return"function"==typeof e?await e():e}async function p(e){let t="";t+=`<li><code>${e.id}</code> ${e.name}\n`,t+="<details>\n",t+=`<summary>${function(e,t=1024){if(e.length>t)return e.substring(0,t)+"...";return e}(e.reason)}</summary>\n`,t+='<pre><code class="lang-json">';const a=e.response,n={};for(const[e,t]of a.headers.entries())"date"===e?n[e]="<timestamp>":["cf-ray","server-timing"].includes(e)?n[e]="<omitted>":n[e]=t;let o,s="";try{s=await a.text(),o=JSON.parse(s)}catch(e){}return t+=JSON.stringify({status:a.status,statusText:a.statusText,headers:n,body:o||((null==s?void 0:s.length)>5120?"<body is too long>":s)||null},((e,t)=>{if(null!=t&&"object"==typeof t&&!Array.isArray(t)){const e={};return Object.keys(t).sort().reverse().reduce(((e,a)=>(e[a]=t[a],e)),e)}return t}),2)+"\n",t+="</code></pre>\n",t+="</details>\n",t+="</li>\n",t}e.auditServer=async function(e){const t=i(e);return await Promise.all(t.map((({fn:e})=>e())))},e.renderAuditResultsToHTML=async function(e){const t={total:0,ok:[],notice:[],warn:[],error:[]};for(const a of e)t.total++,a.status,t[a.status].push(a);let a="<i>* This report was auto-generated by graphql-http</i>\n";if(a+="\n",a+="<h1>GraphQL over HTTP audit report</h1>\n",a+="\n",a+="<ul>\n",a+=`<li><b>${t.total}</b> audits in total</li>\n`,t.ok.length&&(a+=`<li><span style="font-family: monospace">✅</span> <b>${t.ok.length}</b> pass</li>\n`),t.notice.length&&(a+=`<li><span style="font-family: monospace">💡</span> <b>${t.notice.length}</b> notices (suggestions)</li>\n`),t.warn.length&&(a+=`<li><span style="font-family: monospace">⚠️</span> <b>${t.warn.length}</b> warnings (optional)</li>\n`),t.error.length&&(a+=`<li><span style="font-family: monospace">❌</span> <b>${t.error.length}</b> errors (required)</li>\n`),a+="</ul>\n",a+="\n",t.ok.length){a+="<h2>Passing</h2>\n",a+="<ol>\n";for(const[,e]of t.ok.entries())a+=`<li><code>${e.id}</code> ${e.name}</li>\n`;a+="</ol>\n",a+="\n"}if(t.notice.length){a+="<h2>Notices</h2>\n",a+="The server <i>MAY</i> support these, but are truly optional. These are suggestions following recommended conventions.\n",a+="<ol>\n";for(const[,e]of t.notice.entries())a+=await p(e);a+="</ol>\n",a+="\n"}if(t.warn.length){a+="<h2>Warnings</h2>\n",a+="The server <i>SHOULD</i> support these, but is not required.\n",a+="<ol>\n";for(const[,e]of t.warn.entries())a+=await p(e);a+="</ol>\n",a+="\n"}if(t.error.length){a+="<h2>Errors</h2>\n",a+="The server <b>MUST</b> support these.\n",a+="<ol>\n";for(const[,e]of t.error.entries())a+=await p(e);a+="</ol>\n"}return a},e.serverAudits=i}));
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).graphqlHttpAudits={})}(this,(function(e){"use strict";function t(e){return null===e?"null":Array.isArray(e)?"array":typeof e}function a(e,t,a){return{id:e,name:t,fn:async()=>{try{return await a(),{id:e,name:t,status:"ok"}}catch(a){if(!(a instanceof n))throw a;return{id:e,name:t,status:t.startsWith("MUST")?"error":t.startsWith("SHOULD")?"warn":"notice",reason:a.reason,response:a.response}}}}}class n{constructor(e,t){this.response=e,this.reason=t}}function o(e){return{status:{toBe(t){if(e.status!==t)throw new n(e,`Response status code is not ${t}`)},toBeBetween:(t,a)=>{if(!(t<=e.status&&e.status<=a))throw new n(e,`Response status is not between ${t} and ${a}`)}},header:t=>({toContain(a){var o;if(!(null===(o=e.headers.get(t))||void 0===o?void 0:o.includes(a)))throw new n(e,`Response header ${t} does not contain ${a}`)},notToContain(a){var o;if(null===(o=e.headers.get(t))||void 0===o?void 0:o.includes(a))throw new n(e,`Response header ${t} contains ${a}`)}}),bodyAsExecutionResult:{data:{async toBe(t){const a=e.clone();if((await s(e)).data!==t)throw new n(a,`Response body execution result data is not "${t}"`)}},async toHaveProperty(t){const a=e.clone();if(!(t in await s(e)))throw new n(a,`Response body execution result does not have a property "${t}"`)},async notToHaveProperty(t){const a=e.clone();if(t in await s(e))throw new n(a,`Response body execution result has a property "${t}"`)}}}}async function s(e){let t,a;try{const a=new TextDecoder("utf-8"),n=await e.arrayBuffer();t=a.decode(n)}catch(t){throw new n(e,"Response body is not UTF-8 encoded")}try{a=JSON.parse(t)}catch(t){throw new n(e,"Response body is not valid JSON")}return a}function i(e){const s=e.fetchFn||fetch;return[a("22EB","SHOULD accept application/graphql-response+json and match the content-type",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/graphql-response+json")})),a("4655","MUST accept application/json and match the content-type",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/json")})),a("47DE","SHOULD accept */* and use application/json for the content-type",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"*/*"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/json")})),a("80D8","SHOULD assume application/json content-type when accept is missing",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/json")})),a("82A3","MUST use utf-8 encoding when responding",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200);try{new TextDecoder("utf-8").decode(await t.arrayBuffer())}catch(e){throw new n(t,"Response body is not UTF-8 encoded")}})),a("BF61","MUST accept utf-8 encoded request",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify({query:'{ __type(name: "Run🏃Swim🏊") { name } }'})})).status.toBe(200)})),a("78D5","MUST assume utf-8 in request if encoding is unspecified",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("2C94","MUST accept POST requests",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("5A70","MAY accept application/x-www-form-urlencoded formatted GET requests",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","{ __typename }");o(await s(t.toString())).status.toBe(200)})),a("9C48","MAY NOT allow executing mutations on GET requests",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","mutation { __typename }");o(await s(t.toString(),{headers:{accept:"application/graphql-response+json"}})).status.toBeBetween(400,499)})),a("9ABE","MAY respond with 4xx status code if content-type is not supplied on POST requests",(async()=>{o(await s(await r(e.url),{method:"POST"})).status.toBeBetween(400,499)})),a("03D4","MUST accept application/json POST requests",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("A5BF","MAY use 400 status code when request body is missing on POST",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"}})).status.toBe(400)})),a("423L","MAY use 400 status code on missing {query} parameter",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({notquery:"{ __typename }"})})).status.toBe(400)})),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`LKJ${i}`,`MAY use 400 status code on ${t(n)} {query} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:n})})).status.toBe(400)})))),a("34A2","SHOULD allow string {query} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("13EE","MUST allow string {query} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`6C0${i}`,`MAY use 400 status code on ${t(n)} {operationName} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({operationName:n,query:"{ __typename }"})})).status.toBe(400)})))),a("8161","SHOULD allow string {operationName} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({operationName:"Query",query:"query Query { __typename }"})})).status.toBe(200)})),a("B8B3","MUST allow string {operationName} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({operationName:"Query",query:"query Query { __typename }"})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),...["variables","operationName","extensions"].flatMap(((t,n)=>[a(`94B${n}`,`SHOULD allow null {${t}} parameter when accepting application/graphql-response+json`,(async()=>{const a=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }",[t]:null})});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")})),a(`022${n}`,`MUST allow null {${t}} parameter when accepting application/json`,(async()=>{const a=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }",[t]:null})});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")}))])),...["string",0,!1,["array"]].map(((n,i)=>a(`476${i}`,`MAY use 400 status code on ${t(n)} {variables} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }",variables:n})})).status.toBe(400)})))),a("2EA1","SHOULD allow map {variables} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"query Type($name: String!) { __type(name: $name) { name } }",variables:{name:"sometype"}})})).status.toBe(200)})),a("28B9","MUST allow map {variables} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"query Type($name: String!) { __type(name: $name) { name } }",variables:{name:"sometype"}})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),a("D6D5","MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","query Type($name: String!) { __type(name: $name) { name } }"),t.searchParams.set("variables",JSON.stringify({name:"sometype"}));o(await s(t.toString(),{method:"GET",headers:{accept:"application/graphql-response+json"}})).status.toBe(200)})),a("6A70","MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","query Type($name: String!) { __type(name: $name) { name } }"),t.searchParams.set("variables",JSON.stringify({name:"sometype"}));const a=await s(t.toString(),{method:"GET",headers:{accept:"application/json"}});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")})),...["string",0,!1,["array"]].map(((n,i)=>a(`58B${i}`,`MAY use 400 status code on ${t(n)} {extensions} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }",extensions:n})})).status.toBe(400)})))),a("428F","SHOULD allow map {extensions} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }",extensions:{some:"value"}})})).status.toBe(200)})),a("1B7A","MUST allow map {extensions} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }",extensions:{some:"value"}})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),a("B6DC","MAY use 4xx or 5xx status codes on JSON parsing failure",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:'{ "not a JSON'})).status.toBeBetween(400,499)})),a("BCF8","MAY use 400 status code on JSON parsing failure",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:'{ "not a JSON'})).status.toBe(400)})),a("8764","MAY use 4xx or 5xx status codes if parameters are invalid",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({qeury:"{ __typename }"})})).status.toBeBetween(400,599)})),a("3E3A","MAY use 400 status code if parameters are invalid",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({qeury:"{ __typename }"})})).status.toBe(400)})),a("572B","SHOULD use 200 status code on document parsing failure when accepting application/json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{"})})).status.toBe(200)})),a("FDE2","SHOULD use 200 status code on document validation failure when accepting application/json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})})).status.toBe(200)})),a("7B9B","SHOULD use a status code of 200 on variable coercion failure when accepting application/json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"query CoerceFailure($id: ID!){ __typename }",variables:{id:null}})})).status.toBe(200)})),a("865D","SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{"})})).status.toBeBetween(400,599)})),a("556A","SHOULD use 400 status code on document parsing failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{"})})).status.toBe(400)})),a("D586","SHOULD not contain the data entry on document parsing failure when accepting application/graphql-response+json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{"})});await o(t).bodyAsExecutionResult.data.toBe(void 0)})),a("51FE","SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})})).status.toBeBetween(400,599)})),a("74FF","SHOULD use 400 status code on document validation failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})})).status.toBe(400)})),a("5E5B","SHOULD not contain the data entry on document validation failure when accepting application/graphql-response+json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})});await o(t).bodyAsExecutionResult.data.toBe(void 0)})),a("86EE","SHOULD use a status code of 400 on variable coercion failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"query CoerceFailure($id: ID!){ __typename }",variables:{id:null}})})).status.toBe(400)}))]}async function r(e){return"function"==typeof e?await e():e}async function p(e){var t;let a="";a+=`<li><code>${e.id}</code> ${e.name}\n`,a+="<details>\n",a+=`<summary>${function(e,t=1024){if(e.length>t)return e.substring(0,t)+"...";return e}(e.reason)}</summary>\n`,a+='<pre><code class="lang-json">';const n=e.response,o={};for(const[e,t]of n.headers.entries())"date"===e?o[e]="<timestamp>":["cf-ray","server-timing","set-cookie"].includes(e)?o[e]="<omitted>":o[e]=t;let s,i="";try{i=await n.text(),s=JSON.parse(i)}catch(e){(null===(t=n.headers.get("content-type"))||void 0===t?void 0:t.includes("text/html"))&&(i="<html omitted>")}return a+=JSON.stringify({status:n.status,statusText:n.statusText,headers:o,body:s||((null==i?void 0:i.length)>5120?"<body is too long>":i)||null},((e,t)=>{if(null!=t&&"object"==typeof t&&!Array.isArray(t)){const e={};return Object.keys(t).sort().reverse().reduce(((e,a)=>(e[a]=t[a],e)),e)}return t}),2)+"\n",a+="</code></pre>\n",a+="</details>\n",a+="</li>\n",a}e.auditServer=async function(e){const t=i(e);return await Promise.all(t.map((({fn:e})=>e())))},e.renderAuditResultsToHTML=async function(e){const t={total:0,ok:[],notice:[],warn:[],error:[]};for(const a of e)t.total++,a.status,t[a.status].push(a);let a="<i>* This report was auto-generated by graphql-http</i>\n";if(a+="\n",a+="<h1>GraphQL over HTTP audit report</h1>\n",a+="\n",a+="<ul>\n",a+=`<li><b>${t.total}</b> audits in total</li>\n`,t.ok.length&&(a+=`<li><span style="font-family: monospace">✅</span> <b>${t.ok.length}</b> pass</li>\n`),t.notice.length&&(a+=`<li><span style="font-family: monospace">💡</span> <b>${t.notice.length}</b> notices (suggestions)</li>\n`),t.warn.length&&(a+=`<li><span style="font-family: monospace">❗️</span> <b>${t.warn.length}</b> warnings (optional)</li>\n`),t.error.length&&(a+=`<li><span style="font-family: monospace">❌</span> <b>${t.error.length}</b> errors (required)</li>\n`),a+="</ul>\n",a+="\n",t.ok.length){a+="<h2>Passing</h2>\n",a+="<ol>\n";for(const[,e]of t.ok.entries())a+=`<li><code>${e.id}</code> ${e.name}</li>\n`;a+="</ol>\n",a+="\n"}if(t.notice.length){a+="<h2>Notices</h2>\n",a+="The server <i>MAY</i> support these, but are truly optional. These are suggestions following recommended conventions.\n",a+="<ol>\n";for(const[,e]of t.notice.entries())a+=await p(e);a+="</ol>\n",a+="\n"}if(t.warn.length){a+="<h2>Warnings</h2>\n",a+="The server <i>SHOULD</i> support these, but is not required.\n",a+="<ol>\n";for(const[,e]of t.warn.entries())a+=await p(e);a+="</ol>\n",a+="\n"}if(t.error.length){a+="<h2>Errors</h2>\n",a+="The server <b>MUST</b> support these.\n",a+="<ol>\n";for(const[,e]of t.error.entries())a+=await p(e);a+="</ol>\n"}return a},e.serverAudits=i}));
Binary file
Binary file