Skip to content

Commit e5744fc

Browse files
committed
parse fragment signatures when building execution context and reuse getArgumentValues
to completely reuse getArgumentValues without greater changes requires omiited arguments to be present as undefined needs further investigation
1 parent b6a4300 commit e5744fc

File tree

6 files changed

+81
-98
lines changed

6 files changed

+81
-98
lines changed

src/execution/__tests__/executor-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1172,7 +1172,7 @@ describe('Execute: Handles basic execution tasks', () => {
11721172
const result = executeSync({ schema, document });
11731173
expect(result).to.deep.equal({
11741174
data: {
1175-
field: '{ a: true, c: false, e: 0 }',
1175+
field: '{ a: true, b: undefined, c: false, d: undefined, e: 0 }',
11761176
},
11771177
});
11781178
});

src/execution/__tests__/variables-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ describe('Execute: Handles inputs', () => {
579579

580580
expect(result).to.deep.equal({
581581
data: {
582-
fieldWithNullableStringInput: null,
582+
fieldWithNullableStringInput: 'undefined',
583583
},
584584
});
585585
});

src/execution/collectFields.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import type {
1313
import { OperationTypeNode } from '../language/ast.js';
1414
import { Kind } from '../language/kinds.js';
1515

16-
import type { GraphQLObjectType } from '../type/definition.js';
16+
import type {
17+
GraphQLInputType,
18+
GraphQLObjectType,
19+
} from '../type/definition.js';
1720
import { isAbstractType } from '../type/definition.js';
1821
import {
1922
GraphQLDeferDirective,
@@ -24,7 +27,7 @@ import type { GraphQLSchema } from '../type/schema.js';
2427

2528
import { typeFromAST } from '../utilities/typeFromAST.js';
2629

27-
import { getArgumentValuesFromSpread, getDirectiveValues } from './values.js';
30+
import { getArgumentValues, getDirectiveValues } from './values.js';
2831

2932
export interface DeferUsage {
3033
label: string | undefined;
@@ -41,9 +44,18 @@ export type FieldGroup = ReadonlyArray<FieldDetails>;
4144

4245
export type GroupedFieldSet = ReadonlyMap<string, FieldGroup>;
4346

47+
export interface GraphQLFragmentSignature {
48+
name: string;
49+
type: GraphQLInputType;
50+
defaultValue: unknown;
51+
}
52+
4453
interface CollectFieldsContext {
4554
schema: GraphQLSchema;
46-
fragments: ObjMap<FragmentDefinitionNode>;
55+
fragments: ObjMap<{
56+
definition: FragmentDefinitionNode;
57+
signatures: ReadonlyArray<GraphQLFragmentSignature>;
58+
}>;
4759
operation: OperationDefinitionNode;
4860
runtimeType: GraphQLObjectType;
4961
visitedFragmentNames: Set<string>;
@@ -62,7 +74,10 @@ interface CollectFieldsContext {
6274
*/
6375
export function collectFields(
6476
schema: GraphQLSchema,
65-
fragments: ObjMap<FragmentDefinitionNode>,
77+
fragments: ObjMap<{
78+
definition: FragmentDefinitionNode;
79+
signatures: ReadonlyArray<GraphQLFragmentSignature>;
80+
}>,
6681
variableValues: { [variable: string]: unknown },
6782
runtimeType: GraphQLObjectType,
6883
operation: OperationDefinitionNode,
@@ -104,7 +119,10 @@ export function collectFields(
104119
// eslint-disable-next-line max-params
105120
export function collectSubfields(
106121
schema: GraphQLSchema,
107-
fragments: ObjMap<FragmentDefinitionNode>,
122+
fragments: ObjMap<{
123+
definition: FragmentDefinitionNode;
124+
signatures: ReadonlyArray<GraphQLFragmentSignature>;
125+
}>,
108126
variableValues: { [variable: string]: unknown },
109127
operation: OperationDefinitionNode,
110128
returnType: GraphQLObjectType,
@@ -232,7 +250,7 @@ function collectFieldsImpl(
232250
const fragment = fragments[fragmentName];
233251
if (
234252
fragment == null ||
235-
!doesFragmentConditionMatch(schema, fragment, runtimeType)
253+
!doesFragmentConditionMatch(schema, fragment.definition, runtimeType)
236254
) {
237255
continue;
238256
}
@@ -246,11 +264,10 @@ function collectFieldsImpl(
246264
// scope as that variable can still get used in spreads later on in the selectionSet.
247265
// - when a value is passed in through the fragment-spread we need to copy over the key-value
248266
// into our variable-values.
249-
context.localVariableValues = fragment.variableDefinitions
250-
? getArgumentValuesFromSpread(
267+
context.localVariableValues = fragment.definition.variableDefinitions
268+
? getArgumentValues(
251269
selection,
252-
schema,
253-
fragment.variableDefinitions,
270+
fragment.signatures,
254271
variableValues,
255272
context.localVariableValues,
256273
)
@@ -260,7 +277,7 @@ function collectFieldsImpl(
260277
visitedFragmentNames.add(fragmentName);
261278
collectFieldsImpl(
262279
context,
263-
fragment.selectionSet,
280+
fragment.definition.selectionSet,
264281
groupedFieldSet,
265282
newDeferUsages,
266283
deferUsage,
@@ -269,7 +286,7 @@ function collectFieldsImpl(
269286
newDeferUsages.push(newDeferUsage);
270287
collectFieldsImpl(
271288
context,
272-
fragment.selectionSet,
289+
fragment.definition.selectionSet,
273290
groupedFieldSet,
274291
newDeferUsages,
275292
newDeferUsage,

src/execution/execute.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { isAsyncIterable } from '../jsutils/isAsyncIterable.js';
55
import { isIterableObject } from '../jsutils/isIterableObject.js';
66
import { isObjectLike } from '../jsutils/isObjectLike.js';
77
import { isPromise } from '../jsutils/isPromise.js';
8+
import { mapValue } from '../jsutils/mapValue.js';
89
import type { Maybe } from '../jsutils/Maybe.js';
910
import { memoize3 } from '../jsutils/memoize3.js';
1011
import type { ObjMap } from '../jsutils/ObjMap.js';
@@ -39,6 +40,7 @@ import type {
3940
} from '../type/definition.js';
4041
import {
4142
isAbstractType,
43+
isInputType,
4244
isLeafType,
4345
isListType,
4446
isNonNullType,
@@ -48,11 +50,15 @@ import { GraphQLStreamDirective } from '../type/directives.js';
4850
import type { GraphQLSchema } from '../type/schema.js';
4951
import { assertValidSchema } from '../type/validate.js';
5052

53+
import { typeFromAST } from '../utilities/typeFromAST.js';
54+
import { valueFromAST } from '../utilities/valueFromAST.js';
55+
5156
import type { DeferUsageSet, ExecutionPlan } from './buildExecutionPlan.js';
5257
import { buildExecutionPlan } from './buildExecutionPlan.js';
5358
import type {
5459
DeferUsage,
5560
FieldGroup,
61+
GraphQLFragmentSignature,
5662
GroupedFieldSet,
5763
} from './collectFields.js';
5864
import {
@@ -132,7 +138,10 @@ const collectSubfields = memoize3(
132138
*/
133139
export interface ExecutionContext {
134140
schema: GraphQLSchema;
135-
fragments: ObjMap<FragmentDefinitionNode>;
141+
fragments: ObjMap<{
142+
definition: FragmentDefinitionNode;
143+
signatures: ReadonlyArray<GraphQLFragmentSignature>;
144+
}>;
136145
rootValue: unknown;
137146
contextValue: unknown;
138147
operation: OperationDefinitionNode;
@@ -445,7 +454,10 @@ export function buildExecutionContext(
445454
assertValidSchema(schema);
446455

447456
let operation: OperationDefinitionNode | undefined;
448-
const fragments: ObjMap<FragmentDefinitionNode> = Object.create(null);
457+
const fragments: ObjMap<{
458+
definition: FragmentDefinitionNode;
459+
signatures: ReadonlyArray<GraphQLFragmentSignature>;
460+
}> = Object.create(null);
449461
for (const definition of document.definitions) {
450462
switch (definition.kind) {
451463
case Kind.OPERATION_DEFINITION:
@@ -462,9 +474,24 @@ export function buildExecutionContext(
462474
operation = definition;
463475
}
464476
break;
465-
case Kind.FRAGMENT_DEFINITION:
466-
fragments[definition.name.value] = definition;
477+
case Kind.FRAGMENT_DEFINITION: {
478+
const signatures: Array<GraphQLFragmentSignature> = [];
479+
if (definition.variableDefinitions) {
480+
for (const varDef of definition.variableDefinitions) {
481+
const varType = typeFromAST(schema, varDef.type);
482+
if (isInputType(varType)) {
483+
const signature: GraphQLFragmentSignature = {
484+
name: varDef.variable.name.value,
485+
type: varType,
486+
defaultValue: valueFromAST(varDef.defaultValue, varType),
487+
};
488+
signatures.push(signature);
489+
}
490+
}
491+
}
492+
fragments[definition.name.value] = { definition, signatures };
467493
break;
494+
}
468495
default:
469496
// ignore non-executable definitions
470497
}
@@ -807,7 +834,10 @@ export function buildResolveInfo(
807834
parentType,
808835
path,
809836
schema: exeContext.schema,
810-
fragments: exeContext.fragments,
837+
fragments: mapValue(
838+
exeContext.fragments,
839+
(fragment) => fragment.definition,
840+
),
811841
rootValue: exeContext.rootValue,
812842
operation: exeContext.operation,
813843
variableValues: exeContext.variableValues,

src/execution/values.ts

Lines changed: 6 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import type { GraphQLSchema } from '../type/schema.js';
2222
import { coerceInputValue } from '../utilities/coerceInputValue.js';
2323
import { typeFromAST } from '../utilities/typeFromAST.js';
2424
import { valueFromAST } from '../utilities/valueFromAST.js';
25-
import { valueFromASTUntyped } from '../utilities/valueFromASTUntyped.js';
25+
26+
import type { GraphQLFragmentSignature } from './collectFields.js';
2627

2728
type CoercedVariableValues =
2829
| { errors: ReadonlyArray<GraphQLError>; coerced?: never }
@@ -159,8 +160,8 @@ function coerceVariableValues(
159160
* Object prototype.
160161
*/
161162
export function getArgumentValues(
162-
node: FieldNode | DirectiveNode,
163-
argDefs: ReadonlyArray<GraphQLArgument>,
163+
node: FieldNode | DirectiveNode | FragmentSpreadNode,
164+
argDefs: ReadonlyArray<GraphQLArgument | GraphQLFragmentSignature>,
164165
variableValues: Maybe<ObjMap<unknown>>,
165166
fragmentArgValues?: Maybe<ObjMap<unknown>>,
166167
): { [argument: string]: unknown } {
@@ -183,6 +184,8 @@ export function getArgumentValues(
183184
'was not provided.',
184185
{ nodes: node },
185186
);
187+
} else {
188+
coercedValues[name] = undefined;
186189
}
187190
continue;
188191
}
@@ -248,79 +251,6 @@ export function getArgumentValues(
248251
return coercedValues;
249252
}
250253

251-
export function getArgumentValuesFromSpread(
252-
/** NOTE: For error annotations only */
253-
node: FragmentSpreadNode,
254-
schema: GraphQLSchema,
255-
fragmentVarDefs: ReadonlyArray<VariableDefinitionNode>,
256-
variableValues: Maybe<ObjMap<unknown>>,
257-
fragmentArgValues?: Maybe<ObjMap<unknown>>,
258-
): { [argument: string]: unknown } {
259-
const coercedValues: { [argument: string]: unknown } = {};
260-
const argNodeMap = new Map(
261-
node.arguments?.map((arg) => [arg.name.value, arg]),
262-
);
263-
264-
for (const varDef of fragmentVarDefs) {
265-
const name = varDef.variable.name.value;
266-
const argType = typeFromAST(schema, varDef.type);
267-
const argumentNode = argNodeMap.get(name);
268-
269-
if (argumentNode == null) {
270-
if (varDef.defaultValue !== undefined) {
271-
coercedValues[name] = valueFromASTUntyped(varDef.defaultValue);
272-
} else if (isNonNullType(argType)) {
273-
throw new GraphQLError(
274-
`Argument "${name}" of required type "${inspect(argType)}" ` +
275-
'was not provided.',
276-
{ nodes: node },
277-
);
278-
} else {
279-
coercedValues[name] = undefined;
280-
}
281-
continue;
282-
}
283-
284-
const valueNode = argumentNode.value;
285-
286-
let hasValue = valueNode.kind !== Kind.NULL;
287-
if (valueNode.kind === Kind.VARIABLE) {
288-
const variableName = valueNode.name.value;
289-
if (
290-
fragmentArgValues != null &&
291-
Object.hasOwn(fragmentArgValues, variableName)
292-
) {
293-
hasValue = fragmentArgValues[variableName] != null;
294-
} else if (
295-
variableValues != null &&
296-
Object.hasOwn(variableValues, variableName)
297-
) {
298-
hasValue = variableValues[variableName] != null;
299-
}
300-
}
301-
302-
if (!hasValue && isNonNullType(argType)) {
303-
throw new GraphQLError(
304-
`Argument "${name}" of non-null type "${inspect(argType)}" ` +
305-
'must not be null.',
306-
{ nodes: valueNode },
307-
);
308-
}
309-
310-
// TODO: Make this follow the spec more closely
311-
let coercedValue;
312-
if (argType && isInputType(argType)) {
313-
coercedValue = valueFromAST(valueNode, argType, {
314-
...variableValues,
315-
...fragmentArgValues,
316-
});
317-
}
318-
319-
coercedValues[name] = coercedValue;
320-
}
321-
return coercedValues;
322-
}
323-
324254
/**
325255
* Prepares an object map of argument values given a directive definition
326256
* and a AST node which may contain directives. Optionally also accepts a map

src/validation/rules/SingleFieldSubscriptionsRule.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import type {
1010
import { Kind } from '../../language/kinds.js';
1111
import type { ASTVisitor } from '../../language/visitor.js';
1212

13-
import type { FieldGroup } from '../../execution/collectFields.js';
13+
import type {
14+
FieldGroup,
15+
GraphQLFragmentSignature,
16+
} from '../../execution/collectFields.js';
1417
import { collectFields } from '../../execution/collectFields.js';
1518

1619
import type { ValidationContext } from '../ValidationContext.js';
@@ -41,10 +44,13 @@ export function SingleFieldSubscriptionsRule(
4144
[variable: string]: any;
4245
} = Object.create(null);
4346
const document = context.getDocument();
44-
const fragments: ObjMap<FragmentDefinitionNode> = Object.create(null);
47+
const fragments: ObjMap<{
48+
definition: FragmentDefinitionNode;
49+
signatures: ReadonlyArray<GraphQLFragmentSignature>;
50+
}> = Object.create(null);
4551
for (const definition of document.definitions) {
4652
if (definition.kind === Kind.FRAGMENT_DEFINITION) {
47-
fragments[definition.name.value] = definition;
53+
fragments[definition.name.value] = { definition, signatures: [] };
4854
}
4955
}
5056
const { groupedFieldSet } = collectFields(

0 commit comments

Comments
 (0)