Skip to content

Commit 46ac468

Browse files
committed
Implement onError proposal
1 parent c18e9f6 commit 46ac468

File tree

7 files changed

+71
-3
lines changed

7 files changed

+71
-3
lines changed

src/error/ErrorBehavior.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export type GraphQLErrorBehavior = 'PROPAGATE' | 'NO_PROPAGATE' | 'ABORT';
2+
3+
export function isErrorBehavior(
4+
onError: unknown,
5+
): onError is GraphQLErrorBehavior {
6+
return (
7+
onError === 'PROPAGATE' || onError === 'NO_PROPAGATE' || onError === 'ABORT'
8+
);
9+
}

src/error/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export type {
99
export { syntaxError } from './syntaxError';
1010

1111
export { locatedError } from './locatedError';
12+
export type { GraphQLErrorBehavior } from './ErrorBehavior';

src/execution/__tests__/executor-test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ describe('Execute: Handles basic execution tasks', () => {
264264
'rootValue',
265265
'operation',
266266
'variableValues',
267+
'errorBehavior',
267268
);
268269

269270
const operation = document.definitions[0];
@@ -276,6 +277,7 @@ describe('Execute: Handles basic execution tasks', () => {
276277
schema,
277278
rootValue,
278279
operation,
280+
errorBehavior: 'PROPAGATE',
279281
});
280282

281283
const field = operation.selectionSet.selections[0];

src/execution/execute.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { promiseForObject } from '../jsutils/promiseForObject';
1313
import type { PromiseOrValue } from '../jsutils/PromiseOrValue';
1414
import { promiseReduce } from '../jsutils/promiseReduce';
1515

16+
import type { GraphQLErrorBehavior } from '../error/ErrorBehavior';
17+
import { isErrorBehavior } from '../error/ErrorBehavior';
1618
import type { GraphQLFormattedError } from '../error/GraphQLError';
1719
import { GraphQLError } from '../error/GraphQLError';
1820
import { locatedError } from '../error/locatedError';
@@ -115,6 +117,7 @@ export interface ExecutionContext {
115117
typeResolver: GraphQLTypeResolver<any, any>;
116118
subscribeFieldResolver: GraphQLFieldResolver<any, any>;
117119
errors: Array<GraphQLError>;
120+
errorBehavior: GraphQLErrorBehavior;
118121
}
119122

120123
/**
@@ -130,6 +133,7 @@ export interface ExecutionResult<
130133
> {
131134
errors?: ReadonlyArray<GraphQLError>;
132135
data?: TData | null;
136+
onError?: GraphQLErrorBehavior;
133137
extensions?: TExtensions;
134138
}
135139

@@ -152,6 +156,15 @@ export interface ExecutionArgs {
152156
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
153157
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
154158
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
159+
/**
160+
* Experimental. Set to NO_PROPAGATE to prevent error propagation. Set to ABORT to
161+
* abort a request when any error occurs.
162+
*
163+
* Default: PROPAGATE
164+
*
165+
* @experimental
166+
*/
167+
onError?: GraphQLErrorBehavior;
155168
/** Additional execution options. */
156169
options?: {
157170
/** Set the maximum number of errors allowed for coercing (defaults to 50). */
@@ -291,9 +304,18 @@ export function buildExecutionContext(
291304
fieldResolver,
292305
typeResolver,
293306
subscribeFieldResolver,
307+
onError,
294308
options,
295309
} = args;
296310

311+
if (onError != null && !isErrorBehavior(onError)) {
312+
return [
313+
new GraphQLError(
314+
'Unsupported `onError` value; supported values are `PROPAGATE`, `NO_PROPAGATE` and `ABORT`.',
315+
),
316+
];
317+
}
318+
297319
let operation: OperationDefinitionNode | undefined;
298320
const fragments: ObjMap<FragmentDefinitionNode> = Object.create(null);
299321
for (const definition of document.definitions) {
@@ -353,6 +375,7 @@ export function buildExecutionContext(
353375
typeResolver: typeResolver ?? defaultTypeResolver,
354376
subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver,
355377
errors: [],
378+
errorBehavior: onError ?? 'PROPAGATE',
356379
};
357380
}
358381

@@ -591,6 +614,7 @@ export function buildResolveInfo(
591614
rootValue: exeContext.rootValue,
592615
operation: exeContext.operation,
593616
variableValues: exeContext.variableValues,
617+
errorBehavior: exeContext.errorBehavior,
594618
};
595619
}
596620

@@ -599,10 +623,25 @@ function handleFieldError(
599623
returnType: GraphQLOutputType,
600624
exeContext: ExecutionContext,
601625
): null {
602-
// If the field type is non-nullable, then it is resolved without any
603-
// protection from errors, however it still properly locates the error.
604-
if (isNonNullType(returnType)) {
626+
if (exeContext.errorBehavior === 'PROPAGATE') {
627+
// If the field type is non-nullable, then it is resolved without any
628+
// protection from errors, however it still properly locates the error.
629+
// Note: semantic non-null types are treated as nullable for the purposes
630+
// of error handling.
631+
if (isNonNullType(returnType)) {
632+
throw error;
633+
}
634+
} else if (exeContext.errorBehavior === 'ABORT') {
635+
// In this mode, any error aborts the request
605636
throw error;
637+
} else if (exeContext.errorBehavior === 'NO_PROPAGATE') {
638+
// In this mode, the client takes responsibility for error handling, so we
639+
// treat the field as if it were nullable.
640+
} else {
641+
invariant(
642+
false,
643+
'Unexpected errorBehavior setting: ' + inspect(exeContext.errorBehavior),
644+
);
606645
}
607646

608647
// Otherwise, error protection is applied, logging the error and resolving

src/graphql.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { isPromise } from './jsutils/isPromise';
33
import type { Maybe } from './jsutils/Maybe';
44
import type { PromiseOrValue } from './jsutils/PromiseOrValue';
55

6+
import type { GraphQLErrorBehavior } from './error/ErrorBehavior';
7+
68
import { parse } from './language/parser';
79
import type { Source } from './language/source';
810

@@ -66,6 +68,15 @@ export interface GraphQLArgs {
6668
operationName?: Maybe<string>;
6769
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
6870
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
71+
/**
72+
* Experimental. Set to NO_PROPAGATE to prevent error propagation. Set to ABORT to
73+
* abort a request when any error occurs.
74+
*
75+
* Default: PROPAGATE
76+
*
77+
* @experimental
78+
*/
79+
onError?: GraphQLErrorBehavior;
6980
}
7081

7182
export function graphql(args: GraphQLArgs): Promise<ExecutionResult> {
@@ -106,6 +117,7 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
106117
operationName,
107118
fieldResolver,
108119
typeResolver,
120+
onError,
109121
} = args;
110122

111123
// Validate Schema
@@ -138,5 +150,6 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
138150
operationName,
139151
fieldResolver,
140152
typeResolver,
153+
onError,
141154
});
142155
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ export {
395395
} from './error/index';
396396

397397
export type {
398+
GraphQLErrorBehavior,
398399
GraphQLErrorOptions,
399400
GraphQLFormattedError,
400401
GraphQLErrorExtensions,

src/type/definition.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { PromiseOrValue } from '../jsutils/PromiseOrValue';
1414
import { suggestionList } from '../jsutils/suggestionList';
1515
import { toObjMap } from '../jsutils/toObjMap';
1616

17+
import type { GraphQLErrorBehavior } from '../error/ErrorBehavior';
1718
import { GraphQLError } from '../error/GraphQLError';
1819

1920
import type {
@@ -988,6 +989,8 @@ export interface GraphQLResolveInfo {
988989
readonly rootValue: unknown;
989990
readonly operation: OperationDefinitionNode;
990991
readonly variableValues: { [variable: string]: unknown };
992+
/** @experimental */
993+
readonly errorBehavior: GraphQLErrorBehavior;
991994
}
992995

993996
/**

0 commit comments

Comments
 (0)