@envelop/extended-validation 1.7.0-alpha-aca44e1.0 → 1.7.0-alpha-385b86b.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cjs/common.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDirectiveFromAstNode = void 0;
4
+ function getDirectiveFromAstNode(astNode, names) {
5
+ const directives = astNode.directives || [];
6
+ const namesArr = Array.isArray(names) ? names : [names];
7
+ const authDirective = directives.find(d => namesArr.includes(d.name.value));
8
+ return authDirective || null;
9
+ }
10
+ exports.getDirectiveFromAstNode = getDirectiveFromAstNode;
package/cjs/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./plugin.js"), exports);
5
+ tslib_1.__exportStar(require("./common.js"), exports);
6
+ tslib_1.__exportStar(require("./rules/one-of.js"), exports);
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
package/cjs/plugin.js ADDED
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useExtendedValidation = void 0;
4
+ const graphql_1 = require("graphql");
5
+ const symbolExtendedValidationRules = Symbol('extendedValidationContext');
6
+ const useExtendedValidation = (options) => {
7
+ let schemaTypeInfo;
8
+ function getTypeInfo() {
9
+ return schemaTypeInfo;
10
+ }
11
+ return {
12
+ onSchemaChange({ schema }) {
13
+ schemaTypeInfo = new graphql_1.TypeInfo(schema);
14
+ },
15
+ onContextBuilding({ context, extendContext }) {
16
+ // We initialize the validationRules context in onContextBuilding as onExecute is already too late!
17
+ let validationRulesContext = context[symbolExtendedValidationRules];
18
+ if (validationRulesContext === undefined) {
19
+ validationRulesContext = {
20
+ rules: [],
21
+ didRun: false,
22
+ };
23
+ extendContext({
24
+ [symbolExtendedValidationRules]: validationRulesContext,
25
+ });
26
+ }
27
+ validationRulesContext.rules.push(...options.rules);
28
+ },
29
+ onSubscribe: buildHandler('subscribe', getTypeInfo, options.onValidationFailed),
30
+ onExecute: buildHandler('execute', getTypeInfo, options.onValidationFailed),
31
+ };
32
+ };
33
+ exports.useExtendedValidation = useExtendedValidation;
34
+ function buildHandler(name, getTypeInfo, onValidationFailed) {
35
+ return function handler({ args, setResultAndStopExecution, }) {
36
+ var _a;
37
+ // We hook into onExecute/onSubscribe even though this is a validation pattern. The reasoning behind
38
+ // it is that hooking right after validation and before execution has started is the
39
+ // same as hooking into the validation step. The benefit of this approach is that
40
+ // we may use execution context in the validation rules.
41
+ const validationRulesContext = args.contextValue[symbolExtendedValidationRules];
42
+ if (validationRulesContext === undefined) {
43
+ throw new Error('Plugin has not been properly set up. ' +
44
+ `The 'contextFactory' function is not invoked and the result has not been passed to '${name}'.`);
45
+ }
46
+ // we only want to run the extended execution once.
47
+ if (validationRulesContext.didRun === false) {
48
+ validationRulesContext.didRun = true;
49
+ if (validationRulesContext.rules.length !== 0) {
50
+ const errors = [];
51
+ // We replicate the default validation step manually before execution starts.
52
+ const typeInfo = (_a = getTypeInfo()) !== null && _a !== void 0 ? _a : new graphql_1.TypeInfo(args.schema);
53
+ const validationContext = new graphql_1.ValidationContext(args.schema, args.document, typeInfo, e => {
54
+ errors.push(e);
55
+ });
56
+ const visitor = (0, graphql_1.visitInParallel)(validationRulesContext.rules.map(rule => rule(validationContext, args)));
57
+ (0, graphql_1.visit)(args.document, (0, graphql_1.visitWithTypeInfo)(typeInfo, visitor));
58
+ if (errors.length > 0) {
59
+ let result = {
60
+ data: null,
61
+ errors,
62
+ };
63
+ if (onValidationFailed) {
64
+ onValidationFailed({ args, result, setResult: newResult => (result = newResult) });
65
+ }
66
+ setResultAndStopExecution(result);
67
+ }
68
+ }
69
+ }
70
+ };
71
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OneOfInputObjectsRule = exports.ONE_OF_DIRECTIVE_SDL = void 0;
4
+ const graphql_1 = require("graphql");
5
+ const utils_1 = require("@graphql-tools/utils");
6
+ const common_js_1 = require("../common.js");
7
+ exports.ONE_OF_DIRECTIVE_SDL = `
8
+ directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
9
+ `;
10
+ const OneOfInputObjectsRule = (validationContext, executionArgs) => {
11
+ return {
12
+ Field: node => {
13
+ var _a, _b;
14
+ if ((_a = node.arguments) === null || _a === void 0 ? void 0 : _a.length) {
15
+ const fieldType = validationContext.getFieldDef();
16
+ if (!fieldType) {
17
+ return;
18
+ }
19
+ const values = (0, utils_1.getArgumentValues)(fieldType, node, executionArgs.variableValues || undefined);
20
+ const isOneOfFieldType = ((_b = fieldType.extensions) === null || _b === void 0 ? void 0 : _b.oneOf) || (fieldType.astNode && (0, common_js_1.getDirectiveFromAstNode)(fieldType.astNode, 'oneOf'));
21
+ if (isOneOfFieldType) {
22
+ if (Object.keys(values).length !== 1) {
23
+ validationContext.reportError(new graphql_1.GraphQLError(`Exactly one key must be specified for input for field "${fieldType.type.toString()}.${node.name.value}"`, [node]));
24
+ }
25
+ }
26
+ for (const arg of node.arguments) {
27
+ const argType = fieldType.args.find(typeArg => typeArg.name === arg.name.value);
28
+ if (argType) {
29
+ traverseVariables(validationContext, arg, argType.type, values[arg.name.value]);
30
+ }
31
+ }
32
+ }
33
+ },
34
+ };
35
+ };
36
+ exports.OneOfInputObjectsRule = OneOfInputObjectsRule;
37
+ function getNonNullType(ttype) {
38
+ if (ttype instanceof graphql_1.GraphQLNonNull) {
39
+ return ttype.ofType;
40
+ }
41
+ return ttype;
42
+ }
43
+ function traverseVariables(validationContext, arg, graphqlType, currentValue) {
44
+ var _a;
45
+ // if the current value is empty we don't need to traverse deeper
46
+ // if it shouldn't be empty, the "original" validation phase should complain.
47
+ if (currentValue == null) {
48
+ return;
49
+ }
50
+ const unwrappedType = getNonNullType(graphqlType);
51
+ if ((0, graphql_1.isListType)(unwrappedType)) {
52
+ if (!Array.isArray(currentValue)) {
53
+ // because of graphql type coercion a single object should be treated as an array of one object
54
+ currentValue = [currentValue];
55
+ }
56
+ currentValue.forEach(value => {
57
+ traverseVariables(validationContext, arg, unwrappedType.ofType, value);
58
+ });
59
+ return;
60
+ }
61
+ if (typeof currentValue !== 'object' || currentValue == null) {
62
+ // in case the value is not an object, the "original" validation phase should complain.
63
+ return;
64
+ }
65
+ const inputType = (0, graphql_1.getNamedType)(graphqlType);
66
+ const isOneOfInputType = ((_a = inputType.extensions) === null || _a === void 0 ? void 0 : _a.oneOf) || (inputType.astNode && (0, common_js_1.getDirectiveFromAstNode)(inputType.astNode, 'oneOf'));
67
+ if (isOneOfInputType) {
68
+ if (Object.keys(currentValue).length !== 1) {
69
+ validationContext.reportError(new graphql_1.GraphQLError(`Exactly one key must be specified for input type "${inputType.name}"`, [arg]));
70
+ }
71
+ }
72
+ if (inputType instanceof graphql_1.GraphQLInputObjectType) {
73
+ for (const [name, fieldConfig] of Object.entries(inputType.getFields())) {
74
+ traverseVariables(validationContext, arg, fieldConfig.type, currentValue[name]);
75
+ }
76
+ }
77
+ }
package/esm/common.js ADDED
@@ -0,0 +1,6 @@
1
+ export function getDirectiveFromAstNode(astNode, names) {
2
+ const directives = astNode.directives || [];
3
+ const namesArr = Array.isArray(names) ? names : [names];
4
+ const authDirective = directives.find(d => namesArr.includes(d.name.value));
5
+ return authDirective || null;
6
+ }
package/esm/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './plugin.js';
2
+ export * from './common.js';
3
+ export * from './rules/one-of.js';
package/esm/plugin.js ADDED
@@ -0,0 +1,67 @@
1
+ import { TypeInfo, ValidationContext, visit, visitInParallel, visitWithTypeInfo, } from 'graphql';
2
+ const symbolExtendedValidationRules = Symbol('extendedValidationContext');
3
+ export const useExtendedValidation = (options) => {
4
+ let schemaTypeInfo;
5
+ function getTypeInfo() {
6
+ return schemaTypeInfo;
7
+ }
8
+ return {
9
+ onSchemaChange({ schema }) {
10
+ schemaTypeInfo = new TypeInfo(schema);
11
+ },
12
+ onContextBuilding({ context, extendContext }) {
13
+ // We initialize the validationRules context in onContextBuilding as onExecute is already too late!
14
+ let validationRulesContext = context[symbolExtendedValidationRules];
15
+ if (validationRulesContext === undefined) {
16
+ validationRulesContext = {
17
+ rules: [],
18
+ didRun: false,
19
+ };
20
+ extendContext({
21
+ [symbolExtendedValidationRules]: validationRulesContext,
22
+ });
23
+ }
24
+ validationRulesContext.rules.push(...options.rules);
25
+ },
26
+ onSubscribe: buildHandler('subscribe', getTypeInfo, options.onValidationFailed),
27
+ onExecute: buildHandler('execute', getTypeInfo, options.onValidationFailed),
28
+ };
29
+ };
30
+ function buildHandler(name, getTypeInfo, onValidationFailed) {
31
+ return function handler({ args, setResultAndStopExecution, }) {
32
+ var _a;
33
+ // We hook into onExecute/onSubscribe even though this is a validation pattern. The reasoning behind
34
+ // it is that hooking right after validation and before execution has started is the
35
+ // same as hooking into the validation step. The benefit of this approach is that
36
+ // we may use execution context in the validation rules.
37
+ const validationRulesContext = args.contextValue[symbolExtendedValidationRules];
38
+ if (validationRulesContext === undefined) {
39
+ throw new Error('Plugin has not been properly set up. ' +
40
+ `The 'contextFactory' function is not invoked and the result has not been passed to '${name}'.`);
41
+ }
42
+ // we only want to run the extended execution once.
43
+ if (validationRulesContext.didRun === false) {
44
+ validationRulesContext.didRun = true;
45
+ if (validationRulesContext.rules.length !== 0) {
46
+ const errors = [];
47
+ // We replicate the default validation step manually before execution starts.
48
+ const typeInfo = (_a = getTypeInfo()) !== null && _a !== void 0 ? _a : new TypeInfo(args.schema);
49
+ const validationContext = new ValidationContext(args.schema, args.document, typeInfo, e => {
50
+ errors.push(e);
51
+ });
52
+ const visitor = visitInParallel(validationRulesContext.rules.map(rule => rule(validationContext, args)));
53
+ visit(args.document, visitWithTypeInfo(typeInfo, visitor));
54
+ if (errors.length > 0) {
55
+ let result = {
56
+ data: null,
57
+ errors,
58
+ };
59
+ if (onValidationFailed) {
60
+ onValidationFailed({ args, result, setResult: newResult => (result = newResult) });
61
+ }
62
+ setResultAndStopExecution(result);
63
+ }
64
+ }
65
+ }
66
+ };
67
+ }
@@ -0,0 +1,73 @@
1
+ import { GraphQLError, GraphQLInputObjectType, GraphQLNonNull, isListType, getNamedType, } from 'graphql';
2
+ import { getArgumentValues } from '@graphql-tools/utils';
3
+ import { getDirectiveFromAstNode } from '../common.js';
4
+ export const ONE_OF_DIRECTIVE_SDL = /* GraphQL */ `
5
+ directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
6
+ `;
7
+ export const OneOfInputObjectsRule = (validationContext, executionArgs) => {
8
+ return {
9
+ Field: node => {
10
+ var _a, _b;
11
+ if ((_a = node.arguments) === null || _a === void 0 ? void 0 : _a.length) {
12
+ const fieldType = validationContext.getFieldDef();
13
+ if (!fieldType) {
14
+ return;
15
+ }
16
+ const values = getArgumentValues(fieldType, node, executionArgs.variableValues || undefined);
17
+ const isOneOfFieldType = ((_b = fieldType.extensions) === null || _b === void 0 ? void 0 : _b.oneOf) || (fieldType.astNode && getDirectiveFromAstNode(fieldType.astNode, 'oneOf'));
18
+ if (isOneOfFieldType) {
19
+ if (Object.keys(values).length !== 1) {
20
+ validationContext.reportError(new GraphQLError(`Exactly one key must be specified for input for field "${fieldType.type.toString()}.${node.name.value}"`, [node]));
21
+ }
22
+ }
23
+ for (const arg of node.arguments) {
24
+ const argType = fieldType.args.find(typeArg => typeArg.name === arg.name.value);
25
+ if (argType) {
26
+ traverseVariables(validationContext, arg, argType.type, values[arg.name.value]);
27
+ }
28
+ }
29
+ }
30
+ },
31
+ };
32
+ };
33
+ function getNonNullType(ttype) {
34
+ if (ttype instanceof GraphQLNonNull) {
35
+ return ttype.ofType;
36
+ }
37
+ return ttype;
38
+ }
39
+ function traverseVariables(validationContext, arg, graphqlType, currentValue) {
40
+ var _a;
41
+ // if the current value is empty we don't need to traverse deeper
42
+ // if it shouldn't be empty, the "original" validation phase should complain.
43
+ if (currentValue == null) {
44
+ return;
45
+ }
46
+ const unwrappedType = getNonNullType(graphqlType);
47
+ if (isListType(unwrappedType)) {
48
+ if (!Array.isArray(currentValue)) {
49
+ // because of graphql type coercion a single object should be treated as an array of one object
50
+ currentValue = [currentValue];
51
+ }
52
+ currentValue.forEach(value => {
53
+ traverseVariables(validationContext, arg, unwrappedType.ofType, value);
54
+ });
55
+ return;
56
+ }
57
+ if (typeof currentValue !== 'object' || currentValue == null) {
58
+ // in case the value is not an object, the "original" validation phase should complain.
59
+ return;
60
+ }
61
+ const inputType = getNamedType(graphqlType);
62
+ const isOneOfInputType = ((_a = inputType.extensions) === null || _a === void 0 ? void 0 : _a.oneOf) || (inputType.astNode && getDirectiveFromAstNode(inputType.astNode, 'oneOf'));
63
+ if (isOneOfInputType) {
64
+ if (Object.keys(currentValue).length !== 1) {
65
+ validationContext.reportError(new GraphQLError(`Exactly one key must be specified for input type "${inputType.name}"`, [arg]));
66
+ }
67
+ }
68
+ if (inputType instanceof GraphQLInputObjectType) {
69
+ for (const [name, fieldConfig] of Object.entries(inputType.getFields())) {
70
+ traverseVariables(validationContext, arg, fieldConfig.type, currentValue[name]);
71
+ }
72
+ }
73
+ }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@envelop/extended-validation",
3
- "version": "1.7.0-alpha-aca44e1.0",
3
+ "version": "1.7.0-alpha-385b86b.0",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
- "@envelop/core": "2.4.0-alpha-aca44e1.0",
6
+ "@envelop/core": "2.4.0-alpha-385b86b.0",
7
7
  "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
8
8
  },
