Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/utilities/TypeInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ export class TypeInfo {
return this._inputTypeStack.at(-1);
}

// Note: continues to expose the closest enclosing valid input type if
// traversal descends into syntax with no corresponding GraphQL input type.
getParentInputType(): Maybe<GraphQLInputType> {
return this._inputTypeStack.at(-2);
}
Expand Down Expand Up @@ -254,7 +256,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);
Expand Down
115 changes: 115 additions & 0 deletions src/utilities/__tests__/TypeInfo-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,121 @@ describe('visitWithTypeInfo', () => {
]);
});

it('supports traversals of object literals in custom scalar positions', () => {
const schema = buildSchema(`
scalar GeoPoint
`);
const ast = parseValue('{x: 4.0, y: 2.0}');
const scalarType = schema.getType('GeoPoint');
assert(scalarType != null);

const typeInfo = new TypeInfo(schema, scalarType);

const visited: Array<any> = [];
visit(
ast,
visitWithTypeInfo(typeInfo, {
enter(node) {
const type = typeInfo.getInputType();
const parentType = typeInfo.getParentInputType();
visited.push([
'enter',
node.kind,
node.kind === 'Name' ? node.value : null,
String(type),
String(parentType),
]);
},
leave(node) {
const type = typeInfo.getInputType();
const parentType = typeInfo.getParentInputType();
visited.push([
'leave',
node.kind,
node.kind === 'Name' ? node.value : null,
String(type),
String(parentType),
]);
},
}),
);

expect(visited).to.deep.equal([
// Everything within ObjectValue should have type: undefined since the
// contents of custom scalars are not part of the GraphQL type system.
// getParentInputType() continues to report the closest enclosing valid
// input type even after traversal leaves the GraphQL input type system.
['enter', 'ObjectValue', null, 'GeoPoint', 'undefined'],
['enter', 'ObjectField', null, 'undefined', 'GeoPoint'],
['enter', 'Name', 'x', 'undefined', 'GeoPoint'],
['leave', 'Name', 'x', 'undefined', 'GeoPoint'],
['enter', 'FloatValue', null, 'undefined', 'GeoPoint'],
['leave', 'FloatValue', null, 'undefined', 'GeoPoint'],
['leave', 'ObjectField', null, 'undefined', 'GeoPoint'],
['enter', 'ObjectField', null, 'undefined', 'GeoPoint'],
['enter', 'Name', 'y', 'undefined', 'GeoPoint'],
['leave', 'Name', 'y', 'undefined', 'GeoPoint'],
['enter', 'FloatValue', null, 'undefined', 'GeoPoint'],
['leave', 'FloatValue', null, 'undefined', 'GeoPoint'],
['leave', 'ObjectField', null, 'undefined', 'GeoPoint'],
['leave', 'ObjectValue', null, 'GeoPoint', 'undefined'],
]);
});

it('supports traversals of list literals in custom scalar positions', () => {
const schema = buildSchema(`
scalar GeoPoint
`);
const ast = parseValue('[4.0, 2.0]');
const scalarType = schema.getType('GeoPoint');
assert(scalarType != null);

const typeInfo = new TypeInfo(schema, scalarType);

const visited: Array<any> = [];
visit(
ast,
visitWithTypeInfo(typeInfo, {
enter(node) {
const type = typeInfo.getInputType();
const parentType = typeInfo.getParentInputType();
visited.push([
'enter',
node.kind,
node.kind === 'Name' ? node.value : null,
String(type),
String(parentType),
]);
},
leave(node) {
const type = typeInfo.getInputType();
const parentType = typeInfo.getParentInputType();
visited.push([
'leave',
node.kind,
node.kind === 'Name' ? node.value : null,
String(type),
String(parentType),
]);
},
}),
);

expect(visited).to.deep.equal([
// Everything including ListValue should have type: undefined since the
// contents of custom scalars are not part of the GraphQL type system.
// ListValues carry the item type, so the item type is also undefined.
// getParentInputType() continues to report the closest enclosing valid
// input type even after traversal leaves the GraphQL input type system.
['enter', 'ListValue', null, 'undefined', 'GeoPoint'],
['enter', 'FloatValue', null, 'undefined', 'GeoPoint'],
['leave', 'FloatValue', null, 'undefined', 'GeoPoint'],
['enter', 'FloatValue', null, 'undefined', 'GeoPoint'],
['leave', 'FloatValue', null, 'undefined', 'GeoPoint'],
['leave', 'ListValue', null, 'undefined', 'GeoPoint'],
]);
});

it('supports traversals of fragment arguments', () => {
const typeInfo = new TypeInfo(testSchema);

Expand Down
2 changes: 2 additions & 0 deletions src/validation/ValidationContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ export class ValidationContext extends ASTValidationContext {
return this._typeInfo.getInputType();
}

// Note: continues to expose the closest enclosing valid input type if
// traversal descends into syntax with no corresponding GraphQL input type.
getParentInputType(): Maybe<GraphQLInputType> {
return this._typeInfo.getParentInputType();
}
Expand Down
20 changes: 20 additions & 0 deletions src/validation/__tests__/VariablesInAllowedPositionRule-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,3 +534,23 @@ describe('Validates OneOf Input Objects', () => {
]);
});
});

describe('Non-specified behavior of variables within custom scalars', () => {
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])
}
}`);
});
});
3 changes: 3 additions & 0 deletions src/validation/__tests__/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export const testSchema: GraphQLSchema = buildSchema(`
DOWN
}

scalar GeoPoint

type Dog implements Pet & Mammal & Canine {
name(surname: Boolean): String
nickname: String
Expand All @@ -44,6 +46,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
}
Expand Down
Loading