Skip to content

Commit f2070c8

Browse files
committed
Improve the inspect() function. (#1380)
As well as use it in more places, replace use of JSON.stringify, and ensure validation rule message formatters expect strings instead of types.
1 parent faeea7f commit f2070c8

17 files changed

+100
-87
lines changed

src/execution/__tests__/executor-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1054,7 +1054,7 @@ describe('Execute: Handles basic execution tasks', () => {
10541054
errors: [
10551055
{
10561056
message:
1057-
'Expected value of type "SpecialType" but got: [object Object].',
1057+
'Expected value of type "SpecialType" but got: {value: "bar"}.',
10581058
locations: [{ line: 1, column: 3 }],
10591059
path: ['specials', 1],
10601060
},

src/execution/__tests__/variables-test.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ describe('Execute: Handles inputs', () => {
371371
{
372372
message:
373373
'Variable "$input" got invalid value ' +
374-
'{"a":"foo","b":"bar","c":null}; ' +
374+
'{a: "foo", b: "bar", c: null}; ' +
375375
'Expected non-nullable type String! not to be null at value.c.',
376376
locations: [{ line: 2, column: 16 }],
377377
},
@@ -401,7 +401,7 @@ describe('Execute: Handles inputs', () => {
401401
errors: [
402402
{
403403
message:
404-
'Variable "$input" got invalid value {"a":"foo","b":"bar"}; ' +
404+
'Variable "$input" got invalid value {a: "foo", b: "bar"}; ' +
405405
'Field value.c of required type String! was not provided.',
406406
locations: [{ line: 2, column: 16 }],
407407
},
@@ -421,13 +421,13 @@ describe('Execute: Handles inputs', () => {
421421
errors: [
422422
{
423423
message:
424-
'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' +
424+
'Variable "$input" got invalid value {na: {a: "foo"}}; ' +
425425
'Field value.na.c of required type String! was not provided.',
426426
locations: [{ line: 2, column: 18 }],
427427
},
428428
{
429429
message:
430-
'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' +
430+
'Variable "$input" got invalid value {na: {a: "foo"}}; ' +
431431
'Field value.nb of required type String! was not provided.',
432432
locations: [{ line: 2, column: 18 }],
433433
},
@@ -446,7 +446,7 @@ describe('Execute: Handles inputs', () => {
446446
{
447447
message:
448448
'Variable "$input" got invalid value ' +
449-
'{"a":"foo","b":"bar","c":"baz","extra":"dog"}; ' +
449+
'{a: "foo", b: "bar", c: "baz", extra: "dog"}; ' +
450450
'Field "extra" is not defined by type TestInputObject.',
451451
locations: [{ line: 2, column: 16 }],
452452
},
@@ -693,8 +693,8 @@ describe('Execute: Handles inputs', () => {
693693
errors: [
694694
{
695695
message:
696-
'Variable "$value" got invalid value [1,2,3]; Expected type ' +
697-
'String; String cannot represent an array value: [1,2,3]',
696+
'Variable "$value" got invalid value [1, 2, 3]; Expected type ' +
697+
'String; String cannot represent an array value: [1, 2, 3]',
698698
locations: [{ line: 2, column: 16 }],
699699
},
700700
],
@@ -841,7 +841,7 @@ describe('Execute: Handles inputs', () => {
841841
errors: [
842842
{
843843
message:
844-
'Variable "$input" got invalid value ["A",null,"B"]; ' +
844+
'Variable "$input" got invalid value ["A", null, "B"]; ' +
845845
'Expected non-nullable type String! not to be null at value[1].',
846846
locations: [{ line: 2, column: 16 }],
847847
},
@@ -891,7 +891,7 @@ describe('Execute: Handles inputs', () => {
891891
errors: [
892892
{
893893
message:
894-
'Variable "$input" got invalid value ["A",null,"B"]; ' +
894+
'Variable "$input" got invalid value ["A", null, "B"]; ' +
895895
'Expected non-nullable type String! not to be null at value[1].',
896896
locations: [{ line: 2, column: 16 }],
897897
},

src/execution/execute.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1046,7 +1046,7 @@ function ensureValidRuntimeType(
10461046
throw new GraphQLError(
10471047
`Abstract type ${returnType.name} must resolve to an Object type at ` +
10481048
`runtime for field ${info.parentType.name}.${info.fieldName} with ` +
1049-
`value "${inspect(result)}", received "${inspect(runtimeType)}". ` +
1049+
`value ${inspect(result)}, received "${inspect(runtimeType)}". ` +
10501050
`Either the ${returnType.name} type should provide a "resolveType" ` +
10511051
'function or each possible types should provide an ' +
10521052
'"isTypeOf" function.',

src/execution/values.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export function getVariableValues(
9999
coercionErrors.forEach(error => {
100100
error.message =
101101
`Variable "$${varName}" got invalid ` +
102-
`value ${JSON.stringify(value)}; ${error.message}`;
102+
`value ${inspect(value)}; ${error.message}`;
103103
});
104104
errors.push(...coercionErrors);
105105
} else {

src/jsutils/inspect.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,23 @@
77
* @flow strict
88
*/
99

10+
/**
11+
* Used to print values in error messages.
12+
*/
1013
export default function inspect(value: mixed): string {
11-
if (Array.isArray(value)) {
12-
return '[' + String(value) + ']';
13-
}
14-
return String(value);
14+
return value && typeof value === 'object'
15+
? typeof value.inspect === 'function'
16+
? value.inspect()
17+
: Array.isArray(value)
18+
? '[' + value.map(inspect).join(', ') + ']'
19+
: '{' +
20+
Object.keys(value)
21+
.map(k => `${k}: ${inspect(value[k])}`)
22+
.join(', ') +
23+
'}'
24+
: typeof value === 'string'
25+
? '"' + value + '"'
26+
: typeof value === 'function'
27+
? `[function ${value.name}]`
28+
: String(value);
1529
}

src/language/__tests__/parser-test.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@ describe('Parser', () => {
2929
});
3030

3131
it('asserts that a source to parse was provided', () => {
32-
expect(() => parse({})).to.throw(
33-
'Must provide Source. Received: [object Object]',
34-
);
32+
expect(() => parse({})).to.throw('Must provide Source. Received: {}');
3533
});
3634

3735
it('parse provides useful errors', () => {

src/subscription/__tests__/subscribe-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ describe('Subscription Initialization Phase', () => {
373373

374374
await expectPromiseToThrow(
375375
() => createSubscription(pubsub, invalidEmailSchema),
376-
'Subscription field must return Async Iterable. Received: test',
376+
'Subscription field must return Async Iterable. Received: "test"',
377377
);
378378
});
379379

@@ -474,7 +474,7 @@ describe('Subscription Initialization Phase', () => {
474474
{
475475
message:
476476
'Variable "$priority" got invalid value "meow"; Expected ' +
477-
'type Int; Int cannot represent non-integer value: meow',
477+
'type Int; Int cannot represent non-integer value: "meow"',
478478
locations: [{ line: 2, column: 21 }],
479479
},
480480
],

src/type/__tests__/definition-test.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
import { describe, it } from 'mocha';
2424
import { expect } from 'chai';
2525

26+
import inspect from '../../jsutils/inspect';
2627
import { isObjectType, isInputType, isOutputType } from '../definition';
2728

2829
const BlogImage = new GraphQLObjectType({
@@ -657,7 +658,7 @@ describe('Type System: Object fields must have valid resolve values', () => {
657658
it('rejects an empty Object field resolver', () => {
658659
expect(() => schemaWithObjectWithFieldResolver({})).to.throw(
659660
'BadResolver.badField field resolver must be a function if provided, ' +
660-
'but got: [object Object].',
661+
'but got: {}.',
661662
);
662663
});
663664

@@ -1139,7 +1140,7 @@ describe('Type System: List must accept only types', () => {
11391140
notTypes.forEach(type => {
11401141
it(`rejects a non-type as item type of list: ${type}`, () => {
11411142
expect(() => GraphQLList(type)).to.throw(
1142-
`Expected ${type} to be a GraphQL type.`,
1143+
`Expected ${inspect(type)} to be a GraphQL type.`,
11431144
);
11441145
});
11451146
});
@@ -1175,7 +1176,7 @@ describe('Type System: NonNull must only accept non-nullable types', () => {
11751176
notNullableTypes.forEach(type => {
11761177
it(`rejects a non-type as nullable type of non-null: ${type}`, () => {
11771178
expect(() => GraphQLNonNull(type)).to.throw(
1178-
`Expected ${type} to be a GraphQL nullable type.`,
1179+
`Expected ${inspect(type)} to be a GraphQL nullable type.`,
11791180
);
11801181
});
11811182
});

src/type/__tests__/enumType-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ describe('Type System: Enum Values', () => {
205205
data: { colorEnum: null },
206206
errors: [
207207
{
208-
message: 'Expected a value of type "Color" but received: GREEN',
208+
message: 'Expected a value of type "Color" but received: "GREEN"',
209209
locations: [{ line: 1, column: 3 }],
210210
path: ['colorEnum'],
211211
},
@@ -384,7 +384,7 @@ describe('Type System: Enum Values', () => {
384384
errors: [
385385
{
386386
message:
387-
'Expected a value of type "Complex" but received: [object Object]',
387+
'Expected a value of type "Complex" but received: {someRandomValue: 123}',
388388
locations: [{ line: 6, column: 9 }],
389389
path: ['bad'],
390390
},

src/type/__tests__/serialization-test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe('Type System: Scalar coercion', () => {
3838
'Int cannot represent non-integer value: -1.1',
3939
);
4040
expect(() => GraphQLInt.serialize('-1.1')).to.throw(
41-
'Int cannot represent non-integer value: -1.1',
41+
'Int cannot represent non-integer value: "-1.1"',
4242
);
4343
// Maybe a safe JavaScript int, but bigger than 2^32, so not
4444
// representable as a GraphQL Int
@@ -56,7 +56,7 @@ describe('Type System: Scalar coercion', () => {
5656
'Int cannot represent non 32-bit signed integer value: -1e+100',
5757
);
5858
expect(() => GraphQLInt.serialize('one')).to.throw(
59-
'Int cannot represent non-integer value: one',
59+
'Int cannot represent non-integer value: "one"',
6060
);
6161
// Doesn't represent number
6262
expect(() => GraphQLInt.serialize('')).to.throw(
@@ -92,7 +92,7 @@ describe('Type System: Scalar coercion', () => {
9292
'Float cannot represent non numeric value: Infinity',
9393
);
9494
expect(() => GraphQLFloat.serialize('one')).to.throw(
95-
'Float cannot represent non numeric value: one',
95+
'Float cannot represent non numeric value: "one"',
9696
);
9797
expect(() => GraphQLFloat.serialize('')).to.throw(
9898
'Float cannot represent non numeric value: (empty string)',

src/type/__tests__/validation-test.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ describe('Type System: A Schema must have Object root types', () => {
383383
});
384384
expect(validateSchema(schema)).to.deep.equal([
385385
{
386-
message: 'Expected directive but got: somedirective.',
386+
message: 'Expected directive but got: "somedirective".',
387387
},
388388
]);
389389
});
@@ -861,10 +861,10 @@ describe('Type System: Object fields must have output types', () => {
861861
const schema = schemaWithObjectFieldOfType(Number);
862862
expect(validateSchema(schema)).to.deep.equal([
863863
{
864-
message: `The type of BadObject.badField must be Output Type but got: ${Number}.`,
864+
message: `The type of BadObject.badField must be Output Type but got: [function Number].`,
865865
},
866866
{
867-
message: `Expected GraphQL named type but got: ${Number}.`,
867+
message: `Expected GraphQL named type but got: [function Number].`,
868868
},
869869
]);
870870
});
@@ -1162,13 +1162,13 @@ describe('Type System: Interface fields must have output types', () => {
11621162
const schema = schemaWithInterfaceFieldOfType(Number);
11631163
expect(validateSchema(schema)).to.deep.equal([
11641164
{
1165-
message: `The type of BadInterface.badField must be Output Type but got: ${Number}.`,
1165+
message: `The type of BadInterface.badField must be Output Type but got: [function Number].`,
11661166
},
11671167
{
1168-
message: `Expected GraphQL named type but got: ${Number}.`,
1168+
message: `Expected GraphQL named type but got: [function Number].`,
11691169
},
11701170
{
1171-
message: `The type of BadImplementing.badField must be Output Type but got: ${Number}.`,
1171+
message: `The type of BadImplementing.badField must be Output Type but got: [function Number].`,
11721172
},
11731173
]);
11741174
});
@@ -1275,10 +1275,10 @@ describe('Type System: Field arguments must have input types', () => {
12751275
const schema = schemaWithArgOfType(Number);
12761276
expect(validateSchema(schema)).to.deep.equal([
12771277
{
1278-
message: `The type of BadObject.badField(badArg:) must be Input Type but got: ${Number}.`,
1278+
message: `The type of BadObject.badField(badArg:) must be Input Type but got: [function Number].`,
12791279
},
12801280
{
1281-
message: `Expected GraphQL named type but got: ${Number}.`,
1281+
message: `Expected GraphQL named type but got: [function Number].`,
12821282
},
12831283
]);
12841284
});
@@ -1359,10 +1359,10 @@ describe('Type System: Input Object fields must have input types', () => {
13591359
const schema = schemaWithInputFieldOfType(Number);
13601360
expect(validateSchema(schema)).to.deep.equal([
13611361
{
1362-
message: `The type of BadInputObject.badField must be Input Type but got: ${Number}.`,
1362+
message: `The type of BadInputObject.badField must be Input Type but got: [function Number].`,
13631363
},
13641364
{
1365-
message: `Expected GraphQL named type but got: ${Number}.`,
1365+
message: `Expected GraphQL named type but got: [function Number].`,
13661366
},
13671367
]);
13681368
});

src/utilities/__tests__/coerceValue-test.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe('coerceValue', () => {
3535
it('returns error for array input as string', () => {
3636
const result = coerceValue([1, 2, 3], scalar);
3737
expectErrors(result).to.deep.equal([
38-
`Expected type ${scalar}; String cannot represent an array value: [1,2,3]`,
38+
`Expected type ${scalar}; String cannot represent an array value: [1, 2, 3]`,
3939
]);
4040
});
4141
});
@@ -79,7 +79,7 @@ describe('coerceValue', () => {
7979
it('returns a single error for float input as int', () => {
8080
const result = coerceValue('1.5', GraphQLInt);
8181
expectErrors(result).to.deep.equal([
82-
'Expected type Int; Int cannot represent non-integer value: 1.5',
82+
'Expected type Int; Int cannot represent non-integer value: "1.5"',
8383
]);
8484
});
8585

@@ -100,14 +100,14 @@ describe('coerceValue', () => {
100100
it('returns a single error for char input', () => {
101101
const result = coerceValue('a', GraphQLInt);
102102
expectErrors(result).to.deep.equal([
103-
'Expected type Int; Int cannot represent non-integer value: a',
103+
'Expected type Int; Int cannot represent non-integer value: "a"',
104104
]);
105105
});
106106

107107
it('returns a single error for char input', () => {
108108
const result = coerceValue('meow', GraphQLInt);
109109
expectErrors(result).to.deep.equal([
110-
'Expected type Int; Int cannot represent non-integer value: meow',
110+
'Expected type Int; Int cannot represent non-integer value: "meow"',
111111
]);
112112
});
113113
});
@@ -157,14 +157,14 @@ describe('coerceValue', () => {
157157
it('returns a single error for char input', () => {
158158
const result = coerceValue('a', GraphQLFloat);
159159
expectErrors(result).to.deep.equal([
160-
'Expected type Float; Float cannot represent non numeric value: a',
160+
'Expected type Float; Float cannot represent non numeric value: "a"',
161161
]);
162162
});
163163

164164
it('returns a single error for char input', () => {
165165
const result = coerceValue('meow', GraphQLFloat);
166166
expectErrors(result).to.deep.equal([
167-
'Expected type Float; Float cannot represent non numeric value: meow',
167+
'Expected type Float; Float cannot represent non numeric value: "meow"',
168168
]);
169169
});
170170
});
@@ -226,15 +226,15 @@ describe('coerceValue', () => {
226226
it('returns no error for an invalid field', () => {
227227
const result = coerceValue({ foo: 'abc' }, TestInputObject);
228228
expectErrors(result).to.deep.equal([
229-
'Expected type Int at value.foo; Int cannot represent non-integer value: abc',
229+
'Expected type Int at value.foo; Int cannot represent non-integer value: "abc"',
230230
]);
231231
});
232232

233233
it('returns multiple errors for multiple invalid fields', () => {
234234
const result = coerceValue({ foo: 'abc', bar: 'def' }, TestInputObject);
235235
expectErrors(result).to.deep.equal([
236-
'Expected type Int at value.foo; Int cannot represent non-integer value: abc',
237-
'Expected type Int at value.bar; Int cannot represent non-integer value: def',
236+
'Expected type Int at value.foo; Int cannot represent non-integer value: "abc"',
237+
'Expected type Int at value.bar; Int cannot represent non-integer value: "def"',
238238
]);
239239
});
240240

0 commit comments

Comments
 (0)