Skip to content

Commit 14d3f8d

Browse files
committed
Preserve sources of variable values
Depends on #3074 By way of introducing type `VariableValues`, allows `getVariableValues` to return both the coerced values as well as the original sources, which are then made available in `ExecutionContext`. While variable sources are not used directly here, they're used directly in #3065. This PR is pulled out as a pre-req to aid review
1 parent dd31191 commit 14d3f8d

11 files changed

+170
-83
lines changed

src/execution/execute.d.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {
2323
GraphQLObjectType,
2424
} from '../type/definition';
2525

26+
import { VariableValues } from './values';
27+
2628
/**
2729
* Terminology
2830
*
@@ -55,7 +57,7 @@ export interface ExecutionContext {
5557
contextValue: unknown;
5658
fragments: ObjMap<FragmentDefinitionNode>;
5759
operation: OperationDefinitionNode;
58-
variableValues: { [key: string]: unknown };
60+
variableValues: VariableValues;
5961
fieldResolver: GraphQLFieldResolver<any, any>;
6062
typeResolver: GraphQLTypeResolver<any, any>;
6163
errors: Array<GraphQLError>;

src/execution/execute.js

+7-6
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
import { typeFromAST } from '../utilities/typeFromAST';
6161
import { getOperationRootType } from '../utilities/getOperationRootType';
6262

63+
import type { VariableValues } from './values';
6364
import {
6465
getVariableValues,
6566
getArgumentValues,
@@ -98,7 +99,7 @@ export type ExecutionContext = {|
9899
rootValue: mixed,
99100
contextValue: mixed,
100101
operation: OperationDefinitionNode,
101-
variableValues: { [variable: string]: mixed, ... },
102+
variableValues: VariableValues,
102103
fieldResolver: GraphQLFieldResolver<any, any>,
103104
typeResolver: GraphQLTypeResolver<any, any>,
104105
errors: Array<GraphQLError>,
@@ -295,15 +296,15 @@ export function buildExecutionContext(
295296
// istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
296297
const variableDefinitions = operation.variableDefinitions ?? [];
297298

298-
const coercedVariableValues = getVariableValues(
299+
const variableValuesOrErrors = getVariableValues(
299300
schema,
300301
variableDefinitions,
301302
rawVariableValues ?? {},
302303
{ maxErrors: 50 },
303304
);
304305

305-
if (coercedVariableValues.errors) {
306-
return coercedVariableValues.errors;
306+
if (variableValuesOrErrors.errors) {
307+
return variableValuesOrErrors.errors;
307308
}
308309

309310
// $FlowFixMe[incompatible-return]
@@ -313,7 +314,7 @@ export function buildExecutionContext(
313314
rootValue,
314315
contextValue,
315316
operation,
316-
variableValues: coercedVariableValues.coerced,
317+
variableValues: variableValuesOrErrors.variableValues,
317318
fieldResolver: fieldResolver ?? defaultFieldResolver,
318319
typeResolver: typeResolver ?? defaultTypeResolver,
319320
errors: [],
@@ -679,7 +680,7 @@ export function buildResolveInfo(
679680
fragments: exeContext.fragments,
680681
rootValue: exeContext.rootValue,
681682
operation: exeContext.operation,
682-
variableValues: exeContext.variableValues,
683+
variableValues: exeContext.variableValues.coerced,
683684
};
684685
}
685686

src/execution/values.d.ts

+17-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Maybe } from '../jsutils/Maybe';
2-
import { ObjMap } from '../jsutils/ObjMap';
2+
import { ReadOnlyObjMap } from '../jsutils/ObjMap';
33

44
import { GraphQLError } from '../error/GraphQLError';
55
import {
@@ -10,11 +10,20 @@ import {
1010

1111
import { GraphQLDirective } from '../type/directives';
1212
import { GraphQLSchema } from '../type/schema';
13-
import { GraphQLField } from '../type/definition';
13+
import { GraphQLField, GraphQLInputType } from '../type/definition';
1414

15-
type CoercedVariableValues =
16-
| { errors: ReadonlyArray<GraphQLError>; coerced?: never }
17-
| { errors?: never; coerced: { [key: string]: unknown } };
15+
export type VariableValues = {
16+
readonly sources: ReadOnlyObjMap<{
17+
readonly variable: VariableDefinitionNode;
18+
readonly type: GraphQLInputType;
19+
readonly value: unknown;
20+
}>;
21+
readonly coerced: ReadOnlyObjMap<unknown>;
22+
};
23+
24+
type VariableValuesOrErrors =
25+
| { variableValues: VariableValues; errors?: never }
26+
| { errors: ReadonlyArray<GraphQLError>; variableValues?: never };
1827

1928
/**
2029
* Prepares an object map of variableValues of the correct type based on the
@@ -30,7 +39,7 @@ export function getVariableValues(
3039
varDefNodes: ReadonlyArray<VariableDefinitionNode>,
3140
inputs: { [key: string]: unknown },
3241
options?: { maxErrors?: number },
33-
): CoercedVariableValues;
42+
): VariableValuesOrErrors;
3443

3544
/**
3645
* Prepares an object map of argument values given a list of argument
@@ -43,7 +52,7 @@ export function getVariableValues(
4352
export function getArgumentValues(
4453
def: GraphQLField<unknown, unknown> | GraphQLDirective,
4554
node: FieldNode | DirectiveNode,
46-
variableValues?: Maybe<ObjMap<unknown>>,
55+
variableValues?: Maybe<VariableValues>,
4756
): { [key: string]: unknown };
4857

4958
/**
@@ -62,5 +71,5 @@ export function getDirectiveValues(
6271
node: {
6372
readonly directives?: ReadonlyArray<DirectiveNode>;
6473
},
65-
variableValues?: Maybe<ObjMap<unknown>>,
74+
variableValues?: Maybe<VariableValues>,
6675
): undefined | { [key: string]: unknown };

src/execution/values.js

+38-24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { ObjMap } from '../jsutils/ObjMap';
2-
import { keyMap } from '../jsutils/keyMap';
1+
import type { ReadOnlyObjMap, ReadOnlyObjMapLike } from '../jsutils/ObjMap';
32
import { inspect } from '../jsutils/inspect';
3+
import { keyMap } from '../jsutils/keyMap';
44
import { printPathArray } from '../jsutils/printPathArray';
55

66
import { GraphQLError } from '../error/GraphQLError';
@@ -14,7 +14,7 @@ import { Kind } from '../language/kinds';
1414
import { print } from '../language/printer';
1515

1616
import type { GraphQLSchema } from '../type/schema';
17-
import type { GraphQLField } from '../type/definition';
17+
import type { GraphQLInputType, GraphQLField } from '../type/definition';
1818
import type { GraphQLDirective } from '../type/directives';
1919
import { isInputType, isNonNullType } from '../type/definition';
2020

@@ -25,9 +25,18 @@ import {
2525
coerceDefaultValue,
2626
} from '../utilities/coerceInputValue';
2727

28-
type CoercedVariableValues =
29-
| {| errors: $ReadOnlyArray<GraphQLError> |}
30-
| {| coerced: { [variable: string]: mixed, ... } |};
28+
export type VariableValues = {|
29+
+sources: ReadOnlyObjMap<{|
30+
+variable: VariableDefinitionNode,
31+
+type: GraphQLInputType,
32+
+value: mixed,
33+
|}>,
34+
+coerced: ReadOnlyObjMap<mixed>,
35+
|};
36+
37+
type VariableValuesOrErrors =
38+
| {| variableValues: VariableValues |}
39+
| {| errors: $ReadOnlyArray<GraphQLError> |};
3140

3241
/**
3342
* Prepares an object map of variableValues of the correct type based on the
@@ -43,13 +52,13 @@ type CoercedVariableValues =
4352
export function getVariableValues(
4453
schema: GraphQLSchema,
4554
varDefNodes: $ReadOnlyArray<VariableDefinitionNode>,
46-
inputs: { +[variable: string]: mixed, ... },
55+
inputs: ReadOnlyObjMapLike<mixed>,
4756
options?: {| maxErrors?: number |},
48-
): CoercedVariableValues {
57+
): VariableValuesOrErrors {
4958
const errors = [];
5059
const maxErrors = options?.maxErrors;
5160
try {
52-
const coerced = coerceVariableValues(
61+
const variableValues = coerceVariableValues(
5362
schema,
5463
varDefNodes,
5564
inputs,
@@ -64,7 +73,7 @@ export function getVariableValues(
6473
);
6574

6675
if (errors.length === 0) {
67-
return { coerced };
76+
return { variableValues };
6877
}
6978
} catch (error) {
7079
errors.push(error);
@@ -76,10 +85,11 @@ export function getVariableValues(
7685
function coerceVariableValues(
7786
schema: GraphQLSchema,
7887
varDefNodes: $ReadOnlyArray<VariableDefinitionNode>,
79-
inputs: { +[variable: string]: mixed, ... },
88+
inputs: ReadOnlyObjMapLike<mixed>,
8089
onError: (error: GraphQLError) => void,
81-
): { [variable: string]: mixed, ... } {
82-
const coercedValues = {};
90+
): VariableValues {
91+
const sources = Object.create(null);
92+
const coerced = Object.create(null);
8393
for (const varDefNode of varDefNodes) {
8494
const varName = varDefNode.variable.name.value;
8595
const varType = typeFromAST(schema, varDefNode.type);
@@ -97,11 +107,14 @@ function coerceVariableValues(
97107
}
98108

99109
if (!hasOwnProperty(inputs, varName)) {
100-
if (varDefNode.defaultValue) {
101-
coercedValues[varName] = coerceInputLiteral(
102-
varDefNode.defaultValue,
103-
varType,
104-
);
110+
const defaultValue = varDefNode.defaultValue;
111+
if (defaultValue) {
112+
sources[varName] = {
113+
variable: varDefNode,
114+
type: varType,
115+
value: undefined,
116+
};
117+
coerced[varName] = coerceInputLiteral(defaultValue, varType);
105118
} else if (isNonNullType(varType)) {
106119
const varTypeStr = inspect(varType);
107120
onError(
@@ -126,7 +139,8 @@ function coerceVariableValues(
126139
continue;
127140
}
128141

129-
coercedValues[varName] = coerceInputValue(
142+
sources[varName] = { variable: varDefNode, type: varType, value };
143+
coerced[varName] = coerceInputValue(
130144
value,
131145
varType,
132146
(path, invalidValue, error) => {
@@ -149,7 +163,7 @@ function coerceVariableValues(
149163
);
150164
}
151165

152-
return coercedValues;
166+
return { sources, coerced };
153167
}
154168

155169
/**
@@ -165,7 +179,7 @@ function coerceVariableValues(
165179
export function getArgumentValues(
166180
def: GraphQLField<mixed, mixed> | GraphQLDirective,
167181
node: FieldNode | DirectiveNode,
168-
variableValues?: ?ObjMap<mixed>,
182+
variableValues?: ?VariableValues,
169183
): { [argument: string]: mixed, ... } {
170184
const coercedValues = {};
171185

@@ -201,7 +215,7 @@ export function getArgumentValues(
201215
const variableName = valueNode.name.value;
202216
if (
203217
variableValues == null ||
204-
!hasOwnProperty(variableValues, variableName)
218+
variableValues.coerced[variableName] === undefined
205219
) {
206220
if (argDef.defaultValue) {
207221
coercedValues[name] = coerceDefaultValue(
@@ -217,7 +231,7 @@ export function getArgumentValues(
217231
}
218232
continue;
219233
}
220-
isNull = variableValues[variableName] == null;
234+
isNull = variableValues.coerced[variableName] == null;
221235
}
222236

223237
if (isNull && isNonNullType(argType)) {
@@ -257,7 +271,7 @@ export function getArgumentValues(
257271
export function getDirectiveValues(
258272
directiveDef: GraphQLDirective,
259273
node: { +directives?: $ReadOnlyArray<DirectiveNode>, ... },
260-
variableValues?: ?ObjMap<mixed>,
274+
variableValues?: ?VariableValues,
261275
): void | { [argument: string]: mixed, ... } {
262276
// istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
263277
const directiveNode = node.directives?.find(

src/type/definition.d.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Maybe } from '../jsutils/Maybe';
55

66
import { PromiseOrValue } from '../jsutils/PromiseOrValue';
77
import { Path } from '../jsutils/Path';
8-
import { ObjMap } from '../jsutils/ObjMap';
8+
import { ObjMap, ReadOnlyObjMap } from '../jsutils/ObjMap';
99

1010
import {
1111
ScalarTypeDefinitionNode,
@@ -345,7 +345,7 @@ export type GraphQLScalarValueParser<TInternal> = (
345345
) => Maybe<TInternal>;
346346
export type GraphQLScalarLiteralParser<TInternal> = (
347347
valueNode: ValueNode,
348-
variables: Maybe<ObjMap<unknown>>,
348+
variables: Maybe<ReadOnlyObjMap<unknown>>,
349349
) => Maybe<TInternal>;
350350

351351
export interface GraphQLScalarTypeConfig<TInternal, TExternal> {
@@ -487,7 +487,7 @@ export interface GraphQLResolveInfo {
487487
readonly fragments: ObjMap<FragmentDefinitionNode>;
488488
readonly rootValue: unknown;
489489
readonly operation: OperationDefinitionNode;
490-
readonly variableValues: { [variableName: string]: unknown };
490+
readonly variableValues: ReadOnlyObjMap<unknown>;
491491
}
492492

493493
/**
@@ -789,7 +789,7 @@ export class GraphQLEnumType {
789789
parseValue(value: unknown): Maybe<any>;
790790
parseLiteral(
791791
valueNode: ValueNode,
792-
_variables: Maybe<ObjMap<unknown>>,
792+
_variables: Maybe<ReadOnlyObjMap<unknown>>,
793793
): Maybe<any>;
794794

795795
toConfig(): GraphQLEnumTypeConfig & {

src/type/definition.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ export type GraphQLScalarValueParser<TInternal> = (
641641
642642
export type GraphQLScalarLiteralParser<TInternal> = (
643643
valueNode: ValueNode,
644-
variables: ?ObjMap<mixed>,
644+
variables: ?ReadOnlyObjMap<mixed>,
645645
) => ?TInternal;
646646
647647
export type GraphQLScalarTypeConfig<TInternal, TExternal> = {|
@@ -909,7 +909,7 @@ export type GraphQLResolveInfo = {|
909909
+fragments: ObjMap<FragmentDefinitionNode>,
910910
+rootValue: mixed,
911911
+operation: OperationDefinitionNode,
912-
+variableValues: { [variable: string]: mixed, ... },
912+
+variableValues: ReadOnlyObjMap<mixed>,
913913
|};
914914

915915
export type GraphQLFieldConfig<
@@ -1351,7 +1351,10 @@ export class GraphQLEnumType /* <T> */ {
13511351
return enumValue.value;
13521352
}
13531353

1354-
parseLiteral(valueNode: ValueNode, _variables: ?ObjMap<mixed>): ?any /* T */ {
1354+
parseLiteral(
1355+
valueNode: ValueNode,
1356+
_variables: ?ReadOnlyObjMap<mixed>,
1357+
): ?any /* T */ {
13551358
// Note: variables will be resolved to a value before calling this function.
13561359
if (valueNode.kind !== Kind.ENUM) {
13571360
const valueStr = print(valueNode);

0 commit comments

Comments
 (0)