9
9
  "dependencies": {
@@ -11,26 +11,47 @@
11
11
  },
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "https://github.com/dotansimha/envelop.git",
14
+ "url": "https://github.com/n1ru4l/envelop.git",
15
15
  "directory": "packages/plugins/extended-validation"
16
16
  },
17
17
  "author": "Dotan Simha <dotansimha@gmail.com>",
18
18
  "license": "MIT",
19
- "main": "index.js",
20
- "module": "index.mjs",
21
- "typings": "index.d.ts",
19
+ "main": "cjs/index.js",
20
+ "module": "esm/index.js",
21
+ "typings": "typings/index.d.ts",
22
22
  "typescript": {
23
- "definition": "index.d.ts"
23
+ "definition": "typings/index.d.ts"
24
24
  },
25
+ "type": "module",
25
26
  "exports": {
26
27
  ".": {
27
- "require": "./index.js",
28
- "import": "./index.mjs"
28
+ "require": {
29
+ "types": "./typings/index.d.ts",
30
+ "default": "./cjs/index.js"
31
+ },
32
+ "import": {
33
+ "types": "./typings/index.d.ts",
34
+ "default": "./esm/index.js"
35
+ },
36
+ "default": {
37
+ "types": "./typings/index.d.ts",
38
+ "default": "./esm/index.js"
39
+ }
29
40
  },
30
41
  "./*": {
31
- "require": "./*.js",
32
- "import": "./*.mjs"
42
+ "require": {
43
+ "types": "./typings/*.d.ts",
44
+ "default": "./cjs/*.js"
45
+ },
46
+ "import": {
47
+ "types": "./typings/*.d.ts",
48
+ "default": "./esm/*.js"
49
+ },
50
+ "default": {
51
+ "types": "./typings/*.d.ts",
52
+ "default": "./esm/*.js"
53
+ }
33
54
  },
34
55
  "./package.json": "./package.json"
35
56
  }
