Skip to content

Commit 062c9d9

Browse files
committed
Adds support for resolving union/interface types when using a generated schema
1 parent eb01a23 commit 062c9d9

File tree

4 files changed

+180
-18
lines changed

4 files changed

+180
-18
lines changed

src/execution/execute.js

+31-10
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import type {
3333
GraphQLAbstractType,
3434
GraphQLField,
3535
GraphQLFieldResolver,
36+
GraphQLTypeResolver,
3637
GraphQLResolveInfo,
3738
ResponsePath,
3839
} from '../type/definition';
@@ -91,6 +92,7 @@ export type ExecutionContext = {
9192
operation: OperationDefinitionNode;
9293
variableValues: {[key: string]: mixed};
9394
fieldResolver: GraphQLFieldResolver<any, any>;
95+
typeResolver: GraphQLTypeResolver<any, any>;
9496
errors: Array<GraphQLError>;
9597
};
9698

@@ -137,7 +139,8 @@ declare function execute(
137139
contextValue?: mixed,
138140
variableValues?: ?{[key: string]: mixed},
139141
operationName?: ?string,
140-
fieldResolver?: ?GraphQLFieldResolver<any, any>
142+
fieldResolver?: ?GraphQLFieldResolver<any, any>,
143+
typeResolver?: ?GraphQLTypeResolver<any, any>
141144
): Promise<ExecutionResult>;
142145
export function execute(
143146
argsOrSchema,
@@ -146,7 +149,8 @@ export function execute(
146149
contextValue,
147150
variableValues,
148151
operationName,
149-
fieldResolver
152+
fieldResolver,
153+
typeResolver
150154
) {
151155
// Extract arguments from object args if provided.
152156
return arguments.length === 1 ?
@@ -157,7 +161,8 @@ export function execute(
157161
argsOrSchema.contextValue,
158162
argsOrSchema.variableValues,
159163
argsOrSchema.operationName,
160-
argsOrSchema.fieldResolver
164+
argsOrSchema.fieldResolver,
165+
argsOrSchema.typeResolver
161166
) :
162167
executeImpl(
163168
argsOrSchema,
@@ -166,7 +171,8 @@ export function execute(
166171
contextValue,
167172
variableValues,
168173
operationName,
169-
fieldResolver
174+
fieldResolver,
175+
typeResolver
170176
);
171177
}
172178

@@ -177,7 +183,8 @@ function executeImpl(
177183
contextValue,
178184
variableValues,
179185
operationName,
180-
fieldResolver
186+
fieldResolver,
187+
typeResolver
181188
) {
182189
// If arguments are missing or incorrect, throw an error.
183190
assertValidExecutionArguments(
@@ -197,7 +204,8 @@ function executeImpl(
197204
contextValue,
198205
variableValues,
199206
operationName,
200-
fieldResolver
207+
fieldResolver,
208+
typeResolver
201209
);
202210
} catch (error) {
203211
return Promise.resolve({ errors: [ error ] });
@@ -281,7 +289,8 @@ export function buildExecutionContext(
281289
contextValue: mixed,
282290
rawVariableValues: ?{[key: string]: mixed},
283291
operationName: ?string,
284-
fieldResolver: ?GraphQLFieldResolver<any, any>
292+
fieldResolver: ?GraphQLFieldResolver<any, any>,
293+
typeResolver: ?GraphQLTypeResolver<any, any>,
285294
): ExecutionContext {
286295
const errors: Array<GraphQLError> = [];
287296
let operation: ?OperationDefinitionNode;
@@ -330,6 +339,7 @@ export function buildExecutionContext(
330339
operation,
331340
variableValues,
332341
fieldResolver: fieldResolver || defaultFieldResolver,
342+
typeResolver: typeResolver || defaultTypeResolver,
333343
errors,
334344
};
335345
}
@@ -1037,9 +1047,9 @@ function completeAbstractValue(
10371047
path: ResponsePath,
10381048
result: mixed
10391049
): mixed {
1040-
const runtimeType = returnType.resolveType ?
1041-
returnType.resolveType(result, exeContext.contextValue, info) :
1042-
defaultResolveTypeFn(result, exeContext.contextValue, info, returnType);
1050+
const runtimeType = exeContext.typeResolver(
1051+
result, exeContext, info, returnType
1052+
);
10431053
10441054
const promise = getPromise(runtimeType);
10451055
if (promise) {
@@ -1256,6 +1266,17 @@ function (source, args, context, info) {
12561266
}
12571267
};
12581268

1269+
export const defaultTypeResolver: GraphQLTypeResolver<any, *> =
1270+
function (result, context, info, returnType) {
1271+
if (result && result.__typename) {
1272+
return result.__typename;
1273+
}
1274+
1275+
return returnType.resolveType ?
1276+
returnType.resolveType(result, context.contextValue, info) :
1277+
defaultResolveTypeFn(result, context.contextValue, info, returnType);
1278+
};
1279+
12591280
/**
12601281
* Only returns the value if it acts like a Promise, i.e. has a "then" function,
12611282
* otherwise returns void.

src/graphql.js

+16-7
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import { parse } from './language/parser';
1212
import { validate } from './validation/validate';
1313
import { execute } from './execution/execute';
1414
import type { Source } from './language/source';
15-
import type { GraphQLFieldResolver } from './type/definition';
15+
import type {
16+
GraphQLFieldResolver,
17+
GraphQLTypeResolver
18+
} from './type/definition';
1619
import type { GraphQLSchema } from './type/schema';
1720
import type { ExecutionResult } from './execution/execute';
1821

@@ -53,7 +56,8 @@ declare function graphql({|
5356
contextValue?: mixed,
5457
variableValues?: ?{[key: string]: mixed},
5558
operationName?: ?string,
56-
fieldResolver?: ?GraphQLFieldResolver<any, any>
59+
fieldResolver?: ?GraphQLFieldResolver<any, any>,
60+
typeResolver?: ?GraphQLTypeResolver<any, any>
5761
|}, ..._: []): Promise<ExecutionResult>;
5862
/* eslint-disable no-redeclare */
5963
declare function graphql(
@@ -72,7 +76,8 @@ export function graphql(
7276
contextValue,
7377
variableValues,
7478
operationName,
75-
fieldResolver
79+
fieldResolver,
80+
typeResolver
7681
) {
7782
// Extract arguments from object args if provided.
7883
return arguments.length === 1 ?
@@ -83,7 +88,8 @@ export function graphql(
8388
argsOrSchema.contextValue,
8489
argsOrSchema.variableValues,
8590
argsOrSchema.operationName,
86-
argsOrSchema.fieldResolver
91+
argsOrSchema.fieldResolver,
92+
argsOrSchema.typeResolver
8793
) :
8894
graphqlImpl(
8995
argsOrSchema,
@@ -92,7 +98,8 @@ export function graphql(
9298
contextValue,
9399
variableValues,
94100
operationName,
95-
fieldResolver
101+
fieldResolver,
102+
typeResolver
96103
);
97104
}
98105

@@ -103,7 +110,8 @@ function graphqlImpl(
103110
contextValue,
104111
variableValues,
105112
operationName,
106-
fieldResolver
113+
fieldResolver,
114+
typeResolver
107115
) {
108116
return new Promise(resolve => {
109117
// Parse
@@ -129,7 +137,8 @@ function graphqlImpl(
129137
contextValue,
130138
variableValues,
131139
operationName,
132-
fieldResolver
140+
fieldResolver,
141+
typeResolver
133142
)
134143
);
135144
});

src/type/definition.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,8 @@ export type GraphQLObjectTypeConfig<TSource, TContext> = {
614614
export type GraphQLTypeResolver<TSource, TContext> = (
615615
value: TSource,
616616
context: TContext,
617-
info: GraphQLResolveInfo
617+
info: GraphQLResolveInfo,
618+
returnValue: any
618619
) => ?GraphQLObjectType | string | Promise<?GraphQLObjectType | string>;
619620

620621
export type GraphQLIsTypeOfFn<TSource, TContext> = (

src/utilities/__tests__/buildASTSchema-test.js

+131
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,137 @@ describe('Schema Builder', () => {
394394
expect(output).to.equal(body);
395395
});
396396

397+
it('Specifying Union type using __typename', async () => {
398+
const schema = buildSchema(`
399+
schema {
400+
query: Root
401+
}
402+
403+
type Root {
404+
fruits: [Fruit]
405+
}
406+
407+
union Fruit = Apple | Banana
408+
409+
type Apple {
410+
color: String
411+
}
412+
413+
type Banana {
414+
length: Int
415+
}
416+
`);
417+
418+
const query = `
419+
{
420+
fruits {
421+
... on Apple {
422+
color
423+
}
424+
... on Banana {
425+
length
426+
}
427+
}
428+
}
429+
`;
430+
431+
const root = {
432+
fruits: [
433+
{
434+
color: 'green',
435+
__typename: 'Apple',
436+
},
437+
{
438+
length: 5,
439+
__typename: 'Banana',
440+
}
441+
]
442+
};
443+
444+
expect(await graphql(schema, query, root)).to.deep.equal({
445+
data: {
446+
fruits: [
447+
{
448+
color: 'green',
449+
},
450+
{
451+
length: 5,
452+
}
453+
]
454+
}
455+
});
456+
});
457+
458+
it('Specifying Interface type using __typename', async () => {
459+
const schema = buildSchema(`
460+
schema {
461+
query: Root
462+
}
463+
464+
type Root {
465+
characters: [Character]
466+
}
467+
468+
interface Character {
469+
name: String!
470+
}
471+
472+
type Human implements Character {
473+
name: String!
474+
totalCredits: Int
475+
}
476+
477+
type Droid implements Character {
478+
name: String!
479+
primaryFunction: String
480+
}
481+
`);
482+
483+
const query = `
484+
{
485+
characters {
486+
name
487+
... on Human {
488+
totalCredits
489+
}
490+
... on Droid {
491+
primaryFunction
492+
}
493+
}
494+
}
495+
`;
496+
497+
const root = {
498+
characters: [
499+
{
500+
name: 'Han Solo',
501+
totalCredits: 10,
502+
__typename: 'Human',
503+
},
504+
{
505+
name: 'R2-D2',
506+
primaryFunction: 'Astromech',
507+
__typename: 'Droid',
508+
}
509+
]
510+
};
511+
512+
expect(await graphql(schema, query, root)).to.deep.equal({
513+
data: {
514+
characters: [
515+
{
516+
name: 'Han Solo',
517+
totalCredits: 10,
518+
},
519+
{
520+
name: 'R2-D2',
521+
primaryFunction: 'Astromech',
522+
}
523+
]
524+
}
525+
});
526+
});
527+
397528
it('Custom Scalar', () => {
398529
const body = dedent`
399530
schema {

0 commit comments

Comments
 (0)