Skip to content

Commit

Permalink
Added 'GraphQLSchema.getRootType' and deprecate getOperationRootType
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanGoncharov committed Oct 11, 2021
1 parent 302ab18 commit a50e047
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 30 deletions.
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
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 @@ -335,6 +335,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 @@ -346,12 +344,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 @@ -363,8 +368,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 { ExecutionResult, ExecutionContext } from './execute';
import { collectFields } from './collectFields';
import { getArgumentValues } from './values';
Expand Down Expand Up @@ -191,16 +189,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 @@ -210,8 +216,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

0 comments on commit a50e047

Please sign in to comment.