Skip to content

Commit c0d54cf

Browse files
committed
Merge branch 'on-error' into semantic-non-null
2 parents 70dc6f8 + 1cff421 commit c0d54cf

File tree

2 files changed

+222
-0
lines changed

2 files changed

+222
-0
lines changed

src/execution/__tests__/executor-test.ts

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,70 @@ describe('Execute: Handles basic execution tasks', () => {
287287
});
288288
});
289289

290+
it('reflects onError:NO_PROPAGATE via errorBehavior', () => {
291+
let resolvedInfo;
292+
const testType = new GraphQLObjectType({
293+
name: 'Test',
294+
fields: {
295+
test: {
296+
type: GraphQLString,
297+
resolve(_val, _args, _ctx, info) {
298+
resolvedInfo = info;
299+
},
300+
},
301+
},
302+
});
303+
const schema = new GraphQLSchema({ query: testType });
304+
305+
const document = parse('query ($var: String) { result: test }');
306+
const rootValue = { root: 'val' };
307+
const variableValues = { var: 'abc' };
308+
309+
executeSync({
310+
schema,
311+
document,
312+
rootValue,
313+
variableValues,
314+
onError: 'NO_PROPAGATE',
315+
});
316+
317+
expect(resolvedInfo).to.include({
318+
errorBehavior: 'NO_PROPAGATE',
319+
});
320+
});
321+
322+
it('reflects onError:ABORT via errorBehavior', () => {
323+
let resolvedInfo;
324+
const testType = new GraphQLObjectType({
325+
name: 'Test',
326+
fields: {
327+
test: {
328+
type: GraphQLString,
329+
resolve(_val, _args, _ctx, info) {
330+
resolvedInfo = info;
331+
},
332+
},
333+
},
334+
});
335+
const schema = new GraphQLSchema({ query: testType });
336+
337+
const document = parse('query ($var: String) { result: test }');
338+
const rootValue = { root: 'val' };
339+
const variableValues = { var: 'abc' };
340+
341+
executeSync({
342+
schema,
343+
document,
344+
rootValue,
345+
variableValues,
346+
onError: 'ABORT',
347+
});
348+
349+
expect(resolvedInfo).to.include({
350+
errorBehavior: 'ABORT',
351+
});
352+
});
353+
290354
it('populates path correctly with complex types', () => {
291355
let path;
292356
const someObject = new GraphQLObjectType({
@@ -741,6 +805,163 @@ describe('Execute: Handles basic execution tasks', () => {
741805
});
742806
});
743807

808+
it('Full response path is included for non-nullable fields with onError:NO_PROPAGATE', () => {
809+
const A: GraphQLObjectType = new GraphQLObjectType({
810+
name: 'A',
811+
fields: () => ({
812+
nullableA: {
813+
type: A,
814+
resolve: () => ({}),
815+
},
816+
nonNullA: {
817+
type: new GraphQLNonNull(A),
818+
resolve: () => ({}),
819+
},
820+
throws: {
821+
type: new GraphQLNonNull(GraphQLString),
822+
resolve: () => {
823+
throw new Error('Catch me if you can');
824+
},
825+
},
826+
}),
827+
});
828+
const schema = new GraphQLSchema({
829+
query: new GraphQLObjectType({
830+
name: 'query',
831+
fields: () => ({
832+
nullableA: {
833+
type: A,
834+
resolve: () => ({}),
835+
},
836+
}),
837+
}),
838+
});
839+
840+
const document = parse(`
841+
query {
842+
nullableA {
843+
aliasedA: nullableA {
844+
nonNullA {
845+
anotherA: nonNullA {
846+
throws
847+
}
848+
}
849+
}
850+
}
851+
}
852+
`);
853+
854+
const result = executeSync({ schema, document, onError: 'NO_PROPAGATE' });
855+
expectJSON(result).toDeepEqual({
856+
data: {
857+
nullableA: {
858+
aliasedA: {
859+
nonNullA: {
860+
anotherA: {
861+
throws: null,
862+
},
863+
},
864+
},
865+
},
866+
},
867+
errors: [
868+
{
869+
message: 'Catch me if you can',
870+
locations: [{ line: 7, column: 17 }],
871+
path: ['nullableA', 'aliasedA', 'nonNullA', 'anotherA', 'throws'],
872+
},
873+
],
874+
});
875+
});
876+
877+
it('Full response path is included for non-nullable fields with onError:ABORT', () => {
878+
const A: GraphQLObjectType = new GraphQLObjectType({
879+
name: 'A',
880+
fields: () => ({
881+
nullableA: {
882+
type: A,
883+
resolve: () => ({}),
884+
},
885+
nonNullA: {
886+
type: new GraphQLNonNull(A),
887+
resolve: () => ({}),
888+
},
889+
throws: {
890+
type: new GraphQLNonNull(GraphQLString),
891+
resolve: () => {
892+
throw new Error('Catch me if you can');
893+
},
894+
},
895+
}),
896+
});
897+
const schema = new GraphQLSchema({
898+
query: new GraphQLObjectType({
899+
name: 'query',
900+
fields: () => ({
901+
nullableA: {
902+
type: A,
903+
resolve: () => ({}),
904+
},
905+
}),
906+
}),
907+
});
908+
909+
const document = parse(`
910+
query {
911+
nullableA {
912+
aliasedA: nullableA {
913+
nonNullA {
914+
anotherA: nonNullA {
915+
throws
916+
}
917+
}
918+
}
919+
}
920+
}
921+
`);
922+
923+
const result = executeSync({ schema, document, onError: 'ABORT' });
924+
expectJSON(result).toDeepEqual({
925+
data: null,
926+
errors: [
927+
{
928+
message: 'Catch me if you can',
929+
locations: [{ line: 7, column: 17 }],
930+
path: ['nullableA', 'aliasedA', 'nonNullA', 'anotherA', 'throws'],
931+
},
932+
],
933+
});
934+
});
935+
936+
it('raises request error with invalid onError', () => {
937+
const schema = new GraphQLSchema({
938+
query: new GraphQLObjectType({
939+
name: 'query',
940+
fields: () => ({
941+
a: {
942+
type: GraphQLInt,
943+
},
944+
}),
945+
}),
946+
});
947+
948+
const document = parse('{ a }');
949+
const result = executeSync({
950+
schema,
951+
document,
952+
// @ts-expect-error
953+
onError: 'DANCE',
954+
});
955+
expectJSON(result).toDeepEqual({
956+
errors: [
957+
{
958+
message:
959+
'Unsupported `onError` value; supported values are `PROPAGATE`, `NO_PROPAGATE` and `ABORT`.',
960+
},
961+
],
962+
});
963+
});
964+
744965
it('uses the inline operation if no operation name is provided', () => {
745966
const schema = new GraphQLSchema({
746967
query: new GraphQLObjectType({

src/execution/execute.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,7 @@ function handleFieldError(
632632
} else if (exeContext.errorBehavior === 'NO_PROPAGATE') {
633633
// In this mode, the client takes responsibility for error handling, so we
634634
// treat the field as if it were nullable.
635+
/* c8 ignore next 6 */
635636
} else {
636637
invariant(
637638
false,

0 commit comments

Comments
 (0)