36
- }
57
+ }
File without changes
@@ -0,0 +1,3 @@
1
+ export * from './plugin.js';
2
+ export * from './common.js';
3
+ export * from './rules/one-of.js';
@@ -1,6 +1,6 @@
1
1
  import { Plugin } from '@envelop/core';
2
2
  import { ExecutionArgs, ExecutionResult } from 'graphql';
3
- import { ExtendedValidationRule } from './common';
3
+ import { ExtendedValidationRule } from './common.js';
4
4
  declare type OnValidationFailedCallback = (params: {
5
5
  args: ExecutionArgs;
6
6
  result: ExecutionResult;
@@ -1,3 +1,3 @@
1
- import { ExtendedValidationRule } from '../common';
1
+ import { ExtendedValidationRule } from '../common.js';
2
2
  export declare const ONE_OF_DIRECTIVE_SDL = "\n directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION\n";
3
3
  export declare const OneOfInputObjectsRule: ExtendedValidationRule;
package/README.md DELETED
@@ -1,135 +0,0 @@
1
- ## `@envelop/extended-validation`
2
-
3
- Extended validation plugin adds support for writing GraphQL validation rules, that has access to all `execute` parameters, including variables.
4
-
5
- While GraphQL supports fair amount of built-in validations, and validations could be extended, it's doesn't expose `variables` to the validation rules, since operation variables are not available during `validate` flow (it's only available through execution of the operation, after input/variables coercion is done).
6
-
7
- This plugin runs before `validate` but allow developers to write their validation rules in the same way GraphQL `ValidationRule` is defined (based on a GraphQL visitor).
8
-
9
- ## Getting Started
10
-
11
- Start by installing the plugin:
12
-
13
- ```
14
- yarn add @envelop/extended-validation
15
- ```
16
-
17
- Then, use the plugin with your validation rules:
18
-
19
- ```ts
20
- import { useExtendedValidation } from '@envelop/extended-validation';
21
-
22
- const getEnveloped = evelop({
23
- plugins: [
24
- useExtendedValidation({
25
- rules: [ ... ] // your rules here
26
- })
27
- ]
28
- })
29
- ```
30
-
31
- To create your custom rules, implement the `ExtendedValidationRule` interface and return your GraphQL AST visitor.
32
-
33
- For example:
34
-
35
- ```ts
36
- import { ExtendedValidationRule } from '@envelop/extended-validation';
37
-
38
- export const MyRule: ExtendedValidationRule = (validationContext, executionArgs) => {
39
- return {
40
- OperationDefinition: node => {
41
- // This will run for every executed Query/Mutation/Subscription
42
- // And now you also have access to the execution params like variables, context and so on.
43
- // If you wish to report an error, use validationContext.reportError or throw an exception.
44
- },
45
- };
46
- };
47
- ```
48
-
49
- ## Built-in Rules
50
-
51
- ### Union Inputs: `@oneOf`
52
-
53
- This directive provides validation for input types and implements the concept of union inputs. You can find the [complete spec RFC here](https://github.com/graphql/graphql-spec/pull/825).
54
-
55
- You can use union inputs either via a the SDL flow, by annotating types and fields with `@oneOf` or via the `extensions` field.
56
-
57
- First, make sure to add that rule to your plugin usage:
58
-
59
- ```ts
60
- import { useExtendedValidation, OneOfInputObjectsRule } from '@envelop/extended-validation';
61
-
62
- const getEnveloped = evelop({
63
- plugins: [
64
- useExtendedValidation({
65
- rules: [OneOfInputObjectsRule],
66
- }),
67
- ],
68
- });
69
- ```
70
-
71
- #### Schema Directive Flow
72
-
73
- Make sure to include the following directive in your schema:
74
-
75
- ```graphql
76
- directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
77
- ```
78
-
79
- Then, apply it to field definitions, or to a complete `input` type:
80
-
81
- ```graphql
82
- ## Apply to entire input type
83
- input FindUserInput @oneOf {
84
- id: ID
85
- organizationAndRegistrationNumber: GraphQLInt
86
- }
87
-
88
- ## Or, apply to a set of input arguments
89
-
90
- type Query {
91
- foo(id: ID, str1: String, str2: String): String @oneOf
92
- }
93
- ```
94
-
95
- #### Programmatic extensions flow
96
-
97
- ```tsx
98
- const GraphQLFindUserInput = new GraphQLInputObjectType({
99
- name: 'FindUserInput',
100
- fields: {
101
- id: {
102
- type: GraphQLID,
103
- },
104
- organizationAndRegistrationNumber: {
105
- type: GraphQLInt,
106
- },
107
- },
108
- extensions: {
109
- oneOf: true,
110
- },
111
- });
112
-
113
- const Query = new GraphQLObjectType({
114
- name: 'Query',
115
- fields: {
116
- foo: {
117
- type: GraphQLString,
118
- args: {
119
- id: {
120
- type: GraphQLID,
121
- },
122
- str1: {
123
- type: GraphQLString,
124
- },
125
- str2: {
126
- type: GraphQLString,
127
- },
128
- },
129
- extensions: {
130
- oneOf: true,
131
- },
132
- },
133
- },
134
- });
135
- ```
package/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export * from './plugin';
2
- export * from './common';
3
- export * from './rules/one-of';
package/index.js DELETED
@@ -1,156 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- const graphql = require('graphql');
6
- const utils = require('@graphql-tools/utils');
7
-
8
- const symbolExtendedValidationRules = Symbol('extendedValidationContext');
9
- const useExtendedValidation = (options) => {
10
- let schemaTypeInfo;
11
- function getTypeInfo() {
12
- return schemaTypeInfo;
13
- }
14
- return {
15
- onSchemaChange({ schema }) {
16
- schemaTypeInfo = new graphql.TypeInfo(schema);
17
- },
18
- onContextBuilding({ context, extendContext }) {
19
- // We initialize the validationRules context in onContextBuilding as onExecute is already too late!
20
- let validationRulesContext = context[symbolExtendedValidationRules];
21
- if (validationRulesContext === undefined) {
22
- validationRulesContext = {
23
- rules: [],
24
- didRun: false,
25
- };
26
- extendContext({
27
- [symbolExtendedValidationRules]: validationRulesContext,
28
- });
29
- }
30
- validationRulesContext.rules.push(...options.rules);
31
- },
32
- onSubscribe: buildHandler('subscribe', getTypeInfo, options.onValidationFailed),
33
- onExecute: buildHandler('execute', getTypeInfo, options.onValidationFailed),
34
- };
35
- };
36
- function buildHandler(name, getTypeInfo, onValidationFailed) {
37
- return function handler({ args, setResultAndStopExecution, }) {
38
- var _a;
39
- // We hook into onExecute/onSubscribe even though this is a validation pattern. The reasoning behind
40
- // it is that hooking right after validation and before execution has started is the
41
- // same as hooking into the validation step. The benefit of this approach is that
42
- // we may use execution context in the validation rules.
43
- const validationRulesContext = args.contextValue[symbolExtendedValidationRules];
44
- if (validationRulesContext === undefined) {
45
- throw new Error('Plugin has not been properly set up. ' +
46
- `The 'contextFactory' function is not invoked and the result has not been passed to '${name}'.`);
47
- }
48
- // we only want to run the extended execution once.
49
- if (validationRulesContext.didRun === false) {
50
- validationRulesContext.didRun = true;
51
- if (validationRulesContext.rules.length !== 0) {
52
- const errors = [];
53
- // We replicate the default validation step manually before execution starts.
54
- const typeInfo = (_a = getTypeInfo()) !== null && _a !== void 0 ? _a : new graphql.TypeInfo(args.schema);
55
- const validationContext = new graphql.ValidationContext(args.schema, args.document, typeInfo, e => {
56
- errors.push(e);
57
- });
58
- const visitor = graphql.visitInParallel(validationRulesContext.rules.map(rule => rule(validationContext, args)));
59
- graphql.visit(args.document, graphql.visitWithTypeInfo(typeInfo, visitor));
60
- if (errors.length > 0) {
61
- let result = {
62
- data: null,
63
- errors,
64
- };
65
- if (onValidationFailed) {
66
- onValidationFailed({ args, result, setResult: newResult => (result = newResult) });
67
- }
68
- setResultAndStopExecution(result);
69
- }
70
- }
71
- }
72
- };
73
- }
74
-
75
- function getDirectiveFromAstNode(astNode, names) {
76
- const directives = astNode.directives || [];
77
- const namesArr = Array.isArray(names) ? names : [names];
78
- const authDirective = directives.find(d => namesArr.includes(d.name.value));
79
- return authDirective || null;
80
- }
81
-
82
- const ONE_OF_DIRECTIVE_SDL = /* GraphQL */ `
83
- directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
84
- `;
85
- const OneOfInputObjectsRule = (validationContext, executionArgs) => {
86
- return {
87
- Field: node => {
88
- var _a, _b;
89
- if ((_a = node.arguments) === null || _a === void 0 ? void 0 : _a.length) {
90
- const fieldType = validationContext.getFieldDef();
91
- if (!fieldType) {
92
- return;
93
- }
94
- const values = utils.getArgumentValues(fieldType, node, executionArgs.variableValues || undefined);
95
- const isOneOfFieldType = ((_b = fieldType.extensions) === null || _b === void 0 ? void 0 : _b.oneOf) || (fieldType.astNode && getDirectiveFromAstNode(fieldType.astNode, 'oneOf'));
96
- if (isOneOfFieldType) {
97
- if (Object.keys(values).length !== 1) {
98
- validationContext.reportError(new graphql.GraphQLError(`Exactly one key must be specified for input for field "${fieldType.type.toString()}.${node.name.value}"`, [node]));
99
- }
100
- }
101
- for (const arg of node.arguments) {
102
- const argType = fieldType.args.find(typeArg => typeArg.name === arg.name.value);
103
- if (argType) {
104
- traverseVariables(validationContext, arg, argType.type, values[arg.name.value]);
105
- }
106
- }
107
- }
108
- },
109
- };
110
- };
111
- function getNonNullType(ttype) {
112
- if (ttype instanceof graphql.GraphQLNonNull) {
113
- return ttype.ofType;
114
- }
115
- return ttype;
116
- }
117
- function traverseVariables(validationContext, arg, graphqlType, currentValue) {
118
- var _a;
119
- // if the current value is empty we don't need to traverse deeper
120
- // if it shouldn't be empty, the "original" validation phase should complain.
121
- if (currentValue == null) {
122
- return;
123
- }
124
- const unwrappedType = getNonNullType(graphqlType);
125
- if (graphql.isListType(unwrappedType)) {
126
- if (!Array.isArray(currentValue)) {
127
- // because of graphql type coercion a single object should be treated as an array of one object
128
- currentValue = [currentValue];
129
- }
130
- currentValue.forEach(value => {
131
- traverseVariables(validationContext, arg, unwrappedType.ofType, value);
132
- });
133
- return;
134
- }
135
- if (typeof currentValue !== 'object' || currentValue == null) {
136
- // in case the value is not an object, the "original" validation phase should complain.
137
- return;
138
- }
139
- const inputType = graphql.getNamedType(graphqlType);
140
- const isOneOfInputType = ((_a = inputType.extensions) === null || _a === void 0 ? void 0 : _a.oneOf) || (inputType.astNode && getDirectiveFromAstNode(inputType.astNode, 'oneOf'));
141
- if (isOneOfInputType) {
142
- if (Object.keys(currentValue).length !== 1) {
143
- validationContext.reportError(new graphql.GraphQLError(`Exactly one key must be specified for input type "${inputType.name}"`, [arg]));
144
- }
145
- }
146
- if (inputType instanceof graphql.GraphQLInputObjectType) {
147
- for (const [name, fieldConfig] of Object.entries(inputType.getFields())) {
148
- traverseVariables(validationContext, arg, fieldConfig.type, currentValue[name]);
149
- }
150
- }
151
- }
152
-
153
- exports.ONE_OF_DIRECTIVE_SDL = ONE_OF_DIRECTIVE_SDL;
154
- exports.OneOfInputObjectsRule = OneOfInputObjectsRule;
155
- exports.getDirectiveFromAstNode = getDirectiveFromAstNode;
156
- exports.useExtendedValidation = useExtendedValidation;
package/index.mjs DELETED
@@ -1,149 +0,0 @@
1
- import { TypeInfo, ValidationContext, visitInParallel, visit, visitWithTypeInfo, GraphQLError, isListType, getNamedType, GraphQLInputObjectType, GraphQLNonNull } from 'graphql';
2
- import { getArgumentValues } from '@graphql-tools/utils';
3
-
4
- const symbolExtendedValidationRules = Symbol('extendedValidationContext');
5
- const useExtendedValidation = (options) => {
6
- let schemaTypeInfo;
7
- function getTypeInfo() {
8
- return schemaTypeInfo;
9
- }
10
- return {
11
- onSchemaChange({ schema }) {
12
- schemaTypeInfo = new TypeInfo(schema);
13
- },
14
- onContextBuilding({ context, extendContext }) {
15
- // We initialize the validationRules context in onContextBuilding as onExecute is already too late!
16
- let validationRulesContext = context[symbolExtendedValidationRules];
17
- if (validationRulesContext === undefined) {
18
- validationRulesContext = {
19
- rules: [],
20
- didRun: false,
21
- };
22
- extendContext({
23
- [symbolExtendedValidationRules]: validationRulesContext,
24
- });
25
- }
26
- validationRulesContext.rules.push(...options.rules);
27
- },
28
- onSubscribe: buildHandler('subscribe', getTypeInfo, options.onValidationFailed),
29
- onExecute: buildHandler('execute', getTypeInfo, options.onValidationFailed),
30
- };
31
- };
32
- function buildHandler(name, getTypeInfo, onValidationFailed) {
33
- return function handler({ args, setResultAndStopExecution, }) {
34
- var _a;
35
- // We hook into onExecute/onSubscribe even though this is a validation pattern. The reasoning behind
36
- // it is that hooking right after validation and before execution has started is the
37
- // same as hooking into the validation step. The benefit of this approach is that
38
- // we may use execution context in the validation rules.
39
- const validationRulesContext = args.contextValue[symbolExtendedValidationRules];
40
- if (validationRulesContext === undefined) {
41
- throw new Error('Plugin has not been properly set up. ' +
42
- `The 'contextFactory' function is not invoked and the result has not been passed to '${name}'.`);
43
- }
44
- // we only want to run the extended execution once.
45
- if (validationRulesContext.didRun === false) {
46
- validationRulesContext.didRun = true;
47
- if (validationRulesContext.rules.length !== 0) {
48
- const errors = [];
49
- // We replicate the default validation step manually before execution starts.
50
- const typeInfo = (_a = getTypeInfo()) !== null && _a !== void 0 ? _a : new TypeInfo(args.schema);
51
- const validationContext = new ValidationContext(args.schema, args.document, typeInfo, e => {
52
- errors.push(e);
53
- });
54
- const visitor = visitInParallel(validationRulesContext.rules.map(rule => rule(validationContext, args)));
55
- visit(args.document, visitWithTypeInfo(typeInfo, visitor));
56
- if (errors.length > 0) {
57
- let result = {
58
- data: null,
59
- errors,
60
- };
61
- if (onValidationFailed) {
62
- onValidationFailed({ args, result, setResult: newResult => (result = newResult) });
63
- }
64
- setResultAndStopExecution(result);
65
- }
66
- }
67
- }
68
- };
69
- }
70
-
71
- function getDirectiveFromAstNode(astNode, names) {
72
- const directives = astNode.directives || [];
73
- const namesArr = Array.isArray(names) ? names : [names];
74
- const authDirective = directives.find(d => namesArr.includes(d.name.value));
75
- return authDirective || null;
76
- }
77
-
78
- const ONE_OF_DIRECTIVE_SDL = /* GraphQL */ `
79
- directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
80
- `;
81
- const OneOfInputObjectsRule = (validationContext, executionArgs) => {
82
- return {
83
- Field: node => {
84
- var _a, _b;
85
- if ((_a = node.arguments) === null || _a === void 0 ? void 0 : _a.length) {
86
- const fieldType = validationContext.getFieldDef();
87
- if (!fieldType) {
88
- return;
89
- }
90
- const values = getArgumentValues(fieldType, node, executionArgs.variableValues || undefined);
91
- const isOneOfFieldType = ((_b = fieldType.extensions) === null || _b === void 0 ? void 0 : _b.oneOf) || (fieldType.astNode && getDirectiveFromAstNode(fieldType.astNode, 'oneOf'));
92
- if (isOneOfFieldType) {
93
- if (Object.keys(values).length !== 1) {
94
- validationContext.reportError(new GraphQLError(`Exactly one key must be specified for input for field "${fieldType.type.toString()}.${node.name.value}"`, [node]));
95
- }
96
- }
97
- for (const arg of node.arguments) {
98
- const argType = fieldType.args.find(typeArg => typeArg.name === arg.name.value);
99
- if (argType) {
100
- traverseVariables(validationContext, arg, argType.type, values[arg.name.value]);
101
- }
102
- }
103
- }
104
- },
105
- };
106
- };
107
- function getNonNullType(ttype) {
108
- if (ttype instanceof GraphQLNonNull) {
109
- return ttype.ofType;
110
- }
111
- return ttype;
112
- }
113
- function traverseVariables(validationContext, arg, graphqlType, currentValue) {
114
- var _a;
115
- // if the current value is empty we don't need to traverse deeper
116
- // if it shouldn't be empty, the "original" validation phase should complain.
117
- if (currentValue == null) {
118
- return;
119
- }
120
- const unwrappedType = getNonNullType(graphqlType);
121
- if (isListType(unwrappedType)) {
122
- if (!Array.isArray(currentValue)) {
123
- // because of graphql type coercion a single object should be treated as an array of one object
124
- currentValue = [currentValue];
125
- }
126
- currentValue.forEach(value => {
127
- traverseVariables(validationContext, arg, unwrappedType.ofType, value);
128
- });
129
- return;
130
- }
131
- if (typeof currentValue !== 'object' || currentValue == null) {
132
- // in case the value is not an object, the "original" validation phase should complain.
133
- return;
134
- }
135
- const inputType = getNamedType(graphqlType);
136
- const isOneOfInputType = ((_a = inputType.extensions) === null || _a === void 0 ? void 0 : _a.oneOf) || (inputType.astNode && getDirectiveFromAstNode(inputType.astNode, 'oneOf'));
137
- if (isOneOfInputType) {
138
- if (Object.keys(currentValue).length !== 1) {
139
- validationContext.reportError(new GraphQLError(`Exactly one key must be specified for input type "${inputType.name}"`, [arg]));
140
- }
141
- }
142
- if (inputType instanceof GraphQLInputObjectType) {
143
- for (const [name, fieldConfig] of Object.entries(inputType.getFields())) {
144
- traverseVariables(validationContext, arg, fieldConfig.type, currentValue[name]);
145
- }
146
- }
147
- }
148
-
149
- export { ONE_OF_DIRECTIVE_SDL, OneOfInputObjectsRule, getDirectiveFromAstNode, useExtendedValidation };