diff --git a/src/execution/__tests__/subscribe-test.ts b/src/execution/__tests__/subscribe-test.ts index 4852d86ad3..5f256ca868 100644 --- a/src/execution/__tests__/subscribe-test.ts +++ b/src/execution/__tests__/subscribe-test.ts @@ -14,7 +14,7 @@ import { GraphQLList, GraphQLObjectType } from '../../type/definition'; import { GraphQLBoolean, GraphQLInt, GraphQLString } from '../../type/scalars'; import { GraphQLSchema } from '../../type/schema'; -import type { ExecutionResult } from '../execute'; +import type { ExecutionArgs, ExecutionResult } from '../execute'; import { createSourceEventStream, subscribe } from '../execute'; import { SimplePubSub } from './simplePubSub'; @@ -189,9 +189,15 @@ function subscribeWithBadFn( }); const document = parse('subscription { foo }'); + return subscribeWithBadArgs({ schema, document }); +} + +function subscribeWithBadArgs( + args: ExecutionArgs, +): PromiseOrValue> { return expectEqualPromisesOrValues( - subscribe({ schema, document }), - createSourceEventStream({ schema, document }), + subscribe(args), + createSourceEventStream(args), ); } @@ -394,7 +400,7 @@ describe('Subscription Initialization Phase', () => { const schema = new GraphQLSchema({ query: DummyQueryType }); const document = parse('subscription { unknownField }'); - const result = subscribe({ schema, document }); + const result = subscribeWithBadArgs({ schema, document }); expectJSON(result).toDeepEqual({ errors: [ { @@ -418,7 +424,7 @@ describe('Subscription Initialization Phase', () => { }); const document = parse('subscription { unknownField }'); - const result = subscribe({ schema, document }); + const result = subscribeWithBadArgs({ schema, document }); expectJSON(result).toDeepEqual({ errors: [ { @@ -441,7 +447,7 @@ describe('Subscription Initialization Phase', () => { }); // @ts-expect-error - expect(() => subscribe({ schema, document: {} })).to.throw(); + expect(() => subscribeWithBadArgs({ schema, document: {} })).to.throw(); }); it('throws an error if subscribe does not return an iterator', async () => { @@ -526,7 +532,7 @@ describe('Subscription Initialization Phase', () => { // If we receive variables that cannot be coerced correctly, subscribe() will // resolve to an ExecutionResult that contains an informative error description. - const result = subscribe({ schema, document, variableValues }); + const result = subscribeWithBadArgs({ schema, document, variableValues }); expectJSON(result).toDeepEqual({ errors: [ { diff --git a/src/execution/execute.ts b/src/execution/execute.ts index bcebc879ff..bad4afacf1 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -174,6 +174,12 @@ export function execute(args: ExecutionArgs): PromiseOrValue { return { errors: exeContext }; } + return executeImpl(exeContext); +} + +function executeImpl( + exeContext: ExecutionContext, +): PromiseOrValue { // Return a Promise that will eventually resolve to the data described by // The "Response" section of the GraphQL specification. // @@ -319,6 +325,17 @@ export function buildExecutionContext( }; } +function buildPerEventExecutionContext( + exeContext: ExecutionContext, + payload: unknown, +): ExecutionContext { + return { + ...exeContext, + rootValue: payload, + errors: [], + }; +} + /** * Implements the "Executing operations" section of the spec. */ @@ -1017,20 +1034,29 @@ export function subscribe( ): PromiseOrValue< AsyncGenerator | ExecutionResult > { - const resultOrStream = createSourceEventStream(args); + // If a valid execution context cannot be created due to incorrect arguments, + // a "Response" with only errors is returned. + const exeContext = buildExecutionContext(args); + + // Return early errors if execution context failed. + if (!('schema' in exeContext)) { + return { errors: exeContext }; + } + + const resultOrStream = createSourceEventStreamImpl(exeContext); if (isPromise(resultOrStream)) { return resultOrStream.then((resolvedResultOrStream) => - mapSourceToResponse(resolvedResultOrStream, args), + mapSourceToResponse(exeContext, resolvedResultOrStream), ); } - return mapSourceToResponse(resultOrStream, args); + return mapSourceToResponse(exeContext, resultOrStream); } function mapSourceToResponse( + exeContext: ExecutionContext, resultOrStream: ExecutionResult | AsyncIterable, - args: ExecutionArgs, ): PromiseOrValue< AsyncGenerator | ExecutionResult > { @@ -1045,10 +1071,7 @@ function mapSourceToResponse( // "ExecuteSubscriptionEvent" algorithm, as it is nearly identical to the // "ExecuteQuery" algorithm, for which `execute` is also used. return mapAsyncIterator(resultOrStream, (payload: unknown) => - execute({ - ...args, - rootValue: payload, - }), + executeImpl(buildPerEventExecutionContext(exeContext, payload)), ); } @@ -1092,6 +1115,12 @@ export function createSourceEventStream( return { errors: exeContext }; } + return createSourceEventStreamImpl(exeContext); +} + +function createSourceEventStreamImpl( + exeContext: ExecutionContext, +): PromiseOrValue | ExecutionResult> { try { const eventStream = executeSubscription(exeContext); if (isPromise(eventStream)) {