Skip to content

Commit 7f2434e

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

File tree

4 files changed

+155
-16
lines changed

4 files changed

+155
-16
lines changed

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 {

src/utilities/buildASTSchema.js

+3-8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import invariant from '../jsutils/invariant';
1212
import keyValMap from '../jsutils/keyValMap';
1313
import { valueFromAST } from './valueFromAST';
14+
import { resolveTypeForGeneratedSchema } from './resolveTypeForGeneratedSchema';
1415
import { TokenKind } from '../language/lexer';
1516
import { parse } from '../language/parser';
1617
import type { Source } from '../language/source';
@@ -398,7 +399,7 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema {
398399
description: getDescription(def),
399400
fields: () => makeFieldDefMap(def),
400401
astNode: def,
401-
resolveType: cannotExecuteSchema,
402+
resolveType: resolveTypeForGeneratedSchema,
402403
});
403404
}
404405

@@ -424,7 +425,7 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema {
424425
name: def.name.value,
425426
description: getDescription(def),
426427
types: def.types.map(t => produceObjectType(t)),
427-
resolveType: cannotExecuteSchema,
428+
resolveType: resolveTypeForGeneratedSchema,
428429
astNode: def,
429430
});
430431
}
@@ -516,9 +517,3 @@ function leadingSpaces(str) {
516517
}
517518
return i;
518519
}
519-
520-
function cannotExecuteSchema() {
521-
throw new Error(
522-
'Generated Schema cannot use Interface or Union types for execution.'
523-
);
524-
}

src/utilities/extendSchema.js

+3-8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
getDeprecationReason,
1717
} from './buildASTSchema';
1818
import { valueFromAST } from './valueFromAST';
19+
import { resolveTypeForGeneratedSchema } from './resolveTypeForGeneratedSchema';
1920
import { GraphQLError } from '../error/GraphQLError';
2021
import { GraphQLSchema } from '../type/schema';
2122

@@ -479,7 +480,7 @@ export function extendSchema(
479480
description: getDescription(typeNode),
480481
fields: () => buildFieldMap(typeNode),
481482
astNode: typeNode,
482-
resolveType: cannotExecuteExtendedSchema,
483+
resolveType: resolveTypeForGeneratedSchema,
483484
});
484485
}
485486

@@ -489,7 +490,7 @@ export function extendSchema(
489490
description: getDescription(typeNode),
490491
types: typeNode.types.map(getObjectTypeFromAST),
491492
astNode: typeNode,
492-
resolveType: cannotExecuteExtendedSchema,
493+
resolveType: resolveTypeForGeneratedSchema,
493494
});
494495
}
495496

@@ -608,9 +609,3 @@ export function extendSchema(
608609
return getOutputTypeFromAST(typeNode);
609610
}
610611
}
611-
612-
function cannotExecuteExtendedSchema() {
613-
throw new Error(
614-
'Extended Schema cannot use Interface or Union types for execution.'
615-
);
616-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @flow
2+
3+
import type { GraphQLTypeResolver } from '../type/definition';
4+
5+
export const resolveTypeForGeneratedSchema: GraphQLTypeResolver<any, *> =
6+
function (value: any): string {
7+
if (
8+
value &&
9+
typeof value === 'object' &&
10+
typeof value.__typename === 'string'
11+
) {
12+
return value.__typename;
13+
}
14+
throw new Error(
15+
'To resolve Interface or Union types for a generated Schema the result ' +
16+
'must have a __typename property containing the name of the actual type.'
17+
);
18+
};

0 commit comments

Comments
 (0)