├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── src └── index.js └── test ├── index.js ├── mocha.opts ├── setup.js └── utils.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | .idea 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .babelrc 2 | .eslintrc 3 | .gitignore 4 | .npmignore 5 | .nyc_output 6 | .travis.yml 7 | coverage 8 | src 9 | test 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "9" 4 | - "8" 5 | - "7" 6 | - "6" 7 | - "5" 8 | - "4" 9 | after_success: npm run coverage 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Liron Goldenberg 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 | # graphql-custom-directive 2 | [![Build Status](https://travis-ci.org/lirown/graphql-custom-directive.svg?branch=master)](https://travis-ci.org/lirown/graphql-custom-directive) 3 | [![Coverage Status](https://coveralls.io/repos/github/lirown/graphql-custom-directive/badge.svg?branch=master)](https://coveralls.io/github/lirown/graphql-custom-directive?branch=master) 4 | [![npm version](https://badge.fury.io/js/graphql-custom-directive.svg)](https://badge.fury.io/js/graphql-custom-directive) 5 | [![Dependency Status](https://david-dm.org/lirown/graphql-custom-directive.svg)](https://david-dm.org/lirown/graphql-custom-directive) 6 | [![Known Vulnerabilities](https://snyk.io/test/github/lirown/graphql-custom-directive/badge.svg)](https://snyk.io/test/github/lirown/graphql-custom-directive) 7 | [![License](http://img.shields.io/:license-mit-blue.svg)](http://doge.mit-license.org) 8 | 9 | A custom directive for GraphQL with the ability to hook the query execution. 10 | 11 | ### Install 12 | ``` 13 | npm install --save graphql-custom-directive 14 | ``` 15 | 16 | ### Usage 17 | ```javascript 18 | import { 19 | GraphQLString, 20 | GraphQLSchema, 21 | GraphQLObjectType, 22 | graphql 23 | } from 'graphql'; 24 | 25 | import { 26 | DirectiveLocation 27 | } from 'graphql/type/directives'; 28 | 29 | import { 30 | GraphQLCustomDirective, 31 | applySchemaCustomDirectives 32 | } from 'graphql-custom-directive'; 33 | 34 | // Define a directive that upper case the result 35 | 36 | const GraphQLCustomDuplicateDirective = new GraphQLCustomDirective({ 37 | name: 'toUpperCase', 38 | description: 39 | 'change the case of a string to uppercase', 40 | locations: [ 41 | DirectiveLocation.FIELD 42 | ], 43 | resolve(resolve) { 44 | return resolve() 45 | .then(result => result.toUpperCase()); 46 | } 47 | }); 48 | 49 | const schema = new GraphQLSchema({ 50 | directives: [ 51 | GraphQLCustomDuplicateDirective 52 | ], 53 | query: new GraphQLObjectType({ 54 | name: 'Query', 55 | fields: { 56 | value: { 57 | type: GraphQLString, 58 | resolve: () => 'test' 59 | } 60 | } 61 | }) 62 | }); 63 | 64 | applySchemaCustomDirectives(schema); 65 | 66 | graphql(schema, `{ value @toUpperCase }`) 67 | .then(({ result, errors }) => { 68 | console.log(result); 69 | // will print { value: "TEST" } 70 | }); 71 | ``` 72 | 73 | ### Options 74 | ```javascript 75 | GraphQLCustomDirective({ 76 | // name to be used in placing the directive (e.g @duplicate) 77 | // [*required] 78 | name: String = 'duplicate', 79 | 80 | // explain the directive usage 81 | description: String = 'duplicate the string sperating them with space', 82 | 83 | // areas in the query you can place the directive 84 | // [*required] 85 | locations: [String] = [ DirectiveLocation.FIELD ], 86 | 87 | // object of passed variables from directive to the resolve method 88 | args: Object = { by: { type: GraphQLInt, description: "foo bar" } } ), 89 | 90 | // method that hooks the execution and transforms the input to a new output 91 | // arguments: 92 | // 1. resolve - a field promise that will result in the field's value 93 | // (either the raw field or the previous directive output). 94 | // 2. source - a parent object of execution field result. 95 | // 3. args - a object of directive arguments defined in query exectution. 96 | // 4. context - a value to pass as the context to the graphql() function. 97 | // 5. info - a collection of information about the current execution state. 98 | resolve: Function = (resolve, source, args, context, info) => 99 | { return resolve.then(input => input); } 100 | }) 101 | ``` 102 | 103 | ### Examples 104 | 105 | This show the ability to configure directive on the query side or in the schema side 106 | 107 | ```javascript 108 | import { 109 | GraphQLString, 110 | GraphQLNotNull, 111 | GraphQLSchema, 112 | GraphQLObjectType, 113 | graphql 114 | } from 'graphql'; 115 | 116 | import { 117 | DirectiveLocation 118 | } from 'graphql/type/directives'; 119 | 120 | import { 121 | GraphQLCustomDirective, 122 | applySchemaCustomDirectives 123 | } from 'graphql-custom-directive'; 124 | 125 | // Define a directive that duplicates the input 126 | 127 | const GraphQLCustomDuplicateDirective = new GraphQLCustomDirective({ 128 | name: 'duplicate', 129 | description: 130 | 'duplicate the string sperating them with space', 131 | locations: [ 132 | DirectiveLocation.FIELD 133 | ], 134 | args: { 135 | by: { 136 | type: new GraphQLNotNull(GraphQLInt), 137 | description: 'the times to duplicate the string' 138 | } 139 | }, 140 | resolve(resolve, source, { by }, context, info) { 141 | return resolve().then(result => { 142 | let times = []; 143 | 144 | for (let i = 0; i < by; i++) { 145 | times.push(result); 146 | } 147 | 148 | return times.join(' '); 149 | }); 150 | } 151 | }); 152 | 153 | // Use directive in a query 154 | 155 | const schema = new GraphQLSchema({ 156 | directives: [ 157 | GraphQLCustomDuplicateDirective 158 | ], 159 | query: new GraphQLObjectType({ 160 | name: 'Query', 161 | fields: { 162 | input: { 163 | type: GraphQLString, 164 | args: { 165 | value: { 166 | type: GraphQLString 167 | } 168 | }, 169 | resolve: (source, {value}) => value 170 | } 171 | } 172 | }) 173 | }); 174 | 175 | applySchemaCustomDirectives(schema); 176 | 177 | graphql(schema, `{ input(value: "test") @duplicate(by:2) }`) 178 | .then(({ result, errors }) => { 179 | console.log(result); 180 | // will print { input: "test test" } 181 | }); 182 | 183 | // Or use the directive in a schema 184 | 185 | const schema2 = new GraphQLSchema({ 186 | directives: [ 187 | GraphQLCustomDuplicateDirective 188 | ], 189 | query: new GraphQLObjectType({ 190 | name: 'Query', 191 | fields: { 192 | input: { 193 | type: GraphQLString, 194 | args: { 195 | value: { 196 | type: GraphQLString 197 | } 198 | }, 199 | directives: { 200 | duplicate: { // directive name 201 | by: 2 // directive args 202 | } 203 | }, 204 | resolve: (source, {value}) => value 205 | } 206 | } 207 | }) 208 | }); 209 | 210 | applySchemaCustomDirectives(schema2); 211 | 212 | graphql(schema2, `{ input(value: "test") }`) 213 | .then(({ result, errors }) => { 214 | console.log(result); 215 | // will print { input: "test test" } 216 | }); 217 | 218 | ``` 219 | ### License 220 | ``` 221 | The MIT License (MIT) 222 | 223 | Copyright (c) 2016 Lirown 224 | 225 | Permission is hereby granted, free of charge, to any person obtaining a copy 226 | of this software and associated documentation files (the "Software"), to deal 227 | in the Software without restriction, including without limitation the rights 228 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 229 | copies of the Software, and to permit persons to whom the Software is 230 | furnished to do so, subject to the following conditions: 231 | 232 | The above copyright notice and this permission notice shall be included in all 233 | copies or substantial portions of the Software. 234 | 235 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 236 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 237 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 238 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 239 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 240 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 241 | SOFTWARE. 242 | ``` 243 | 244 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-custom-directive", 3 | "version": "0.2.0", 4 | "description": "A custom directive for GraphQL which hooks the query or schema execution", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "babel src --out-dir dist", 8 | "build-watch": "babel src --watch --out-dir dist", 9 | "clear": "rm -rf ./dist ./coverage ./.nyc_output", 10 | "coverage": "nyc npm test && nyc report --reporter=text-lcov | coveralls", 11 | "nyc": "nyc npm test && nyc report --reporter=lcov", 12 | "retest": "npm run clear && npm test", 13 | "pretest": "npm run build", 14 | "start": "npm test", 15 | "test": "mocha", 16 | "test-watch": "mocha --watch", 17 | "update-D": "npm install --save-dev babel-cli@latest babel-preset-es2015@latest babel-preset-stage-0@latest babel-register@latest chai@latest chai-as-promised@latest coveralls@latest graphql@latest mocha@latest nyc@latest", 18 | "watch": "npm run build-watch & npm run test-watch" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/lirown/graphql-custom-directive.git" 23 | }, 24 | "keywords": [ 25 | "graphql", 26 | "directive" 27 | ], 28 | "author": "Liron Goldenberg", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/lirown/graphql-custom-directive/issues" 32 | }, 33 | "homepage": "https://github.com/lirown/graphql-custom-directive#readme", 34 | "peerDependencies": { 35 | "graphql": "*" 36 | }, 37 | "dependencies": {}, 38 | "devDependencies": { 39 | "babel-cli": "^6.16.0", 40 | "babel-preset-es2015": "^6.16.0", 41 | "babel-preset-stage-0": "^6.16.0", 42 | "babel-register": "^6.16.3", 43 | "chai": "^3.5.0", 44 | "chai-as-promised": "^6.0.0", 45 | "coveralls": "^2.11.14", 46 | "eslint": "^3.8.0", 47 | "eslint-config-google": "^0.7.0", 48 | "graphql": "*", 49 | "mocha": "^3.1.2", 50 | "nyc": "^8.3.1", 51 | "prettier": "^1.13.5" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import {GraphQLDirective} from 'graphql/type/directives'; 2 | import {GraphQLSchema, parse} from 'graphql'; 3 | 4 | const DEFAULT_DIRECTIVES = ['skip', 'include']; 5 | 6 | /** 7 | * If a resolve function is not given, then a default resolve behavior is used 8 | * which takes the property of the source object of the same name as the field 9 | * and returns it as the result, or if it's a function, returns the result 10 | * of calling that function. 11 | */ 12 | function defaultResolveFn(source, args, context, info) { 13 | var fieldName = info.fieldName; 14 | // ensure source is a value for which property access is acceptable. 15 | if (typeof source === 'object' || typeof source === 'function') { 16 | return typeof source[fieldName] === 'function' 17 | ? source[fieldName]() 18 | : source[fieldName]; 19 | } 20 | } 21 | 22 | /** 23 | * resolving field using directive resolver 24 | */ 25 | function resolveWithDirective(resolve, source, directive, context, info) { 26 | source = source || ((info || {}).variableValues || {}).input_0 || ((info || {}).variableValues || {}).input || {}; 27 | let directiveConfig = info.schema._directives.filter( 28 | d => directive.name.value === d.name, 29 | )[0]; 30 | 31 | let args = {}; 32 | 33 | for (let arg of directive.arguments) { 34 | args[arg.name.value] = arg.value.value; 35 | } 36 | 37 | return directiveConfig.resolve(resolve, source, args, context, info); 38 | } 39 | 40 | /** 41 | * parse directives from a schema defenition form them as graphql directive structure 42 | */ 43 | function parseSchemaDirectives(directives) { 44 | let schemaDirectives = []; 45 | 46 | if ( 47 | !directives || 48 | !(directives instanceof Object) || 49 | Object.keys(directives).length === 0 50 | ) { 51 | return []; 52 | } 53 | 54 | for (let directiveName in directives) { 55 | let argsList = [], 56 | args = ''; 57 | 58 | Object.keys(directives[directiveName]).map(key => { 59 | argsList.push(`${key}:"${directives[directiveName][key]}"`); 60 | }); 61 | 62 | if (argsList.length > 0) { 63 | args = `(${argsList.join(',')})`; 64 | } 65 | 66 | schemaDirectives.push(`@${directiveName}${args}`); 67 | } 68 | 69 | return parse(`{ a: String ${schemaDirectives.join(' ')} }`).definitions[0] 70 | .selectionSet.selections[0].directives; 71 | } 72 | 73 | /** 74 | * If the directive is defined on a field it will execute the custom directive 75 | * resolve right after executing the resolve of the field otherwise it will execute 76 | * the original resolve of the field 77 | */ 78 | function resolveMiddlewareWrapper(resolve = defaultResolveFn, directives = {}) { 79 | const serverDirectives = parseSchemaDirectives(directives); 80 | 81 | return (source, args, context, info) => { 82 | const directives = serverDirectives.concat( 83 | (info.fieldASTs || info.fieldNodes)[0].directives, 84 | ); 85 | const directive = directives.filter( 86 | d => DEFAULT_DIRECTIVES.indexOf(d.name.value) === -1, 87 | )[0]; 88 | 89 | if (!directive) { 90 | return resolve(source, args, context, info); 91 | } 92 | 93 | let defer = resolveWithDirective( 94 | () => Promise.resolve(resolve(source, args, context, info)), 95 | source, 96 | directive, 97 | context, 98 | info, 99 | ); 100 | defer.catch(e => 101 | resolveWithDirective( 102 | /* istanbul ignore next */ 103 | () => Promise.reject(e), 104 | source, 105 | directive, 106 | context, 107 | info, 108 | ), 109 | ); 110 | 111 | if (directives.length <= 1) { 112 | return defer; 113 | } 114 | 115 | for (let directiveNext of directives.slice(1)) { 116 | defer = defer.then(result => 117 | resolveWithDirective( 118 | () => Promise.resolve(result), 119 | source, 120 | directiveNext, 121 | context, 122 | info, 123 | ), 124 | ); 125 | defer.catch(e => 126 | resolveWithDirective( 127 | () => Promise.reject(e), 128 | source, 129 | directiveNext, 130 | context, 131 | info, 132 | ), 133 | ); 134 | } 135 | 136 | return defer; 137 | }; 138 | } 139 | 140 | /** 141 | * Scanning the shema and wrapping the resolve of each field with the support 142 | * of the graphql custom directives resolve execution 143 | */ 144 | function wrapFieldsWithMiddleware(type, deepWrap = true, typeMet = {}) { 145 | if (!type) { 146 | return; 147 | } 148 | 149 | let fields = type._fields; 150 | typeMet[type.name] = true; 151 | for (let label in fields) { 152 | let field = fields[label]; 153 | if (field && !typeMet[field.type.name]) { 154 | if (!!field && typeof field == 'object') { 155 | field.resolve = resolveMiddlewareWrapper( 156 | field.resolve, 157 | field.directives, 158 | ); 159 | if (field.type._fields && deepWrap) { 160 | wrapFieldsWithMiddleware(field.type, deepWrap, typeMet); 161 | } else if (field.type.ofType && field.type.ofType._fields && deepWrap) { 162 | let child = field.type; 163 | while (child.ofType) { 164 | child = child.ofType; 165 | } 166 | if (child._fields) { 167 | wrapFieldsWithMiddleware(child._fields); 168 | } 169 | } 170 | } 171 | } 172 | } 173 | } 174 | 175 | /** 176 | * create a new graphql custom directive which contain a resolve 177 | * function for altering the execution of the graphql 178 | */ 179 | exports.GraphQLCustomDirective = function(config) { 180 | const directive = new GraphQLDirective(config); 181 | 182 | if (config.resolve) { 183 | directive.resolve = config.resolve; 184 | } 185 | 186 | return directive; 187 | }; 188 | 189 | /** 190 | * Apply custom directives support in the graphql schema 191 | */ 192 | exports.applySchemaCustomDirectives = function(schema) { 193 | if (!(schema instanceof GraphQLSchema)) { 194 | throw new Error('Schema must be instanceof GraphQLSchema'); 195 | } 196 | 197 | wrapFieldsWithMiddleware(schema._queryType); 198 | wrapFieldsWithMiddleware(schema._mutationType, false); 199 | 200 | return true; 201 | }; 202 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLCustomDirective, 3 | applySchemaCustomDirectives, 4 | } from '../src/index'; 5 | import { 6 | GraphQLInt, 7 | GraphQLSchema, 8 | GraphQLObjectType, 9 | GraphQLNonNull, 10 | GraphQLList, 11 | graphql, 12 | buildSchema, 13 | } from 'graphql'; 14 | import {DirectiveLocation} from 'graphql/language/directiveLocation'; 15 | import { 16 | createGraphQLQueryDeepObject, 17 | testEqual, 18 | testNullEqual, 19 | runQuery, 20 | } from './utils'; 21 | 22 | import {expect} from 'chai'; 23 | 24 | let GraphQLTestDirective, 25 | GraphQLTestDirectiveTrows, 26 | GraphQLTestDirectiveCatch, 27 | errors, 28 | schema; 29 | 30 | describe('GraphQLCustomDirective', () => { 31 | before(() => { 32 | GraphQLTestDirective = new GraphQLCustomDirective({ 33 | name: 'duplicate', 34 | description: 'duplicate the string sperating them with space', 35 | locations: [DirectiveLocation.FIELD], 36 | args: { 37 | by: { 38 | type: GraphQLInt, 39 | description: 'the times to duplicate the string', 40 | }, 41 | }, 42 | resolve: function(resolve, source, {by}, schema, info) { 43 | return resolve().then(result => { 44 | if (!result) { 45 | return result; 46 | } 47 | 48 | let times = []; 49 | 50 | for (let i = 0; i < (by || 2); i++) { 51 | times.push(result); 52 | } 53 | 54 | return times.join(' '); 55 | }); 56 | }, 57 | }); 58 | GraphQLTestDirectiveTrows = new GraphQLCustomDirective({ 59 | name: 'throws', 60 | description: 'throws an error after promise is resolved', 61 | locations: [DirectiveLocation.FIELD], 62 | resolve: function(resolve, source, {by}, schema, info) { 63 | return resolve().then(() => { 64 | throw 'Test Error'; 65 | }); 66 | }, 67 | }); 68 | 69 | GraphQLTestDirectiveCatch = new GraphQLCustomDirective({ 70 | name: 'catch', 71 | description: 'catch error and store it locally', 72 | locations: [DirectiveLocation.FIELD], 73 | resolve: function(resolve, source, {by}, schema, info) { 74 | return resolve() 75 | .then(result => { 76 | return result; 77 | }) 78 | .catch(e => { 79 | errors.push(e); 80 | }); 81 | }, 82 | }); 83 | 84 | errors = []; 85 | }); 86 | 87 | it('expected to have name property', () => { 88 | expect(GraphQLTestDirective.name).to.eql('duplicate'); 89 | }); 90 | 91 | it('expected to have description property', () => { 92 | expect(GraphQLTestDirective.description).to.eql( 93 | 'duplicate the string sperating them with space', 94 | ); 95 | }); 96 | 97 | it('expected to have args properties', () => { 98 | expect(GraphQLTestDirective.args).to.a('array'); 99 | }); 100 | 101 | it('expected to have locations list', () => { 102 | expect(GraphQLTestDirective.locations).to.a('array'); 103 | }); 104 | 105 | it('expected to have resolve function', () => { 106 | expect(GraphQLTestDirective.resolve).to.be.function; 107 | }); 108 | 109 | it('expected regular execution of graphql', done => { 110 | const query = `{ value }`, 111 | input = {value: null}, 112 | expected = {value: null}; 113 | 114 | testEqual({query, expected, input, done}); 115 | }); 116 | 117 | it('expected directive to alter execution of graphql and result test test', done => { 118 | const query = `{ value(input: "test") @duplicate }`, 119 | passServer = true, 120 | directives = [GraphQLTestDirective], 121 | expected = {value: 'test test test test'}; 122 | 123 | testEqual({directives, query, expected, done, passServer}); 124 | }); 125 | 126 | it('expected directive to alter execution of graphql and result test test', done => { 127 | const query = `{ value(input: "test") @duplicate(by:6) }`, 128 | directives = [GraphQLTestDirective], 129 | expected = {value: 'test test test test test test'}; 130 | 131 | testEqual({directives, query, expected, done}); 132 | }); 133 | 134 | it('expected directive to alter execution of graphql and result test test test', done => { 135 | const query = `{ value @duplicate(by:6) }`, 136 | schema = `type Query { value: String } schema { query: Query }`, 137 | input = {value: 'test'}, 138 | directives = [GraphQLTestDirective], 139 | expected = {value: 'test test test test test test'}; 140 | 141 | testEqual({directives, query, schema, input, expected, done}); 142 | }); 143 | 144 | it('expected directive to alter execution of graphql and result null', done => { 145 | const query = `{ value @duplicate }`, 146 | directives = [GraphQLTestDirective], 147 | expected = {value: null}; 148 | 149 | testEqual({directives, query, expected, done}); 150 | }); 151 | 152 | it('expected directive catch error that was thrown', done => { 153 | expect(errors.length).to.equal(0); 154 | const query = `{ value(input: "test") @duplicate @throws @catch }`, 155 | passServer = true, 156 | directives = [ 157 | GraphQLTestDirective, 158 | GraphQLTestDirectiveTrows, 159 | GraphQLTestDirectiveCatch, 160 | ]; 161 | 162 | runQuery({directives, query, done, passServer}) 163 | .then(() => { 164 | done(`Expected to enter .catch function`); 165 | }) 166 | .catch(() => { 167 | expect(errors.length).to.equal(1); 168 | done(); 169 | }); 170 | }); 171 | }); 172 | 173 | describe('applySchemaCustomDirectives', () => { 174 | it('expected to throw error when invalid schema', () => { 175 | expect(applySchemaCustomDirectives.bind({})).throw( 176 | /Schema must be instanceof GraphQLSchema/, 177 | ); 178 | }); 179 | 180 | it('expected to apply custom directives to schema', () => { 181 | let schema = `type Test { input: String!, output: String } type Query { test1: Test, test2: [Test] } schema { query: Query }`; 182 | let executionSchema = buildSchema(schema); 183 | 184 | expect(applySchemaCustomDirectives(executionSchema)).to.eql(true); 185 | }); 186 | }); 187 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:babel-register 2 | --require ./test/setup.js 3 | --reporter spec 4 | --recursive 5 | --timeout 5000 -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | chai.use(require('chai-as-promised')); 3 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLString, 3 | GraphQLSchema, 4 | GraphQLObjectType, 5 | GraphQLNonNull, 6 | GraphQLList, 7 | graphql, 8 | buildSchema, 9 | } from 'graphql'; 10 | import {applySchemaCustomDirectives} from '../src/index'; 11 | 12 | import {expect} from 'chai'; 13 | 14 | const DEFAULT_TEST_SCHEMA = `type Query { value(input: String): String } schema { query: Query }`; 15 | 16 | const _runQuery = function({ 17 | directives, 18 | query, 19 | schema, 20 | input, 21 | passServer = false, 22 | done, 23 | context, 24 | }) { 25 | let executionSchema = buildSchema(schema || DEFAULT_TEST_SCHEMA); 26 | 27 | if (!schema) { 28 | executionSchema._queryType._fields.value.resolve = ( 29 | source, 30 | {input, context}, 31 | ) => input; 32 | if (passServer) { 33 | executionSchema._queryType._fields.value.directives = { 34 | duplicate: {by: 2}, 35 | }; 36 | } 37 | } 38 | 39 | if (directives) 40 | executionSchema._directives = executionSchema._directives.concat( 41 | directives, 42 | ); 43 | 44 | applySchemaCustomDirectives(executionSchema); 45 | 46 | return graphql(executionSchema, query, input, context); 47 | }; 48 | 49 | exports.testEqual = function({ 50 | directives, 51 | query, 52 | schema, 53 | input, 54 | passServer = false, 55 | expected, 56 | done, 57 | context, 58 | }) { 59 | return _runQuery({ 60 | directives, 61 | query, 62 | schema, 63 | input, 64 | passServer, 65 | done, 66 | context, 67 | }) 68 | .then(({data, errors}) => { 69 | if (errors) { 70 | throw new Error(errors); 71 | } 72 | expect(data).to.eql(expected); 73 | }) 74 | .then(done, done); 75 | }; 76 | 77 | exports.runQuery = function({ 78 | directives, 79 | query, 80 | schema, 81 | input, 82 | passServer = false, 83 | done, 84 | context, 85 | }) { 86 | return _runQuery({ 87 | directives, 88 | query, 89 | schema, 90 | input, 91 | passServer, 92 | done, 93 | context, 94 | }) 95 | .then(({data, errors}) => { 96 | if (errors) { 97 | throw new Error(errors); 98 | } 99 | }) 100 | .catch(e => { 101 | throw e; 102 | }); 103 | }; 104 | --------------------------------------------------------------------------------