Skip to content

Commit 5dfef02

Browse files
committed
Allow providing custom default field resolver.
This adds an argument to `execute()`, `createSourceEventStream()` and `subscribe()` which allows for providing a custom field resolver. For subscriptions, this allows for externalizing the resolving of event streams to a separate function (mentioned in #846) or to provide a custom function for externalizing the resolving of values (mentioned in #733) cc @stubailo @helfer
1 parent e56f9d5 commit 5dfef02

File tree

4 files changed

+60
-10
lines changed

4 files changed

+60
-10
lines changed

src/execution/__tests__/executor-test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,4 +935,34 @@ describe('Execute: Handles basic execution tasks', () => {
935935
});
936936
});
937937

938+
it('uses a custom field resolver', async () => {
939+
const query = parse('{ foo }');
940+
941+
const schema = new GraphQLSchema({
942+
query: new GraphQLObjectType({
943+
name: 'Query',
944+
fields: {
945+
foo: { type: GraphQLString }
946+
}
947+
})
948+
});
949+
950+
// For the purposes of test, just return the name of the field!
951+
function customResolver(source, args, context, info) {
952+
return info.fieldName;
953+
}
954+
955+
const result = await execute(
956+
schema,
957+
query,
958+
null,
959+
null,
960+
null,
961+
null,
962+
customResolver
963+
);
964+
965+
expect(result).to.jsonEqual({ data: { foo: 'foo' } });
966+
});
967+
938968
});

src/execution/execute.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export type ExecutionContext = {
8787
contextValue: mixed;
8888
operation: OperationDefinitionNode;
8989
variableValues: {[key: string]: mixed};
90+
fieldResolver: GraphQLFieldResolver<any, any>;
9091
errors: Array<GraphQLError>;
9192
};
9293

@@ -115,7 +116,8 @@ export function execute(
115116
rootValue?: mixed,
116117
contextValue?: mixed,
117118
variableValues?: ?{[key: string]: mixed},
118-
operationName?: ?string
119+
operationName?: ?string,
120+
fieldResolver?: ?GraphQLFieldResolver<any, any>
119121
): Promise<ExecutionResult> {
120122
// If a valid context cannot be created due to incorrect arguments,
121123
// this will throw an error.
@@ -125,7 +127,8 @@ export function execute(
125127
rootValue,
126128
contextValue,
127129
variableValues,
128-
operationName
130+
operationName,
131+
fieldResolver
129132
);
130133

131134
// Return a Promise that will eventually resolve to the data described by
@@ -187,7 +190,8 @@ export function buildExecutionContext(
187190
rootValue: mixed,
188191
contextValue: mixed,
189192
rawVariableValues: ?{[key: string]: mixed},
190-
operationName: ?string
193+
operationName: ?string,
194+
fieldResolver: ?GraphQLFieldResolver<any, any>
191195
): ExecutionContext {
192196
invariant(schema, 'Must provide schema');
193197
invariant(document, 'Must provide document');
@@ -251,7 +255,8 @@ export function buildExecutionContext(
251255
contextValue,
252256
operation,
253257
variableValues,
254-
errors
258+
fieldResolver: fieldResolver || defaultFieldResolver,
259+
errors,
255260
};
256261
}
257262

@@ -580,7 +585,7 @@ function resolveField(
580585
return;
581586
}
582587

583-
const resolveFn = fieldDef.resolve || defaultFieldResolver;
588+
const resolveFn = fieldDef.resolve || exeContext.fieldResolver;
584589

585590
const info = buildResolveInfo(
586591
exeContext,

src/graphql.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Source } from './language/source';
1212
import { parse } from './language/parser';
1313
import { validate } from './validation/validate';
1414
import { execute } from './execution/execute';
15+
import type { GraphQLFieldResolver } from './type/definition';
1516
import type { GraphQLSchema } from './type/schema';
1617
import type { ExecutionResult } from './execution/execute';
1718

@@ -39,14 +40,19 @@ import type { ExecutionResult } from './execution/execute';
3940
* The name of the operation to use if requestString contains multiple
4041
* possible operations. Can be omitted if requestString contains only
4142
* one operation.
43+
* fieldResolver:
44+
* A resolver function to use when one is not provided by the schema.
45+
* If not provided, the default field resolver is used (which looks for a
46+
* value or method on the source value with the field's name).
4247
*/
4348
export function graphql(
4449
schema: GraphQLSchema,
4550
requestString: string,
4651
rootValue?: mixed,
4752
contextValue?: mixed,
4853
variableValues?: ?{[key: string]: mixed},
49-
operationName?: ?string
54+
operationName?: ?string,
55+
fieldResolver?: ?GraphQLFieldResolver<any, any>
5056
): Promise<ExecutionResult> {
5157
return new Promise(resolve => {
5258
const source = new Source(requestString || '', 'GraphQL request');
@@ -62,7 +68,8 @@ export function graphql(
6268
rootValue,
6369
contextValue,
6470
variableValues,
65-
operationName
71+
operationName,
72+
fieldResolver
6673
)
6774
);
6875
}

src/subscription/subscribe.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import mapAsyncIterator from './mapAsyncIterator';
2727

2828
import type { ExecutionResult } from '../execution/execute';
2929
import type { DocumentNode } from '../language/ast';
30+
import type { GraphQLFieldResolver } from '../type/definition';
3031

3132
/**
3233
* Implements the "Subscribe" algorithm described in the GraphQL specification.
@@ -43,14 +44,18 @@ export function subscribe(
4344
contextValue?: mixed,
4445
variableValues?: ?{[key: string]: mixed},
4546
operationName?: ?string,
47+
fieldResolver?: ?GraphQLFieldResolver<any, any>,
48+
subscribeFieldResolver?: ?GraphQLFieldResolver<any, any>
4649
): AsyncIterator<ExecutionResult> {
4750
const subscription = createSourceEventStream(
4851
schema,
4952
document,
5053
rootValue,
5154
contextValue,
5255
variableValues,
53-
operationName);
56+
operationName,
57+
subscribeFieldResolver
58+
);
5459

5560
// For each payload yielded from a subscription, map it over the normal
5661
// GraphQL `execute` function, with `payload` as the rootValue.
@@ -66,7 +71,8 @@ export function subscribe(
6671
payload,
6772
contextValue,
6873
variableValues,
69-
operationName
74+
operationName,
75+
fieldResolver
7076
)
7177
);
7278
}
@@ -92,6 +98,7 @@ export function createSourceEventStream(
9298
contextValue?: mixed,
9399
variableValues?: ?{[key: string]: mixed},
94100
operationName?: ?string,
101+
fieldResolver?: ?GraphQLFieldResolver<any, any>
95102
): AsyncIterable<mixed> {
96103
// If a valid context cannot be created due to incorrect arguments,
97104
// this will throw an error.
@@ -101,7 +108,8 @@ export function createSourceEventStream(
101108
rootValue,
102109
contextValue,
103110
variableValues,
104-
operationName
111+
operationName,
112+
fieldResolver
105113
);
106114

107115
const type = getOperationRootType(schema, exeContext.operation);

0 commit comments

Comments
 (0)