Skip to content

Commit 31b5fcb

Browse files
committed
Fix TypeInfo.getInputType() for custom scalar list literals.
Fix #4512. Also improves test coverage of TypeInfo and the affected validation rule. See linked issue.
1 parent d26810b commit 31b5fcb

4 files changed

Lines changed: 183 additions & 3 deletions

File tree

src/utilities/TypeInfo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ export class TypeInfo {
214214
const listType: unknown = getNullableType(this.getInputType());
215215
const itemType: unknown = isListType(listType)
216216
? listType.ofType
217-
: listType;
217+
: undefined;
218218
// List positions never have a default value.
219219
this._defaultValueStack.push(undefined);
220220
this._inputTypeStack.push(isInputType(itemType) ? itemType : undefined);

src/utilities/__tests__/TypeInfo-test.ts

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,10 +390,10 @@ describe('visitWithTypeInfo', () => {
390390
['enter', 'ObjectField', null, '[String]'],
391391
['enter', 'Name', 'stringListField', '[String]'],
392392
['leave', 'Name', 'stringListField', '[String]'],
393-
['enter', 'ListValue', null, 'String'],
393+
['enter', 'ListValue', null, 'String' /* the item type, not list type */],
394394
['enter', 'StringValue', null, 'String'],
395395
['leave', 'StringValue', null, 'String'],
396-
['leave', 'ListValue', null, 'String'],
396+
['leave', 'ListValue', null, 'String' /* the item type, not list type */],
397397
['leave', 'ObjectField', null, '[String]'],
398398
['leave', 'ObjectValue', null, 'ComplexInput'],
399399
]);
@@ -457,4 +457,104 @@ describe('visitWithTypeInfo', () => {
457457
['leave', 'SelectionSet', null, 'Human', 'Human'],
458458
]);
459459
});
460+
461+
it('supports traversals of object literals of custom scalars', () => {
462+
const schema = buildSchema(`
463+
scalar GeoPoint
464+
`);
465+
const ast = parseValue('{x: 4.0, y: 2.0}');
466+
const scalarType = schema.getType('GeoPoint');
467+
invariant(scalarType != null);
468+
469+
const typeInfo = new TypeInfo(schema, scalarType);
470+
471+
const visited: Array<any> = [];
472+
visit(
473+
ast,
474+
visitWithTypeInfo(typeInfo, {
475+
enter(node) {
476+
const type = typeInfo.getInputType();
477+
visited.push([
478+
'enter',
479+
node.kind,
480+
node.kind === 'Name' ? node.value : null,
481+
String(type),
482+
]);
483+
},
484+
leave(node) {
485+
const type = typeInfo.getInputType();
486+
visited.push([
487+
'leave',
488+
node.kind,
489+
node.kind === 'Name' ? node.value : null,
490+
String(type),
491+
]);
492+
},
493+
}),
494+
);
495+
496+
expect(visited).to.deep.equal([
497+
// Everything within ObjectValue should have type: undefined since the
498+
// contents of custom scalars aren't part of GraphQL schema definitions.
499+
['enter', 'ObjectValue', null, 'GeoPoint'],
500+
['enter', 'ObjectField', null, 'undefined'],
501+
['enter', 'Name', 'x', 'undefined'],
502+
['leave', 'Name', 'x', 'undefined'],
503+
['enter', 'FloatValue', null, 'undefined'],
504+
['leave', 'FloatValue', null, 'undefined'],
505+
['leave', 'ObjectField', null, 'undefined'],
506+
['enter', 'ObjectField', null, 'undefined'],
507+
['enter', 'Name', 'y', 'undefined'],
508+
['leave', 'Name', 'y', 'undefined'],
509+
['enter', 'FloatValue', null, 'undefined'],
510+
['leave', 'FloatValue', null, 'undefined'],
511+
['leave', 'ObjectField', null, 'undefined'],
512+
['leave', 'ObjectValue', null, 'GeoPoint'],
513+
]);
514+
});
515+
516+
it('supports traversals of list literals of custom scalars', () => {
517+
const schema = buildSchema(`
518+
scalar GeoPoint
519+
`);
520+
const ast = parseValue('[4.0, 2.0]');
521+
const scalarType = schema.getType('GeoPoint');
522+
invariant(scalarType != null);
523+
524+
const typeInfo = new TypeInfo(schema, scalarType);
525+
526+
const visited: Array<any> = [];
527+
visit(
528+
ast,
529+
visitWithTypeInfo(typeInfo, {
530+
enter(node) {
531+
const type = typeInfo.getInputType();
532+
visited.push([
533+
'enter',
534+
node.kind,
535+
node.kind === 'Name' ? node.value : null,
536+
String(type),
537+
]);
538+
},
539+
leave(node) {
540+
const type = typeInfo.getInputType();
541+
visited.push([
542+
'leave',
543+
node.kind,
544+
node.kind === 'Name' ? node.value : null,
545+
String(type),
546+
]);
547+
},
548+
}),
549+
);
550+
551+
expect(visited).to.deep.equal([
552+
['enter', 'ListValue', null, 'undefined'],
553+
['enter', 'FloatValue', null, 'undefined'],
554+
['leave', 'FloatValue', null, 'undefined'],
555+
['enter', 'FloatValue', null, 'undefined'],
556+
['leave', 'FloatValue', null, 'undefined'],
557+
['leave', 'ListValue', null, 'undefined'],
558+
]);
559+
});
460560
});

