diff --git a/src/utilities/__tests__/buildASTSchema-test.js b/src/utilities/__tests__/buildASTSchema-test.js index c92ed212d3..7d06723e9e 100644 --- a/src/utilities/__tests__/buildASTSchema-test.js +++ b/src/utilities/__tests__/buildASTSchema-test.js @@ -394,6 +394,137 @@ describe('Schema Builder', () => { expect(output).to.equal(body); }); + it('Specifying Union type using __typename', async () => { + const schema = buildSchema(` + schema { + query: Root + } + + type Root { + fruits: [Fruit] + } + + union Fruit = Apple | Banana + + type Apple { + color: String + } + + type Banana { + length: Int + } + `); + + const query = ` + { + fruits { + ... on Apple { + color + } + ... on Banana { + length + } + } + } + `; + + const root = { + fruits: [ + { + color: 'green', + __typename: 'Apple', + }, + { + length: 5, + __typename: 'Banana', + } + ] + }; + + expect(await graphql(schema, query, root)).to.deep.equal({ + data: { + fruits: [ + { + color: 'green', + }, + { + length: 5, + } + ] + } + }); + }); + + it('Specifying Interface type using __typename', async () => { + const schema = buildSchema(` + schema { + query: Root + } + + type Root { + characters: [Character] + } + + interface Character { + name: String! + } + + type Human implements Character { + name: String! + totalCredits: Int + } + + type Droid implements Character { + name: String! + primaryFunction: String + } + `); + + const query = ` + { + characters { + name + ... on Human { + totalCredits + } + ... on Droid { + primaryFunction + } + } + } + `; + + const root = { + characters: [ + { + name: 'Han Solo', + totalCredits: 10, + __typename: 'Human', + }, + { + name: 'R2-D2', + primaryFunction: 'Astromech', + __typename: 'Droid', + } + ] + }; + + expect(await graphql(schema, query, root)).to.deep.equal({ + data: { + characters: [ + { + name: 'Han Solo', + totalCredits: 10, + }, + { + name: 'R2-D2', + primaryFunction: 'Astromech', + } + ] + } + }); + }); + it('Custom Scalar', () => { const body = dedent` schema { diff --git a/src/utilities/buildASTSchema.js b/src/utilities/buildASTSchema.js index f1038b3955..3280a4a12b 100644 --- a/src/utilities/buildASTSchema.js +++ b/src/utilities/buildASTSchema.js @@ -11,6 +11,7 @@ import invariant from '../jsutils/invariant'; import keyValMap from '../jsutils/keyValMap'; import { valueFromAST } from './valueFromAST'; +import { resolveTypeForGeneratedSchema } from './resolveTypeForGeneratedSchema'; import { TokenKind } from '../language/lexer'; import { parse } from '../language/parser'; import type { Source } from '../language/source'; @@ -398,7 +399,7 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { description: getDescription(def), fields: () => makeFieldDefMap(def), astNode: def, - resolveType: cannotExecuteSchema, + resolveType: resolveTypeForGeneratedSchema, }); } @@ -424,7 +425,7 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { name: def.name.value, description: getDescription(def), types: def.types.map(t => produceObjectType(t)), - resolveType: cannotExecuteSchema, + resolveType: resolveTypeForGeneratedSchema, astNode: def, }); } @@ -516,9 +517,3 @@ function leadingSpaces(str) { } return i; } - -function cannotExecuteSchema() { - throw new Error( - 'Generated Schema cannot use Interface or Union types for execution.' - ); -} diff --git a/src/utilities/extendSchema.js b/src/utilities/extendSchema.js index 70776ed31c..bd97c624b3 100644 --- a/src/utilities/extendSchema.js +++ b/src/utilities/extendSchema.js @@ -16,6 +16,7 @@ import { getDeprecationReason, } from './buildASTSchema'; import { valueFromAST } from './valueFromAST'; +import { resolveTypeForGeneratedSchema } from './resolveTypeForGeneratedSchema'; import { GraphQLError } from '../error/GraphQLError'; import { GraphQLSchema } from '../type/schema'; @@ -479,7 +480,7 @@ export function extendSchema( description: getDescription(typeNode), fields: () => buildFieldMap(typeNode), astNode: typeNode, - resolveType: cannotExecuteExtendedSchema, + resolveType: resolveTypeForGeneratedSchema, }); } @@ -489,7 +490,7 @@ export function extendSchema( description: getDescription(typeNode), types: typeNode.types.map(getObjectTypeFromAST), astNode: typeNode, - resolveType: cannotExecuteExtendedSchema, + resolveType: resolveTypeForGeneratedSchema, }); } @@ -608,9 +609,3 @@ export function extendSchema( return getOutputTypeFromAST(typeNode); } } - -function cannotExecuteExtendedSchema() { - throw new Error( - 'Extended Schema cannot use Interface or Union types for execution.' - ); -} diff --git a/src/utilities/resolveTypeForGeneratedSchema.js b/src/utilities/resolveTypeForGeneratedSchema.js new file mode 100644 index 0000000000..18d2999ea6 --- /dev/null +++ b/src/utilities/resolveTypeForGeneratedSchema.js @@ -0,0 +1,18 @@ +// @flow + +import type { GraphQLTypeResolver } from '../type/definition'; + +export const resolveTypeForGeneratedSchema: GraphQLTypeResolver = +function (value: any): string { + if ( + value && + typeof value === 'object' && + typeof value.__typename === 'string' + ) { + return value.__typename; + } + throw new Error( + 'To resolve Interface or Union types for a generated Schema the result ' + + 'must have a __typename property containing the name of the actual type.' + ); +};