Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added 'GraphQLSchema.getRootType' and deprecate getOperationRootType #3305

Merged
merged 1 commit into from Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/execution/__tests__/executor-test.ts
Expand Up @@ -908,6 +908,29 @@ describe('Execute: Handles basic execution tasks', () => {
expect(result).to.deep.equal({ data: { a: 'b' } });
});

it('resolves to an error if schema does not support operation', () => {
const schema = new GraphQLSchema({ assumeValid: true });

const document = parse(`
query Q { __typename }
mutation M { __typename }
subscription S { __typename }
`);

// FIXME: errors should be wrapped into ExecutionResult
IvanGoncharov marked this conversation as resolved.
Show resolved Hide resolved
expect(() =>
executeSync({ schema, document, operationName: 'Q' }),
).to.throw('Schema is not configured to execute query operation.');

expect(() =>
executeSync({ schema, document, operationName: 'M' }),
).to.throw('Schema is not configured to execute mutation operation.');

expect(() =>
executeSync({ schema, document, operationName: 'S' }),
).to.throw('Schema is not configured to execute subscription operation.');
});

it('correct field ordering despite execution order', async () => {
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
Expand Down
16 changes: 16 additions & 0 deletions src/execution/__tests__/subscribe-test.ts
Expand Up @@ -367,6 +367,22 @@ describe('Subscription Initialization Phase', () => {
);
});

it('resolves to an error if schema does not support subscriptions', async () => {
const schema = new GraphQLSchema({ query: DummyQueryType });
const document = parse('subscription { unknownField }');

const result = await subscribe({ schema, document });
expectJSON(result).to.deep.equal({
errors: [
{
message:
'Schema is not configured to execute subscription operation.',
locations: [{ line: 1, column: 1 }],
},
],
});
});

it('resolves to an error for unknown subscription field', async () => {
const schema = new GraphQLSchema({
query: DummyQueryType,
Expand Down
25 changes: 18 additions & 7 deletions src/execution/execute.ts
Expand Up @@ -51,8 +51,6 @@ import {
isNonNullType,
} from '../type/definition';

import { getOperationRootType } from '../utilities/getOperationRootType';

import { getVariableValues, getArgumentValues } from './values';
import {
collectFields,
Expand Down Expand Up @@ -336,12 +334,19 @@ function executeOperation(
operation: OperationDefinitionNode,
rootValue: unknown,
): PromiseOrValue<ObjMap<unknown> | null> {
const type = getOperationRootType(exeContext.schema, operation);
const fields = collectFields(
const rootType = exeContext.schema.getRootType(operation.operation);
if (rootType == null) {
throw new GraphQLError(
`Schema is not configured to execute ${operation.operation} operation.`,
operation,
);
}

const rootFields = collectFields(
exeContext.schema,
exeContext.fragments,
exeContext.variableValues,
type,
rootType,
operation.selectionSet,
);

Expand All @@ -353,8 +358,14 @@ function executeOperation(
try {
const result =
operation.operation === 'mutation'
? executeFieldsSerially(exeContext, type, rootValue, path, fields)
: executeFields(exeContext, type, rootValue, path, fields);
? executeFieldsSerially(
exeContext,
rootType,
rootValue,
path,
rootFields,
)
: executeFields(exeContext, rootType, rootValue, path, rootFields);
if (isPromise(result)) {
return result.then(undefined, (error) => {
exeContext.errors.push(error);
Expand Down
30 changes: 21 additions & 9 deletions src/execution/subscribe.ts
Expand Up @@ -11,8 +11,6 @@ import type { DocumentNode } from '../language/ast';
import type { GraphQLSchema } from '../type/schema';
import type { GraphQLFieldResolver } from '../type/definition';

import { getOperationRootType } from '../utilities/getOperationRootType';

import type {
ExecutionArgs,
ExecutionResult,
Expand Down Expand Up @@ -194,16 +192,24 @@ async function executeSubscription(
): Promise<unknown> {
const { schema, fragments, operation, variableValues, rootValue } =
exeContext;
const type = getOperationRootType(schema, operation);
const fields = collectFields(

const rootType = schema.getSubscriptionType();
if (rootType == null) {
throw new GraphQLError(
'Schema is not configured to execute subscription operation.',
operation,
);
}

const rootFields = collectFields(
schema,
fragments,
variableValues,
type,
rootType,
operation.selectionSet,
);
const [responseName, fieldNodes] = [...fields.entries()][0];
const fieldDef = getFieldDef(schema, type, fieldNodes[0]);
const [responseName, fieldNodes] = [...rootFields.entries()][0];
const fieldDef = getFieldDef(schema, rootType, fieldNodes[0]);

if (!fieldDef) {
const fieldName = fieldNodes[0].name.value;
Expand All @@ -213,8 +219,14 @@ async function executeSubscription(
);
}

const path = addPath(undefined, responseName, type.name);
const info = buildResolveInfo(exeContext, fieldDef, fieldNodes, type, path);
const path = addPath(undefined, responseName, rootType.name);
const info = buildResolveInfo(
exeContext,
fieldDef,
fieldNodes,
rootType,
path,
);

try {
// Implements the "ResolveFieldEventStream" algorithm from GraphQL specification.
Expand Down
12 changes: 12 additions & 0 deletions src/type/schema.ts
Expand Up @@ -9,6 +9,7 @@ import type { Maybe } from '../jsutils/Maybe';
import type { GraphQLError } from '../error/GraphQLError';

import type {
OperationTypeNode,
SchemaDefinitionNode,
SchemaExtensionNode,
} from '../language/ast';
Expand Down Expand Up @@ -275,6 +276,17 @@ export class GraphQLSchema {
return this._subscriptionType;
}

getRootType(operation: OperationTypeNode): Maybe<GraphQLObjectType> {
switch (operation) {
case 'query':
return this.getQueryType();
case 'mutation':
return this.getMutationType();
case 'subscription':
return this.getSubscriptionType();
}
}

getTypeMap(): TypeMap {
return this._typeMap;
}
Expand Down
15 changes: 2 additions & 13 deletions src/utilities/TypeInfo.ts
Expand Up @@ -169,19 +169,8 @@ export class TypeInfo {
this._directive = schema.getDirective(node.name.value);
break;
case Kind.OPERATION_DEFINITION: {
let type: unknown;
switch (node.operation) {
case 'query':
type = schema.getQueryType();
break;
case 'mutation':
type = schema.getMutationType();
break;
case 'subscription':
type = schema.getSubscriptionType();
break;
}
this._typeStack.push(isObjectType(type) ? type : undefined);
const rootType = schema.getRootType(node.operation);
this._typeStack.push(isObjectType(rootType) ? rootType : undefined);
break;
}
case Kind.INLINE_FRAGMENT:
Expand Down
2 changes: 1 addition & 1 deletion src/utilities/__tests__/getOperationRootType-test.ts
Expand Up @@ -40,7 +40,7 @@ function getOperationNode(doc: DocumentNode): OperationDefinitionNode {
return operationNode;
}

describe('getOperationRootType', () => {
describe('Deprecated - getOperationRootType', () => {
it('Gets a Query type for an unnamed OperationDefinitionNode', () => {
const testSchema = new GraphQLSchema({
query: queryType,
Expand Down
2 changes: 2 additions & 0 deletions src/utilities/getOperationRootType.ts
Expand Up @@ -10,6 +10,8 @@ import type { GraphQLObjectType } from '../type/definition';

/**
* Extracts the root type of the operation from the schema.
*
* @deprecated Please use `GraphQLSchema.getRootType` instead. Will be removed in v17
*/
export function getOperationRootType(
schema: GraphQLSchema,
Expand Down