├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── src ├── index.js ├── utils.js └── validators.js ├── test └── test.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "standard" 4 | ], 5 | "env": { 6 | "jest": true 7 | } 8 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | .eslintrc 3 | .travis.yml 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | - 9 5 | - 10 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | ## [3.2.2](https://github.com/vsimko/node-graphql-constraint-lambda/compare/v3.2.1...v3.2.2) (2019-02-04) 7 | 8 | 9 | 10 | 11 | ## [3.2.1](https://github.com/vsimko/node-graphql-constraint-lambda/compare/v3.2.0...v3.2.1) (2019-01-31) 12 | 13 | 14 | 15 | 16 | # [3.2.0](https://github.com/vsimko/node-graphql-constraint-lambda/compare/v3.1.0...v3.2.0) (2018-11-23) 17 | 18 | 19 | ### Features 20 | 21 | * remove dependency on ramda ([471ae16](https://github.com/vsimko/node-graphql-constraint-lambda/commit/471ae16)) 22 | 23 | 24 | 25 | 26 | # [3.1.0](https://github.com/vsimko/node-graphql-constraint-lambda/compare/v3.0.0...v3.1.0) (2018-11-21) 27 | 28 | 29 | ### Features 30 | 31 | * add function createValidationCallback in src/validators.js ([1ea5747](https://github.com/vsimko/node-graphql-constraint-lambda/commit/1ea5747)) 32 | 33 | 34 | 35 | 36 | # [3.0.0](https://github.com/vsimko/node-graphql-constraint-lambda/compare/v2.0.3...v3.0.0) (2018-11-21) 37 | 38 | 39 | ### improvement 40 | 41 | * rename function `getSchemaDSL` into `getSDL` ([fe67663](https://github.com/vsimko/node-graphql-constraint-lambda/commit/fe67663)) 42 | 43 | 44 | ### BREAKING CHANGES 45 | 46 | * Clients should change to `getSDL()` in their code. 47 | 48 | 49 | 50 | 51 | ## [2.0.3](https://github.com/vsimko/node-graphql-constraint-lambda/compare/v2.0.1...v2.0.3) (2018-11-20) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * added exported functions from validators.js to index.js ([b48be3a](https://github.com/vsimko/node-graphql-constraint-lambda/commit/b48be3a)) 57 | * problem with empty string in maxLength and minLength ([039716d](https://github.com/vsimko/node-graphql-constraint-lambda/commit/039716d)) 58 | 59 | 60 | 61 | 62 | ## [2.0.2](https://github.com/vsimko/node-graphql-constraint-lambda/compare/v2.0.1...v2.0.2) (2018-11-20) 63 | 64 | 65 | ### Bug Fixes 66 | 67 | * problem with empty string in maxLength and minLength ([039716d](https://github.com/vsimko/node-graphql-constraint-lambda/commit/039716d)) 68 | 69 | 70 | 71 | 72 | ## [2.0.1](https://github.com/vsimko/node-graphql-constraint-lambda/compare/v2.0.0...v2.0.1) (2018-11-20) 73 | 74 | 75 | 76 | 77 | # [2.0.0](https://github.com/vsimko/node-graphql-constraint-lambda/compare/v1.0.0...v2.0.0) (2018-11-20) 78 | 79 | 80 | ### Features 81 | 82 | * validators and messages can be changed by user ([d910823](https://github.com/vsimko/node-graphql-constraint-lambda/commit/d910823)) 83 | * we now use callbacks for validation and error messages ([ec1c192](https://github.com/vsimko/node-graphql-constraint-lambda/commit/ec1c192)) 84 | 85 | 86 | ### BREAKING CHANGES 87 | 88 | * new functions are exported in `src/validators.js` 89 | * validators are refactored into separate file 90 | 91 | 92 | 93 | 94 | # [1.0.0](https://github.com/vsimko/node-graphql-constraint-lambda/compare/v0.3.0...v1.0.0) (2018-11-19) 95 | 96 | 97 | ### Chores 98 | 99 | * wrap constraint directive class in `module.exports` ([71a2ac9](https://github.com/vsimko/node-graphql-constraint-lambda/commit/71a2ac9)) 100 | 101 | 102 | ### BREAKING CHANGES 103 | 104 | * the constraint directive is now wrapped in 105 | `module.exports` as `{constraint}` instead of just returning the 106 | whole class. 107 | 108 | 109 | 110 | 111 | # [0.3.0](https://github.com/vsimko/node-graphql-constraint-lambda/compare/v0.2.2...v0.3.0) (2018-11-13) 112 | 113 | 114 | ### Features 115 | 116 | * add getSchemaDSL method ([c66917c](https://github.com/vsimko/node-graphql-constraint-lambda/commit/c66917c)) 117 | 118 | 119 | 120 | 121 | ## [0.2.2](https://github.com/vsimko/node-graphql-constraint-lambda/compare/v0.2.1...v0.2.2) (2018-11-13) 122 | 123 | 124 | 125 | 126 | ## 0.2.1 (2018-11-13) 127 | 128 | 129 | ### Bug Fixes 130 | 131 | * primary entry point `main` in packate.json ([97c872b](https://github.com/vsimko/node-graphql-constraint-lambda/commit/97c872b)) 132 | 133 | 134 | 135 | 136 | ## 0.1.1 (2018-06-20) 137 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Viliam Simko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-graphql-constraint-lambda 2 | 3 | [![Build Status](https://travis-ci.org/vsimko/node-graphql-constraint-lambda.svg?branch=master)](https://travis-ci.org/vsimko/node-graphql-constraint-lambda) 4 | [![npm module](https://badge.fury.io/js/node-graphql-constraint-lambda.svg)](https://www.npmjs.org/package/node-graphql-constraint-lambda) 5 | [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) 6 | 7 | GraphQL constraint directive written in functional programming style. 8 | This directive provides declarative validation of GraphQL arguments. 9 | 10 | ## Install 11 | 12 | ```sh 13 | yarn add node-graphql-constraint-lambda 14 | # or 15 | npm install node-graphql-constraint-lambda 16 | ``` 17 | 18 | ## Usage 19 | 20 | Example GraphQL Schema: 21 | ```graphql 22 | type Query { 23 | createUser ( 24 | name: String! @constraint(minLength: 5, maxLength: 40) 25 | emailAddr: String @constraint(format: "email") 26 | otherEmailAddr: String @constraint(format: "email", differsFrom: "emailAddr") 27 | age: Int @constraint(min: 18) 28 | ): User 29 | } 30 | ``` 31 | 32 | Use the constraint from your code: 33 | ```js 34 | // when using es6 modules 35 | import { constraint } from 'node-graphql-constraint-lambda' 36 | 37 | // when using commonjs 38 | const { constraint } = require('node-graphql-constraint-lambda') 39 | 40 | // ... initialize your typeDefs and resolvers here ... 41 | 42 | const server = new GraphQLServer({ 43 | typeDefs, 44 | resolvers, 45 | schemaDirectives: { 46 | constraint 47 | } 48 | // ... additional graphql server config 49 | }) 50 | // ... start your server 51 | ``` 52 | 53 | You may need to declare the directive in the schema: 54 | 55 | ```graphql 56 | directive @constraint( 57 | minLength: Int 58 | maxLength: Int 59 | startsWith: String 60 | endsWith: String 61 | contains: String 62 | notContains: String 63 | pattern: String 64 | format: String 65 | differsFrom: String 66 | min: Float 67 | max: Float 68 | exclusiveMin: Float 69 | exclusiveMax: Float 70 | notEqual: Float 71 | ) on ARGUMENT_DEFINITION 72 | ``` 73 | 74 | ## API 75 | 76 | ### Available constraints 77 | See `stringValidators`, `numericValidators` and `formatValidator` mapping in [src/validators.js]. 78 | 79 | ### Available formats 80 | We use some functions from the `validator` package. 81 | See `format2fun` mapping in [src/validators.js]. 82 | 83 | [src/validators.js]: https://github.com/vsimko/node-graphql-constraint-lambda/blob/master/src/validators.js 84 | 85 | 86 | ## Customization 87 | 88 | ### Default behavoir 89 | 90 | The following code shows how the constraint directive is configured with default behaviour: 91 | ```js 92 | // this code: 93 | import { constraint } from 'node-graphql-constraint-lambda' 94 | 95 | // is equivalent to: 96 | import { 97 | prepareConstraintDirective, 98 | defaultValidationCallback, 99 | defaultErrorMessageCallback 100 | } from 'node-graphql-constraint-lambda' 101 | 102 | const constraint = prepareConstraintDirective( 103 | defaultValidationCallback, defaultErrorMessageCallback ) 104 | 105 | ``` 106 | 107 | ### Custom error messages 108 | 109 | Error messages are generated using a **callback** function that by default 110 | shows a generic error message. It is possible to change this behavior 111 | by implementing a custom callback similar to this one: 112 | ```js 113 | const myErrorMessageCallback = ({ argName, cName, cVal, data }) => 114 | `Error at field ${argName} in constraint ${cName}:${cVal}, data=${data}` 115 | ``` 116 | 117 | You might also want to customize certain messages and to keep the default callback as a fallback for all other messages: 118 | ```js 119 | const myErrorMessageCallback = input => { 120 | const { argName, cName, cVal, data } = input 121 | if (/* decide whether to show custom message */) 122 | return "custom error message" // based on input 123 | else 124 | return defaultErrorMessageCallback(input) 125 | } 126 | 127 | const constraint = prepareConstraintDirective( 128 | defaultValidationCallback, myErrorMessageCallback ) 129 | ``` 130 | 131 | ### Custom validation functions 132 | 133 | Also the validation functions are implemented through a callback function. 134 | The constraint directive comes with a set of useful defaults but if you 135 | want to add your own validator, it can be done as follows: 136 | ```js 137 | import { 138 | createValidationCallback, 139 | prepareConstraintDirective, 140 | defaultValidators } from 'node-graphql-constraint-lambda' 141 | 142 | // you can merge default validators with your own validator 143 | const myValidators = { 144 | ...defaultValidators, 145 | 146 | // your custom validator comes here 147 | constraintName: constrintValue => dataToValidate => true/false 148 | 149 | // Example: numerical pin codes of certain size `@constraint(pin:4)` 150 | pin: size => code => length(code) === size && match(/[0-9]+/)(code) 151 | } 152 | 153 | const myValidationCallback = createValidationCallback(myValidators) 154 | 155 | // now you can create the constraint class 156 | const constraint = prepareConstraintDirective( 157 | myValidationCallback, defaultErrorMessageCallback ) 158 | ``` 159 | 160 | There is a special `format` validator that supports the following: 161 | - `@constraint(format: "email")` 162 | - `@constraint(format: "base64")` 163 | - `@constraint(format: "date")` 164 | - `@constraint(format: "ipv4")` 165 | - `@constraint(format: "ipv6")` 166 | - `@constraint(format: "url")` 167 | - `@constraint(format: "uuid")` 168 | - `@constraint(format: "futuredate")` 169 | - `@constraint(format: "pastdate")` 170 | - `@constraint(format: "creditcard")` 171 | 172 | Let's say we want to extend it to support `format: "uppercase"` format that checks whether all characters are just uppercase letters: 173 | ```js 174 | import { 175 | formatValidator, 176 | numericValidators, 177 | format2fun, 178 | stringValidators } from 'node-graphql-constraint-lambda' 179 | 180 | const customFormat2Fun = { 181 | ...format2fun, 182 | 183 | uppercase: x => match(/[A-Z]*/)(x) 184 | // we could have omitted the `x` parameter due to currying in the 185 | // `match` function from ramda 186 | } 187 | 188 | const validators = { 189 | ...formatValidator(customFormat2Fun), 190 | ...numericValidators, 191 | ...stringValidators 192 | } 193 | 194 | // now you can create the constraint class 195 | const constraint = prepareConstraintDirective( 196 | createValidationCallback(validators), 197 | defaultErrorMessageCallback 198 | ) 199 | 200 | ``` 201 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-graphql-constraint-lambda", 3 | "version": "3.2.2", 4 | "description": "GraphQL 'constraint' directive written in functional programming style", 5 | "bugs": { 6 | "url": "https://github.com/vsimko/node-graphql-constraint-lambda/issues" 7 | }, 8 | "keywords": [ 9 | "graphql", 10 | "validate", 11 | "validation", 12 | "validator", 13 | "directive", 14 | "constraint", 15 | "checking", 16 | "schema" 17 | ], 18 | "main": "src/index.js", 19 | "repository": "https://github.com/vsimko/node-graphql-constraint-lambda.git", 20 | "author": "Viliam Simko ", 21 | "license": "MIT", 22 | "dependencies": { 23 | "graphql": "^14.0.2", 24 | "graphql-tools": "^4.0.3", 25 | "validator": "^13.7.0" 26 | }, 27 | "peerDependencies": { 28 | "graphql": "^0.13.0" 29 | }, 30 | "engines": { 31 | "node": ">=8.0.0" 32 | }, 33 | "devDependencies": { 34 | "eslint": "^4.19.1", 35 | "eslint-config-standard": "^11.0.0", 36 | "eslint-plugin-import": "^2.12.0", 37 | "eslint-plugin-node": "^6.0.1", 38 | "eslint-plugin-promise": "^3.8.0", 39 | "eslint-plugin-standard": "^3.1.0", 40 | "jest": "^23.1.0", 41 | "jest-cli": "^23.1.0", 42 | "standard-version": "^8.0.1" 43 | }, 44 | "scripts": { 45 | "git-cred": "git config credential.helper store", 46 | "lint": "eslint .", 47 | "test": "jest", 48 | "release": "standard-version", 49 | "release:push": "git push --follow-tags origin master", 50 | "release:npm": "yarn publish" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { mapObjIndexed, compose, map, filter, values } = require('./utils') 2 | const { SchemaDirectiveVisitor } = require('graphql-tools') 3 | const { 4 | defaultValidationCallback, 5 | defaultErrorMessageCallback 6 | } = require('./validators') 7 | 8 | const { 9 | DirectiveLocation, 10 | GraphQLDirective, 11 | GraphQLInt, 12 | GraphQLFloat, 13 | GraphQLString, 14 | GraphQLSchema, 15 | printSchema 16 | } = require('graphql') 17 | 18 | const prepareConstraintDirective = (validationCallback, errorMessageCallback) => 19 | class extends SchemaDirectiveVisitor { 20 | /** 21 | * When using e.g. graphql-yoga, we need to include schema of this directive 22 | * into our SDL, otherwise the graphql schema validator would report errors. 23 | */ 24 | static getSDL () { 25 | const constraintDirective = this.getDirectiveDeclaration('constraint') 26 | const schema = new GraphQLSchema({ 27 | directives: [constraintDirective] 28 | }) 29 | return printSchema(schema) 30 | } 31 | 32 | static getDirectiveDeclaration (directiveName, schema) { 33 | return new GraphQLDirective({ 34 | name: directiveName, 35 | locations: [DirectiveLocation.ARGUMENT_DEFINITION], 36 | args: { 37 | /* Strings */ 38 | minLength: { type: GraphQLInt }, 39 | maxLength: { type: GraphQLInt }, 40 | startsWith: { type: GraphQLString }, 41 | endsWith: { type: GraphQLString }, 42 | contains: { type: GraphQLString }, 43 | notContains: { type: GraphQLString }, 44 | pattern: { type: GraphQLString }, 45 | format: { type: GraphQLString }, 46 | differsFrom: { type: GraphQLString }, 47 | 48 | /* Numbers (Int/Float) */ 49 | min: { type: GraphQLFloat }, 50 | max: { type: GraphQLFloat }, 51 | exclusiveMin: { type: GraphQLFloat }, 52 | exclusiveMax: { type: GraphQLFloat }, 53 | notEqual: { type: GraphQLFloat } 54 | } 55 | }) 56 | } 57 | 58 | /** 59 | * @param {GraphQLArgument} argument 60 | * @param {{field:GraphQLField, objectType:GraphQLObjectType | GraphQLInterfaceType}} details 61 | */ 62 | visitArgumentDefinition (argument, details) { 63 | // preparing the resolver 64 | const originalResolver = details.field.resolve 65 | details.field.resolve = async (...resolveArgs) => { 66 | const argName = argument.name 67 | const args = resolveArgs[1] // (parent, args, context, info) 68 | const valueToValidate = args[argName] 69 | 70 | const validate = compose( 71 | map(errorMessageCallback), 72 | filter(x => !x.result), // keep only failed validation results 73 | values, 74 | mapObjIndexed((cVal, cName) => 75 | validationCallback({ argName, cName, cVal, data: valueToValidate }) 76 | ) 77 | ) 78 | 79 | const errors = validate(this.args) 80 | if (errors && errors.length > 0) throw new Error(errors) 81 | 82 | return originalResolver.apply(this, resolveArgs) 83 | } 84 | } 85 | } 86 | 87 | module.exports = { 88 | constraint: prepareConstraintDirective( 89 | defaultValidationCallback, 90 | defaultErrorMessageCallback 91 | ), 92 | prepareConstraintDirective, 93 | ...require('./validators') 94 | } 95 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | // api same as ramda 2 | const map = fn => list => list.map(fn) 3 | const filter = fn => list => list.filter(fn) 4 | const values = obj => Object.keys(obj).map(key => obj[key]) 5 | const length = strOrArray => (strOrArray != null ? strOrArray.length : 0) 6 | const isString = x => x != null && x.constructor === String 7 | 8 | const compose = (...fnlist) => data => 9 | [...fnlist, data].reduceRight((prev, fn) => fn(prev)) 10 | 11 | const mapObjIndexed = fn => obj => { 12 | const acc = {} 13 | Object.keys(obj).forEach(key => (acc[key] = fn(obj[key], key, obj))) 14 | return acc 15 | } 16 | 17 | module.exports = { 18 | mapObjIndexed, 19 | compose, 20 | map, 21 | filter, 22 | values, 23 | isString, 24 | length 25 | } 26 | -------------------------------------------------------------------------------- /src/validators.js: -------------------------------------------------------------------------------- 1 | const { 2 | isAfter, 3 | isBefore, 4 | isCreditCard, 5 | isUUID, 6 | isURL, 7 | isEmail, 8 | isBase64, 9 | isRFC3339, 10 | isIP, 11 | matches, 12 | contains 13 | } = require('validator') 14 | 15 | const { length } = require('./utils') 16 | 17 | // default formats, you can extend this in your code 18 | const format2fun = { 19 | email: isEmail, 20 | base64: isBase64, 21 | date: isRFC3339, 22 | ipv4: x => isIP(x, 4), 23 | ipv6: x => isIP(x, 6), 24 | url: isURL, 25 | uuid: isUUID, 26 | futuredate: isAfter, 27 | pastdate: isBefore, 28 | creditcard: isCreditCard 29 | } 30 | 31 | // needs to be configured using format2fun 32 | const formatValidator = format2fun => ({ 33 | format: fmtName => format2fun[fmtName] 34 | }) 35 | 36 | const stringValidators = { 37 | minLength: min => strOrArray => length(strOrArray) >= min, 38 | maxLength: max => strOrArray => length(strOrArray) <= max, 39 | startsWith: prefix => str => str != null && str.startsWith(prefix), 40 | endsWith: suffix => str => str != null && str.endsWith(suffix), 41 | contains: el => str => contains(str, el), 42 | notContains: el => str => !contains(str, el), 43 | pattern: pat => str => matches(str, pat), 44 | differsFrom: argName => (value, queryArgs) => value !== queryArgs[argName] 45 | } 46 | 47 | const numericValidators = { 48 | min: min => x => x >= min, 49 | max: max => x => x <= max, 50 | exclusiveMin: min => x => x > min, 51 | exclusiveMax: max => x => x < max, 52 | notEqual: neq => x => x !== neq 53 | } 54 | 55 | const defaultErrorMessageCallback = ({ argName, cName, cVal, data }) => 56 | `Constraint '${cName}:${cVal}' violated in field '${argName}'` 57 | 58 | const defaultValidators = { 59 | ...formatValidator(format2fun), 60 | ...numericValidators, 61 | ...stringValidators 62 | } 63 | 64 | const createValidationCallback = validators => input => ({ 65 | ...input, 66 | result: validators[input.cName](input.cVal)(input.data) 67 | }) 68 | 69 | module.exports = { 70 | defaultValidators, 71 | defaultValidationCallback: createValidationCallback(defaultValidators), 72 | defaultErrorMessageCallback, 73 | createValidationCallback, 74 | stringValidators, 75 | numericValidators, 76 | formatValidator, 77 | format2fun 78 | } 79 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const { makeExecutableSchema } = require('graphql-tools') 2 | const { GraphQLSchema } = require('graphql') 3 | 4 | const { constraint, prepareConstraintDirective } = require('../src/index') 5 | const { 6 | format2fun, 7 | defaultValidators, 8 | defaultValidationCallback, 9 | defaultErrorMessageCallback 10 | } = require('../src/validators') 11 | 12 | describe('constraint directive usage', () => { 13 | it('customization of messages', async () => { 14 | // this is just an example showing that it works, it can be implemented better 15 | const customizedMessageCallback = input => { 16 | if (input.argName === 'primaryEmail') return 'Wrong primary email' 17 | else return defaultErrorMessageCallback(input) 18 | } 19 | 20 | const MyConstraintClass = prepareConstraintDirective( 21 | defaultValidationCallback, 22 | customizedMessageCallback 23 | ) 24 | 25 | const cInst = new MyConstraintClass({ args: { format: 'email' } }) 26 | 27 | const details = { field: {} } 28 | 29 | cInst.visitArgumentDefinition({ name: 'primaryEmail' }, details) 30 | await expect( 31 | details.field.resolve(null, { primaryEmail: 'some_wrong_email' }) 32 | ).rejects.toEqual(Error([`Wrong primary email`])) 33 | 34 | cInst.visitArgumentDefinition({ name: 'secondaryEmail' }, details) 35 | await expect( 36 | details.field.resolve(null, { secondaryEmail: 'some_wrong_email' }) 37 | ).rejects.toEqual( 38 | Error([`Constraint 'format:email' violated in field 'secondaryEmail'`]) 39 | ) 40 | }) 41 | 42 | it('constraint violations should be reported', async () => { 43 | // eslint-disable-next-line new-cap 44 | const cInst = new constraint({ 45 | args: { 46 | maxLength: 5, 47 | format: 'email', 48 | minLength: 100 49 | } 50 | }) 51 | 52 | const details = { field: {} } 53 | cInst.visitArgumentDefinition({ name: 'primaryEmail' }, details) 54 | 55 | await expect( 56 | details.field.resolve(null, { primaryEmail: 'test@test.com' }) 57 | ).rejects.toEqual( 58 | Error([ 59 | `Constraint 'maxLength:5' violated in field 'primaryEmail'`, 60 | `Constraint 'minLength:100' violated in field 'primaryEmail'` 61 | ]) 62 | ) 63 | }) 64 | 65 | describe('format2fun', () => { 66 | it('email', () => { 67 | expect(format2fun.email('test@test.com')).toEqual(true) 68 | expect(format2fun.email('testtest.com')).toEqual(false) 69 | }) 70 | }) 71 | 72 | describe('defaultValidators', () => { 73 | it('contains', () => { 74 | const { contains } = defaultValidators 75 | expect(contains('@')('test@test.com')).toEqual(true) 76 | expect(contains('@')('testtest.com')).toEqual(false) 77 | }) 78 | it('startsWith', () => { 79 | const { startsWith } = defaultValidators 80 | expect(startsWith('b')('ab')).toEqual(false) 81 | expect(startsWith('a')('ab')).toEqual(true) 82 | expect(startsWith('')('')).toEqual(true) 83 | expect(startsWith('')('a')).toEqual(true) 84 | expect(startsWith('a')('')).toEqual(false) 85 | }) 86 | 87 | it('endsWith', () => { 88 | const { endsWith } = defaultValidators 89 | expect(endsWith('b')('ab')).toEqual(true) 90 | expect(endsWith('a')('ab')).toEqual(false) 91 | expect(endsWith('')('')).toEqual(true) 92 | expect(endsWith('')('a')).toEqual(true) 93 | expect(endsWith('a')('')).toEqual(false) 94 | }) 95 | 96 | it('minLength', () => { 97 | const { minLength } = defaultValidators 98 | expect(minLength(10)('ab')).toEqual(false) 99 | expect(minLength(2)('ab')).toEqual(true) 100 | expect(minLength(0)('ab')).toEqual(true) 101 | expect(minLength(0)('')).toEqual(true) 102 | expect(minLength(1)('')).toEqual(false) 103 | }) 104 | }) 105 | }) 106 | 107 | describe('constraint directive class', () => { 108 | it('should provide its own graphql SDL', () => { 109 | const sdl = constraint.getSDL() 110 | expect(sdl).toMatch('directive @constraint') 111 | }) 112 | 113 | it('should work when used properly in other graphql schema', () => { 114 | const withOtherSchema = ` 115 | ${constraint.getSDL()} 116 | type Mutation { 117 | signup( 118 | name: String @constraint(maxLength:20) 119 | ): Boolean 120 | } 121 | ` 122 | const schema = makeExecutableSchema({ 123 | typeDefs: withOtherSchema, 124 | schemaDirectives: { constraint } 125 | }) 126 | 127 | expect(schema).toBeInstanceOf(GraphQLSchema) 128 | }) 129 | 130 | it('should NOT work when using unknown parameter', () => { 131 | const withOtherSchema = ` 132 | ${constraint.getSDL()} 133 | type Mutation { 134 | signup( 135 | name: String @constraint(DUMMY:123) 136 | ): Boolean 137 | } 138 | ` 139 | expect(() => 140 | makeExecutableSchema({ 141 | typeDefs: withOtherSchema, 142 | schemaDirectives: { constraint } 143 | }) 144 | ).toThrowError('Unknown argument "DUMMY" on directive "@constraint"') 145 | }) 146 | }) 147 | --------------------------------------------------------------------------------