Skip to content

Commit e27a10e

Browse files
committed
Replace fragment argument variables at execution time with passed-in arguments.
1 parent 5b12367 commit e27a10e

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

src/execution/__tests__/variables-test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ function executeQuery(
130130
return executeSync({ schema, document, variableValues });
131131
}
132132

133+
function executeQueryWithFragmentArguments(
134+
query: string,
135+
variableValues?: { [variable: string]: unknown },
136+
) {
137+
const document = parse(query, {allowFragmentArguments: true});
138+
return executeSync({ schema, document, variableValues });
139+
}
140+
133141
describe('Execute: Handles inputs', () => {
134142
describe('Handles objects and nullability', () => {
135143
describe('using inline structs', () => {
@@ -1005,6 +1013,25 @@ describe('Execute: Handles inputs', () => {
10051013
});
10061014
});
10071015

1016+
describe('using fragment arguments', () => {
1017+
const result = executeQueryWithFragmentArguments(`
1018+
query {
1019+
...a(value: "A")
1020+
}
1021+
1022+
fragment a($value: String!) on TestType {
1023+
fieldWithNonNullableStringInput(input: $value)
1024+
}
1025+
`);
1026+
console.log(JSON.stringify(result));
1027+
expect(result).to.deep.equal({
1028+
data: {
1029+
fieldWithNonNullableStringInput:
1030+
'"A"',
1031+
},
1032+
});
1033+
});
1034+
10081035
describe('getVariableValues: limit maximum number of coercion errors', () => {
10091036
const doc = parse(`
10101037
query ($input: [String!]) {

src/execution/execute.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ import type {
2525
FragmentSpreadNode,
2626
InlineFragmentNode,
2727
FragmentDefinitionNode,
28+
ArgumentNode,
29+
ValueNode,
30+
DirectiveNode,
2831
} from '../language/ast';
2932
import { Kind } from '../language/kinds';
33+
import { print } from '../language/printer';
3034

3135
import type { GraphQLSchema } from '../type/schema';
3236
import type {
@@ -66,6 +70,7 @@ import {
6670
getArgumentValues,
6771
getDirectiveValues,
6872
} from './values';
73+
import { visit } from '../language';
6974

7075
/**
7176
* Terminology
@@ -511,10 +516,14 @@ export function collectFields(
511516
) {
512517
continue;
513518
}
519+
const selectionSet = selectionSetWithFragmentArgumentsApplied(
520+
fragment,
521+
selection.arguments,
522+
);
514523
collectFields(
515524
exeContext,
516525
runtimeType,
517-
fragment.selectionSet,
526+
selectionSet,
518527
fields,
519528
visitedFragmentNames,
520529
);
@@ -525,6 +534,38 @@ export function collectFields(
525534
return fields;
526535
}
527536

537+
function selectionSetWithFragmentArgumentsApplied(fragment: FragmentDefinitionNode, fragmentArguments?: ReadonlyArray<ArgumentNode>): SelectionSetNode {
538+
if (fragment.variableDefinitions == null) {
539+
return fragment.selectionSet;
540+
}
541+
542+
const providedArguments: Map<string, ArgumentNode> = new Map();
543+
for (const arg of fragmentArguments ?? []) {
544+
providedArguments.set(arg.name.value, arg);
545+
}
546+
const fragmentArgumentValues: Map<string, ValueNode> = new Map();
547+
for (const argDef of fragment.variableDefinitions ?? []) {
548+
const argName = argDef.variable.name.value;
549+
const providedArg = providedArguments.get(argName);
550+
const argDefaultValue = argDef.defaultValue;
551+
if (providedArg != null) {
552+
fragmentArgumentValues.set(argName, providedArg.value);
553+
} else if (argDefaultValue != null) {
554+
fragmentArgumentValues.set(argName, argDefaultValue);
555+
}
556+
}
557+
558+
return visit(fragment.selectionSet, {
559+
Variable(variable) {
560+
const replacementValue = fragmentArgumentValues.get(variable.name.value);
561+
if (replacementValue != null) {
562+
return replacementValue;
563+
}
564+
return variable;
565+
}
566+
});
567+
}
568+
528569
/**
529570
* Determines if a field should be included based on the @include and @skip
530571
* directives, where @skip has higher precedence than @include.

0 commit comments

Comments
 (0)