GraphQL 'constraint' directive written in functional programming style
GraphQL constraint directive written in functional programming style.
This directive provides declarative validation of GraphQL arguments.
yarn add node-graphql-constraint-lambda
# or
npm install node-graphql-constraint-lambda
Example GraphQL Schema:
type Query {
createUser (
name: String! @constraint(minLength: 5, maxLength: 40)
emailAddr: String @constraint(format: "email")
otherEmailAddr: String @constraint(format: "email", differsFrom: "emailAddr")
age: Int @constraint(min: 18)
): User
}
Use the constraint from your code:
// when using es6 modules
import { constraint } from 'node-graphql-constraint-lambda'
// when using commonjs
const { constraint } = require('node-graphql-constraint-lambda')
// ... initialize your typeDefs and resolvers here ...
const server = new GraphQLServer({
typeDefs,
resolvers,
schemaDirectives: {
constraint
}
// ... additional graphql server config
})
// ... start your server
You may need to declare the directive in the schema:
directive @constraint(
minLength: Int
maxLength: Int
startsWith: String
endsWith: String
contains: String
notContains: String
pattern: String
format: String
differsFrom: String
min: Float
max: Float
exclusiveMin: Float
exclusiveMax: Float
notEqual: Float
) on ARGUMENT_DEFINITION
See stringValidators
, numericValidators
and formatValidator
mapping in src/validators.js.
We use some functions from the validator
package.
See format2fun
mapping in src/validators.js.
The following code shows how the constraint directive is configured with default behaviour:
// this code:
import { constraint } from 'node-graphql-constraint-lambda'
// is equivalent to:
import {
prepareConstraintDirective,
defaultValidationCallback,
defaultErrorMessageCallback
} from 'node-graphql-constraint-lambda'
const constraint = prepareConstraintDirective(
defaultValidationCallback, defaultErrorMessageCallback )
Error messages are generated using a callback function that by default
shows a generic error message. It is possible to change this behavior
by implementing a custom callback similar to this one:
const myErrorMessageCallback = ({ argName, cName, cVal, data }) =>
`Error at field ${argName} in constraint ${cName}:${cVal}, data=${data}`
You might also want to customize certain messages and to keep the default callback as a fallback for all other messages:
const myErrorMessageCallback = input => {
const { argName, cName, cVal, data } = input
if (/* decide whether to show custom message */)
return "custom error message" // based on input
else
return defaultErrorMessageCallback(input)
}
const constraint = prepareConstraintDirective(
defaultValidationCallback, myErrorMessageCallback )
Also the validation functions are implemented through a callback function.
The constraint directive comes with a set of useful defaults but if you
want to add your own validator, it can be done as follows:
import {
createValidationCallback,
prepareConstraintDirective,
defaultValidators } from 'node-graphql-constraint-lambda'
// you can merge default validators with your own validator
const myValidators = {
...defaultValidators,
// your custom validator comes here
constraintName: constrintValue => dataToValidate => true/false
// Example: numerical pin codes of certain size `@constraint(pin:4)`
pin: size => code => length(code) === size && match(/[0-9]+/)(code)
}
const myValidationCallback = createValidationCallback(myValidators)
// now you can create the constraint class
const constraint = prepareConstraintDirective(
myValidationCallback, defaultErrorMessageCallback )
There is a special format
validator that supports the following:
@constraint(format: "email")
@constraint(format: "base64")
@constraint(format: "date")
@constraint(format: "ipv4")
@constraint(format: "ipv6")
@constraint(format: "url")
@constraint(format: "uuid")
@constraint(format: "futuredate")
@constraint(format: "pastdate")
@constraint(format: "creditcard")
Let’s say we want to extend it to support format: "uppercase"
format that checks whether all characters are just uppercase letters:
import {
formatValidator,
numericValidators,
format2fun,
stringValidators } from 'node-graphql-constraint-lambda'
const customFormat2Fun = {
...format2fun,
uppercase: x => match(/[A-Z]*/)(x)
// we could have omitted the `x` parameter due to currying in the
// `match` function from ramda
}
const validators = {
...formatValidator(customFormat2Fun),
...numericValidators,
...stringValidators
}
// now you can create the constraint class
const constraint = prepareConstraintDirective(
createValidationCallback(validators),
defaultErrorMessageCallback
)