src/validation/__tests__/VariablesInAllowedPositionRule-test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,4 +398,81 @@ describe('Validates OneOf Input Objects', () => {
398398
},
399399
]);
400400
});
401+
402+
it('Custom scalars as arg', () => {
403+
expectValid(`
404+
query Query($point: GeoPoint) {
405+
dog {
406+
distanceFrom(loc: $point)
407+
}
408+
}`);
409+
});
410+
411+
it('Forbids using custom scalar as builtin arg', () => {
412+
expectErrors(`
413+
query Query($point: GeoPoint) {
414+
dog {
415+
isAtLocation(x: $point, y: 10)
416+
}
417+
}
418+
`).toDeepEqual([
419+
{
420+
locations: [
421+
{
422+
column: 19,
423+
line: 2,
424+
},
425+
{
426+
column: 27,
427+
line: 4,
428+
},
429+
],
430+
message:
431+
'Variable "$point" of type "GeoPoint" used in position expecting type "Int".',
432+
},
433+
]);
434+
});
435+
436+
it('Forbids using builtin scalar as custom scalar arg', () => {
437+
expectErrors(`
438+
query Query($x: Float) {
439+
dog {
440+
distanceFrom(loc: $x)
441+
}
442+
}
443+
`).toDeepEqual([
444+
{
445+
locations: [
446+
{
447+
column: 19,
448+
line: 2,
449+
},
450+
{
451+
column: 29,
452+
line: 4,
453+
},
454+
],
455+
message:
456+
'Variable "$x" of type "Float" used in position expecting type "GeoPoint".',
457+
},
458+
]);
459+
});
460+
461+
it('Allows using variables inside object literal in custom scalar', () => {
462+
expectValid(`
463+
query Query($x: Float) {
464+
dog {
465+
distanceFrom(loc: {x: $x, y: 10.0})
466+
}
467+
}`);
468+
});
469+
470+
it('Allows using variables inside list literal in custom scalar', () => {
471+
expectValid(`
472+
query Query($x: Float) {
473+
dog {
474+
distanceFrom(loc: [$x, 10.0])
475+
}
476+
}`);
477+
});
401478
});

src/validation/__tests__/harness.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export const testSchema: GraphQLSchema = buildSchema(`
3333
DOWN
3434
}
3535
36+
scalar GeoPoint
37+
3638
type Dog implements Pet & Mammal & Canine {
3739
name(surname: Boolean): String
3840
nickname: String
@@ -41,6 +43,7 @@ export const testSchema: GraphQLSchema = buildSchema(`
4143
doesKnowCommand(dogCommand: DogCommand): Boolean
4244
isHouseTrained(atOtherHomes: Boolean = true): Boolean
4345
isAtLocation(x: Int, y: Int): Boolean
46+
distanceFrom(loc: GeoPoint): Float
4447
mother: Dog
4548
father: Dog
4649
}

0 commit comments

Comments
 (0)