Skip to content

Commit

Permalink
Allow providing custom default field resolver.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
leebyron committed May 18, 2017
1 parent e56f9d5 commit 956d58b
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 12 deletions.
30 changes: 30 additions & 0 deletions src/execution/__tests__/executor-test.js
Expand Up @@ -935,4 +935,34 @@ describe('Execute: Handles basic execution tasks', () => {
});
});

it('uses a custom field resolver', async () => {
const query = parse('{ foo }');

const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
foo: { type: GraphQLString }
}
})
});

// For the purposes of test, just return the name of the field!
function customResolver(source, args, context, info) {
return info.fieldName;
}

const result = await execute(
schema,
query,
null,
null,
null,
null,
customResolver
);

expect(result).to.jsonEqual({ data: { foo: 'foo' } });
});

});
15 changes: 10 additions & 5 deletions src/execution/execute.js
Expand Up @@ -87,6 +87,7 @@ export type ExecutionContext = {
contextValue: mixed;
operation: OperationDefinitionNode;
variableValues: {[key: string]: mixed};
fieldResolver: GraphQLFieldResolver<any, any>;
errors: Array<GraphQLError>;
};

Expand Down Expand Up @@ -115,7 +116,8 @@ export function execute(
rootValue?: mixed,
contextValue?: mixed,
variableValues?: ?{[key: string]: mixed},
operationName?: ?string
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any, any>
): Promise<ExecutionResult> {
// If a valid context cannot be created due to incorrect arguments,
// this will throw an error.
Expand All @@ -125,7 +127,8 @@ export function execute(
rootValue,
contextValue,
variableValues,
operationName
operationName,
fieldResolver
);

// Return a Promise that will eventually resolve to the data described by
Expand Down Expand Up @@ -187,7 +190,8 @@ export function buildExecutionContext(
rootValue: mixed,
contextValue: mixed,
rawVariableValues: ?{[key: string]: mixed},
operationName: ?string
operationName: ?string,
fieldResolver: ?GraphQLFieldResolver<any, any>
): ExecutionContext {
invariant(schema, 'Must provide schema');
invariant(document, 'Must provide document');
Expand Down Expand Up @@ -251,7 +255,8 @@ export function buildExecutionContext(
contextValue,
operation,
variableValues,
errors
fieldResolver: fieldResolver || defaultFieldResolver,
errors,
};
}

Expand Down Expand Up @@ -580,7 +585,7 @@ function resolveField(
return;
}

const resolveFn = fieldDef.resolve || defaultFieldResolver;
const resolveFn = fieldDef.resolve || exeContext.fieldResolver;

const info = buildResolveInfo(
exeContext,
Expand Down
11 changes: 9 additions & 2 deletions src/graphql.js
Expand Up @@ -12,6 +12,7 @@ import { Source } from './language/source';
import { parse } from './language/parser';
import { validate } from './validation/validate';
import { execute } from './execution/execute';
import type { GraphQLFieldResolver } from './type/definition';
import type { GraphQLSchema } from './type/schema';
import type { ExecutionResult } from './execution/execute';

Expand Down Expand Up @@ -39,14 +40,19 @@ import type { ExecutionResult } from './execution/execute';
* The name of the operation to use if requestString contains multiple
* possible operations. Can be omitted if requestString contains only
* one operation.
* fieldResolver:
* A resolver function to use when one is not provided by the schema.
* If not provided, the default field resolver is used (which looks for a
* value or method on the source value with the field's name).
*/
export function graphql(
schema: GraphQLSchema,
requestString: string,
rootValue?: mixed,
contextValue?: mixed,
variableValues?: ?{[key: string]: mixed},
operationName?: ?string
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any, any>
): Promise<ExecutionResult> {
return new Promise(resolve => {
const source = new Source(requestString || '', 'GraphQL request');
Expand All @@ -62,7 +68,8 @@ export function graphql(
rootValue,
contextValue,
variableValues,
operationName
operationName,
fieldResolver
)
);
}
Expand Down
17 changes: 12 additions & 5 deletions src/subscription/subscribe.js
Expand Up @@ -14,7 +14,6 @@ import {
addPath,
buildExecutionContext,
collectFields,
defaultFieldResolver,
execute,
getFieldDef,
getOperationRootType,
Expand All @@ -27,6 +26,7 @@ import mapAsyncIterator from './mapAsyncIterator';

import type { ExecutionResult } from '../execution/execute';
import type { DocumentNode } from '../language/ast';
import type { GraphQLFieldResolver } from '../type/definition';

/**
* Implements the "Subscribe" algorithm described in the GraphQL specification.
Expand All @@ -43,14 +43,18 @@ export function subscribe(
contextValue?: mixed,
variableValues?: ?{[key: string]: mixed},
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any, any>,
subscribeFieldResolver?: ?GraphQLFieldResolver<any, any>
): AsyncIterator<ExecutionResult> {
const subscription = createSourceEventStream(
schema,
document,
rootValue,
contextValue,
variableValues,
operationName);
operationName,
subscribeFieldResolver
);

// For each payload yielded from a subscription, map it over the normal
// GraphQL `execute` function, with `payload` as the rootValue.
Expand All @@ -66,7 +70,8 @@ export function subscribe(
payload,
contextValue,
variableValues,
operationName
operationName,
fieldResolver
)
);
}
Expand All @@ -92,6 +97,7 @@ export function createSourceEventStream(
contextValue?: mixed,
variableValues?: ?{[key: string]: mixed},
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any, any>
): AsyncIterable<mixed> {
// If a valid context cannot be created due to incorrect arguments,
// this will throw an error.
Expand All @@ -101,7 +107,8 @@ export function createSourceEventStream(
rootValue,
contextValue,
variableValues,
operationName
operationName,
fieldResolver
);

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

// Call the `subscribe()` resolver or the default resolver to produce an
// AsyncIterable yielding raw payloads.
const resolveFn = fieldDef.subscribe || defaultFieldResolver;
const resolveFn = fieldDef.subscribe || exeContext.fieldResolver;

const info = buildResolveInfo(
exeContext,
Expand Down

0 comments on commit 956d58b

Please sign in to comment.