forked from graphql/graphql-js
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathVariablesInAllowedPositionRule.ts
137 lines (126 loc) · 4.5 KB
/
VariablesInAllowedPositionRule.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { inspect } from '../../jsutils/inspect.js';
import type { Maybe } from '../../jsutils/Maybe.js';
import { GraphQLError } from '../../error/GraphQLError.js';
import type { ValueNode, VariableDefinitionNode } from '../../language/ast.js';
import { Kind } from '../../language/kinds.js';
import type { ASTVisitor } from '../../language/visitor.js';
import type {
GraphQLDefaultValueUsage,
GraphQLType,
} from '../../type/definition.js';
import {
isInputObjectType,
isNonNullType,
isNullableType,
} from '../../type/definition.js';
import type { GraphQLSchema } from '../../type/schema.js';
import { isTypeSubTypeOf } from '../../utilities/typeComparators.js';
import { typeFromAST } from '../../utilities/typeFromAST.js';
import type { ValidationContext } from '../ValidationContext.js';
/**
* Variables in allowed position
*
* Variable usages must be compatible with the arguments they are passed to.
*
* See https://spec.graphql.org/draft/#sec-All-Variable-Usages-are-Allowed
*/
export function VariablesInAllowedPositionRule(
context: ValidationContext,
): ASTVisitor {
let varDefMap: Map<string, VariableDefinitionNode>;
return {
OperationDefinition: {
enter() {
varDefMap = new Map();
},
leave(operation) {
const usages = context.getRecursiveVariableUsages(operation);
for (const {
node,
type,
parentType,
defaultValue,
fragmentVariableDefinition,
} of usages) {
const varName = node.name.value;
let varDef = fragmentVariableDefinition;
if (!varDef) {
varDef = varDefMap.get(varName);
}
if (varDef && type) {
// A var type is allowed if it is the same or more strict (e.g. is
// a subtype of) than the expected type. It can be more strict if
// the variable type is non-null when the expected type is nullable.
// If both are list types, the variable item type can be more strict
// than the expected item type (contravariant).
const schema = context.getSchema();
const varType = typeFromAST(schema, varDef.type);
if (
varType &&
!allowedVariableUsage(
schema,
varType,
varDef.defaultValue,
type,
defaultValue,
)
) {
const varTypeStr = inspect(varType);
const typeStr = inspect(type);
context.reportError(
new GraphQLError(
`Variable "$${varName}" of type "${varTypeStr}" used in position expecting type "${typeStr}".`,
{ nodes: [varDef, node] },
),
);
}
if (
isInputObjectType(parentType) &&
parentType.isOneOf &&
isNullableType(varType)
) {
const varTypeStr = inspect(varType);
const parentTypeStr = inspect(parentType);
context.reportError(
new GraphQLError(
`Variable "$${varName}" is of type "${varTypeStr}" but must be non-nullable to be used for OneOf Input Object "${parentTypeStr}".`,
{ nodes: [varDef, node] },
),
);
}
}
}
},
},
VariableDefinition(node) {
varDefMap.set(node.variable.name.value, node);
},
};
}
/**
* Returns true if the variable is allowed in the location it was found,
* including considering if default values exist for either the variable
* or the location at which it is located.
*
* OneOf Input Object Type fields are considered separately above to
* provide a more descriptive error message.
*/
function allowedVariableUsage(
schema: GraphQLSchema,
varType: GraphQLType,
varDefaultValue: Maybe<ValueNode>,
locationType: GraphQLType,
locationDefaultValue: GraphQLDefaultValueUsage | undefined,
): boolean {
if (isNonNullType(locationType) && !isNonNullType(varType)) {
const hasNonNullVariableDefaultValue =
varDefaultValue != null && varDefaultValue.kind !== Kind.NULL;
const hasLocationDefaultValue = locationDefaultValue !== undefined;
if (!hasNonNullVariableDefaultValue && !hasLocationDefaultValue) {
return false;
}
const nullableLocationType = locationType.ofType;
return isTypeSubTypeOf(schema, varType, nullableLocationType);
}
return isTypeSubTypeOf(schema, varType, locationType);
}