├── .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 | [](https://travis-ci.org/vsimko/node-graphql-constraint-lambda)
4 | [](https://www.npmjs.org/package/node-graphql-constraint-lambda)
5 | [](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 |
--------------------------------------------------------------------------------