mercurius-validation
Advanced tools
Comparing version
# mercurius-validation | ||
- [Plugin options](#plugin-options) | ||
- [Registration](#registration) | ||
## Plugin options | ||
<!-- TODO --> | ||
**mercurius-validation** supports the following options: | ||
@@ -126,57 +124,1 @@ | ||
The [JTD](https://jsontypedef.com/docs/) schema definition for the input object type, type field or field argument. | ||
## Registration | ||
The plugin must be registered **after** Mercurius is registered. | ||
```js | ||
'use strict' | ||
const Fastify = require('fastify') | ||
const mercurius = require('mercurius') | ||
const mercuriusValidation = require('mercurius-validation') | ||
const app = Fastify() | ||
const schema = ` | ||
directive @validation( | ||
requires: Role = ADMIN, | ||
) on OBJECT | FIELD_DEFINITION | ||
enum Role { | ||
ADMIN | ||
REVIEWER | ||
USER | ||
UNKNOWN | ||
} | ||
type Query { | ||
add(x: Int, y: Int): Int @validation(requires: USER) | ||
} | ||
` | ||
const resolvers = { | ||
Query: { | ||
add: async (_, { x, y }) => x + y | ||
} | ||
} | ||
app.register(mercurius, { | ||
schema, | ||
resolvers | ||
}) | ||
app.register(mercuriusValidation, { | ||
validationContext (context) { | ||
return { | ||
identity: context.reply.request.headers['x-user'] | ||
} | ||
}, | ||
async applyPolicy (validationDirectiveAST, parent, args, context, info) { | ||
return context.validation.identity === 'admin' | ||
}, | ||
validationDirective: 'validation' | ||
}) | ||
app.listen(3000) | ||
``` |
# Directive validation | ||
- [Using the GraphQL definitions within your schema](#using-the-graphql-definitions-within-your-schema) | ||
- [GraphQL argument validation](#graphql-argument-validation) | ||
- [GraphQL Input Object type field validation](#graphql-input-object-type-field-validation) | ||
- [GraphQL Input Object type validation](#graphql-input-object-type-validation) | ||
- [Additional AJV options](#additional-ajv-options) | ||
- [Turning off directive validation](#turning-off-directive-validation) | ||
- [Unsupported JSON Schema keywords](#unsupported-json-schema-keywords) | ||
By default, Mercurius validation supports `@constraint` Directives out of the box. It is defined as follows: | ||
@@ -49,2 +57,47 @@ | ||
## Using the GraphQL definitions within your schema | ||
`mercurius-validation` provides `GraphQLDirective` and type definitions to allow one to use the `@constraint` directive within a GraphQL schema. | ||
For string-based schema definitions, you can use as follows: | ||
```js | ||
'use strict' | ||
const mercuriusValidation = require('mercurius-validation') | ||
const schema = ` | ||
${mercuriusValidation.graphQLTypeDefs} | ||
type Message { | ||
id: ID! | ||
text: String | ||
} | ||
input Filters { | ||
id: ID | ||
text: String @constraint(minLength: 1) | ||
} | ||
type Query { | ||
message(id: ID @constraint(type: "string", minLength: 1)): Message | ||
messages(filters: Filters): [Message] | ||
} | ||
` | ||
``` | ||
For executable schema definitions, you can use as follows: | ||
```js | ||
'use strict' | ||
const { parse, GraphQLSchema } = require('graphql') | ||
const mercuriusValidation = require('mercurius-validation') | ||
// Define your executable schema as normal | ||
const graphQLSchemaToExtend = new GraphQLSchema({ ... }) | ||
const schema = extendSchema(graphQLSchemaToExtend, parse(mercuriusValidation.graphQLTypeDefs)) | ||
``` | ||
## GraphQL argument validation | ||
@@ -286,2 +339,13 @@ | ||
## Turning off directive validation | ||
If you don't want to run directive validation within the plugin, you can turn it off during plugin registration: | ||
```js | ||
app.register(mercuriusValidation, { | ||
directiveValidation: false | ||
// Additional options here | ||
}) | ||
``` | ||
## Unsupported JSON Schema keywords | ||
@@ -288,0 +352,0 @@ |
# Function validation | ||
- [GraphQL argument validation](#graphql-argument-validation) | ||
You can setup Mercurius validation to run custom functions on arguments when defining in-band validation schemas. It supports the following validation definitions: | ||
@@ -4,0 +6,0 @@ |
# JSON Schema validation | ||
- [GraphQL argument validation](#graphql-argument-validation) | ||
- [GraphQL Input Object type field validation](#graphql-input-object-type-field-validation) | ||
- [GraphQL Input Object type validation](#graphql-input-object-type-validation) | ||
- [Additional AJV options](#additional-ajv-options) | ||
- [Custom errors](#custom-errors) | ||
- [Type inference](#type-inference) | ||
- [Caveats](#caveats) | ||
By default, Mercurius validation runs in JSON Schema mode when defining in-band validation schemas. It supports the following validation definitions: | ||
@@ -4,0 +12,0 @@ |
# JTD validation | ||
- [GraphQL argument validation](#graphql-argument-validation) | ||
- [GraphQL Input Object type field validation](#graphql-input-object-type-field-validation) | ||
- [GraphQL Input Object type validation](#graphql-input-object-type-validation) | ||
- [Additional AJV options](#additional-ajv-options) | ||
You can setup Mercurius validation to run in JTD mode when defining in-band validation schemas. It supports the following validation definitions: | ||
@@ -11,3 +16,3 @@ | ||
To enable JTD mode, set the following at registration: | ||
To enable JTD mode, set the `mode` options to `"JTD"` at registration: | ||
@@ -14,0 +19,0 @@ ```js |
@@ -38,11 +38,11 @@ 'use strict' | ||
function inferJSONSchemaType (type) { | ||
function inferJSONSchemaType (type, isNonNull) { | ||
if (type === GraphQLString) { | ||
return { type: 'string' } | ||
return isNonNull ? { type: 'string' } : { type: ['string', 'null'] } | ||
} | ||
if (type === GraphQLInt) { | ||
return { type: 'integer' } | ||
return isNonNull ? { type: 'integer' } : { type: ['integer', 'null'] } | ||
} | ||
if (type === GraphQLFloat) { | ||
return { type: 'number' } | ||
return isNonNull ? { type: 'number' } : { type: ['number', 'null'] } | ||
} | ||
@@ -53,4 +53,5 @@ return {} | ||
function getTypeInfo (graphQLType) { | ||
const type = isNonNullType(graphQLType.type) ? graphQLType.type.ofType : graphQLType.type | ||
return [type, getNamedType(type)] | ||
const isNonNull = isNonNullType(graphQLType.type) | ||
const type = isNonNull ? graphQLType.type.ofType : graphQLType.type | ||
return [type, getNamedType(type), isNonNull] | ||
} | ||
@@ -57,0 +58,0 @@ |
@@ -13,5 +13,5 @@ 'use strict' | ||
class JSONSchemaValidator extends Validator { | ||
[kValidationSchema] (type, namedType, typeValidation, id) { | ||
[kValidationSchema] (type, namedType, isNonNull, typeValidation, id) { | ||
let builtValidationSchema = { | ||
...inferJSONSchemaType(namedType), | ||
...inferJSONSchemaType(namedType, isNonNull), | ||
$id: id | ||
@@ -30,3 +30,3 @@ } | ||
} else if (isListType(type)) { | ||
let items = { ...inferJSONSchemaType(namedType), ...builtValidationSchema.items } | ||
let items = { ...inferJSONSchemaType(namedType, isNonNull), ...builtValidationSchema.items } | ||
if (typeValidation !== null) { | ||
@@ -55,7 +55,7 @@ items = { ...items, ...typeValidation.items } | ||
for (const argument of schemaTypeField.args) { | ||
const [argumentType, namedArgumentType] = getTypeInfo(argument) | ||
const [argumentType, namedArgumentType, isNonNull] = getTypeInfo(argument) | ||
const argumentValidation = fieldValidation !== null ? fieldValidation[argument.name] || null : null | ||
const id = `https://mercurius.dev/validation/${typeName}/${fieldName}/${argument.name}` | ||
const argumentValidationSchema = this[kValidationSchema](argumentType, namedArgumentType, argumentValidation, id) | ||
const argumentValidationSchema = this[kValidationSchema](argumentType, namedArgumentType, isNonNull, argumentValidation, id) | ||
@@ -70,6 +70,6 @@ fieldArgumentsValidationSchema.properties[argument.name] = argumentValidationSchema | ||
[kBuildInputTypeFieldSchema] (typeName, fieldName, schemaTypeField, fieldValidation) { | ||
const [fieldType, namedFieldType] = getTypeInfo(schemaTypeField) | ||
const [fieldType, namedFieldType, isNonNull] = getTypeInfo(schemaTypeField) | ||
const id = `https://mercurius.dev/validation/${typeName}/${fieldName}` | ||
const builtFieldValidationSchema = this[kValidationSchema](fieldType, namedFieldType, fieldValidation, id) | ||
const builtFieldValidationSchema = this[kValidationSchema](fieldType, namedFieldType, isNonNull, fieldValidation, id) | ||
@@ -83,3 +83,3 @@ // Only consider fields where we have inferred the type to avoid any AJV errors | ||
if (typeof builtFieldValidationSchema.type === 'string') { | ||
if (typeof builtFieldValidationSchema.type === 'string' || Array.isArray(builtFieldValidationSchema.type)) { | ||
return builtFieldValidationSchema | ||
@@ -86,0 +86,0 @@ } |
{ | ||
"name": "mercurius-validation", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"description": "Mercurius Validation Plugin adds configurable Validation support to Mercurius.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -23,5 +23,8 @@ # mercurius-validation | ||
- [Quick Start](#quick-start) | ||
- [Validation with JSON SChema definitions](#validation-with-json-schema-definitions) | ||
- [Validation with the GraphQL `@constraint` directive](#validation-with-the-graphql-constraint-directive) | ||
- [Examples](#examples) | ||
- [Benchmarks](#benchmarks) | ||
- [API](docs/api/options.md) | ||
- [Registration](docs/registration.md) | ||
- [JSON Schema Validation](docs/json-schema-validation.md) | ||
@@ -40,2 +43,6 @@ - [JTD Validation](docs/jtd-validation.md) | ||
### Validation with JSON Schema definitions | ||
You can setup `mercurius-validation` using a JSON Schema validation definition as follows: | ||
```js | ||
@@ -118,2 +125,72 @@ 'use strict' | ||
### Validation with the GraphQL `@constraint` directive | ||
You can setup `mercurius-validation` with the `@constraint` GraphQL directive. `mercurius-validation` provides the type definitions to include this directive definition within your GraphQL schema. | ||
```js | ||
'use strict' | ||
const Fastify = require('fastify') | ||
const mercurius = require('mercurius') | ||
const mercuriusValidation = require('mercurius-validation') | ||
const schema = ` | ||
${mercuriusValidation.graphQLTypeDefs} | ||
type Message { | ||
id: ID! | ||
text: String | ||
} | ||
input Filters { | ||
id: ID | ||
text: String @constraint(minLength: 1) | ||
} | ||
type Query { | ||
message(id: ID @constraint(type: "string", minLength: 1)): Message | ||
messages(filters: Filters): [Message] | ||
} | ||
` | ||
const messages = [ | ||
{ | ||
id: 0, | ||
text: 'Some system message.' | ||
}, | ||
{ | ||
id: 1, | ||
text: 'Hello there' | ||
}, | ||
{ | ||
id: 2, | ||
text: 'Give me a place to stand, a lever long enough and a fulcrum. And I can move the Earth.' | ||
}, | ||
{ | ||
id: 3, | ||
text: '' | ||
} | ||
] | ||
const resolvers = { | ||
Query: { | ||
message: async (_, { id }) => { | ||
return messages.find(message => message.id === Number(id)) | ||
}, | ||
messages: async () => { | ||
return messages | ||
} | ||
} | ||
} | ||
const app = Fastify() | ||
app.register(mercurius, { | ||
schema, | ||
resolvers | ||
}) | ||
app.register(mercuriusValidation) | ||
app.listen(3000) | ||
``` | ||
## Benchmarks | ||
@@ -120,0 +197,0 @@ |
@@ -170,3 +170,3 @@ 'use strict' | ||
minLength: 1, | ||
type: 'string', | ||
type: ['string', 'null'], | ||
$id: 'https://mercurius.dev/validation/Filters/text' | ||
@@ -190,3 +190,3 @@ }, | ||
items: { | ||
type: 'string' | ||
type: ['string', 'null'] | ||
} | ||
@@ -228,3 +228,3 @@ }, | ||
items: { | ||
type: 'string' | ||
type: ['string', 'null'] | ||
} | ||
@@ -558,3 +558,3 @@ }, | ||
items: { | ||
type: 'string' | ||
type: ['string', 'null'] | ||
} | ||
@@ -561,0 +561,0 @@ }, |
@@ -102,2 +102,3 @@ 'use strict' | ||
topPosts(count: Int!): [Post] | ||
topPostsFloat(count: Float!): [Post] | ||
}` | ||
@@ -120,2 +121,5 @@ | ||
return Object.values(posts).filter(p => p.authorId === user.id).slice(0, count) | ||
}, | ||
topPostsFloat: (user, { count }, context, info) => { | ||
return Object.values(posts).filter(p => p.authorId === user.id).slice(0, count) | ||
} | ||
@@ -153,2 +157,5 @@ }, | ||
count: { type: 'integer', minimum: 1 } | ||
}, | ||
topPostsFloat: { | ||
count: { type: 'number', minimum: 1 } | ||
} | ||
@@ -181,2 +188,8 @@ } | ||
} | ||
topPostsFloat(count: 2) { | ||
pid | ||
author { | ||
id | ||
} | ||
} | ||
} | ||
@@ -214,2 +227,16 @@ topPosts(count: 2) { | ||
} | ||
], | ||
topPostsFloat: [ | ||
{ | ||
pid: 'p1', | ||
author: { | ||
id: 'u1' | ||
} | ||
}, | ||
{ | ||
pid: 'p3', | ||
author: { | ||
id: 'u1' | ||
} | ||
} | ||
] | ||
@@ -299,3 +326,3 @@ }, | ||
$id: 'https://mercurius.dev/validation/Query/me/id', | ||
type: 'integer', | ||
type: ['integer', 'null'], | ||
minimum: 1 | ||
@@ -335,3 +362,3 @@ }, | ||
$id: 'https://mercurius.dev/validation/Query/topPosts/count', | ||
type: 'integer', | ||
type: ['integer', 'null'], | ||
minimum: 1 | ||
@@ -338,0 +365,0 @@ }, |
@@ -72,3 +72,3 @@ 'use strict' | ||
t.test('JSON Schema validators', t => { | ||
t.plan(17) | ||
t.plan(18) | ||
@@ -280,3 +280,3 @@ t.test('should protect the schema and not affect operations when everything is okay', async (t) => { | ||
$id: 'https://mercurius.dev/validation/Filters/text', | ||
type: 'string', | ||
type: ['string', 'null'], | ||
minLength: 1 | ||
@@ -354,3 +354,3 @@ }, | ||
$id: 'https://mercurius.dev/validation/Filters/text', | ||
type: 'string', | ||
type: ['string', 'null'], | ||
minLength: 1 | ||
@@ -576,3 +576,3 @@ }, | ||
$id: 'https://mercurius.dev/validation/Filters/text', | ||
type: 'string', | ||
type: ['string', 'null'], | ||
minLength: 1 | ||
@@ -965,3 +965,3 @@ }, | ||
text: { | ||
type: 'string', | ||
type: ['string', 'null'], | ||
$id: 'https://mercurius.dev/validation/Filters/text' | ||
@@ -1051,3 +1051,3 @@ } | ||
minLength: 1, | ||
type: 'string', | ||
type: ['string', 'null'], | ||
$id: 'https://mercurius.dev/validation/Filters/text' | ||
@@ -1072,3 +1072,3 @@ } | ||
minLength: 1, | ||
type: 'string', | ||
type: ['string', 'null'], | ||
$id: 'https://mercurius.dev/validation/Filters/text' | ||
@@ -1275,3 +1275,3 @@ }, | ||
parentSchema: { | ||
type: 'string', | ||
type: ['string', 'null'], | ||
$id: 'https://mercurius.dev/validation/Query/message/id', | ||
@@ -1310,3 +1310,3 @@ minLength: 1 | ||
parentSchema: { | ||
type: 'string', | ||
type: ['string', 'null'], | ||
$id: 'https://mercurius.dev/validation/Filters/text', | ||
@@ -1327,3 +1327,3 @@ minLength: 1 | ||
parentSchema: { | ||
type: 'string', | ||
type: ['string', 'null'], | ||
$id: 'https://mercurius.dev/validation/Filters/text', | ||
@@ -1344,3 +1344,3 @@ minLength: 1 | ||
parentSchema: { | ||
type: 'string', | ||
type: ['string', 'null'], | ||
minLength: 1 | ||
@@ -1387,3 +1387,3 @@ }, | ||
parentSchema: { | ||
type: 'string', | ||
type: ['string', 'null'], | ||
$id: 'https://mercurius.dev/validation/Filters/text', | ||
@@ -1516,3 +1516,3 @@ minLength: 1 | ||
parentSchema: { | ||
type: 'integer', | ||
type: ['integer', 'null'], | ||
$id: 'https://mercurius.dev/validation/Query/message/id', | ||
@@ -1552,3 +1552,3 @@ minimum: 1 | ||
parentSchema: { | ||
type: 'integer', | ||
type: ['integer', 'null'], | ||
$id: 'https://mercurius.dev/validation/Filters/value', | ||
@@ -1570,3 +1570,3 @@ minimum: 1 | ||
parentSchema: { | ||
type: 'integer', | ||
type: ['integer', 'null'], | ||
$id: 'https://mercurius.dev/validation/Filters/value', | ||
@@ -1588,3 +1588,3 @@ minimum: 1 | ||
parentSchema: { | ||
type: 'integer', | ||
type: ['integer', 'null'], | ||
minimum: 1 | ||
@@ -1632,3 +1632,3 @@ }, | ||
parentSchema: { | ||
type: 'integer', | ||
type: ['integer', 'null'], | ||
$id: 'https://mercurius.dev/validation/Filters/value', | ||
@@ -1761,3 +1761,3 @@ minimum: 1 | ||
parentSchema: { | ||
type: 'number', | ||
type: ['number', 'null'], | ||
$id: 'https://mercurius.dev/validation/Query/message/id', | ||
@@ -1797,3 +1797,3 @@ minimum: 1 | ||
parentSchema: { | ||
type: 'number', | ||
type: ['number', 'null'], | ||
$id: 'https://mercurius.dev/validation/Filters/value', | ||
@@ -1815,3 +1815,3 @@ minimum: 1 | ||
parentSchema: { | ||
type: 'number', | ||
type: ['number', 'null'], | ||
$id: 'https://mercurius.dev/validation/Filters/value', | ||
@@ -1833,3 +1833,3 @@ minimum: 1 | ||
parentSchema: { | ||
type: 'number', | ||
type: ['number', 'null'], | ||
minimum: 1 | ||
@@ -1877,3 +1877,3 @@ }, | ||
parentSchema: { | ||
type: 'number', | ||
type: ['number', 'null'], | ||
$id: 'https://mercurius.dev/validation/Filters/value', | ||
@@ -2076,2 +2076,40 @@ minimum: 1 | ||
}) | ||
t.test('should support nullable input type arguments', async (t) => { | ||
t.plan(1) | ||
const app = Fastify() | ||
t.teardown(app.close.bind(app)) | ||
app.register(mercurius, { | ||
schema: `type Query { | ||
nullableInput(input: String): String | ||
}`, | ||
resolvers: { | ||
Query: { | ||
nullableInput: async (_, { input }) => { | ||
return input | ||
} | ||
} | ||
} | ||
}) | ||
app.register(mercuriusValidation) | ||
const query = `query { | ||
nullableInput(input: null) | ||
}` | ||
const response = await app.inject({ | ||
method: 'POST', | ||
headers: { 'content-type': 'application/json' }, | ||
url: '/graphql', | ||
body: JSON.stringify({ query }) | ||
}) | ||
t.same(response.json(), { | ||
data: { | ||
nullableInput: null | ||
} | ||
}) | ||
}) | ||
}) |
@@ -279,3 +279,3 @@ 'use strict' | ||
$id: 'https://mercurius.dev/validation/Filters/text', | ||
type: 'string', | ||
type: ['string', 'null'], | ||
minLength: 1 | ||
@@ -282,0 +282,0 @@ }, |
Sorry, the diff of this file is too big to display
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
303275
2.18%52
1.96%8960
0.67%306
33.62%