mercurius-validation
Advanced tools
Comparing version 1.0.0 to 1.0.1
# 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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
303275
52
8960
306