Skip to content

Commit 4f7f62b

Browse files
committed
Add ability to turn directive validation on/off and add missing benchmarks
1 parent b05ebf2 commit 4f7f62b

9 files changed

+178
-32
lines changed

README.md

+57-21
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ app.listen(3000)
117117

118118
### Normal GraphQL Server Mode | Without Validation
119119

120-
Last run: `2021-09-18`
120+
Last run: `2021-09-27`
121121

122122
```text
123123
Running 10s test @ http://localhost:3000/graphql
@@ -126,61 +126,97 @@ Running 10s test @ http://localhost:3000/graphql
126126
┌─────────┬──────┬──────┬───────┬───────┬─────────┬─────────┬───────┐
127127
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
128128
├─────────┼──────┼──────┼───────┼───────┼─────────┼─────────┼───────┤
129-
│ Latency │ 6 ms │ 6 ms │ 13 ms │ 19 ms │ 6.83 ms │ 2.62 ms │ 64 ms │
129+
│ Latency │ 4 ms │ 5 ms │ 8 ms │ 14 ms │ 5.33 ms │ 2.28 ms │ 63 ms │
130130
└─────────┴──────┴──────┴───────┴───────┴─────────┴─────────┴───────┘
131-
┌───────────┬────────┬────────┬─────────┬─────────┬─────────┬─────────┬────────┐
132-
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
133-
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼─────────┼────────┤
134-
│ Req/Sec │ 80158015144311491113649.281899.448013
135-
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼─────────┼────────┤
136-
│ Bytes/Sec │ 3.5 MB │ 3.5 MB │ 6.31 MB │ 6.52 MB │ 5.96 MB │ 830 kB │ 3.5 MB │
137-
└───────────┴────────┴────────┴─────────┴─────────┴─────────┴─────────┴────────┘
131+
┌───────────┬────────┬────────┬─────────┬─────────┬─────────┬─────────┬────────┐
132+
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min
133+
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼─────────┼────────┤
134+
│ Req/Sec │ 98319831181111902317069.12470.469827
135+
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼─────────┼────────┤
136+
│ Bytes/Sec │ 4.3 MB │ 4.3 MB │ 7.91 MB │ 8.31 MB │ 7.46 MB │ 1.08 MB │ 4.29 MB │
137+
└───────────┴────────┴────────┴─────────┴─────────┴─────────┴─────────┴────────┘
138138
139139
Req/Bytes counts sampled once per second.
140140
141-
150k requests in 11.04s, 65.6 MB read
141+
188k requests in 11.03s, 82 MB read
142142
```
143143

144144
### Normal GraphQL Server Mode | With Validation
145145

146-
Last run: `2021-09-18`
146+
Last run: `2021-09-27`
147147

148148
```text
149149
Running 10s test @ http://localhost:3000/graphql
150150
100 connections
151151
152-
┌─────────┬──────┬──────┬───────┬───────┬────────┬─────────┬───────┐
153-
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
154-
├─────────┼──────┼──────┼───────┼───────┼────────┼─────────┼───────┤
155-
│ Latency │ 6 ms │ 6 ms │ 13 ms │ 19 ms │ 6.8 ms │ 2.86 ms │ 84 ms │
156-
└─────────┴──────┴──────┴───────┴───────┴────────┴─────────┴───────┘
152+
┌─────────┬──────┬──────┬───────┬───────┬────────┬─────────┬───────┐
153+
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
154+
├─────────┼──────┼──────┼───────┼───────┼────────┼─────────┼───────┤
155+
│ Latency │ 4 ms │ 5 ms │ 8 ms │ 15 ms │ 5.48 ms │ 2.07 ms │ 55 ms │
156+
└─────────┴──────┴──────┴───────┴───────┴────────┴─────────┴───────┘
157157
┌───────────┬─────────┬─────────┬─────────┬─────────┬──────────┬─────────┬─────────┐
158158
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
159159
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
160-
│ Req/Sec │ 75317531147831507113784.192108.747531
160+
│ Req/Sec │ 93999399172151894316704.732427.119398
161161
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
162-
│ Bytes/Sec │ 3.29 MB │ 3.29 MB │ 6.46 MB │ 6.59 MB │ 6.02 MB │ 921 kB │ 3.29 MB │
162+
│ Bytes/Sec │ 4.11 MB │ 4.11 MB │ 7.52 MB │ 8.27 MB │ 7.3 MB │ 1.06 MB │ 4.11 MB │
163163
└───────────┴─────────┴─────────┴─────────┴─────────┴──────────┴─────────┴─────────┘
164164
165165
Req/Bytes counts sampled once per second.
166166
167-
152k requests in 11.04s, 66.3 MB read
167+
184k requests in 11.03s, 80.3 MB read
168168
```
169169

