Skip to content

Commit 710f92c

Browse files
committed
Errors thrown from resolvers have the execution path
This path is also passed in the `info` object to resolvers. This information is useful for ease of debugging and more detailed logging.
1 parent 359ec76 commit 710f92c

File tree

8 files changed

+212
-15
lines changed

8 files changed

+212
-15
lines changed

src/__tests__/starWarsIntrospection-test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,13 @@ describe('Star Wars Introspection Tests', () => {
205205
kind: 'LIST'
206206
}
207207
},
208+
{
209+
name: 'secretBackstory',
210+
type: {
211+
name: 'String',
212+
kind: 'SCALAR'
213+
}
214+
},
208215
{
209216
name: 'primaryFunction',
210217
type: {
@@ -284,6 +291,14 @@ describe('Star Wars Introspection Tests', () => {
284291
}
285292
}
286293
},
294+
{
295+
name: 'secretBackstory',
296+
type: {
297+
name: 'String',
298+
kind: 'SCALAR',
299+
ofType: null
300+
}
301+
},
287302
{
288303
name: 'primaryFunction',
289304
type: {

src/__tests__/starWarsQuery-test.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,4 +364,80 @@ describe('Star Wars Query Tests', () => {
364364
expect(result).to.deep.equal({ data: expected });
365365
});
366366
});
367+
368+
describe('Reporting errors raised in resolvers', () => {
369+
it('Correctly reports error on accessing secretBackstory', async () => {
370+
const query = `
371+
query HeroNameQuery {
372+
hero {
373+
name
374+
secretBackstory
375+
}
376+
}
377+
`;
378+
const expected = {
379+
hero: {
380+
name: 'R2-D2',
381+
secretBackstory: null
382+
}
383+
};
384+
const expectedErrors = [ 'secretBackstory is secret.' ];
385+
const result = await graphql(StarWarsSchema, query);
386+
expect(result.data).to.deep.equal(expected);
387+
expect(result.errors.map(e => e.message)).to.deep.equal(expectedErrors);
388+
expect(
389+
result.errors.map(e => e.originalError.executionPath)).to.deep.equal(
390+
[ [ 'hero', 'secretBackstory' ] ]);
391+
});
392+
393+
it('Correctly reports error on accessing secretBackstory in a list', async () => {
394+
const query = `
395+
query HeroNameQuery {
396+
hero {
397+
name
398+
friends {
399+
name
400+
secretBackstory
401+
}
402+
}
403+
}
404+
`;
405+
const expected = {
406+
hero: {
407+
name: 'R2-D2',
408+
friends: [
409+
{
410+
name: 'Luke Skywalker',
411+
secretBackstory: null,
412+
},
413+
{
414+
name: 'Han Solo',
415+
secretBackstory: null,
416+
},
417+
{
418+
name: 'Leia Organa',
419+
secretBackstory: null,
420+
},
421+
]
422+
}
423+
};
424+
const expectedErrors = [
425+
'secretBackstory is secret.',
426+
'secretBackstory is secret.',
427+
'secretBackstory is secret.',
428+
];
429+
const result = await graphql(StarWarsSchema, query);
430+
expect(result.data).to.deep.equal(expected);
431+
expect(result.errors.map(e => e.message)).to.deep.equal(expectedErrors);
432+
expect(
433+
result.errors.map(e => e.originalError.executionPath)
434+
).to.deep.equal(
435+
[
436+
[ 'hero', 'friends', 0, 'secretBackstory' ],
437+
[ 'hero', 'friends', 1, 'secretBackstory' ],
438+
[ 'hero', 'friends', 2, 'secretBackstory' ],
439+
]);
440+
});
441+
442+
});
367443
});

src/__tests__/starWarsSchema.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ const episodeEnum = new GraphQLEnumType({
102102
* name: String
103103
* friends: [Character]
104104
* appearsIn: [Episode]
105+
* secretBackstory: String
105106
* }
106107
*/
107108
const characterInterface = new GraphQLInterfaceType({
@@ -125,6 +126,10 @@ const characterInterface = new GraphQLInterfaceType({
125126
type: new GraphQLList(episodeEnum),
126127
description: 'Which movies they appear in.',
127128
},
129+
secretBackstory: {
130+
type: GraphQLString,
131+
description: 'All secrets about their past.',
132+
},
128133
}),
129134
resolveType: character => {
130135
return getHuman(character.id) ? humanType : droidType;
@@ -140,6 +145,7 @@ const characterInterface = new GraphQLInterfaceType({
140145
* name: String
141146
* friends: [Character]
142147
* appearsIn: [Episode]
148+
* secretBackstory: String
143149
* }
144150
*/
145151
const humanType = new GraphQLObjectType({
@@ -168,6 +174,13 @@ const humanType = new GraphQLObjectType({
168174
type: GraphQLString,
169175
description: 'The home planet of the human, or null if unknown.',
170176
},
177+
secretBackstory: {
178+
type: GraphQLString,
179+
description: 'Where are they from and how they came to be who they are.',
180+
resolve: () => {
181+
throw new Error('secretBackstory is secret.');
182+
},
183+
},
171184
}),
172185
interfaces: [ characterInterface ]
173186
});
@@ -181,6 +194,7 @@ const humanType = new GraphQLObjectType({
181194
* name: String
182195
* friends: [Character]
183196
* appearsIn: [Episode]
197+
* secretBackstory: String
184198
* primaryFunction: String
185199
* }
186200
*/
@@ -206,6 +220,13 @@ const droidType = new GraphQLObjectType({
206220
type: new GraphQLList(episodeEnum),
207221
description: 'Which movies they appear in.',
208222
},
223+
secretBackstory: {
224+
type: GraphQLString,
225+
description: 'Construction date and the name of the designer.',
226+
resolve: () => {
227+
throw new Error('secretBackstory is secret.');
228+
},
229+
},
209230
primaryFunction: {
210231
type: GraphQLString,
211232
description: 'The primary function of the droid.',

src/error/PathedError.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* @flow */
2+
/**
3+
* Copyright (c) 2015, Facebook, Inc.
4+
* All rights reserved.
5+
*
6+
* This source code is licensed under the BSD-style license found in the
7+
* LICENSE file in the root directory of this source tree. An additional grant
8+
* of patent rights can be found in the PATENTS file in the same directory.
9+
*/
10+
11+
12+
export class PathedError extends Error {
13+
message: string;
14+
stack: string;
15+
executionPath: Array<string | number>;
16+
17+
constructor(
18+
message: string,
19+
stack?: ?string,
20+
executionPath: Array<string | number>
21+
) {
22+
super(message);
23+
this.message = message;
24+
this.executionPath = executionPath;
25+
26+
Object.defineProperty(this, 'stack', { value: stack || message });
27+
}
28+
}

src/error/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010

1111
export { GraphQLError } from './GraphQLError';
12+
export { PathedError } from './PathedError';
1213
export { syntaxError } from './syntaxError';
1314
export { locatedError } from './locatedError';
1415
export { formatError } from './formatError';

src/error/locatedError.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010

1111
import { GraphQLError } from './GraphQLError';
12+
import { PathedError } from './PathedError';
1213

1314

1415
/**
@@ -17,7 +18,7 @@ import { GraphQLError } from './GraphQLError';
1718
* document responsible for the original Error.
1819
*/
1920
export function locatedError(
20-
originalError: ?Error,
21+
originalError: ?PathedError,
2122
nodes: Array<any>
2223
): GraphQLError {
2324
const message = originalError ?

0 commit comments

Comments
 (0)