Skip to content

Commit d216342

Browse files
yuchenshiyaacovCR
authored andcommitted
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 2988492 commit d216342

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
@@ -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
]);
@@ -519,6 +519,106 @@ describe('visitWithTypeInfo', () => {
519519
]);
520520
});
521521

522+
it('supports traversals of object literals of custom scalars', () => {
523+
const schema = buildSchema(`
524+
scalar GeoPoint
525+
`);
526+
const ast = parseValue('{x: 4.0, y: 2.0}');
527+
const scalarType = schema.getType('GeoPoint');
528+
invariant(scalarType != null);
529+
530+
const typeInfo = new TypeInfo(schema, scalarType);
531+
532+
const visited: Array<any> = [];
533+
visit(
534+
ast,
535+
visitWithTypeInfo(typeInfo, {
536+
enter(node) {
537+
const type = typeInfo.getInputType();
538+
visited.push([
539+
'enter',
540+
node.kind,
541+
node.kind === 'Name' ? node.value : null,
542+
String(type),
543+
]);
544+
},
545+
leave(node) {
546+
const type = typeInfo.getInputType();
547+
visited.push([
548+
'leave',
549+
node.kind,
550+
node.kind === 'Name' ? node.value : null,
551+
String(type),
552+
]);
553+
},
554+
}),
555+
);
556+
557+
expect(visited).to.deep.equal([
558+
// Everything within ObjectValue should have type: undefined since the
559+
// contents of custom scalars aren't part of GraphQL schema definitions.
560+
['enter', 'ObjectValue', null, 'GeoPoint'],
561+
['enter', 'ObjectField', null, 'undefined'],
562+
['enter', 'Name', 'x', 'undefined'],
563+
['leave', 'Name', 'x', 'undefined'],
564+
['enter', 'FloatValue', null, 'undefined'],
565+
['leave', 'FloatValue', null, 'undefined'],
566+
['leave', 'ObjectField', null, 'undefined'],
567+
['enter', 'ObjectField', null, 'undefined'],
568+
['enter', 'Name', 'y', 'undefined'],
569+
['leave', 'Name', 'y', 'undefined'],
570+
['enter', 'FloatValue', null, 'undefined'],
571+
['leave', 'FloatValue', null, 'undefined'],
572+
['leave', 'ObjectField', null, 'undefined'],
573+
['leave', 'ObjectValue', null, 'GeoPoint'],
574+
]);
575+
});
576+
577+
it('supports traversals of list literals of custom scalars', () => {
578+
const schema = buildSchema(`
579+
scalar GeoPoint
580+
`);
581+
const ast = parseValue('[4.0, 2.0]');
582+
const scalarType = schema.getType('GeoPoint');
583+
invariant(scalarType != null);
584+
585+
const typeInfo = new TypeInfo(schema, scalarType);
586+
587+
const visited: Array<any> = [];
588+
visit(
589+
ast,
590+
visitWithTypeInfo(typeInfo, {
591+
enter(node) {
592+
const type = typeInfo.getInputType();
593+
visited.push([
594+
'enter',
595+
node.kind,
596+
node.kind === 'Name' ? node.value : null,
597+
String(type),
598+
]);
599+
},
600+
leave(node) {
601+
const type = typeInfo.getInputType();
602+
visited.push([
603+
'leave',
604+
node.kind,
605+
node.kind === 'Name' ? node.value : null,
606+
String(type),
607+
]);
608+
},
609+
}),
610+
);
611+
612+
expect(visited).to.deep.equal([
613+
['enter', 'ListValue', null, 'undefined'],
614+
['enter', 'FloatValue', null, 'undefined'],
615+
['leave', 'FloatValue', null, 'undefined'],
616+
['enter', 'FloatValue', null, 'undefined'],
617+
['leave', 'FloatValue', null, 'undefined'],
618+
['leave', 'ListValue', null, 'undefined'],
619+
]);
620+
});
621+
522622
it('supports traversals of fragment arguments', () => {
523623
const typeInfo = new TypeInfo(testSchema);
524624

src/validation/__tests__/VariablesInAllowedPositionRule-test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,4 +533,81 @@ describe('Validates OneOf Input Objects', () => {
533533
},
534534
]);
535535
});
536+
537+
it('Custom scalars as arg', () => {
538+
expectValid(`
539+
query Query($point: GeoPoint) {
540+
dog {
541+
distanceFrom(loc: $point)
542+
}
543+
}`);
544+
});
545+
546+
it('Forbids using custom scalar as builtin arg', () => {
547+
expectErrors(`
548+
query Query($point: GeoPoint) {
549+
dog {
550+
isAtLocation(x: $point, y: 10)
551+
}
552+
}
553+
`).toDeepEqual([
554+
{
555+
locations: [
556+
{
557+
column: 19,
558+
line: 2,
559+
},
560+
{
561+
column: 27,
562+
line: 4,
563+
},
564+
],
565+
message:
566+
'Variable "$point" of type "GeoPoint" used in position expecting type "Int".',
567+
},
568+
]);
569+
});
570+
571+
it('Forbids using builtin scalar as custom scalar arg', () => {
572+
expectErrors(`
573+
query Query($x: Float) {
574+
dog {
575+
distanceFrom(loc: $x)
576+
}
577+
}
578+
`).toDeepEqual([
579+
{
580+
locations: [
581+
{
582+
column: 19,
583+
line: 2,
584+
},
585+
{
586+
column: 29,
587+
line: 4,
588+
},
589+
],
590+
message:
591+
'Variable "$x" of type "Float" used in position expecting type "GeoPoint".',
592+
},
593+
]);
594+
});
595+
596+
it('Allows using variables inside object literal in custom scalar', () => {
597+
expectValid(`
598+
query Query($x: Float) {
599+
dog {
600+
distanceFrom(loc: {x: $x, y: 10.0})
601+
}
602+
}`);
603+
});
604+
605+
it('Allows using variables inside list literal in custom scalar', () => {
606+
expectValid(`
607+
query Query($x: Float) {
608+
dog {
609+
distanceFrom(loc: [$x, 10.0])
610+
}
611+
}`);
612+
});
536613
});

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)