graphql-http 1.16.0 → 1.17.1
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 +36 -9
- package/lib/audits/index.d.mts +3 -3
- package/lib/audits/render.d.mts +1 -1
- package/lib/audits/server.d.mts +1 -1
- package/lib/audits/server.js +31 -1
- package/lib/audits/server.mjs +31 -1
- package/lib/audits/utils.d.mts +2 -2
- package/lib/client.d.mts +2 -2
- package/lib/handler.d.mts +2 -2
- package/lib/index.d.mts +4 -4
- package/lib/use/express.d.mts +12 -4
- package/lib/use/express.d.ts +10 -2
- package/lib/use/express.js +1 -1
- package/lib/use/express.mjs +1 -1
- package/lib/use/fastify.d.mts +12 -4
- package/lib/use/fastify.d.ts +10 -2
- package/lib/use/fastify.js +1 -1
- package/lib/use/fastify.mjs +1 -1
- package/lib/use/fetch.d.mts +2 -2
- package/lib/use/http.d.mts +11 -3
- package/lib/use/http.d.ts +9 -1
- package/lib/use/http.js +1 -1
- package/lib/use/http.mjs +1 -1
- package/lib/use/http2.d.mts +11 -3
- package/lib/use/http2.d.ts +9 -1
- package/lib/use/http2.js +1 -1
- package/lib/use/http2.mjs +1 -1
- package/lib/use/koa.d.mts +12 -4
- package/lib/use/koa.d.ts +10 -2
- package/lib/use/koa.js +1 -1
- package/lib/use/koa.mjs +1 -1
- package/lib/use/node.d.mts +3 -3
- package/package.json +28 -27
- package/umd/graphql-http-audits.js +31 -1
- package/umd/graphql-http-audits.min.js +1 -1
- package/umd/graphql-http-audits.min.js.gz +0 -0
package/README.md
CHANGED
|
@@ -736,6 +736,36 @@ for (const audit of serverAudits({
|
|
|
736
736
|
|
|
737
737
|
</details>
|
|
738
738
|
|
|
739
|
+
<details id="audit-deno">
|
|
740
|
+
<summary><a href="#audit-deno">🔗</a> Audit for servers usage in <a href="https://deno.land">Deno</a> environment</summary>
|
|
741
|
+
|
|
742
|
+
```ts
|
|
743
|
+
import { serverAudits } from 'npm:graphql-http';
|
|
744
|
+
|
|
745
|
+
for (const audit of serverAudits({
|
|
746
|
+
url: 'http://localhost:4000/graphql',
|
|
747
|
+
fetchFn: fetch,
|
|
748
|
+
})) {
|
|
749
|
+
Deno.test(audit.name, async () => {
|
|
750
|
+
const result = await audit.fn();
|
|
751
|
+
if (result.status === 'error') {
|
|
752
|
+
throw result.reason;
|
|
753
|
+
}
|
|
754
|
+
if (result.status === 'warn') {
|
|
755
|
+
console.warn(result.reason); // or throw if you want full compliance (warnings are not requirements)
|
|
756
|
+
}
|
|
757
|
+
// Avoid leaking resources
|
|
758
|
+
if ('body' in result && result.body instanceof ReadableStream) {
|
|
759
|
+
await result.body.cancel();
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
Put the above contents in a file and run it with `deno test --allow-net`.
|
|
766
|
+
|
|
767
|
+
</details>
|
|
768
|
+
|
|
739
769
|
## Only [GraphQL over HTTP](https://graphql.github.io/graphql-over-http/)
|
|
740
770
|
|
|
741
771
|
This is the official [GraphQL over HTTP spec](https://graphql.github.io/graphql-over-http/) reference implementation and as such follows the specification strictly without any additional features (like file uploads, @stream/@defer directives and subscriptions).
|
|
@@ -744,7 +774,9 @@ Having said this, graphql-http is mostly aimed for library authors and simple se
|
|
|
744
774
|
|
|
745
775
|
## [Servers](/implementations)
|
|
746
776
|
|
|
747
|
-
If you want a feature-full server with bleeding edge technologies, you're recommended to use one of the following.
|
|
777
|
+
If you want a feature-full server with bleeding edge technologies, you're recommended to use one of the following servers.
|
|
778
|
+
|
|
779
|
+
Their compliance with the [GraphQL over HTTP spec](https://graphql.github.io/graphql-over-http) is checked automatically and updated regularly.
|
|
748
780
|
|
|
749
781
|
<!-- <ServersTable> -->
|
|
750
782
|
<!-- prettier-ignore-start -->
|
|
@@ -768,16 +800,11 @@ Check the [docs folder](docs/) out for [TypeDoc](https://typedoc.org) generated
|
|
|
768
800
|
|
|
769
801
|
## [Audits](implementations/)
|
|
770
802
|
|
|
771
|
-
Inspect audits of other implementations in the [implementations folder](implementations
|
|
803
|
+
Inspect audits of other implementations in the [implementations folder](implementations).
|
|
804
|
+
Adding your implementation is very welcome, [see how](CONTRIBUTING.md#adding-implementations)!
|
|
772
805
|
|
|
773
806
|
## Want to help?
|
|
774
807
|
|
|
775
|
-
File a bug, contribute with code, or improve documentation? Read
|
|
776
|
-
|
|
777
|
-
This repository is managed by EasyCLA. Project participants must sign the free [GraphQL Specification Membership agreement](https://preview-spec-membership.graphql.org) before making a contribution. You only need to do this one time, and it can be signed by [individual contributors](http://individual-spec-membership.graphql.org/) or their [employers](http://corporate-spec-membership.graphql.org/).
|
|
778
|
-
|
|
779
|
-
To initiate the signature process please open a PR against this repo. The EasyCLA bot will block the merge if we still need a membership agreement from you.
|
|
780
|
-
|
|
781
|
-
You can find [detailed information here](https://github.com/graphql/graphql-wg/tree/main/membership). If you have issues, please email [operations@graphql.org](mailto:operations@graphql.org).
|
|
808
|
+
File a bug, contribute with code, or improve documentation? [Read more in CONTRIBUTING.md](CONTRIBUTING.md).
|
|
782
809
|
|
|
783
810
|
If your company benefits from GraphQL and you would like to provide essential financial support for the systems and people that power our community, please also consider membership in the [GraphQL Foundation](https://foundation.graphql.org/join).
|
package/lib/audits/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from './common';
|
|
2
|
-
export * from './server';
|
|
3
|
-
export * from './render';
|
|
1
|
+
export * from './common.mjs';
|
|
2
|
+
export * from './server.mjs';
|
|
3
|
+
export * from './render.mjs';
|
package/lib/audits/render.d.mts
CHANGED
package/lib/audits/server.d.mts
CHANGED
package/lib/audits/server.js
CHANGED
|
@@ -86,7 +86,9 @@ function serverAudits(opts) {
|
|
|
86
86
|
headers: {
|
|
87
87
|
'content-type': 'application/json; charset=utf-8',
|
|
88
88
|
},
|
|
89
|
-
body: JSON.stringify({
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
query: '{ __type(name: "Run🏃Swim🏊") { name } }',
|
|
91
|
+
}),
|
|
90
92
|
});
|
|
91
93
|
(0, utils_1.ressert)(res).status.toBe(200);
|
|
92
94
|
}),
|
|
@@ -534,6 +536,20 @@ function serverAudits(opts) {
|
|
|
534
536
|
});
|
|
535
537
|
(0, utils_1.ressert)(res).status.toBe(200);
|
|
536
538
|
}),
|
|
539
|
+
(0, utils_1.audit)('7B9B', 'SHOULD use a status code of 200 on variable coercion failure when accepting application/json', async () => {
|
|
540
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
541
|
+
method: 'POST',
|
|
542
|
+
headers: {
|
|
543
|
+
'content-type': 'application/json',
|
|
544
|
+
accept: 'application/json',
|
|
545
|
+
},
|
|
546
|
+
body: JSON.stringify({
|
|
547
|
+
query: 'query CoerceFailure($id: ID!){ __typename }',
|
|
548
|
+
variables: { id: null },
|
|
549
|
+
}),
|
|
550
|
+
});
|
|
551
|
+
(0, utils_1.ressert)(res).status.toBe(200);
|
|
552
|
+
}),
|
|
537
553
|
// Response application/graphql-response+json
|
|
538
554
|
(0, utils_1.audit)(
|
|
539
555
|
// TODO: convert to MUST after watershed
|
|
@@ -682,6 +698,20 @@ function serverAudits(opts) {
|
|
|
682
698
|
});
|
|
683
699
|
await (0, utils_1.ressert)(res).bodyAsExecutionResult.data.toBe(undefined);
|
|
684
700
|
}),
|
|
701
|
+
(0, utils_1.audit)('86EE', 'SHOULD use a status code of 400 on variable coercion failure when accepting application/graphql-response+json', async () => {
|
|
702
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
703
|
+
method: 'POST',
|
|
704
|
+
headers: {
|
|
705
|
+
'content-type': 'application/json',
|
|
706
|
+
accept: 'application/graphql-response+json',
|
|
707
|
+
},
|
|
708
|
+
body: JSON.stringify({
|
|
709
|
+
query: 'query CoerceFailure($id: ID!){ __typename }',
|
|
710
|
+
variables: { id: null },
|
|
711
|
+
}),
|
|
712
|
+
});
|
|
713
|
+
(0, utils_1.ressert)(res).status.toBe(400);
|
|
714
|
+
}),
|
|
685
715
|
// TODO: how to fail and have the data entry?
|
|
686
716
|
// audit('EE52', 'MUST use 2xx status code if response contains the data entry and it is not null when accepting application/graphql-response+json'),
|
|
687
717
|
// TODO: how to make an unauthorized request?
|
package/lib/audits/server.mjs
CHANGED
|
@@ -83,7 +83,9 @@ export function serverAudits(opts) {
|
|
|
83
83
|
headers: {
|
|
84
84
|
'content-type': 'application/json; charset=utf-8',
|
|
85
85
|
},
|
|
86
|
-
body: JSON.stringify({
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
query: '{ __type(name: "Run🏃Swim🏊") { name } }',
|
|
88
|
+
}),
|
|
87
89
|
});
|
|
88
90
|
ressert(res).status.toBe(200);
|
|
89
91
|
}),
|
|
@@ -531,6 +533,20 @@ export function serverAudits(opts) {
|
|
|
531
533
|
});
|
|
532
534
|
ressert(res).status.toBe(200);
|
|
533
535
|
}),
|
|
536
|
+
audit('7B9B', 'SHOULD use a status code of 200 on variable coercion failure when accepting application/json', async () => {
|
|
537
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
538
|
+
method: 'POST',
|
|
539
|
+
headers: {
|
|
540
|
+
'content-type': 'application/json',
|
|
541
|
+
accept: 'application/json',
|
|
542
|
+
},
|
|
543
|
+
body: JSON.stringify({
|
|
544
|
+
query: 'query CoerceFailure($id: ID!){ __typename }',
|
|
545
|
+
variables: { id: null },
|
|
546
|
+
}),
|
|
547
|
+
});
|
|
548
|
+
ressert(res).status.toBe(200);
|
|
549
|
+
}),
|
|
534
550
|
// Response application/graphql-response+json
|
|
535
551
|
audit(
|
|
536
552
|
// TODO: convert to MUST after watershed
|
|
@@ -679,6 +695,20 @@ export function serverAudits(opts) {
|
|
|
679
695
|
});
|
|
680
696
|
await ressert(res).bodyAsExecutionResult.data.toBe(undefined);
|
|
681
697
|
}),
|
|
698
|
+
audit('86EE', 'SHOULD use a status code of 400 on variable coercion failure when accepting application/graphql-response+json', async () => {
|
|
699
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
700
|
+
method: 'POST',
|
|
701
|
+
headers: {
|
|
702
|
+
'content-type': 'application/json',
|
|
703
|
+
accept: 'application/graphql-response+json',
|
|
704
|
+
},
|
|
705
|
+
body: JSON.stringify({
|
|
706
|
+
query: 'query CoerceFailure($id: ID!){ __typename }',
|
|
707
|
+
variables: { id: null },
|
|
708
|
+
}),
|
|
709
|
+
});
|
|
710
|
+
ressert(res).status.toBe(400);
|
|
711
|
+
}),
|
|
682
712
|
// TODO: how to fail and have the data entry?
|
|
683
713
|
// 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
714
|
// 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/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
|
*
|
|
@@ -246,7 +246,7 @@ export type Handler<RequestRaw = unknown, RequestContext = unknown> = (req: Requ
|
|
|
246
246
|
* ```js
|
|
247
247
|
* import http from 'http';
|
|
248
248
|
* import { createHandler } from 'graphql-http';
|
|
249
|
-
* import { schema } from './my-graphql-schema';
|
|
249
|
+
* import { schema } from './my-graphql-schema/index.mjs';
|
|
250
250
|
*
|
|
251
251
|
* // Create the GraphQL over HTTP handler
|
|
252
252
|
* const handler = createHandler({ schema });
|
package/lib/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from './common';
|
|
2
|
-
export * from './handler';
|
|
3
|
-
export * from './client';
|
|
4
|
-
export * from './audits';
|
|
1
|
+
export * from './common.mjs';
|
|
2
|
+
export * from './handler.mjs';
|
|
3
|
+
export * from './client.mjs';
|
|
4
|
+
export * from './audits/index.mjs';
|
package/lib/use/express.d.mts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import type { Request, Handler } from 'express';
|
|
2
|
-
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler';
|
|
1
|
+
import type { Request, Response, Handler } from 'express';
|
|
2
|
+
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler.mjs';
|
|
3
|
+
/**
|
|
4
|
+
* The context in the request for the handler.
|
|
5
|
+
*
|
|
6
|
+
* @category Server/express
|
|
7
|
+
*/
|
|
8
|
+
export interface RequestContext {
|
|
9
|
+
res: Response;
|
|
10
|
+
}
|
|
3
11
|
/**
|
|
4
12
|
* Handler options when using the express adapter.
|
|
5
13
|
*
|
|
6
14
|
* @category Server/express
|
|
7
15
|
*/
|
|
8
|
-
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<Request,
|
|
16
|
+
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<Request, RequestContext, Context>;
|
|
9
17
|
/**
|
|
10
18
|
* Create a GraphQL over HTTP spec compliant request handler for
|
|
11
19
|
* the express framework.
|
|
@@ -13,7 +21,7 @@ export type HandlerOptions<Context extends OperationContext = undefined> = RawHa
|
|
|
13
21
|
* ```js
|
|
14
22
|
* import express from 'express'; // yarn add express
|
|
15
23
|
* import { createHandler } from 'graphql-http/lib/use/express';
|
|
16
|
-
* import { schema } from './my-graphql-schema';
|
|
24
|
+
* import { schema } from './my-graphql-schema/index.mjs';
|
|
17
25
|
*
|
|
18
26
|
* const app = express();
|
|
19
27
|
* app.all('/graphql', createHandler({ schema }));
|
package/lib/use/express.d.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import type { Request, Handler } from 'express';
|
|
1
|
+
import type { Request, Response, Handler } from 'express';
|
|
2
2
|
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler';
|
|
3
|
+
/**
|
|
4
|
+
* The context in the request for the handler.
|
|
5
|
+
*
|
|
6
|
+
* @category Server/express
|
|
7
|
+
*/
|
|
8
|
+
export interface RequestContext {
|
|
9
|
+
res: Response;
|
|
10
|
+
}
|
|
3
11
|
/**
|
|
4
12
|
* Handler options when using the express adapter.
|
|
5
13
|
*
|
|
6
14
|
* @category Server/express
|
|
7
15
|
*/
|
|
8
|
-
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<Request,
|
|
16
|
+
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<Request, RequestContext, Context>;
|
|
9
17
|
/**
|
|
10
18
|
* Create a GraphQL over HTTP spec compliant request handler for
|
|
11
19
|
* the express framework.
|
package/lib/use/express.js
CHANGED
package/lib/use/express.mjs
CHANGED
package/lib/use/fastify.d.mts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import type { FastifyRequest, RouteHandler } from 'fastify';
|
|
2
|
-
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler';
|
|
1
|
+
import type { FastifyRequest, FastifyReply, RouteHandler } from 'fastify';
|
|
2
|
+
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler.mjs';
|
|
3
|
+
/**
|
|
4
|
+
* The context in the request for the handler.
|
|
5
|
+
*
|
|
6
|
+
* @category Server/fastify
|
|
7
|
+
*/
|
|
8
|
+
export interface RequestContext {
|
|
9
|
+
reply: FastifyReply;
|
|
10
|
+
}
|
|
3
11
|
/**
|
|
4
12
|
* Handler options when using the fastify adapter.
|
|
5
13
|
*
|
|
6
14
|
* @category Server/fastify
|
|
7
15
|
*/
|
|
8
|
-
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<FastifyRequest,
|
|
16
|
+
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<FastifyRequest, RequestContext, Context>;
|
|
9
17
|
/**
|
|
10
18
|
* Create a GraphQL over HTTP spec compliant request handler for
|
|
11
19
|
* the fastify framework.
|
|
@@ -13,7 +21,7 @@ export type HandlerOptions<Context extends OperationContext = undefined> = RawHa
|
|
|
13
21
|
* ```js
|
|
14
22
|
* import Fastify from 'fastify'; // yarn add fastify
|
|
15
23
|
* import { createHandler } from 'graphql-http/lib/use/express';
|
|
16
|
-
* import { schema } from './my-graphql-schema';
|
|
24
|
+
* import { schema } from './my-graphql-schema/index.mjs';
|
|
17
25
|
*
|
|
18
26
|
* const fastify = Fastify();
|
|
19
27
|
* fastify.all('/graphql', createHandler({ schema }));
|
package/lib/use/fastify.d.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import type { FastifyRequest, RouteHandler } from 'fastify';
|
|
1
|
+
import type { FastifyRequest, FastifyReply, RouteHandler } from 'fastify';
|
|
2
2
|
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler';
|
|
3
|
+
/**
|
|
4
|
+
* The context in the request for the handler.
|
|
5
|
+
*
|
|
6
|
+
* @category Server/fastify
|
|
7
|
+
*/
|
|
8
|
+
export interface RequestContext {
|
|
9
|
+
reply: FastifyReply;
|
|
10
|
+
}
|
|
3
11
|
/**
|
|
4
12
|
* Handler options when using the fastify adapter.
|
|
5
13
|
*
|
|
6
14
|
* @category Server/fastify
|
|
7
15
|
*/
|
|
8
|
-
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<FastifyRequest,
|
|
16
|
+
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<FastifyRequest, RequestContext, Context>;
|
|
9
17
|
/**
|
|
10
18
|
* Create a GraphQL over HTTP spec compliant request handler for
|
|
11
19
|
* the fastify framework.
|
package/lib/use/fastify.js
CHANGED
package/lib/use/fastify.mjs
CHANGED
package/lib/use/fetch.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler';
|
|
1
|
+
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler.mjs';
|
|
2
2
|
/**
|
|
3
3
|
* The necessary API from the fetch environment for the handler.
|
|
4
4
|
*
|
|
@@ -26,7 +26,7 @@ export type HandlerOptions<Context extends OperationContext = undefined> = RawHa
|
|
|
26
26
|
* import http from 'http';
|
|
27
27
|
* import { createServerAdapter } from '@whatwg-node/server'; // yarn add @whatwg-node/server
|
|
28
28
|
* import { createHandler } from 'graphql-http/lib/use/fetch';
|
|
29
|
-
* import { schema } from './my-graphql-step';
|
|
29
|
+
* import { schema } from './my-graphql-step/index.mjs';
|
|
30
30
|
*
|
|
31
31
|
* // Use this adapter in _any_ environment.
|
|
32
32
|
* const adapter = createServerAdapter({
|
package/lib/use/http.d.mts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import type { IncomingMessage, ServerResponse } from 'http';
|
|
3
|
-
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler';
|
|
3
|
+
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler.mjs';
|
|
4
|
+
/**
|
|
5
|
+
* The context in the request for the handler.
|
|
6
|
+
*
|
|
7
|
+
* @category Server/http
|
|
8
|
+
*/
|
|
9
|
+
export interface RequestContext {
|
|
10
|
+
res: ServerResponse;
|
|
11
|
+
}
|
|
4
12
|
/**
|
|
5
13
|
* Handler options when using the http adapter.
|
|
6
14
|
*
|
|
7
15
|
* @category Server/http
|
|
8
16
|
*/
|
|
9
|
-
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<IncomingMessage,
|
|
17
|
+
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<IncomingMessage, RequestContext, Context>;
|
|
10
18
|
/**
|
|
11
19
|
* Create a GraphQL over HTTP spec compliant request handler for
|
|
12
20
|
* the Node environment http module.
|
|
@@ -14,7 +22,7 @@ export type HandlerOptions<Context extends OperationContext = undefined> = RawHa
|
|
|
14
22
|
* ```js
|
|
15
23
|
* import http from 'http';
|
|
16
24
|
* import { createHandler } from 'graphql-http/lib/use/http';
|
|
17
|
-
* import { schema } from './my-graphql-step';
|
|
25
|
+
* import { schema } from './my-graphql-step/index.mjs';
|
|
18
26
|
*
|
|
19
27
|
* const server = http.createServer(createHandler({ schema }));
|
|
20
28
|
*
|
package/lib/use/http.d.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import type { IncomingMessage, ServerResponse } from 'http';
|
|
3
3
|
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler';
|
|
4
|
+
/**
|
|
5
|
+
* The context in the request for the handler.
|
|
6
|
+
*
|
|
7
|
+
* @category Server/http
|
|
8
|
+
*/
|
|
9
|
+
export interface RequestContext {
|
|
10
|
+
res: ServerResponse;
|
|
11
|
+
}
|
|
4
12
|
/**
|
|
5
13
|
* Handler options when using the http adapter.
|
|
6
14
|
*
|
|
7
15
|
* @category Server/http
|
|
8
16
|
*/
|
|
9
|
-
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<IncomingMessage,
|
|
17
|
+
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<IncomingMessage, RequestContext, Context>;
|
|
10
18
|
/**
|
|
11
19
|
* Create a GraphQL over HTTP spec compliant request handler for
|
|
12
20
|
* the Node environment http module.
|
package/lib/use/http.js
CHANGED
package/lib/use/http.mjs
CHANGED
package/lib/use/http2.d.mts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import type { Http2ServerRequest, Http2ServerResponse } from 'http2';
|
|
3
|
-
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler';
|
|
3
|
+
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler.mjs';
|
|
4
|
+
/**
|
|
5
|
+
* The context in the request for the handler.
|
|
6
|
+
*
|
|
7
|
+
* @category Server/http2
|
|
8
|
+
*/
|
|
9
|
+
export interface RequestContext {
|
|
10
|
+
res: Http2ServerResponse;
|
|
11
|
+
}
|
|
4
12
|
/**
|
|
5
13
|
* Handler options when using the http adapter.
|
|
6
14
|
*
|
|
7
15
|
* @category Server/http2
|
|
8
16
|
*/
|
|
9
|
-
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<Http2ServerRequest,
|
|
17
|
+
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<Http2ServerRequest, RequestContext, Context>;
|
|
10
18
|
/**
|
|
11
19
|
* Create a GraphQL over HTTP spec compliant request handler for
|
|
12
20
|
* the Node environment http2 module.
|
|
@@ -20,7 +28,7 @@ export type HandlerOptions<Context extends OperationContext = undefined> = RawHa
|
|
|
20
28
|
* import fs from 'fs';
|
|
21
29
|
* import http2 from 'http2';
|
|
22
30
|
* import { createHandler } from 'graphql-http/lib/use/http2';
|
|
23
|
-
* import { schema } from './my-graphql-step';
|
|
31
|
+
* import { schema } from './my-graphql-step/index.mjs';
|
|
24
32
|
*
|
|
25
33
|
* const server = http2.createSecureServer(
|
|
26
34
|
* {
|
package/lib/use/http2.d.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import type { Http2ServerRequest, Http2ServerResponse } from 'http2';
|
|
3
3
|
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler';
|
|
4
|
+
/**
|
|
5
|
+
* The context in the request for the handler.
|
|
6
|
+
*
|
|
7
|
+
* @category Server/http2
|
|
8
|
+
*/
|
|
9
|
+
export interface RequestContext {
|
|
10
|
+
res: Http2ServerResponse;
|
|
11
|
+
}
|
|
4
12
|
/**
|
|
5
13
|
* Handler options when using the http adapter.
|
|
6
14
|
*
|
|
7
15
|
* @category Server/http2
|
|
8
16
|
*/
|
|
9
|
-
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<Http2ServerRequest,
|
|
17
|
+
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<Http2ServerRequest, RequestContext, Context>;
|
|
10
18
|
/**
|
|
11
19
|
* Create a GraphQL over HTTP spec compliant request handler for
|
|
12
20
|
* the Node environment http2 module.
|
package/lib/use/http2.js
CHANGED
package/lib/use/http2.mjs
CHANGED
package/lib/use/koa.d.mts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import type { Middleware } from 'koa';
|
|
2
|
+
import type { Middleware, Response } from 'koa';
|
|
3
3
|
import type { IncomingMessage } from 'http';
|
|
4
|
-
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler';
|
|
4
|
+
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler.mjs';
|
|
5
|
+
/**
|
|
6
|
+
* The context in the request for the handler.
|
|
7
|
+
*
|
|
8
|
+
* @category Server/koa
|
|
9
|
+
*/
|
|
10
|
+
export interface RequestContext {
|
|
11
|
+
res: Response;
|
|
12
|
+
}
|
|
5
13
|
/**
|
|
6
14
|
* Handler options when using the koa adapter.
|
|
7
15
|
*
|
|
8
16
|
* @category Server/koa
|
|
9
17
|
*/
|
|
10
|
-
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<IncomingMessage,
|
|
18
|
+
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<IncomingMessage, RequestContext, Context>;
|
|
11
19
|
/**
|
|
12
20
|
* Create a GraphQL over HTTP spec compliant request handler for
|
|
13
21
|
* the Koa framework.
|
|
@@ -16,7 +24,7 @@ export type HandlerOptions<Context extends OperationContext = undefined> = RawHa
|
|
|
16
24
|
* import Koa from 'koa'; // yarn add koa
|
|
17
25
|
* import mount from 'koa-mount'; // yarn add koa-mount
|
|
18
26
|
* import { createHandler } from 'graphql-http/lib/use/koa';
|
|
19
|
-
* import { schema } from './my-graphql-schema';
|
|
27
|
+
* import { schema } from './my-graphql-schema/index.mjs';
|
|
20
28
|
*
|
|
21
29
|
* const app = new Koa();
|
|
22
30
|
* app.use(mount('/', createHandler({ schema })));
|
package/lib/use/koa.d.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import type { Middleware } from 'koa';
|
|
2
|
+
import type { Middleware, Response } from 'koa';
|
|
3
3
|
import type { IncomingMessage } from 'http';
|
|
4
4
|
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../handler';
|
|
5
|
+
/**
|
|
6
|
+
* The context in the request for the handler.
|
|
7
|
+
*
|
|
8
|
+
* @category Server/koa
|
|
9
|
+
*/
|
|
10
|
+
export interface RequestContext {
|
|
11
|
+
res: Response;
|
|
12
|
+
}
|
|
5
13
|
/**
|
|
6
14
|
* Handler options when using the koa adapter.
|
|
7
15
|
*
|
|
8
16
|
* @category Server/koa
|
|
9
17
|
*/
|
|
10
|
-
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<IncomingMessage,
|
|
18
|
+
export type HandlerOptions<Context extends OperationContext = undefined> = RawHandlerOptions<IncomingMessage, RequestContext, Context>;
|
|
11
19
|
/**
|
|
12
20
|
* Create a GraphQL over HTTP spec compliant request handler for
|
|
13
21
|
* the Koa framework.
|
package/lib/use/koa.js
CHANGED
package/lib/use/koa.mjs
CHANGED
package/lib/use/node.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { HandlerOptions as HttpHandlerOptions } from './http';
|
|
3
|
-
import { OperationContext } from '../handler';
|
|
2
|
+
import { HandlerOptions as HttpHandlerOptions } from './http.mjs';
|
|
3
|
+
import { OperationContext } from '../handler.mjs';
|
|
4
4
|
/**
|
|
5
5
|
* Handler options when using the node adapter.
|
|
6
6
|
*
|
|
@@ -16,7 +16,7 @@ export type HandlerOptions<Context extends OperationContext = undefined> = HttpH
|
|
|
16
16
|
* ```js
|
|
17
17
|
* import http from 'http';
|
|
18
18
|
* import { createHandler } from 'graphql-http/lib/use/node';
|
|
19
|
-
* import { schema } from './my-graphql-step';
|
|
19
|
+
* import { schema } from './my-graphql-step/index.mjs';
|
|
20
20
|
*
|
|
21
21
|
* const server = http.createServer(createHandler({ schema }));
|
|
22
22
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphql-http",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.1",
|
|
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.
|
|
27
|
+
"packageManager": "yarn@3.5.0",
|
|
28
28
|
"main": "lib/index.js",
|
|
29
29
|
"module": "lib/index.mjs",
|
|
30
30
|
"browser": "umd/graphql-http.js",
|
|
@@ -88,7 +88,8 @@
|
|
|
88
88
|
"scripts": {
|
|
89
89
|
"gendocs": "typedoc --options typedoc.js src/",
|
|
90
90
|
"lint:eslint": "eslint 'src'",
|
|
91
|
-
"lint:prettier": "prettier
|
|
91
|
+
"lint:prettier": "prettier --check .",
|
|
92
|
+
"lint:fix": "yarn lint:eslint --fix && yarn lint:prettier --write",
|
|
92
93
|
"lint": "yarn lint:eslint && yarn lint:prettier",
|
|
93
94
|
"type-check": "tsc --noEmit",
|
|
94
95
|
"test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 jest",
|
|
@@ -105,47 +106,47 @@
|
|
|
105
106
|
"graphql": ">=0.11 <=16"
|
|
106
107
|
},
|
|
107
108
|
"devDependencies": {
|
|
108
|
-
"@babel/core": "^7.
|
|
109
|
+
"@babel/core": "^7.21.4",
|
|
109
110
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
110
111
|
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
|
|
111
112
|
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
|
|
112
|
-
"@babel/plugin-proposal-optional-chaining": "^7.
|
|
113
|
-
"@babel/preset-env": "^7.
|
|
114
|
-
"@babel/preset-typescript": "^7.
|
|
113
|
+
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
|
114
|
+
"@babel/preset-env": "^7.21.4",
|
|
115
|
+
"@babel/preset-typescript": "^7.21.4",
|
|
115
116
|
"@rollup/plugin-terser": "^0.4.0",
|
|
116
117
|
"@rollup/plugin-typescript": "^11.0.0",
|
|
117
|
-
"@semantic-release/changelog": "^6.0.
|
|
118
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
118
119
|
"@semantic-release/git": "^10.0.1",
|
|
119
|
-
"@types/eslint": "^8.
|
|
120
|
+
"@types/eslint": "^8.37.0",
|
|
120
121
|
"@types/express": "^4.17.17",
|
|
121
|
-
"@types/glob": "^8.0
|
|
122
|
+
"@types/glob": "^8.1.0",
|
|
122
123
|
"@types/html-validator": "^5.0.3",
|
|
123
|
-
"@types/jest": "^29.
|
|
124
|
-
"@types/koa": "^2.13.
|
|
124
|
+
"@types/jest": "^29.5.0",
|
|
125
|
+
"@types/koa": "^2.13.6",
|
|
125
126
|
"@types/koa-mount": "^4.0.2",
|
|
126
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
127
|
-
"@typescript-eslint/parser": "^5.
|
|
128
|
-
"@whatwg-node/fetch": "^0.8.
|
|
129
|
-
"babel-jest": "^29.
|
|
130
|
-
"eslint": "^8.
|
|
131
|
-
"eslint-config-prettier": "^8.
|
|
127
|
+
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
|
128
|
+
"@typescript-eslint/parser": "^5.57.0",
|
|
129
|
+
"@whatwg-node/fetch": "^0.8.4",
|
|
130
|
+
"babel-jest": "^29.5.0",
|
|
131
|
+
"eslint": "^8.37.0",
|
|
132
|
+
"eslint-config-prettier": "^8.8.0",
|
|
132
133
|
"express": "^4.18.2",
|
|
133
|
-
"fastify": "^4.
|
|
134
|
+
"fastify": "^4.15.0",
|
|
134
135
|
"graphql": "^16.6.0",
|
|
135
136
|
"html-validator": "^6.0.1",
|
|
136
|
-
"jest": "^29.
|
|
137
|
-
"jest-jasmine2": "^29.
|
|
137
|
+
"jest": "^29.5.0",
|
|
138
|
+
"jest-jasmine2": "^29.5.0",
|
|
138
139
|
"koa": "^2.14.1",
|
|
139
140
|
"koa-mount": "^4.0.0",
|
|
140
|
-
"node-fetch": "^3.3.
|
|
141
|
-
"prettier": "^2.8.
|
|
142
|
-
"rollup": "^3.
|
|
141
|
+
"node-fetch": "^3.3.1",
|
|
142
|
+
"prettier": "^2.8.7",
|
|
143
|
+
"rollup": "^3.20.2",
|
|
143
144
|
"rollup-plugin-gzip": "^3.1.0",
|
|
144
|
-
"semantic-release": "^
|
|
145
|
+
"semantic-release": "^21.0.0",
|
|
145
146
|
"tslib": "^2.5.0",
|
|
146
|
-
"typedoc": "^0.23.
|
|
147
|
+
"typedoc": "^0.23.28",
|
|
147
148
|
"typedoc-plugin-markdown": "^3.14.0",
|
|
148
|
-
"typescript": "^
|
|
149
|
+
"typescript": "^5.0.3"
|
|
149
150
|
},
|
|
150
151
|
"resolutions": {
|
|
151
152
|
"npm/libnpmversion": "^3.0.6"
|
|
@@ -245,7 +245,9 @@
|
|
|
245
245
|
headers: {
|
|
246
246
|
'content-type': 'application/json; charset=utf-8',
|
|
247
247
|
},
|
|
248
|
-
body: JSON.stringify({
|
|
248
|
+
body: JSON.stringify({
|
|
249
|
+
query: '{ __type(name: "Run🏃Swim🏊") { name } }',
|
|
250
|
+
}),
|
|
249
251
|
});
|
|
250
252
|
ressert(res).status.toBe(200);
|
|
251
253
|
}),
|
|
@@ -693,6 +695,20 @@
|
|
|
693
695
|
});
|
|
694
696
|
ressert(res).status.toBe(200);
|
|
695
697
|
}),
|
|
698
|
+
audit('7B9B', 'SHOULD use a status code of 200 on variable coercion failure when accepting application/json', async () => {
|
|
699
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
700
|
+
method: 'POST',
|
|
701
|
+
headers: {
|
|
702
|
+
'content-type': 'application/json',
|
|
703
|
+
accept: 'application/json',
|
|
704
|
+
},
|
|
705
|
+
body: JSON.stringify({
|
|
706
|
+
query: 'query CoerceFailure($id: ID!){ __typename }',
|
|
707
|
+
variables: { id: null },
|
|
708
|
+
}),
|
|
709
|
+
});
|
|
710
|
+
ressert(res).status.toBe(200);
|
|
711
|
+
}),
|
|
696
712
|
// Response application/graphql-response+json
|
|
697
713
|
audit(
|
|
698
714
|
// TODO: convert to MUST after watershed
|
|
@@ -841,6 +857,20 @@
|
|
|
841
857
|
});
|
|
842
858
|
await ressert(res).bodyAsExecutionResult.data.toBe(undefined);
|
|
843
859
|
}),
|
|
860
|
+
audit('86EE', 'SHOULD use a status code of 400 on variable coercion failure when accepting application/graphql-response+json', async () => {
|
|
861
|
+
const res = await fetchFn(await getUrl(opts.url), {
|
|
862
|
+
method: 'POST',
|
|
863
|
+
headers: {
|
|
864
|
+
'content-type': 'application/json',
|
|
865
|
+
accept: 'application/graphql-response+json',
|
|
866
|
+
},
|
|
867
|
+
body: JSON.stringify({
|
|
868
|
+
query: 'query CoerceFailure($id: ID!){ __typename }',
|
|
869
|
+
variables: { id: null },
|
|
870
|
+
}),
|
|
871
|
+
});
|
|
872
|
+
ressert(res).status.toBe(400);
|
|
873
|
+
}),
|
|
844
874
|
// TODO: how to fail and have the data entry?
|
|
845
875
|
// audit('EE52', 'MUST use 2xx status code if response contains the data entry and it is not null when accepting application/graphql-response+json'),
|
|
846
876
|
// TODO: how to make an unauthorized request?
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("graphql")):"function"==typeof define&&define.amd?define(["exports","graphql"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).graphqlHttpAudits={})}(this,(function(t){"use strict";function e(t){return null===t?"null":Array.isArray(t)?"array":typeof t}function a(t,e,a){return{id:t,name:e,fn:async()=>{try{return await a(),{id:t,name:e,status:"ok"}}catch(a){if(!(a instanceof n))throw a;return{id:t,name:e,status:e.startsWith("MUST")?"error":"warn",reason:a.reason,response:a.response}}}}}class n{constructor(t,e){this.response=t,this.reason=e}}function o(t){return{status:{toBe(e){if(t.status!==e)throw new n(t,`Response status code is not ${e}`)},toBeBetween:(e,a)=>{if(!(e<=t.status&&t.status<=a))throw new n(t,`Response status is not between ${e} and ${a}`)}},header:e=>({toContain(a){var o;if(!(null===(o=t.headers.get(e))||void 0===o?void 0:o.includes(a)))throw new n(t,`Response header ${e} does not contain ${a}`)},notToContain(a){var o;if(null===(o=t.headers.get(e))||void 0===o?void 0:o.includes(a))throw new n(t,`Response header ${e} contains ${a}`)}}),bodyAsExecutionResult:{data:{async toBe(e){const a=t.clone();if((await s(t)).data!==e)throw new n(a,`Response body execution result data is not "${e}"`)}},async toHaveProperty(e){const a=t.clone();if(!(e in await s(t)))throw new n(a,`Response body execution result does not have a property "${e}"`)},async notToHaveProperty(e){const a=t.clone();if(e in await s(t))throw new n(a,`Response body execution result has a property "${e}"`)}}}}async function s(t){let e,a;try{const a=new TextDecoder("utf-8"),n=await t.arrayBuffer();e=a.decode(n)}catch(e){throw new n(t,"Response body is not UTF-8 encoded")}try{a=JSON.parse(e)}catch(e){throw new n(t,"Response body is not valid JSON")}return a}function i(t){const s=t.fetchFn||fetch;return[a("22EB","SHOULD accept application/graphql-response+json and match the content-type",(async()=>{const e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }"})});o(e).status.toBe(200),o(e).header("content-type").toContain("application/graphql-response+json")})),a("4655","MUST accept application/json and match the content-type",(async()=>{const e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(e).status.toBe(200),o(e).header("content-type").toContain("application/json")})),a("47DE","SHOULD accept */* and use application/json for the content-type",(async()=>{const e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"*/*"},body:JSON.stringify({query:"{ __typename }"})});o(e).status.toBe(200),o(e).header("content-type").toContain("application/json")})),a("80D8","SHOULD assume application/json content-type when accept is missing",(async()=>{const e=new URL(await r(t.url));e.searchParams.set("query","{ __typename }");const a=await s(e.toString());o(a).status.toBe(200),o(a).header("content-type").toContain("application/json")})),a("82A3","MUST use utf-8 encoding when responding",(async()=>{const e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(e).status.toBe(200);try{new TextDecoder("utf-8").decode(await e.arrayBuffer())}catch(t){throw new n(e,"Response body is not UTF-8 encoded")}})),a("BF61","MUST accept utf-8 encoded request",(async()=>{o(await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("78D5","MUST assume utf-8 in request if encoding is unspecified",(async()=>{o(await s(await r(t.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(t.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 e=new URL(await r(t.url));e.searchParams.set("query","{ __typename }");o(await s(e.toString())).status.toBe(200)})),a("9C48","MAY NOT allow executing mutations on GET requests",(async()=>{const e=new URL(await r(t.url));e.searchParams.set("query","mutation { __typename }");o(await s(e.toString(),{headers:{accept:"application/graphql-response+json"}})).status.toBeBetween(400,499)})),a("9ABE","SHOULD respond with 4xx status code if content-type is not supplied on POST requests",(async()=>{o(await s(await r(t.url),{method:"POST"})).status.toBeBetween(400,499)})),a("03D4","MUST accept application/json POST requests",(async()=>{o(await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("7267","MUST require a request body on POST",(async()=>{var e;const a=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json"}});(null===(e=a.headers.get("content-type"))||void 0===e?void 0:e.includes("application/json"))?await o(a).bodyAsExecutionResult.toHaveProperty("errors"):o(a).status.toBe(400)})),a("6610","SHOULD use 400 status code on missing {query} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({notquery:"{ __typename }"})})).status.toBe(400)})),a("3715","SHOULD use 200 status code with errors field on missing {query} parameter when accepting application/json",(async()=>{const e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({notquery:"{ __typename }"})});o(e).status.toBe(200),await o(e).bodyAsExecutionResult.toHaveProperty("errors")})),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`4F5${i}`,`SHOULD use 400 status code on ${e(n)} {query} parameter when accepting application/graphql-response+json`,(async()=>{o(await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:n})})).status.toBe(400)})))),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`9FE${i}`,`SHOULD use 200 status code with errors field on ${e(n)} {query} parameter when accepting application/json`,(async()=>{const e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:n})});o(e).status.toBe(200),await o(e).bodyAsExecutionResult.toHaveProperty("errors")})))),a("34A2","SHOULD allow string {query} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(t.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 e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(e).status.toBe(200),await o(e).bodyAsExecutionResult.notToHaveProperty("errors")})),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`E3E${i}`,`SHOULD use 400 status code on ${e(n)} {operationName} parameter when accepting application/graphql-response+json`,(async()=>{o(await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({operationName:n,query:"{ __typename }"})})).status.toBe(400)})))),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`FB9${i}`,`SHOULD use 200 status code with errors field on ${e(n)} {operationName} parameter when accepting application/json`,(async()=>{const e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({operationName:n,query:"{ __typename }"})});o(e).status.toBe(200),await o(e).bodyAsExecutionResult.toHaveProperty("errors")})))),a("8161","SHOULD allow string {operationName} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(t.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 e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({operationName:"Query",query:"query Query { __typename }"})});o(e).status.toBe(200),await o(e).bodyAsExecutionResult.notToHaveProperty("errors")})),...["variables","operationName","extensions"].flatMap(((e,n)=>[a(`94B${n}`,`SHOULD allow null {${e}} parameter when accepting application/graphql-response+json`,(async()=>{const a=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }",[e]:null})});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")})),a(`022${n}`,`MUST allow null {${e}} parameter when accepting application/json`,(async()=>{const a=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }",[e]:null})});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")}))])),...["string",0,!1,["array"]].map(((n,i)=>a(`69B${i}`,`SHOULD use 400 status code on ${e(n)} {variables} parameter when accepting application/graphql-response+json`,(async()=>{o(await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }",variables:n})})).status.toBe(400)})))),...["string",0,!1,["array"]].map(((n,i)=>a(`F05${i}`,`SHOULD use 200 status code with errors field on ${e(n)} {variables} parameter when accepting application/json`,(async()=>{const e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }",variables:n})});o(e).status.toBe(200),await o(e).bodyAsExecutionResult.toHaveProperty("errors")})))),a("2EA1","SHOULD allow map {variables} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(t.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 e=await s(await r(t.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(e).status.toBe(200),await o(e).bodyAsExecutionResult.notToHaveProperty("errors")})),a("D6D5","MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json",(async()=>{const e=new URL(await r(t.url));e.searchParams.set("query","query Type($name: String!) { __type(name: $name) { name } }"),e.searchParams.set("variables",JSON.stringify({name:"sometype"}));o(await s(e.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 e=new URL(await r(t.url));e.searchParams.set("query","query Type($name: String!) { __type(name: $name) { name } }"),e.searchParams.set("variables",JSON.stringify({name:"sometype"}));const a=await s(e.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(`904${i}`,`SHOULD use 400 status code on ${e(n)} {extensions} parameter when accepting application/graphql-response+json`,(async()=>{o(await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }",extensions:n})})).status.toBe(400)})))),...["string",0,!1,["array"]].map(((n,i)=>a(`368${i}`,`SHOULD use 200 status code with errors field on ${e(n)} {extensions} parameter when accepting application/json`,(async()=>{const e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }",extensions:n})});o(e).status.toBe(200),await o(e).bodyAsExecutionResult.toHaveProperty("errors")})))),a("428F","SHOULD allow map {extensions} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(t.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 e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }",extensions:{some:"value"}})});o(e).status.toBe(200),await o(e).bodyAsExecutionResult.notToHaveProperty("errors")})),a("D477","SHOULD use 200 status code on JSON parsing failure when accepting application/json",(async()=>{o(await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:'{ "not a JSON'})).status.toBe(200)})),a("F5AF","SHOULD use 200 status code if parameters are invalid when accepting application/json",(async()=>{o(await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({qeury:"{ __typename }"})})).status.toBe(200)})),a("572B","SHOULD use 200 status code on document parsing failure when accepting application/json",(async()=>{o(await s(await r(t.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(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})})).status.toBe(200)})),a("60AA","SHOULD use 4xx or 5xx status codes on JSON parsing failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:'{ "not a JSON'})).status.toBeBetween(400,499)})),a("2163","SHOULD use 400 status code on JSON parsing failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:'{ "not a JSON'})).status.toBe(400)})),a("3E36","SHOULD use 4xx or 5xx status codes if parameters are invalid when accepting application/graphql-response+json",(async()=>{o(await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({qeury:"{ __typename }"})})).status.toBeBetween(400,599)})),a("17C5","SHOULD use 400 status code if parameters are invalid when accepting application/graphql-response+json",(async()=>{o(await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({qeury:"{ __typename }"})})).status.toBe(400)})),a("34D6","SHOULD not contain the data entry if parameters are invalid when accepting application/graphql-response+json",(async()=>{const e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({qeury:"{ __typename }"})});await o(e).bodyAsExecutionResult.data.toBe(void 0)})),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(t.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(t.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 e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{"})});await o(e).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(t.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(t.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 e=await s(await r(t.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})});await o(e).bodyAsExecutionResult.data.toBe(void 0)}))]}async function r(t){return"function"==typeof t?await t():t}async function p(t){let e="";e+=`<li><code>${t.id}</code> ${t.name}\n`,e+="<details>\n",e+=`<summary>${function(t,e=1024){if(t.length>e)return t.substring(0,e)+"...";return t}(t.reason)}</summary>\n`,e+='<pre><code class="lang-json">';const a=t.response,n={};for(const[t,e]of a.headers.entries())"date"===t?n[t]="<timestamp>":["cf-ray","server-timing"].includes(t)?n[t]="<omitted>":n[t]=e;let o,s="";try{s=await a.text(),o=JSON.parse(s)}catch(t){}return e+=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},((t,e)=>{if(null!=e&&"object"==typeof e&&!Array.isArray(e)){const t={};return Object.keys(e).sort().reverse().reduce(((t,a)=>(t[a]=e[a],t)),t)}return e}),2)+"\n",e+="</code></pre>\n",e+="</details>\n",e+="</li>\n",e}t.auditServer=async function(t){const e=i(t);return await Promise.all(e.map((({fn:t})=>t())))},t.renderAuditResultsToHTML=async function(t){const e={total:0,ok:[],warn:[],error:[]};for(const a of t)e.total++,a.status,e[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>${e.total}</b> audits in total</li>\n`,e.ok.length&&(a+=`<li><span style="font-family: monospace">✅</span> <b>${e.ok.length}</b> pass</li>\n`),e.warn.length&&(a+=`<li><span style="font-family: monospace">⚠️</span> <b>${e.warn.length}</b> warnings (optional)</li>\n`),e.error.length&&(a+=`<li><span style="font-family: monospace">❌</span> <b>${e.error.length}</b> errors (required)</li>\n`),a+="</ul>\n",a+="\n",e.ok.length){a+="<h2>Passing</h2>\n",a+="<ol>\n";for(const[,t]of e.ok.entries())a+=`<li><code>${t.id}</code> ${t.name}</li>\n`;a+="</ol>\n",a+="\n"}if(e.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[,t]of e.warn.entries())a+=await p(t);a+="</ol>\n",a+="\n"}if(e.error.length){a+="<h2>Errors</h2>\n",a+="The server <b>MUST</b> support these.\n",a+="<ol>\n";for(const[,t]of e.error.entries())a+=await p(t);a+="</ol>\n"}return a},t.serverAudits=i}));
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("graphql")):"function"==typeof define&&define.amd?define(["exports","graphql"],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":"warn",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=new URL(await r(e.url));t.searchParams.set("query","{ __typename }");const a=await s(t.toString());o(a).status.toBe(200),o(a).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","SHOULD 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("7267","MUST require a request body on POST",(async()=>{var t;const a=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"}});(null===(t=a.headers.get("content-type"))||void 0===t?void 0:t.includes("application/json"))?await o(a).bodyAsExecutionResult.toHaveProperty("errors"):o(a).status.toBe(400)})),a("6610","SHOULD use 400 status code on missing {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({notquery:"{ __typename }"})})).status.toBe(400)})),a("3715","SHOULD use 200 status code with errors field on missing {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({notquery:"{ __typename }"})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.toHaveProperty("errors")})),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`4F5${i}`,`SHOULD use 400 status code on ${t(n)} {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:n})})).status.toBe(400)})))),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`9FE${i}`,`SHOULD use 200 status code with errors field on ${t(n)} {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:n})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.toHaveProperty("errors")})))),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(`E3E${i}`,`SHOULD use 400 status code on ${t(n)} {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:n,query:"{ __typename }"})})).status.toBe(400)})))),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`FB9${i}`,`SHOULD use 200 status code with errors field on ${t(n)} {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:n,query:"{ __typename }"})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.toHaveProperty("errors")})))),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(`69B${i}`,`SHOULD use 400 status code on ${t(n)} {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:"{ __typename }",variables:n})})).status.toBe(400)})))),...["string",0,!1,["array"]].map(((n,i)=>a(`F05${i}`,`SHOULD use 200 status code with errors field on ${t(n)} {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:"{ __typename }",variables:n})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.toHaveProperty("errors")})))),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(`904${i}`,`SHOULD use 400 status code on ${t(n)} {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:n})})).status.toBe(400)})))),...["string",0,!1,["array"]].map(((n,i)=>a(`368${i}`,`SHOULD use 200 status code with errors field on ${t(n)} {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:n})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.toHaveProperty("errors")})))),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("D477","SHOULD use 200 status code on JSON 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:'{ "not a JSON'})).status.toBe(200)})),a("F5AF","SHOULD use 200 status code if parameters are invalid 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({qeury:"{ __typename }"})})).status.toBe(200)})),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("60AA","SHOULD use 4xx or 5xx status codes on JSON 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:'{ "not a JSON'})).status.toBeBetween(400,499)})),a("2163","SHOULD use 400 status code on JSON 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:'{ "not a JSON'})).status.toBe(400)})),a("3E36","SHOULD use 4xx or 5xx status codes if parameters are invalid 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({qeury:"{ __typename }"})})).status.toBeBetween(400,599)})),a("17C5","SHOULD use 400 status code if parameters are invalid 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({qeury:"{ __typename }"})})).status.toBe(400)})),a("34D6","SHOULD not contain the data entry if parameters are invalid 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({qeury:"{ __typename }"})});await o(t).bodyAsExecutionResult.data.toBe(void 0)})),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:[],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.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.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
|