diff --git a/src/execution/execute.js b/src/execution/execute.js index 6ec28d5ea2..b272c5754d 100644 --- a/src/execution/execute.js +++ b/src/execution/execute.js @@ -19,6 +19,7 @@ import promiseReduce from '../jsutils/promiseReduce'; import type { ObjMap } from '../jsutils/ObjMap'; import type { MaybePromise } from '../jsutils/MaybePromise'; +import { getOperationRootType } from '../utilities/getOperationRootType'; import { typeFromAST } from '../utilities/typeFromAST'; import { Kind } from '../language/kinds'; import { @@ -415,47 +416,6 @@ function executeOperation( } } -/** - * Extracts the root type of the operation from the schema. - */ -export function getOperationRootType( - schema: GraphQLSchema, - operation: OperationDefinitionNode, -): GraphQLObjectType { - switch (operation.operation) { - case 'query': - const queryType = schema.getQueryType(); - if (!queryType) { - throw new GraphQLError( - 'Schema does not define the required query root type.', - [operation], - ); - } - return queryType; - case 'mutation': - const mutationType = schema.getMutationType(); - if (!mutationType) { - throw new GraphQLError('Schema is not configured for mutations.', [ - operation, - ]); - } - return mutationType; - case 'subscription': - const subscriptionType = schema.getSubscriptionType(); - if (!subscriptionType) { - throw new GraphQLError('Schema is not configured for subscriptions.', [ - operation, - ]); - } - return subscriptionType; - default: - throw new GraphQLError( - 'Can only execute queries, mutations and subscriptions.', - [operation], - ); - } -} - /** * Implements the "Evaluating selection sets" section of the spec * for "write" mode. diff --git a/src/index.js b/src/index.js index c439340a0a..697734cf47 100644 --- a/src/index.js +++ b/src/index.js @@ -320,6 +320,8 @@ export { introspectionQuery, // Gets the target Operation from a Document getOperationAST, + // Gets the Type for the target Operation AST. + getOperationRootType, // Convert a GraphQLSchema to an IntrospectionQuery introspectionFromSchema, // Build a GraphQLSchema from an introspection result. diff --git a/src/subscription/subscribe.js b/src/subscription/subscribe.js index a7fc1f69a8..2c8eb4f65f 100644 --- a/src/subscription/subscribe.js +++ b/src/subscription/subscribe.js @@ -18,7 +18,6 @@ import { collectFields, execute, getFieldDef, - getOperationRootType, resolveFieldValueOrError, responsePathAsArray, } from '../execution/execute'; @@ -29,6 +28,7 @@ import type { ObjMap } from '../jsutils/ObjMap'; import type { ExecutionResult } from '../execution/execute'; import type { DocumentNode } from '../language/ast'; import type { GraphQLFieldResolver } from '../type/definition'; +import { getOperationRootType } from '../utilities/getOperationRootType'; /** * Implements the "Subscribe" algorithm described in the GraphQL specification. diff --git a/src/utilities/__tests__/getOperationRootType-test.js b/src/utilities/__tests__/getOperationRootType-test.js new file mode 100644 index 0000000000..e86e109cef --- /dev/null +++ b/src/utilities/__tests__/getOperationRootType-test.js @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { expect } from 'chai'; +import { describe, it } from 'mocha'; +import { getOperationRootType } from '../getOperationRootType'; +import { parse, GraphQLSchema, GraphQLObjectType, GraphQLString } from '../../'; + +const queryType = new GraphQLObjectType({ + name: 'FooQuery', + fields: () => ({ + field: { type: GraphQLString }, + }), +}); + +const mutationType = new GraphQLObjectType({ + name: 'FooMutation', + fields: () => ({ + field: { type: GraphQLString }, + }), +}); + +const subscriptionType = new GraphQLObjectType({ + name: 'FooSubscription', + fields: () => ({ + field: { type: GraphQLString }, + }), +}); + +describe('getOperationRootType', () => { + it('Gets a Query type for an unnamed OperationDefinitionNode', () => { + const testSchema = new GraphQLSchema({ + query: queryType, + }); + const doc = parse('{ field }'); + expect(getOperationRootType(testSchema, doc.definitions[0])).to.equal( + queryType, + ); + }); + + it('Gets a Query type for an named OperationDefinitionNode', () => { + const testSchema = new GraphQLSchema({ + query: queryType, + }); + + const doc = parse('query Q { field }'); + expect(getOperationRootType(testSchema, doc.definitions[0])).to.equal( + queryType, + ); + }); + + it('Gets a type for OperationTypeDefinitionNodes', () => { + const testSchema = new GraphQLSchema({ + query: queryType, + mutation: mutationType, + subscription: subscriptionType, + }); + + const doc = parse( + 'schema { query: FooQuery mutation: FooMutation subscription: FooSubscription }', + ); + const operationTypes = doc.definitions[0].operationTypes; + expect(getOperationRootType(testSchema, operationTypes[0])).to.equal( + queryType, + ); + expect(getOperationRootType(testSchema, operationTypes[1])).to.equal( + mutationType, + ); + expect(getOperationRootType(testSchema, operationTypes[2])).to.equal( + subscriptionType, + ); + }); + + it('Gets a Mutation type for an OperationDefinitionNode', () => { + const testSchema = new GraphQLSchema({ + mutation: mutationType, + }); + + const doc = parse('mutation { field }'); + expect(getOperationRootType(testSchema, doc.definitions[0])).to.equal( + mutationType, + ); + }); + + it('Gets a Subscription type for an OperationDefinitionNode', () => { + const testSchema = new GraphQLSchema({ + subscription: subscriptionType, + }); + + const doc = parse('subscription { field }'); + expect(getOperationRootType(testSchema, doc.definitions[0])).to.equal( + subscriptionType, + ); + }); + + it('Throws when query type not defined in schema', () => { + const testSchema = new GraphQLSchema({}); + + const doc = parse('query { field }'); + expect(() => getOperationRootType(testSchema, doc.definitions[0])).to.throw( + 'Schema does not define the required query root type.', + ); + }); + + it('Throws when mutation type not defined in schema', () => { + const testSchema = new GraphQLSchema({}); + + const doc = parse('mutation { field }'); + expect(() => getOperationRootType(testSchema, doc.definitions[0])).to.throw( + 'Schema is not configured for mutations.', + ); + }); + + it('Throws when subscription type not defined in schema', () => { + const testSchema = new GraphQLSchema({}); + + const doc = parse('subscription { field }'); + expect(() => getOperationRootType(testSchema, doc.definitions[0])).to.throw( + 'Schema is not configured for subscriptions.', + ); + }); + + it('Throws when operation not a valid operation kind', () => { + const testSchema = new GraphQLSchema({}); + + const doc = parse('{ field }'); + doc.definitions[0].operation = 'non_existent_operation'; + expect(() => getOperationRootType(testSchema, doc.definitions[0])).to.throw( + 'Can only have query, mutation and subscription operations.', + ); + }); +}); diff --git a/src/utilities/getOperationRootType.js b/src/utilities/getOperationRootType.js new file mode 100644 index 0000000000..72f7b3d8cb --- /dev/null +++ b/src/utilities/getOperationRootType.js @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + */ + +import { GraphQLError } from '../error/GraphQLError'; +import type { + OperationDefinitionNode, + OperationTypeDefinitionNode, +} from '../language/ast'; +import { GraphQLSchema } from '../type/schema'; +import type { GraphQLObjectType } from '../type/definition'; + +/** + * Extracts the root type of the operation from the schema. + */ +export function getOperationRootType( + schema: GraphQLSchema, + operation: OperationDefinitionNode | OperationTypeDefinitionNode, +): GraphQLObjectType { + switch (operation.operation) { + case 'query': + const queryType = schema.getQueryType(); + if (!queryType) { + throw new GraphQLError( + 'Schema does not define the required query root type.', + [operation], + ); + } + return queryType; + case 'mutation': + const mutationType = schema.getMutationType(); + if (!mutationType) { + throw new GraphQLError('Schema is not configured for mutations.', [ + operation, + ]); + } + return mutationType; + case 'subscription': + const subscriptionType = schema.getSubscriptionType(); + if (!subscriptionType) { + throw new GraphQLError('Schema is not configured for subscriptions.', [ + operation, + ]); + } + return subscriptionType; + default: + throw new GraphQLError( + 'Can only have query, mutation and subscription operations.', + [operation], + ); + } +} diff --git a/src/utilities/index.js b/src/utilities/index.js index e627206197..3fa3529342 100644 --- a/src/utilities/index.js +++ b/src/utilities/index.js @@ -41,6 +41,9 @@ export type { // Gets the target Operation from a Document export { getOperationAST } from './getOperationAST'; +// Gets the Type for the target Operation AST. +export { getOperationRootType } from './getOperationRootType'; + // Convert a GraphQLSchema to an IntrospectionQuery export { introspectionFromSchema } from './introspectionFromSchema';