graphql-constraint-directive
Advanced tools
Comparing version 5.1.1 to 5.2.0
@@ -12,3 +12,3 @@ const { | ||
function createApollo4QueryValidationPlugin () { | ||
function createApollo4QueryValidationPlugin (options = {}) { | ||
return { | ||
@@ -34,3 +34,4 @@ async serverWillStart () { | ||
request.variables, | ||
request.operationName | ||
request.operationName, | ||
options | ||
) | ||
@@ -37,0 +38,0 @@ |
15
index.js
@@ -174,3 +174,3 @@ const { | ||
function validateQuery (schema, query, variables, operationName) { | ||
function validateQuery (schema, query, variables, operationName, pluginOptions = {}) { | ||
const typeInfo = new TypeInfo(schema) | ||
@@ -185,5 +185,7 @@ | ||
) | ||
const visitor = new QueryValidationVisitor(context, { | ||
variables, | ||
operationName | ||
operationName, | ||
pluginOptions | ||
}) | ||
@@ -196,3 +198,3 @@ | ||
function createApolloQueryValidationPlugin ({ schema }) { | ||
function createApolloQueryValidationPlugin ({ schema }, options = {}) { | ||
return { | ||
@@ -210,3 +212,4 @@ async requestDidStart () { | ||
request.variables, | ||
request.operationName | ||
request.operationName, | ||
options | ||
) | ||
@@ -225,6 +228,6 @@ if (errors.length > 0) { | ||
function createEnvelopQueryValidationPlugin () { | ||
function createEnvelopQueryValidationPlugin (options = {}) { | ||
return { | ||
onExecute ({ args, setResultAndStopExecution }) { | ||
const errors = validateQuery(args.schema, args.document, args.variableValues, args.operationName) | ||
const errors = validateQuery(args.schema, args.document, args.variableValues, args.operationName, options) | ||
if (errors.length > 0) { | ||
@@ -231,0 +234,0 @@ setResultAndStopExecution({ errors: errors.map(err => { return new GraphQLError(err.message, err, { code: err.code, field: err.fieldName, context: err.context, exception: err.originalError }) }) }) |
@@ -138,3 +138,3 @@ const { getVariableValues } = require('graphql/execution/values.js') | ||
validateInputTypeValue(this.context, inputObjectTypeDef, argName, variableName, value, this.currentField, variableName) | ||
validateInputTypeValue(this.context, inputObjectTypeDef, argName, variableName, value, this.currentField, variableName, this.options) | ||
} else if (isListType(valueTypeDef)) { | ||
@@ -144,7 +144,7 @@ validateArrayTypeValue(this.context, valueTypeDef, argTypeDef, value, this.currentField, argName, variableName, variableName) | ||
// nothing to validate | ||
if (!value) return | ||
if (!value && value !== '' && value !== 0) return | ||
const fieldNameForError = variableName || this.currentField.name.value + '.' + argName | ||
validateScalarTypeValue(this.context, this.currentField, argTypeDef, valueTypeDef, value, variableName, argName, fieldNameForError, '') | ||
validateScalarTypeValue(this.context, this.currentField, argTypeDef, valueTypeDef, value, variableName, argName, fieldNameForError, '', this.options) | ||
} | ||
@@ -154,3 +154,3 @@ } | ||
function validateScalarTypeValue (context, currentQueryField, typeDefWithDirective, valueTypeDef, value, variableName, argName, fieldNameForError, errMessageAt) { | ||
function validateScalarTypeValue (context, currentQueryField, typeDefWithDirective, valueTypeDef, value, variableName, argName, fieldNameForError, errMessageAt, options = {}) { | ||
if (!typeDefWithDirective.astNode) { return } | ||
@@ -165,3 +165,3 @@ | ||
try { | ||
getConstraintValidateFn(st)(fieldNameForError, directiveArgumentMap, value) | ||
getConstraintValidateFn(st)(fieldNameForError, directiveArgumentMap, value, options) | ||
} catch (e) { | ||
@@ -182,7 +182,7 @@ let error | ||
function validateInputTypeValue (context, inputObjectTypeDef, argName, variableName, value, currentField, parentNames) { | ||
function validateInputTypeValue (context, inputObjectTypeDef, argName, variableName, value, currentField, parentNames, options = {}) { | ||
if (!inputObjectTypeDef.astNode) { return } | ||
// use new visitor to traverse input object structure | ||
const visitor = new InputObjectValidationVisitor(context, inputObjectTypeDef, argName, variableName, value, currentField, parentNames) | ||
const visitor = new InputObjectValidationVisitor(context, inputObjectTypeDef, argName, variableName, value, currentField, parentNames, options) | ||
@@ -238,7 +238,7 @@ visit(inputObjectTypeDef.astNode, visitor) | ||
if (isInputObjectType(valueTypeDefArray)) { | ||
validateInputTypeValue(context, valueTypeDefArray, argName, variableName, element, currentField, iFieldNameFullIndexed) | ||
validateInputTypeValue(context, valueTypeDefArray, argName, variableName, element, currentField, iFieldNameFullIndexed, this.options) | ||
} else if (hasNonListValidation) { | ||
const atMessage = ` at "${iFieldNameFullIndexed}"` | ||
validateScalarTypeValue(context, currentField, typeDefWithDirective, valueTypeDef, element, variableName, argName, iFieldNameFullIndexed, atMessage) | ||
validateScalarTypeValue(context, currentField, typeDefWithDirective, valueTypeDef, element, variableName, argName, iFieldNameFullIndexed, atMessage, this.options) | ||
} | ||
@@ -250,3 +250,3 @@ }) | ||
class InputObjectValidationVisitor { | ||
constructor (context, inputObjectTypeDef, argName, variableName, value, currentField, parentNames) { | ||
constructor (context, inputObjectTypeDef, argName, variableName, value, currentField, parentNames, options = {}) { | ||
this.context = context | ||
@@ -260,2 +260,3 @@ this.argName = argName | ||
this.parentNames = parentNames | ||
this.options = options | ||
@@ -284,3 +285,3 @@ this.InputValueDefinition = { | ||
validateInputTypeValue(this.context, valueTypeDef, this.argName, this.variableName, value, this.currentField, iFieldNameFull) | ||
validateInputTypeValue(this.context, valueTypeDef, this.argName, this.variableName, value, this.currentField, iFieldNameFull, this.options) | ||
} else if (isListType(valueTypeDef)) { | ||
@@ -292,5 +293,5 @@ validateArrayTypeValue(this.context, valueTypeDef, iFieldTypeDef, value, this.currentField, this.argName, this.variableName, iFieldNameFull) | ||
validateScalarTypeValue(this.context, this.currentField, iFieldTypeDef, valueTypeDef, value, this.variableName, this.argName, iFieldNameFull, ` at "${iFieldNameFull}"`) | ||
validateScalarTypeValue(this.context, this.currentField, iFieldTypeDef, valueTypeDef, value, this.variableName, this.argName, iFieldNameFull, ` at "${iFieldNameFull}"`, this.options) | ||
} | ||
} | ||
} |
{ | ||
"name": "graphql-constraint-directive", | ||
"version": "5.1.1", | ||
"version": "5.2.0", | ||
"description": "Validate GraphQL fields", | ||
@@ -38,3 +38,3 @@ "main": "index.js", | ||
"apollo-server-express": "3.12.0", | ||
"@apollo/server": "4.5.0", | ||
"@apollo/server": "4.7.4", | ||
"coveralls": "3.1.1", | ||
@@ -41,0 +41,0 @@ "express": "4.18.2", |
@@ -429,2 +429,28 @@ # graphql-constraint-directive | ||
#### Custom Format | ||
You can add your own custom formats by passing a `formats` object to the plugin options. See example below. | ||
```@constraint(format: "my-custom-format")``` | ||
```js | ||
const formats = { | ||
'my-custom-format': (value) => { | ||
if (value === 'foo') { | ||
return true | ||
} | ||
throw new GraphQLError('Value must be foo') | ||
} | ||
}; | ||
// Envelop | ||
createEnvelopQueryValidationPlugin({ formats }) | ||
// Apollo 3 Server | ||
createApolloQueryValidationPlugin({ formats }) | ||
// Apollo 4 Server | ||
createApollo4QueryValidationPlugin({ formats }) | ||
``` | ||
### Int/Float | ||
@@ -431,0 +457,0 @@ #### min |
const { GraphQLScalarType } = require('graphql') | ||
const { contains, isLength } = require('validator') | ||
const formats = require('./formats') | ||
const defaultFormats = require('./formats') | ||
const ValidationError = require('../lib/error') | ||
class ConstraintStringType extends GraphQLScalarType { | ||
constructor (fieldName, uniqueTypeName, type, args) { | ||
constructor (fieldName, uniqueTypeName, type, args, options = {}) { | ||
super({ | ||
@@ -13,3 +13,3 @@ name: uniqueTypeName, | ||
validate(fieldName, args, value) | ||
validate(fieldName, args, value, options) | ||
@@ -21,3 +21,3 @@ return value | ||
validate(fieldName, args, value) | ||
validate(fieldName, args, value, options) | ||
@@ -29,3 +29,3 @@ return type.parseValue(value) | ||
validate(fieldName, args, value) | ||
validate(fieldName, args, value, options) | ||
@@ -38,3 +38,3 @@ return value | ||
function validate (fieldName, args, value) { | ||
function validate (fieldName, args, value, options = {}) { | ||
if (args.minLength && !isLength(value, { min: args.minLength })) { | ||
@@ -82,2 +82,4 @@ throw new ValidationError(fieldName, | ||
if (args.format) { | ||
const pluginOptions = options.pluginOptions || {} | ||
const formats = { ...defaultFormats, ...(pluginOptions.formats || {}) } | ||
const formatter = formats[args.format] | ||
@@ -84,0 +86,0 @@ |
@@ -7,3 +7,3 @@ const express = require('express') | ||
module.exports = async function ({ typeDefs, formatError, resolvers, schemaCreatedCallback }) { | ||
module.exports = async function ({ typeDefs, formatError, resolvers, schemaCreatedCallback, pluginOptions = {} }) { | ||
let schema = makeExecutableSchema({ | ||
@@ -19,5 +19,3 @@ typeDefs: [constraintDirectiveTypeDefs, typeDefs], | ||
const plugins = [ | ||
createApolloQueryValidationPlugin({ | ||
schema | ||
}) | ||
createApolloQueryValidationPlugin({ schema }, pluginOptions) | ||
] | ||
@@ -24,0 +22,0 @@ |
@@ -10,3 +10,3 @@ const express = require('express') | ||
module.exports = async function ({ typeDefs, formatError, resolvers, schemaCreatedCallback }) { | ||
module.exports = async function ({ typeDefs, formatError, resolvers, schemaCreatedCallback, pluginOptions = {} }) { | ||
let schema = makeExecutableSchema({ | ||
@@ -22,3 +22,3 @@ typeDefs: [constraintDirectiveTypeDefs, typeDefs], | ||
const plugins = [ | ||
createApollo4QueryValidationPlugin() | ||
createApollo4QueryValidationPlugin(pluginOptions) | ||
] | ||
@@ -25,0 +25,0 @@ |
@@ -7,3 +7,3 @@ const express = require('express') | ||
module.exports = async function ({ typeDefs, formatError, resolvers, schemaCreatedCallback }) { | ||
module.exports = async function ({ typeDefs, formatError, resolvers, schemaCreatedCallback, pluginOptions = {} }) { | ||
let schema = makeExecutableSchema({ | ||
@@ -21,3 +21,3 @@ typeDefs: [constraintDirectiveTypeDefs, typeDefs], | ||
schema, | ||
plugins: [createEnvelopQueryValidationPlugin()], | ||
plugins: [createEnvelopQueryValidationPlugin(pluginOptions)], | ||
graphiql: false | ||
@@ -24,0 +24,0 @@ }) |
@@ -7,3 +7,3 @@ const express = require('express') | ||
module.exports = async function ({ typeDefs, formatError, resolvers, schemaCreatedCallback }) { | ||
module.exports = async function ({ typeDefs, formatError, resolvers, schemaCreatedCallback, pluginOptions = {} }) { | ||
let schema = makeExecutableSchema({ | ||
@@ -10,0 +10,0 @@ typeDefs: [constraintDirectiveTypeDefs, typeDefs], |
@@ -7,3 +7,3 @@ const express = require('express') | ||
module.exports = async function ({ typeDefs, formatError, resolvers, schemaCreatedCallback }) { | ||
module.exports = async function ({ typeDefs, formatError, resolvers, schemaCreatedCallback, pluginOptions = {} }) { | ||
let schema = makeExecutableSchema({ | ||
@@ -26,3 +26,4 @@ typeDefs: [constraintDirectiveTypeDefs, typeDefs], | ||
createQueryValidationRule({ | ||
variables | ||
variables, | ||
pluginOptions | ||
}) | ||
@@ -29,0 +30,0 @@ ] |
@@ -5,3 +5,5 @@ const { deepStrictEqual, strictEqual } = require('assert') | ||
module.exports.test = function (setup, implType) { | ||
const queryIntType = valueByImplType(implType, 'size_Int_max_3', 'Int') | ||
const queryIntMaxType = valueByImplType(implType, 'size_Int_max_3', 'Int') | ||
const queryIntMinType = valueByImplType(implType, 'size_Int_min_3', 'Int') | ||
const queryStringMinLengthType = valueByImplType(implType, 'search_String_minLength_3', 'String') | ||
@@ -24,3 +26,3 @@ describe('Variables', function () { | ||
const query = /* GraphQL */` | ||
query ($size: ${queryIntType} = 3) { | ||
query ($size: ${queryIntMaxType} = 3) { | ||
books(size: $size) { | ||
@@ -42,3 +44,3 @@ title | ||
const query = /* GraphQL */` | ||
query ($size: ${queryIntType} = 4) { | ||
query ($size: ${queryIntMaxType} = 4) { | ||
books(size: $size) { | ||
@@ -59,3 +61,101 @@ title | ||
}) | ||
describe('variable int falsy', function () { | ||
before(async function () { | ||
this.typeDefs = /* GraphQL */` | ||
type Query { | ||
books (size: Int @constraint(min: 3)): [Book] | ||
} | ||
type Book { | ||
title: String | ||
} | ||
` | ||
this.request = await setup({ typeDefs: this.typeDefs }) | ||
}) | ||
it('should pass', async function () { | ||
const query = /* GraphQL */` | ||
query ($size: ${queryIntMinType} = 3) { | ||
books(size: $size) { | ||
title | ||
} | ||
} | ||
` | ||
const { body, statusCode } = await this.request | ||
.post('/graphql') | ||
.set('Accept', 'application/json') | ||
.send({ query }) | ||
strictEqual(statusCode, 200) | ||
deepStrictEqual(body, { data: { books: null } }) | ||
}) | ||
it('should fail', async function () { | ||
const query = /* GraphQL */` | ||
query ($size: ${queryIntMinType} = 0) { | ||
books(size: $size) { | ||
title | ||
} | ||
} | ||
` | ||
const { body, statusCode } = await this.request | ||
.post('/graphql') | ||
.set('Accept', 'application/json') | ||
.send({ query }) | ||
isStatusCodeError(statusCode, implType) | ||
strictEqual(body.errors[0].message, | ||
valueByImplType(implType, 'Expected value of type "size_Int_min_3", found 0;', 'Variable "$size" got invalid value 0.') + ' Must be at least 3') | ||
}) | ||
}) | ||
describe('variable string falsy', function () { | ||
before(async function () { | ||
this.typeDefs = /* GraphQL */` | ||
type Query { | ||
books (search: String @constraint(minLength: 3)): [Book] | ||
} | ||
type Book { | ||
title: String | ||
} | ||
` | ||
this.request = await setup({ typeDefs: this.typeDefs }) | ||
}) | ||
it('should pass', async function () { | ||
const query = /* GraphQL */` | ||
query ($search: ${queryStringMinLengthType} = "test") { | ||
books(search: $search) { | ||
title | ||
} | ||
} | ||
` | ||
const { body, statusCode } = await this.request | ||
.post('/graphql') | ||
.set('Accept', 'application/json') | ||
.send({ query }) | ||
strictEqual(statusCode, 200) | ||
deepStrictEqual(body, { data: { books: null } }) | ||
}) | ||
it('should fail - despite being falsy', async function () { | ||
const query = /* GraphQL */` | ||
query ($search: ${queryStringMinLengthType} = "") { | ||
books(search: $search) { | ||
title | ||
} | ||
} | ||
` | ||
const { body, statusCode } = await this.request | ||
.post('/graphql') | ||
.set('Accept', 'application/json') | ||
.send({ query }) | ||
isStatusCodeError(statusCode, implType) | ||
strictEqual(body.errors[0].message, | ||
valueByImplType(implType, 'Expected value of type "search_String_minLength_3", found "";', 'Variable "$search" got invalid value "".') + ' Must be at least 3 characters in length') | ||
}) | ||
}) | ||
}) | ||
} |
Sorry, the diff of this file is too big to display
302936
7120
526