Skip to content

Commit 55d04af

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 3528ee1 commit 55d04af

File tree

4 files changed

+183
-3
lines changed

4 files changed

+183
-3
lines changed

src/utilities/TypeInfo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ export class TypeInfo {
254254
const listType: unknown = getNullableType(this.getInputType());
255255
const itemType: unknown = isListType(listType)
256256
? listType.ofType
257-
: listType;
257+
: undefined;
258258
// List positions never have a default value.
259259
this._defaultValueStack.push(undefined);
260260
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
@@ -451,10 +451,10 @@ describe('visitWithTypeInfo', () => {
451451
['enter', 'ObjectField', null, '[String]'],
452452
['enter', 'Name', 'stringListField', '[String]'],
453453
['leave', 'Name', 'stringListField', '[String]'],
454-
['enter', 'ListValue', null, 'String'],
454+
['enter', 'ListValue', null, 'String' /* the item type, not list type */],
455455
['enter', 'StringValue', null, 'String'],
456456
['leave', 'StringValue', null, 'String'],
457-
['leave', 'ListValue', null, 'String'],
457+
['leave', 'ListValue', null, 'String' /* the item type, not list type */],
458458
['leave', 'ObjectField', null, '[String]'],
459459
['leave', 'ObjectValue', null, 'ComplexInput'],
460460
]);
@@ -735,4 +735,104 @@ describe('visitWithTypeInfo', () => {
735735
['leave', 'Document', null, 'undefined', 'undefined'],
736736
]);
737737
});
738+
739+
it('supports traversals of object literals of custom scalars', () => {
740+
const schema = buildSchema(`
741+
scalar GeoPoint
742+
`);
743+
const ast = parseValue('{x: 4.0, y: 2.0}');
744+
const scalarType = schema.getType('GeoPoint');
745+
invariant(scalarType != null);
746+
747+
const typeInfo = new TypeInfo(schema, scalarType);
748+
749+
const visited: Array<any> = [];
750+
visit(
751+
ast,
752+
visitWithTypeInfo(typeInfo, {
753+
enter(node) {
754+
const type = typeInfo.getInputType();
755+
visited.push([
756+
'enter',
757+
node.kind,
758+
node.kind === 'Name' ? node.value : null,
759+
String(type),
760+
]);
761+
},
762+
leave(node) {
763+
const type = typeInfo.getInputType();
764+
visited.push([
765+
'leave',
766+
node.kind,
767+
node.kind === 'Name' ? node.value : null,
768+
String(type),
769+
]);
770+
},
771+
}),
772+
);
773+
774+
expect(visited).to.deep.equal([
775+
// Everything within ObjectValue should have type: undefined since the
776+
// contents of custom scalars aren't part of GraphQL schema definitions.
777+
['enter', 'ObjectValue', null, 'GeoPoint'],
778+
['enter', 'ObjectField', null, 'undefined'],
779+
['enter', 'Name', 'x', 'undefined'],
780+
['leave', 'Name', 'x', 'undefined'],
781+
['enter', 'FloatValue', null, 'undefined'],
782+
['leave', 'FloatValue', null, 'undefined'],
783+
['leave', 'ObjectField', null, 'undefined'],
784+
['enter', 'ObjectField', null, 'undefined'],
785+
['enter', 'Name', 'y', 'undefined'],
786+
['leave', 'Name', 'y', 'undefined'],
787+
['enter', 'FloatValue', null, 'undefined'],
788+
['leave', 'FloatValue', null, 'undefined'],
789+
['leave', 'ObjectField', null, 'undefined'],
790+
['leave', 'ObjectValue', null, 'GeoPoint'],
791+
]);
792+
});
793+
794+
it('supports traversals of list literals of custom scalars', () => {
795+
const schema = buildSchema(`
796+
scalar GeoPoint
797+
`);
798+
const ast = parseValue('[4.0, 2.0]');
799+
const scalarType = schema.getType('GeoPoint');
800+
invariant(scalarType != null);
801+
802+
const typeInfo = new TypeInfo(schema, scalarType);
803+
804+
const visited: Array<any> = [];
805+
visit(
806+
ast,
807+
visitWithTypeInfo(typeInfo, {
808+
enter(node) {
809+
const type = typeInfo.getInputType();
810+
visited.push([
811+
'enter',
812+
node.kind,
813+
node.kind === 'Name' ? node.value : null,
814+
String(type),
815+
]);
816+
},
817+
leave(node) {
818+
const type = typeInfo.getInputType();
819+
visited.push([
820+
'leave',
821+
node.kind,
822+
node.kind === 'Name' ? node.value : null,
823+
String(type),
824+
]);
825+
},
826+
}),
827+
);
828+
829+
expect(visited).to.deep.equal([
830+
['enter', 'ListValue', null, 'undefined'],
831+
['enter', 'FloatValue', null, 'undefined'],
832+
['leave', 'FloatValue', null, 'undefined'],
833+
['enter', 'FloatValue', null, 'undefined'],
834+
['leave', 'FloatValue', null, 'undefined'],
835+
['leave', 'ListValue', null, 'undefined'],
836+
]);
837+
});
738838
});

