Skip to content

Commit 5510c75

Browse files
authored
Allow providing custom default field resolver. (#865)
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 5510c75

File tree

4 files changed

+61
-12
lines changed

4 files changed

+61
-12
lines changed

src/execution/__tests__/executor-test.js

+30
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

+10-5
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

+9-2
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

+12-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
addPath,
1515
buildExecutionContext,
1616
collectFields,
17-
defaultFieldResolver,
1817
execute,
1918
getFieldDef,
2019
getOperationRootType,
@@ -27,6 +26,7 @@ import mapAsyncIterator from './mapAsyncIterator';
2726

2827
import type { ExecutionResult } from '../execution/execute';
2928
import type { DocumentNode } from '../language/ast';
29+
import type { GraphQLFieldResolver } from '../type/definition';
3030

3131
/**
3232
* Implements the "Subscribe" algorithm described in the GraphQL specification.
@@ -43,14 +43,18 @@ export function subscribe(
4343
contextValue?: mixed,
4444
variableValues?: ?{[key: string]: mixed},
4545
operationName?: ?string,
46+
fieldResolver?: ?GraphQLFieldResolver<any, any>,
47+
subscribeFieldResolver?: ?GraphQLFieldResolver<any, any>
4648
): AsyncIterator<ExecutionResult> {
4749
const subscription = createSourceEventStream(
4850
schema,
4951
document,
5052
rootValue,
5153
contextValue,
5254
variableValues,
53-
operationName);
55+
operationName,
56+
subscribeFieldResolver
57+
);
5458

5559
// For each payload yielded from a subscription, map it over the normal
5660
// GraphQL `execute` function, with `payload` as the rootValue.
@@ -66,7 +70,8 @@ export function subscribe(
6670
payload,
6771
contextValue,
6872
variableValues,
69-
operationName
73+
operationName,
74+
fieldResolver
7075
)
7176
);
7277
}
@@ -92,6 +97,7 @@ export function createSourceEventStream(
9297
contextValue?: mixed,
9398
variableValues?: ?{[key: string]: mixed},
9499
operationName?: ?string,
100+
fieldResolver?: ?GraphQLFieldResolver<any, any>
95101
): AsyncIterable<mixed> {
96102
// If a valid context cannot be created due to incorrect arguments,
97103
// this will throw an error.
@@ -101,7 +107,8 @@ export function createSourceEventStream(
101107
rootValue,
102108
contextValue,
103109
variableValues,
104-
operationName
110+
operationName,
111+
fieldResolver
105112
);
106113

107114
const type = getOperationRootType(schema, exeContext.operation);
@@ -128,7 +135,7 @@ export function createSourceEventStream(
128135

129136
// Call the `subscribe()` resolver or the default resolver to produce an
130137
// AsyncIterable yielding raw payloads.
131-
const resolveFn = fieldDef.subscribe || defaultFieldResolver;
138+
const resolveFn = fieldDef.subscribe || exeContext.fieldResolver;
132139

133140
const info = buildResolveInfo(
134141
exeContext,

0 commit comments

Comments
 (0)