170170
### Gateway GraphQL Server Mode | Without Validation
171171

172-
Last run: `2021-09-18`
172+
Last run: `2021-09-27`
173173

174174
```text
175+
Running 10s test @ http://localhost:3000/graphql
176+
100 connections
177+
178+
┌─────────┬───────┬───────┬───────┬────────┬──────────┬──────────┬────────┐
179+
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
180+
├─────────┼───────┼───────┼───────┼────────┼──────────┼──────────┼────────┤
181+
│ Latency │ 32 ms │ 38 ms │ 71 ms │ 100 ms │ 40.55 ms │ 13.79 ms │ 237 ms │
182+
└─────────┴───────┴───────┴───────┴────────┴──────────┴──────────┴────────┘
183+
┌───────────┬────────┬────────┬────────┬────────┬─────────┬────────┬────────┐
184+
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
185+
├───────────┼────────┼────────┼────────┼────────┼─────────┼────────┼────────┤
186+
│ Req/Sec │ 1079 │ 1079 │ 2577 │ 2853 │ 2434.28 │ 493.75 │ 1079 │
187+
├───────────┼────────┼────────┼────────┼────────┼─────────┼────────┼────────┤
188+
│ Bytes/Sec │ 378 kB │ 378 kB │ 902 kB │ 998 kB │ 852 kB │ 173 kB │ 378 kB │
189+
└───────────┴────────┴────────┴────────┴────────┴─────────┴────────┴────────┘
175190
191+
Req/Bytes counts sampled once per second.
192+
193+
27k requests in 11.03s, 9.37 MB read
176194
```
177195

178196
### Gateway GraphQL Server Mode | With Validation
179197

180-
Last run: `2021-09-18`
198+
Last run: `2021-09-27`
181199

182200
```text
201+
Running 10s test @ http://localhost:3000/graphql
202+
100 connections
203+
204+
┌─────────┬───────┬───────┬───────┬────────┬──────────┬──────────┬────────┐
205+
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
206+
├─────────┼───────┼───────┼───────┼────────┼──────────┼──────────┼────────┤
207+
│ Latency │ 32 ms │ 35 ms │ 70 ms │ 103 ms │ 37.97 ms │ 13.33 ms │ 216 ms │
208+
└─────────┴───────┴───────┴───────┴────────┴──────────┴──────────┴────────┘
209+
┌───────────┬────────┬────────┬────────┬─────────┬────────┬────────┬────────┐
210+
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
211+
├───────────┼────────┼────────┼────────┼─────────┼────────┼────────┼────────┤
212+
│ Req/Sec │ 1153 │ 1153 │ 2711 │ 2969 │ 2597.4 │ 521.83 │ 1153 │
213+
├───────────┼────────┼────────┼────────┼─────────┼────────┼────────┼────────┤
214+
│ Bytes/Sec │ 404 kB │ 404 kB │ 949 kB │ 1.04 MB │ 909 kB │ 183 kB │ 404 kB │
215+
└───────────┴────────┴────────┴────────┴─────────┴────────┴────────┴────────┘
216+
217+
Req/Bytes counts sampled once per second.
183218
219+
26k requests in 10.03s, 9.09 MB read
184220
```
185221

186222
## Examples

bench.sh

+6-4
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ npx concurrently --raw -k \
1717
echo '====================================='
1818
echo '= Gateway Mode | Without Validation ='
1919
echo '====================================='
20-
npx concurrently --raw -k \ [12:23:58]
20+
npx concurrently --raw -k \
2121
"node ./bench/gateway-user-service.js" \
2222
"node ./bench/gateway-post-service.js" \
23-
"npx wait-on tcp:3001 tcp:3002 && node ./bench/gateway-without-validation.js"
23+
"npx wait-on tcp:3001 tcp:3002 && node ./bench/gateway-without-validation.js" \
24+
"npx wait-on tcp:3000 && node ./bench/gateway-bench.js"
2425

