Skip to content

Commit 324768a

Browse files
IvanGoncharovmjmahone
authored andcommitted
Errors: fix how error messages represent arrays (#1333)
* Errors: fix how error messages represent arrays * Add test for invalid return from 'resolveType'
1 parent 0a30b62 commit 324768a

19 files changed

+153
-75
lines changed

src/execution/__tests__/abstract-test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,50 @@ describe('Execute: Handles execution of abstract types', () => {
377377
});
378378
});
379379

380+
it('returning invalid value from resolveType yields useful error', () => {
381+
const fooInterface = new GraphQLInterfaceType({
382+
name: 'FooInterface',
383+
fields: { bar: { type: GraphQLString } },
384+
resolveType: () => [],
385+
});
386+
387+
const fooObject = new GraphQLObjectType({
388+
name: 'FooObject',
389+
fields: { bar: { type: GraphQLString } },
390+
interfaces: [fooInterface],
391+
});
392+
393+
const schema = new GraphQLSchema({
394+
query: new GraphQLObjectType({
395+
name: 'Query',
396+
fields: {
397+
foo: {
398+
type: fooInterface,
399+
resolve: () => 'dummy',
400+
},
401+
},
402+
}),
403+
types: [fooObject],
404+
});
405+
406+
const result = graphqlSync(schema, '{ foo { bar } }');
407+
408+
expect(result).to.deep.equal({
409+
data: { foo: null },
410+
errors: [
411+
{
412+
message:
413+
'Abstract type FooInterface must resolve to an Object type at ' +
414+
'runtime for field Query.foo with value "dummy", received "[]". ' +
415+
'Either the FooInterface type should provide a "resolveType" ' +
416+
'function or each possible types should provide an "isTypeOf" function.',
417+
locations: [{ line: 1, column: 3 }],
418+
path: ['foo'],
419+
},
420+
],
421+
});
422+
});
423+
380424
it('resolveType allows resolving with type name', () => {
381425
const PetType = new GraphQLInterfaceType({
382426
name: 'Pet',

src/execution/execute.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import { forEach, isCollection } from 'iterall';
1111
import { GraphQLError, locatedError } from '../error';
12+
import inspect from '../jsutils/inspect';
1213
import invariant from '../jsutils/invariant';
1314
import isInvalid from '../jsutils/isInvalid';
1415
import isNullish from '../jsutils/isNullish';
@@ -907,7 +908,7 @@ function completeValue(
907908
// Not reachable. All possible output types have been considered.
908909
/* istanbul ignore next */
909910
throw new Error(
910-
`Cannot complete value of unexpected type "${String(
911+
`Cannot complete value of unexpected type "${inspect(
911912
(returnType: empty),
912913
)}".`,
913914
);
@@ -968,8 +969,8 @@ function completeLeafValue(returnType: GraphQLLeafType, result: mixed): mixed {
968969
const serializedResult = returnType.serialize(result);
969970
if (isInvalid(serializedResult)) {
970971
throw new Error(
971-
`Expected a value of type "${String(returnType)}" but ` +
972-
`received: ${String(result)}`,
972+
`Expected a value of type "${inspect(returnType)}" but ` +
973+
`received: ${inspect(result)}`,
973974
);
974975
}
975976
return serializedResult;
@@ -1045,7 +1046,7 @@ function ensureValidRuntimeType(
10451046
throw new GraphQLError(
10461047
`Abstract type ${returnType.name} must resolve to an Object type at ` +
10471048
`runtime for field ${info.parentType.name}.${info.fieldName} with ` +
1048-
`value "${String(result)}", received "${String(runtimeType)}". ` +
1049+
`value "${inspect(result)}", received "${inspect(runtimeType)}". ` +
10491050
`Either the ${returnType.name} type should provide a "resolveType" ` +
10501051
'function or each possible types should provide an ' +
10511052
'"isTypeOf" function.',
@@ -1116,7 +1117,7 @@ function invalidReturnTypeError(
11161117
fieldNodes: $ReadOnlyArray<FieldNode>,
11171118
): GraphQLError {
11181119
return new GraphQLError(
1119-
`Expected value of type "${returnType.name}" but got: ${String(result)}.`,
1120+
`Expected value of type "${returnType.name}" but got: ${inspect(result)}.`,
11201121
fieldNodes,
11211122
);
11221123
}

src/execution/values.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import { GraphQLError } from '../error';
1111
import find from '../jsutils/find';
12+
import inspect from '../jsutils/inspect';
1213
import invariant from '../jsutils/invariant';
1314
import keyMap from '../jsutils/keyMap';
1415
import { coerceValue } from '../utilities/coerceValue';
@@ -78,9 +79,9 @@ export function getVariableValues(
7879
new GraphQLError(
7980
hasValue
8081
? `Variable "$${varName}" of non-null type ` +
81-
`"${String(varType)}" must not be null.`
82+
`"${inspect(varType)}" must not be null.`
8283
: `Variable "$${varName}" of required type ` +
83-
`"${String(varType)}" was not provided.`,
84+
`"${inspect(varType)}" was not provided.`,
8485
[varDefNode],
8586
),
8687
);
@@ -158,21 +159,21 @@ export function getArgumentValues(
158159
// non-null type (required), produce a field error.
159160
if (isNull) {
160161
throw new GraphQLError(
161-
`Argument "${name}" of non-null type "${String(argType)}" ` +
162+
`Argument "${name}" of non-null type "${inspect(argType)}" ` +
162163
'must not be null.',
163164
[argumentNode.value],
164165
);
165166
} else if (argumentNode && argumentNode.value.kind === Kind.VARIABLE) {
166167
const variableName = argumentNode.value.name.value;
167168
throw new GraphQLError(
168-
`Argument "${name}" of required type "${String(argType)}" ` +
169+
`Argument "${name}" of required type "${inspect(argType)}" ` +
169170
`was provided the variable "$${variableName}" ` +
170171
'which was not provided a runtime value.',
171172
[argumentNode.value],
172173
);
173174
} else {
174175
throw new GraphQLError(
175-
`Argument "${name}" of required type "${String(argType)}" ` +
176+
`Argument "${name}" of required type "${inspect(argType)}" ` +
176177
'was not provided.',
177178
[node],
178179
);

src/jsutils/inspect.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict
8+
*/
9+
10+
export default function inspect(value: mixed): string {
11+
if (Array.isArray(value)) {
12+
return '[' + String(value) + ']';
13+
}
14+
return String(value);
15+
}

src/language/parser.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* @flow strict
88
*/
99

10+
import inspect from '../jsutils/inspect';
1011
import { Source } from './source';
1112
import { syntaxError } from '../error';
1213
import type { GraphQLError } from '../error';
@@ -126,7 +127,7 @@ export function parse(
126127
): DocumentNode {
127128
const sourceObj = typeof source === 'string' ? new Source(source) : source;
128129
if (!(sourceObj instanceof Source)) {
129-
throw new TypeError('Must provide Source. Received: ' + String(sourceObj));
130+
throw new TypeError(`Must provide Source. Received: ${inspect(sourceObj)}`);
130131
}
131132
const lexer = createLexer(sourceObj, options || {});
132133
return parseDocument(lexer);

src/subscription/subscribe.js

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

1010
import { isAsyncIterable } from 'iterall';
11+
import inspect from '../jsutils/inspect';
1112
import { GraphQLError } from '../error/GraphQLError';
1213
import { locatedError } from '../error/locatedError';
1314
import {
@@ -279,7 +280,7 @@ export function createSourceEventStream(
279280
}
280281
throw new Error(
281282
'Subscription field must return Async Iterable. Received: ' +
282-
String(eventStream),
283+
inspect(eventStream),
283284
);
284285
});
285286
} catch (error) {

src/type/definition.js

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import instanceOf from '../jsutils/instanceOf';
11+
import inspect from '../jsutils/inspect';
1112
import invariant from '../jsutils/invariant';
1213
import isInvalid from '../jsutils/isInvalid';
1314
import keyMap from '../jsutils/keyMap';
@@ -63,7 +64,7 @@ export function isType(type: mixed): boolean %checks {
6364
}
6465

6566
export function assertType(type: mixed): GraphQLType {
66-
invariant(isType(type), `Expected ${String(type)} to be a GraphQL type.`);
67+
invariant(isType(type), `Expected ${inspect(type)} to be a GraphQL type.`);
6768
return (type: any);
6869
}
6970

@@ -81,7 +82,7 @@ export function isScalarType(type) {
8182
export function assertScalarType(type: mixed): GraphQLScalarType {
8283
invariant(
8384
isScalarType(type),
84-
`Expected ${String(type)} to be a GraphQL Scalar type.`,
85+
`Expected ${inspect(type)} to be a GraphQL Scalar type.`,
8586
);
8687
return type;
8788
}
@@ -96,7 +97,7 @@ export function isObjectType(type) {
9697
export function assertObjectType(type: mixed): GraphQLObjectType {
9798
invariant(
9899
isObjectType(type),
99-
`Expected ${String(type)} to be a GraphQL Object type.`,
100+
`Expected ${inspect(type)} to be a GraphQL Object type.`,
100101
);
101102
return type;
102103
}
@@ -111,7 +112,7 @@ export function isInterfaceType(type) {
111112
export function assertInterfaceType(type: mixed): GraphQLInterfaceType {
112113
invariant(
113114
isInterfaceType(type),
114-
`Expected ${String(type)} to be a GraphQL Interface type.`,
115+
`Expected ${inspect(type)} to be a GraphQL Interface type.`,
115116
);
116117
return type;
117118
}
@@ -126,7 +127,7 @@ export function isUnionType(type) {
126127
export function assertUnionType(type: mixed): GraphQLUnionType {
127128
invariant(
128129
isUnionType(type),
129-
`Expected ${String(type)} to be a GraphQL Union type.`,
130+
`Expected ${inspect(type)} to be a GraphQL Union type.`,
130131
);
131132
return type;
132133
}
@@ -141,7 +142,7 @@ export function isEnumType(type) {
141142
export function assertEnumType(type: mixed): GraphQLEnumType {
142143
invariant(
143144
isEnumType(type),
144-
`Expected ${String(type)} to be a GraphQL Enum type.`,
145+
`Expected ${inspect(type)} to be a GraphQL Enum type.`,
145146
);
146147
return type;
147148
}
@@ -156,7 +157,7 @@ export function isInputObjectType(type) {
156157
export function assertInputObjectType(type: mixed): GraphQLInputObjectType {
157158
invariant(
158159
isInputObjectType(type),
159-
`Expected ${String(type)} to be a GraphQL Input Object type.`,
160+
`Expected ${inspect(type)} to be a GraphQL Input Object type.`,
160161
);
161162
return type;
162163
}
@@ -171,7 +172,7 @@ export function isListType(type) {
171172
export function assertListType(type: mixed): GraphQLList<any> {
172173
invariant(
173174
isListType(type),
174-
`Expected ${String(type)} to be a GraphQL List type.`,
175+
`Expected ${inspect(type)} to be a GraphQL List type.`,
175176
);
176177
return type;
177178
}
@@ -186,7 +187,7 @@ export function isNonNullType(type) {
186187
export function assertNonNullType(type: mixed): GraphQLNonNull<any> {
187188
invariant(
188189
isNonNullType(type),
189-
`Expected ${String(type)} to be a GraphQL Non-Null type.`,
190+
`Expected ${inspect(type)} to be a GraphQL Non-Null type.`,
190191
);
191192
return type;
192193
}
@@ -218,7 +219,7 @@ export function isInputType(type: mixed): boolean %checks {
218219
export function assertInputType(type: mixed): GraphQLInputType {
219220
invariant(
220221
isInputType(type),
221-
`Expected ${String(type)} to be a GraphQL input type.`,
222+
`Expected ${inspect(type)} to be a GraphQL input type.`,
222223
);
223224
return type;
224225
}
@@ -256,7 +257,7 @@ export function isOutputType(type: mixed): boolean %checks {
256257
export function assertOutputType(type: mixed): GraphQLOutputType {
257258
invariant(
258259
isOutputType(type),
259-
`Expected ${String(type)} to be a GraphQL output type.`,
260+
`Expected ${inspect(type)} to be a GraphQL output type.`,
260261
);
261262
return type;
262263
}
@@ -273,7 +274,7 @@ export function isLeafType(type: mixed): boolean %checks {
273274
export function assertLeafType(type: mixed): GraphQLLeafType {
274275
invariant(
275276
isLeafType(type),
276-
`Expected ${String(type)} to be a GraphQL leaf type.`,
277+
`Expected ${inspect(type)} to be a GraphQL leaf type.`,
277278
);
278279
return type;
279280
}
@@ -293,7 +294,7 @@ export function isCompositeType(type: mixed): boolean %checks {
293294
export function assertCompositeType(type: mixed): GraphQLCompositeType {
294295
invariant(
295296
isCompositeType(type),
296-
`Expected ${String(type)} to be a GraphQL composite type.`,
297+
`Expected ${inspect(type)} to be a GraphQL composite type.`,
297298
);
298299
return type;
299300
}
@@ -310,7 +311,7 @@ export function isAbstractType(type: mixed): boolean %checks {
310311
export function assertAbstractType(type: mixed): GraphQLAbstractType {
311312
invariant(
312313
isAbstractType(type),
313-
`Expected ${String(type)} to be a GraphQL abstract type.`,
314+
`Expected ${inspect(type)} to be a GraphQL abstract type.`,
314315
);
315316
return type;
316317
}
@@ -408,7 +409,7 @@ export function isWrappingType(type: mixed): boolean %checks {
408409
export function assertWrappingType(type: mixed): GraphQLWrappingType {
409410
invariant(
410411
isWrappingType(type),
411-
`Expected ${String(type)} to be a GraphQL wrapping type.`,
412+
`Expected ${inspect(type)} to be a GraphQL wrapping type.`,
412413
);
413414
return type;
414415
}
@@ -432,7 +433,7 @@ export function isNullableType(type: mixed): boolean %checks {
432433
export function assertNullableType(type: mixed): GraphQLNullableType {
433434
invariant(
434435
isNullableType(type),
435-
`Expected ${String(type)} to be a GraphQL nullable type.`,
436+
`Expected ${inspect(type)} to be a GraphQL nullable type.`,
436437
);
437438
return type;
438439
}
@@ -473,7 +474,7 @@ export function isNamedType(type: mixed): boolean %checks {
473474
export function assertNamedType(type: mixed): GraphQLNamedType {
474475
invariant(
475476
isNamedType(type),
476-
`Expected ${String(type)} to be a GraphQL named type.`,
477+
`Expected ${inspect(type)} to be a GraphQL named type.`,
477478
);
478479
return type;
479480
}
@@ -736,7 +737,7 @@ function defineFieldMap<TSource, TContext>(
736737
invariant(
737738
isValidResolver(field.resolve),
738739
`${type.name}.${fieldName} field resolver must be a function if ` +
739-
`provided, but got: ${String(field.resolve)}.`,
740+
`provided, but got: ${inspect(field.resolve)}.`,
740741
);
741742
const argsConfig = fieldConfig.args;
742743
if (!argsConfig) {
@@ -1148,7 +1149,7 @@ function defineEnumValues(
11481149
invariant(
11491150
isPlainObj(value),
11501151
`${type.name}.${valueName} must refer to an object with a "value" key ` +
1151-
`representing an internal value but got: ${String(value)}.`,
1152+
`representing an internal value but got: ${inspect(value)}.`,
11521153
);
11531154
invariant(
11541155
!value.hasOwnProperty('isDeprecated'),

0 commit comments

Comments
 (0)