@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 +10 -0
- package/cjs/index.js +6 -0
- package/cjs/package.json +1 -0
- package/cjs/plugin.js +71 -0
- package/cjs/rules/one-of.js +77 -0
- package/esm/common.js +6 -0
- package/esm/index.js +3 -0
- package/esm/plugin.js +67 -0
- package/esm/rules/one-of.js +73 -0
- package/package.json +33 -12
- package/{common.d.ts → typings/common.d.ts} +0 -0
- package/typings/index.d.ts +3 -0
- package/{plugin.d.ts → typings/plugin.d.ts} +1 -1
- package/{rules → typings/rules}/one-of.d.ts +1 -1
- package/README.md +0 -135
- package/index.d.ts +0 -3
- package/index.js +0 -156
- package/index.mjs +0 -149
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);
|
package/cjs/package.json
ADDED
|
@@ -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
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-
|
|
3
|
+
"version": "1.7.0-alpha-385b86b.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"peerDependencies": {
|
|
6
|
-
"@envelop/core": "2.4.0-alpha-
|
|
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/
|
|
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.
|
|
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":
|
|
28
|
-
|
|
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":
|
|
32
|
-
|
|
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
|
|
@@ -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
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 };
|