2526
echo '=================================='
2627
echo '= Gateway Mode | With Validation ='
2728
echo '=================================='
28-
npx concurrently --raw -k \ [12:23:58]
29+
npx concurrently --raw -k \
2930
"node ./bench/gateway-user-service.js" \
3031
"node ./bench/gateway-post-service.js" \
31-
"npx wait-on tcp:3001 tcp:3002 && node ./bench/gateway-with-validation.js"
32+
"npx wait-on tcp:3001 tcp:3002 && node ./bench/gateway-with-validation.js" \
33+
"npx wait-on tcp:3000 && node ./bench/gateway-bench.js"

docs/api/options.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010

1111
Extends: [`AJVOptions`](https://ajv.js.org/options.html)
1212

13-
* **mode** `"JSONSchema" | "JTD"` (optional, default: `JSONSchema`) - the validation mode of the plugin. This is used to specify the type of schema that needs to be compiled.
13+
* **mode** `"JSONSchema" | "JTD"` (optional, default: `"JSONSchema"`) - the validation mode of the plugin. This is used to specify the type of schema that needs to be compiled.
1414
* **schema** `MercuriusValidationSchema` (optional) - the validation schema definition that the plugin with run. One can define JSON Schema or JTD definitions for GraphQL types, fields and arguments or functions for GraphQL arguments.
15+
* **directiveValidation** `boolean` (optional, default: `true`) - turn directive validation on or off. It is on by default.
1516

1617
It extends the [AJV options](https://ajv.js.org/options.html). These can be used to register additional `formats` for example and provide further customization to the AJV validation behavior.
1718

index.d.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,17 @@ export type MercuriusValidationMode = 'JSONSchema' | 'JTD'
7171
*/
7272
export interface MercuriusValidationOptions<TParent = any, TArgs = any, TContext = MercuriusContext> extends Options {
7373
/**
74-
* The mode of operation to use when interpreting Mercurius validation schemas.
74+
* The mode of operation to use when interpreting Mercurius validation schemas (default: `"JSONSchema"`).
7575
*/
7676
mode?: MercuriusValidationMode;
7777
/**
7878
* The validation schema definition for the Mercurius GraphQL server.
7979
*/
8080
schema?: MercuriusValidationSchema<TParent, TArgs, TContext>;
81+
/**
82+
* Turn directive validation on or off (default: `true`).
83+
*/
84+
directiveValidation?: boolean;
8185
}
8286

8387
export default MercuriusValidation

lib/utils.js

+8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ const { MER_VALIDATION_ERR_INVALID_OPTS } = require('./errors')
77
* Perform basic validation on the validation options.
88
*/
99
function validateOpts (opts) {
10+
if (typeof opts.mode !== 'undefined' && typeof opts.mode !== 'string') {
11+
throw new MER_VALIDATION_ERR_INVALID_OPTS('opts.mode must be a string.')
12+
}
13+
14+
if (typeof opts.directiveValidation !== 'undefined' && typeof opts.directiveValidation !== 'boolean') {
15+
throw new MER_VALIDATION_ERR_INVALID_OPTS('opts.directiveValidation must be a boolean.')
16+
}
17+
1018
if (typeof opts.schema !== 'undefined') {
1119
if (typeof opts.schema !== 'object' || opts.schema === null) {
1220
throw new MER_VALIDATION_ERR_INVALID_OPTS('opts.schema must be an object.')

lib/validation.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ const { FunctionValidator, JSONSchemaValidator, JTDValidator, DirectiveValidator
44
const { kValidationSchema, kDirectiveValidator, kSchemaValidator, kFunctionValidator } = require('./symbols')
55

66
class Validation {
7-
constructor (app, { schema, mode, ...opts }) {
7+
constructor (app, { schema, mode = 'JSONSchema', directiveValidation = true, ...opts }) {
88
this[kValidationSchema] = schema || {}
99

10-
this[kDirectiveValidator] = new DirectiveValidator(opts)
10+
// By default, we turn on directive validation
11+
if (directiveValidation) {
12+
this[kDirectiveValidator] = new DirectiveValidator(opts)
13+
} else {
14+
this[kDirectiveValidator] = null
15+
}
1116

1217
if (mode === 'JTD') {
1318
this[kSchemaValidator] = new JTDValidator(opts)
@@ -23,7 +28,9 @@ class Validation {
2328
// - Validation on field responses
2429
registerValidationSchema (graphQLSchema) {
2530
// We register policies in the reverse order of intended operation
26-
this[kDirectiveValidator].registerValidationSchema(graphQLSchema)
31+
if (this[kDirectiveValidator] !== null) {
32+
this[kDirectiveValidator].registerValidationSchema(graphQLSchema)
33+
}
2734
this[kFunctionValidator].registerValidationSchema(graphQLSchema, this[kValidationSchema])
2835
this[kSchemaValidator].registerValidationSchema(graphQLSchema, this[kValidationSchema])
2936
}

test/directive-validation.js

+51-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ const resolvers = {
7373
}
7474

7575
t.test('With directives', t => {
76-
t.plan(26)
76+
t.plan(27)
7777

7878
t.test('should protect the schema and not affect operations when everything is okay', async (t) => {
7979
t.plan(1)
@@ -2677,4 +2677,54 @@ t.test('With directives', t => {
26772677
]
26782678
})
26792679
})
2680+
2681+
t.test('should be able to turn off directive validation', async (t) => {
2682+
t.plan(1)
2683+
2684+
const app = Fastify()
2685+
t.teardown(app.close.bind(app))
2686+
2687+
app.register(mercurius, {
2688+
schema,
2689+
resolvers
2690+
})
2691+
app.register(mercuriusValidation, { directiveValidation: false })
2692+
2693+
const query = `query {
2694+
messages(filters: { text: ""}) {
2695+
id
2696+
text
2697+
}
2698+
}`
2699+
2700+
const response = await app.inject({
2701+
method: 'POST',
2702+
headers: { 'content-type': 'application/json' },
2703+
url: '/graphql',
2704+
body: JSON.stringify({ query })
2705+
})
2706+
2707+
t.same(JSON.parse(response.body), {
2708+
data: {
2709+
messages: [
2710+
{
2711+
id: '0',
2712+
text: 'Some system message.'
2713+
},
2714+
{
2715+
id: '1',
2716+
text: 'Hello there'
2717+
},
2718+
{
2719+
id: '2',
2720+
text: 'Give me a place to stand, a lever long enough and a fulcrum. And I can move the Earth.'
2721+
},
2722+
{
2723+
id: '3',
2724+
text: ''
2725+
}
2726+
]
2727+
}
2728+
})
2729+
})
26802730
})

test/registration.js

+35-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const resolvers = {
2727
}
2828

2929
t.test('registrations', t => {
30-
t.plan(5)
30+
t.plan(7)
3131

3232
t.test('registration - should error if mercurius is not loaded', async (t) => {
3333
t.plan(1)
@@ -61,6 +61,40 @@ t.test('registrations', t => {
6161
)
6262
})
6363

64+
t.test('registration - should error if mode is defined but not a string', async (t) => {
65+
t.plan(1)
66+
67+
const app = Fastify()
68+
t.teardown(app.close.bind(app))
69+
70+
app.register(mercurius, {
71+
schema,
72+
resolvers
73+
})
74+
75+
t.rejects(
76+
app.register(mercuriusValidation, { mode: 123456 }),
77+
new MER_VALIDATION_ERR_INVALID_OPTS('opts.mode must be a string.')
78+
)
79+
})
80+
81+
t.test('registration - should error if directiveValidation is defined but not a boolean', async (t) => {
82+
t.plan(1)
83+
84+
const app = Fastify()
85+
t.teardown(app.close.bind(app))
86+
87+
app.register(mercurius, {
88+
schema,
89+
resolvers
90+
})
91+
92+
t.rejects(
93+
app.register(mercuriusValidation, { directiveValidation: 'string' }),
94+
new MER_VALIDATION_ERR_INVALID_OPTS('opts.directiveValidation must be a boolean.')
95+
)
96+
})
97+
6498
t.test('registration - should error if schema type is not an object', async (t) => {
6599
t.plan(1)
66100

test/types/index.test-d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ app.register(mercuriusValidation, {
2323
app.register(mercuriusValidation, { mode: 'JTD' })
2424
app.register(mercuriusValidation, { mode: 'JSONSchema' })
2525

26+
// Turn directive validation on/off
27+
app.register(mercuriusValidation, { directiveValidation: true })
28+
app.register(mercuriusValidation, { directiveValidation: false })
29+
2630
// Register JSON Schema definitions
2731
app.register(mercuriusValidation, {
2832
mode: 'JSONSchema',

0 commit comments

Comments
 (0)