src/validation/__tests__/VariablesInAllowedPositionRule-test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,4 +492,81 @@ describe('Validate: Variables are in allowed positions', () => {
492492
]);
493493
});
494494
});
495+
496+
it('Custom scalars as arg', () => {
497+
expectValid(`
498+
query Query($point: GeoPoint) {
499+
dog {
500+
distanceFrom(loc: $point)
501+
}
502+
}`);
503+
});
504+
505+
it('Forbids using custom scalar as builtin arg', () => {
506+
expectErrors(`
507+
query Query($point: GeoPoint) {
508+
dog {
509+
isAtLocation(x: $point, y: 10)
510+
}
511+
}
512+
`).toDeepEqual([
513+
{
514+
locations: [
515+
{
516+
column: 19,
517+
line: 2,
518+
},
519+
{
520+
column: 27,
521+
line: 4,
522+
},
523+
],
524+
message:
525+
'Variable "$point" of type "GeoPoint" used in position expecting type "Int".',
526+
},
527+
]);
528+
});
529+
530+
it('Forbids using builtin scalar as custom scalar arg', () => {
531+
expectErrors(`
532+
query Query($x: Float) {
533+
dog {
534+
distanceFrom(loc: $x)
535+
}
536+
}
537+
`).toDeepEqual([
538+
{
539+
locations: [
540+
{
541+
column: 19,
542+
line: 2,
543+
},
544+
{
545+
column: 29,
546+
line: 4,
547+
},
548+
],
549+
message:
550+
'Variable "$x" of type "Float" used in position expecting type "GeoPoint".',
551+
},
552+
]);
553+
});
554+
555+
it('Allows using variables inside object literal in custom scalar', () => {
556+
expectValid(`
557+
query Query($x: Float) {
558+
dog {
559+
distanceFrom(loc: {x: $x, y: 10.0})
560+
}
561+
}`);
562+
});
563+
564+
it('Allows using variables inside list literal in custom scalar', () => {
565+
expectValid(`
566+
query Query($x: Float) {
567+
dog {
568+
distanceFrom(loc: [$x, 10.0])
569+
}
570+
}`);
571+
});
495572
});

src/validation/__tests__/harness.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export const testSchema: GraphQLSchema = buildSchema(`
3636
DOWN
3737
}
3838
39+
scalar GeoPoint
40+
3941
type Dog implements Pet & Mammal & Canine {
4042
name(surname: Boolean): String
4143
nickname: String
@@ -44,6 +46,7 @@ export const testSchema: GraphQLSchema = buildSchema(`
4446
doesKnowCommand(dogCommand: DogCommand): Boolean
4547
isHouseTrained(atOtherHomes: Boolean = true): Boolean
4648
isAtLocation(x: Int, y: Int): Boolean
49+
distanceFrom(loc: GeoPoint): Float
4750
mother: Dog
4851
father: Dog
4952
}

0 commit comments

Comments
 (0)