Skip to content

Commit dab4f44

Browse files
authored
refactor: collectFields to separate utility (#3187)
1 parent 40db639 commit dab4f44

File tree

4 files changed

+175
-170
lines changed

4 files changed

+175
-170
lines changed

src/execution/collectFields.ts

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import type { ObjMap } from '../jsutils/ObjMap';
2+
3+
import type {
4+
SelectionSetNode,
5+
FieldNode,
6+
FragmentSpreadNode,
7+
InlineFragmentNode,
8+
FragmentDefinitionNode,
9+
} from '../language/ast';
10+
import { Kind } from '../language/kinds';
11+
12+
import type { GraphQLSchema } from '../type/schema';
13+
import type { GraphQLObjectType } from '../type/definition';
14+
import {
15+
GraphQLIncludeDirective,
16+
GraphQLSkipDirective,
17+
} from '../type/directives';
18+
import { isAbstractType } from '../type/definition';
19+
20+
import { typeFromAST } from '../utilities/typeFromAST';
21+
22+
import { getDirectiveValues } from './values';
23+
24+
/**
25+
* Given a selectionSet, adds all of the fields in that selection to
26+
* the passed in map of fields, and returns it at the end.
27+
*
28+
* CollectFields requires the "runtime type" of an object. For a field which
29+
* returns an Interface or Union type, the "runtime type" will be the actual
30+
* Object type returned by that field.
31+
*
32+
* @internal
33+
*/
34+
export function collectFields(
35+
schema: GraphQLSchema,
36+
fragments: ObjMap<FragmentDefinitionNode>,
37+
variableValues: { [variable: string]: unknown },
38+
runtimeType: GraphQLObjectType,
39+
selectionSet: SelectionSetNode,
40+
fields: Map<string, Array<FieldNode>>,
41+
visitedFragmentNames: Set<string>,
42+
): Map<string, ReadonlyArray<FieldNode>> {
43+
for (const selection of selectionSet.selections) {
44+
switch (selection.kind) {
45+
case Kind.FIELD: {
46+
if (!shouldIncludeNode(variableValues, selection)) {
47+
continue;
48+
}
49+
const name = getFieldEntryKey(selection);
50+
const fieldList = fields.get(name);
51+
if (fieldList !== undefined) {
52+
fieldList.push(selection);
53+
} else {
54+
fields.set(name, [selection]);
55+
}
56+
break;
57+
}
58+
case Kind.INLINE_FRAGMENT: {
59+
if (
60+
!shouldIncludeNode(variableValues, selection) ||
61+
!doesFragmentConditionMatch(schema, selection, runtimeType)
62+
) {
63+
continue;
64+
}
65+
collectFields(
66+
schema,
67+
fragments,
68+
variableValues,
69+
runtimeType,
70+
selection.selectionSet,
71+
fields,
72+
visitedFragmentNames,
73+
);
74+
break;
75+
}
76+
case Kind.FRAGMENT_SPREAD: {
77+
const fragName = selection.name.value;
78+
if (
79+
visitedFragmentNames.has(fragName) ||
80+
!shouldIncludeNode(variableValues, selection)
81+
) {
82+
continue;
83+
}
84+
visitedFragmentNames.add(fragName);
85+
const fragment = fragments[fragName];
86+
if (
87+
!fragment ||
88+
!doesFragmentConditionMatch(schema, fragment, runtimeType)
89+
) {
90+
continue;
91+
}
92+
collectFields(
93+
schema,
94+
fragments,
95+
variableValues,
96+
runtimeType,
97+
fragment.selectionSet,
98+
fields,
99+
visitedFragmentNames,
100+
);
101+
break;
102+
}
103+
}
104+
}
105+
return fields;
106+
}
107+
108+
/**
109+
* Determines if a field should be included based on the @include and @skip
110+
* directives, where @skip has higher precedence than @include.
111+
*/
112+
function shouldIncludeNode(
113+
variableValues: { [variable: string]: unknown },
114+
node: FragmentSpreadNode | FieldNode | InlineFragmentNode,
115+
): boolean {
116+
const skip = getDirectiveValues(GraphQLSkipDirective, node, variableValues);
117+
if (skip?.if === true) {
118+
return false;
119+
}
120+
121+
const include = getDirectiveValues(
122+
GraphQLIncludeDirective,
123+
node,
124+
variableValues,
125+
);
126+
if (include?.if === false) {
127+
return false;
128+
}
129+
return true;
130+
}
131+
132+
/**
133+
* Determines if a fragment is applicable to the given type.
134+
*/
135+
function doesFragmentConditionMatch(
136+
schema: GraphQLSchema,
137+
fragment: FragmentDefinitionNode | InlineFragmentNode,
138+
type: GraphQLObjectType,
139+
): boolean {
140+
const typeConditionNode = fragment.typeCondition;
141+
if (!typeConditionNode) {
142+
return true;
143+
}
144+
const conditionalType = typeFromAST(schema, typeConditionNode);
145+
if (conditionalType === type) {
146+
return true;
147+
}
148+
if (isAbstractType(conditionalType)) {
149+
return schema.isSubType(conditionalType, type);
150+
}
151+
return false;
152+
}
153+
154+
/**
155+
* Implements the logic to compute the key of a given field's entry
156+
*/
157+
function getFieldEntryKey(node: FieldNode): string {
158+
return node.alias ? node.alias.value : node.name.value;
159+
}

src/execution/execute.ts

Lines changed: 8 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ import { locatedError } from '../error/locatedError';
2020
import type {
2121
DocumentNode,
2222
OperationDefinitionNode,
23-
SelectionSetNode,
2423
FieldNode,
25-
FragmentSpreadNode,
26-
InlineFragmentNode,
2724
FragmentDefinitionNode,
2825
} from '../language/ast';
2926
import { Kind } from '../language/kinds';
@@ -46,10 +43,6 @@ import {
4643
TypeMetaFieldDef,
4744
TypeNameMetaFieldDef,
4845
} from '../type/introspection';
49-
import {
50-
GraphQLIncludeDirective,
51-
GraphQLSkipDirective,
52-
} from '../type/directives';
5346
import {
5447
isObjectType,
5548
isAbstractType,
@@ -58,14 +51,10 @@ import {
5851
isNonNullType,
5952
} from '../type/definition';
6053

61-
import { typeFromAST } from '../utilities/typeFromAST';
6254
import { getOperationRootType } from '../utilities/getOperationRootType';
6355

64-
import {
65-
getVariableValues,
66-
getArgumentValues,
67-
getDirectiveValues,
68-
} from './values';
56+
import { getVariableValues, getArgumentValues } from './values';
57+
import { collectFields } from './collectFields';
6958

7059
/**
7160
* Terminology
@@ -336,7 +325,9 @@ function executeOperation(
336325
): PromiseOrValue<ObjMap<unknown> | null> {
337326
const type = getOperationRootType(exeContext.schema, operation);
338327
const fields = collectFields(
339-
exeContext,
328+
exeContext.schema,
329+
exeContext.fragments,
330+
exeContext.variableValues,
340331
type,
341332
operation.selectionSet,
342333
new Map(),
@@ -447,141 +438,6 @@ function executeFields(
447438
return promiseForObject(results);
448439
}
449440

450-
/**
451-
* Given a selectionSet, adds all of the fields in that selection to
452-
* the passed in map of fields, and returns it at the end.
453-
*
454-
* CollectFields requires the "runtime type" of an object. For a field which
455-
* returns an Interface or Union type, the "runtime type" will be the actual
456-
* Object type returned by that field.
457-
*
458-
* @internal
459-
*/
460-
export function collectFields(
461-
exeContext: ExecutionContext,
462-
runtimeType: GraphQLObjectType,
463-
selectionSet: SelectionSetNode,
464-
fields: Map<string, Array<FieldNode>>,
465-
visitedFragmentNames: Set<string>,
466-
): Map<string, ReadonlyArray<FieldNode>> {
467-
for (const selection of selectionSet.selections) {
468-
switch (selection.kind) {
469-
case Kind.FIELD: {
470-
if (!shouldIncludeNode(exeContext, selection)) {
471-
continue;
472-
}
473-
const name = getFieldEntryKey(selection);
474-
const fieldList = fields.get(name);
475-
if (fieldList !== undefined) {
476-
fieldList.push(selection);
477-
} else {
478-
fields.set(name, [selection]);
479-
}
480-
break;
481-
}
482-
case Kind.INLINE_FRAGMENT: {
483-
if (
484-
!shouldIncludeNode(exeContext, selection) ||
485-
!doesFragmentConditionMatch(exeContext, selection, runtimeType)
486-
) {
487-
continue;
488-
}
489-
collectFields(
490-
exeContext,
491-
runtimeType,
492-
selection.selectionSet,
493-
fields,
494-
visitedFragmentNames,
495-
);
496-
break;
497-
}
498-
case Kind.FRAGMENT_SPREAD: {
499-
const fragName = selection.name.value;
500-
if (
501-
visitedFragmentNames.has(fragName) ||
502-
!shouldIncludeNode(exeContext, selection)
503-
) {
504-
continue;
505-
}
506-
visitedFragmentNames.add(fragName);
507-
const fragment = exeContext.fragments[fragName];
508-
if (
509-
!fragment ||
510-
!doesFragmentConditionMatch(exeContext, fragment, runtimeType)
511-
) {
512-
continue;
513-
}
514-
collectFields(
515-
exeContext,
516-
runtimeType,
517-
fragment.selectionSet,
518-
fields,
519-
visitedFragmentNames,
520-
);
521-
break;
522-
}
523-
}
524-
}
525-
return fields;
526-
}
527-
528-
/**
529-
* Determines if a field should be included based on the @include and @skip
530-
* directives, where @skip has higher precedence than @include.
531-
*/
532-
function shouldIncludeNode(
533-
exeContext: ExecutionContext,
534-
node: FragmentSpreadNode | FieldNode | InlineFragmentNode,
535-
): boolean {
536-
const skip = getDirectiveValues(
537-
GraphQLSkipDirective,
538-
node,
539-
exeContext.variableValues,
540-
);
541-
if (skip?.if === true) {
542-
return false;
543-
}
544-
545-
const include = getDirectiveValues(
546-
GraphQLIncludeDirective,
547-
node,
548-
exeContext.variableValues,
549-
);
550-
if (include?.if === false) {
551-
return false;
552-
}
553-
return true;
554-
}
555-
556-
/**
557-
* Determines if a fragment is applicable to the given type.
558-
*/
559-
function doesFragmentConditionMatch(
560-
exeContext: ExecutionContext,
561-
fragment: FragmentDefinitionNode | InlineFragmentNode,
562-
type: GraphQLObjectType,
563-
): boolean {
564-
const typeConditionNode = fragment.typeCondition;
565-
if (!typeConditionNode) {
566-
return true;
567-
}
568-
const conditionalType = typeFromAST(exeContext.schema, typeConditionNode);
569-
if (conditionalType === type) {
570-
return true;
571-
}
572-
if (isAbstractType(conditionalType)) {
573-
return exeContext.schema.isSubType(conditionalType, type);
574-
}
575-
return false;
576-
}
577-
578-
/**
579-
* Implements the logic to compute the key of a given field's entry
580-
*/
581-
function getFieldEntryKey(node: FieldNode): string {
582-
return node.alias ? node.alias.value : node.name.value;
583-
}
584-
585441
/**
586442
* Implements the "Executing field" section of the spec
587443
* In particular, this function figures out the value that the field returns by
@@ -1081,7 +937,9 @@ function _collectSubfields(
1081937
for (const node of fieldNodes) {
1082938
if (node.selectionSet) {
1083939
subFieldNodes = collectFields(
1084-
exeContext,
940+
exeContext.schema,
941+
exeContext.fragments,
942+
exeContext.variableValues,
1085943
returnType,
1086944
node.selectionSet,
1087945
subFieldNodes,

src/subscription/subscribe.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import { locatedError } from '../error/locatedError';
99
import type { DocumentNode } from '../language/ast';
1010

1111
import type { ExecutionResult, ExecutionContext } from '../execution/execute';
12+
import { collectFields } from '../execution/collectFields';
1213
import { getArgumentValues } from '../execution/values';
1314
import {
1415
assertValidExecutionArguments,
1516
buildExecutionContext,
1617
buildResolveInfo,
17-
collectFields,
1818
execute,
1919
getFieldDef,
2020
} from '../execution/execute';
@@ -189,10 +189,13 @@ export async function createSourceEventStream(
189189
async function executeSubscription(
190190
exeContext: ExecutionContext,
191191
): Promise<unknown> {
192-
const { schema, operation, variableValues, rootValue } = exeContext;
192+
const { schema, fragments, operation, variableValues, rootValue } =
193+
exeContext;
193194
const type = getOperationRootType(schema, operation);
194195
const fields = collectFields(
195-
exeContext,
196+
schema,
197+
fragments,
198+
variableValues,
196199
type,
197200
operation.selectionSet,
198201
new Map(),

0 commit comments

Comments
 (0)