diff --git a/src/utilities/TypeInfo.ts b/src/utilities/TypeInfo.ts index e72dfb01fb..ec8b985761 100644 --- a/src/utilities/TypeInfo.ts +++ b/src/utilities/TypeInfo.ts @@ -214,7 +214,7 @@ export class TypeInfo { const listType: unknown = getNullableType(this.getInputType()); const itemType: unknown = isListType(listType) ? listType.ofType - : listType; + : undefined; // List positions never have a default value. this._defaultValueStack.push(undefined); this._inputTypeStack.push(isInputType(itemType) ? itemType : undefined); diff --git a/src/utilities/__tests__/TypeInfo-test.ts b/src/utilities/__tests__/TypeInfo-test.ts index 5c04458c51..5af6033500 100644 --- a/src/utilities/__tests__/TypeInfo-test.ts +++ b/src/utilities/__tests__/TypeInfo-test.ts @@ -390,10 +390,10 @@ describe('visitWithTypeInfo', () => { ['enter', 'ObjectField', null, '[String]'], ['enter', 'Name', 'stringListField', '[String]'], ['leave', 'Name', 'stringListField', '[String]'], - ['enter', 'ListValue', null, 'String'], + ['enter', 'ListValue', null, 'String' /* the item type, not list type */], ['enter', 'StringValue', null, 'String'], ['leave', 'StringValue', null, 'String'], - ['leave', 'ListValue', null, 'String'], + ['leave', 'ListValue', null, 'String' /* the item type, not list type */], ['leave', 'ObjectField', null, '[String]'], ['leave', 'ObjectValue', null, 'ComplexInput'], ]); @@ -457,4 +457,104 @@ describe('visitWithTypeInfo', () => { ['leave', 'SelectionSet', null, 'Human', 'Human'], ]); }); + + it('supports traversals of object literals of custom scalars', () => { + const schema = buildSchema(` + scalar GeoPoint + `); + const ast = parseValue('{x: 4.0, y: 2.0}'); + const scalarType = schema.getType('GeoPoint'); + invariant(scalarType != null); + + const typeInfo = new TypeInfo(schema, scalarType); + + const visited: Array = []; + visit( + ast, + visitWithTypeInfo(typeInfo, { + enter(node) { + const type = typeInfo.getInputType(); + visited.push([ + 'enter', + node.kind, + node.kind === 'Name' ? node.value : null, + String(type), + ]); + }, + leave(node) { + const type = typeInfo.getInputType(); + visited.push([ + 'leave', + node.kind, + node.kind === 'Name' ? node.value : null, + String(type), + ]); + }, + }), + ); + + expect(visited).to.deep.equal([ + // Everything within ObjectValue should have type: undefined since the + // contents of custom scalars aren't part of GraphQL schema definitions. + ['enter', 'ObjectValue', null, 'GeoPoint'], + ['enter', 'ObjectField', null, 'undefined'], + ['enter', 'Name', 'x', 'undefined'], + ['leave', 'Name', 'x', 'undefined'], + ['enter', 'FloatValue', null, 'undefined'], + ['leave', 'FloatValue', null, 'undefined'], + ['leave', 'ObjectField', null, 'undefined'], + ['enter', 'ObjectField', null, 'undefined'], + ['enter', 'Name', 'y', 'undefined'], + ['leave', 'Name', 'y', 'undefined'], + ['enter', 'FloatValue', null, 'undefined'], + ['leave', 'FloatValue', null, 'undefined'], + ['leave', 'ObjectField', null, 'undefined'], + ['leave', 'ObjectValue', null, 'GeoPoint'], + ]); + }); + + it('supports traversals of list literals of custom scalars', () => { + const schema = buildSchema(` + scalar GeoPoint + `); + const ast = parseValue('[4.0, 2.0]'); + const scalarType = schema.getType('GeoPoint'); + invariant(scalarType != null); + + const typeInfo = new TypeInfo(schema, scalarType); + + const visited: Array = []; + visit( + ast, + visitWithTypeInfo(typeInfo, { + enter(node) { + const type = typeInfo.getInputType(); + visited.push([ + 'enter', + node.kind, + node.kind === 'Name' ? node.value : null, + String(type), + ]); + }, + leave(node) { + const type = typeInfo.getInputType(); + visited.push([ + 'leave', + node.kind, + node.kind === 'Name' ? node.value : null, + String(type), + ]); + }, + }), + ); + + expect(visited).to.deep.equal([ + ['enter', 'ListValue', null, 'undefined'], + ['enter', 'FloatValue', null, 'undefined'], + ['leave', 'FloatValue', null, 'undefined'], + ['enter', 'FloatValue', null, 'undefined'], + ['leave', 'FloatValue', null, 'undefined'], + ['leave', 'ListValue', null, 'undefined'], + ]); + }); }); diff --git a/src/validation/__tests__/VariablesInAllowedPositionRule-test.ts b/src/validation/__tests__/VariablesInAllowedPositionRule-test.ts index ec839d7497..3628bfef99 100644 --- a/src/validation/__tests__/VariablesInAllowedPositionRule-test.ts +++ b/src/validation/__tests__/VariablesInAllowedPositionRule-test.ts @@ -398,4 +398,81 @@ describe('Validates OneOf Input Objects', () => { }, ]); }); + + it('Custom scalars as arg', () => { + expectValid(` + query Query($point: GeoPoint) { + dog { + distanceFrom(loc: $point) + } + }`); + }); + + it('Forbids using custom scalar as builtin arg', () => { + expectErrors(` + query Query($point: GeoPoint) { + dog { + isAtLocation(x: $point, y: 10) + } + } + `).toDeepEqual([ + { + locations: [ + { + column: 19, + line: 2, + }, + { + column: 27, + line: 4, + }, + ], + message: + 'Variable "$point" of type "GeoPoint" used in position expecting type "Int".', + }, + ]); + }); + + it('Forbids using builtin scalar as custom scalar arg', () => { + expectErrors(` + query Query($x: Float) { + dog { + distanceFrom(loc: $x) + } + } + `).toDeepEqual([ + { + locations: [ + { + column: 19, + line: 2, + }, + { + column: 29, + line: 4, + }, + ], + message: + 'Variable "$x" of type "Float" used in position expecting type "GeoPoint".', + }, + ]); + }); + + it('Allows using variables inside object literal in custom scalar', () => { + expectValid(` + query Query($x: Float) { + dog { + distanceFrom(loc: {x: $x, y: 10.0}) + } + }`); + }); + + it('Allows using variables inside list literal in custom scalar', () => { + expectValid(` + query Query($x: Float) { + dog { + distanceFrom(loc: [$x, 10.0]) + } + }`); + }); }); diff --git a/src/validation/__tests__/harness.ts b/src/validation/__tests__/harness.ts index ea4840341c..15e720fe80 100644 --- a/src/validation/__tests__/harness.ts +++ b/src/validation/__tests__/harness.ts @@ -33,6 +33,8 @@ export const testSchema: GraphQLSchema = buildSchema(` DOWN } + scalar GeoPoint + type Dog implements Pet & Mammal & Canine { name(surname: Boolean): String nickname: String @@ -41,6 +43,7 @@ export const testSchema: GraphQLSchema = buildSchema(` doesKnowCommand(dogCommand: DogCommand): Boolean isHouseTrained(atOtherHomes: Boolean = true): Boolean isAtLocation(x: Int, y: Int): Boolean + distanceFrom(loc: GeoPoint): Float mother: Dog father: Dog }