Skip to content

Commit c062eb4

Browse files
committed
Implement onError proposal
1 parent c18e9f6 commit c062eb4

File tree

7 files changed

+74
-3
lines changed

7 files changed

+74
-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: 45 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,21 @@ export function buildExecutionContext(
291304
fieldResolver,
292305
typeResolver,
293306
subscribeFieldResolver,
307+
<<<<<<< HEAD
294308
options,
309+
=======
310+
onError,
311+
>>>>>>> f3109c39 (Implement onError proposal)
295312
} = args;
296313

314+
if (onError != null && !isErrorBehavior(onError)) {
315+
return [
316+
new GraphQLError(
317+
'Unsupported `onError` value; supported values are `PROPAGATE`, `NO_PROPAGATE` and `ABORT`.',
318+
),
319+
];
320+
}
321+
297322
let operation: OperationDefinitionNode | undefined;
298323
const fragments: ObjMap<FragmentDefinitionNode> = Object.create(null);
299324
for (const definition of document.definitions) {
@@ -353,6 +378,7 @@ export function buildExecutionContext(
353378
typeResolver: typeResolver ?? defaultTypeResolver,
354379
subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver,
355380
errors: [],
381+
errorBehavior: onError ?? 'PROPAGATE',
356382
};
357383
}
358384

@@ -591,6 +617,7 @@ export function buildResolveInfo(
591617
rootValue: exeContext.rootValue,
592618
operation: exeContext.operation,
593619
variableValues: exeContext.variableValues,
620+
errorBehavior: exeContext.errorBehavior,
594621
};
595622
}
596623

@@ -599,10 +626,25 @@ function handleFieldError(
599626
returnType: GraphQLOutputType,
600627
exeContext: ExecutionContext,
601628
): 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)) {
629+
if (exeContext.errorBehavior === 'PROPAGATE') {
630+
// If the field type is non-nullable, then it is resolved without any
631+
// protection from errors, however it still properly locates the error.
632+
// Note: semantic non-null types are treated as nullable for the purposes
633+
// of error handling.
634+
if (isNonNullType(returnType)) {
635+
throw error;
636+
}
637+
} else if (exeContext.errorBehavior === 'ABORT') {
638+
// In this mode, any error aborts the request
605639
throw error;
640+
} else if (exeContext.errorBehavior === 'NO_PROPAGATE') {
641+
// In this mode, the client takes responsibility for error handling, so we
642+
// treat the field as if it were nullable.
643+
} else {
644+
invariant(
645+
false,
646+
'Unexpected errorBehavior setting: ' + inspect(exeContext.errorBehavior),
647+
);
606648
}
607649

608650
// 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)