Skip to content

Commit

Permalink
fix(delegate): handle variables correctly (#3320)
Browse files Browse the repository at this point in the history
* fix(delegate): handle variables correctly

* More readable visitor key maps

* Handle variables on directives as well
  • Loading branch information
ardatan committed Aug 5, 2021
1 parent cd3f18a commit 7fdef33
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 111 deletions.
5 changes: 5 additions & 0 deletions .changeset/serious-apes-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-tools/delegate': patch
---

fix(delegate): handle variables correctly
134 changes: 24 additions & 110 deletions packages/delegate/src/finalizeGatewayRequest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
ArgumentNode,
ASTKindToNode,
FragmentDefinitionNode,
getNamedType,
GraphQLField,
Expand All @@ -16,6 +17,7 @@ import {
TypeNameMetaFieldDef,
VariableDefinitionNode,
visit,
VisitorKeyMap,
visitWithTypeInfo,
} from 'graphql';

Expand Down Expand Up @@ -280,6 +282,26 @@ function collectFragmentVariables(
};
}

const filteredSelectionSetVisitorKeys: Partial<VisitorKeyMap<ASTKindToNode>> = {
SelectionSet: ['selections'],
Field: ['selectionSet'],
InlineFragment: ['selectionSet'],
FragmentDefinition: ['selectionSet'],
};

const variablesVisitorKeys: Partial<VisitorKeyMap<ASTKindToNode>> = {
SelectionSet: ['selections'],
Field: ['arguments', 'directives', 'selectionSet'],
Argument: ['value'],

InlineFragment: ['directives', 'selectionSet'],
FragmentDefinition: ['directives', 'selectionSet'],

ObjectValue: ['fields'],
ObjectField: ['name', 'value'],
Directive: ['arguments'],
};

function finalizeSelectionSet(
schema: GraphQLSchema,
type: GraphQLType,
Expand Down Expand Up @@ -390,61 +412,7 @@ function finalizeSelectionSet(
// visitorKeys argument usage a la https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js
// empty keys cannot be removed only because of typescript errors
// will hopefully be fixed in future version of graphql-js to be optional
{
Name: [],

Document: [],
OperationDefinition: [],
VariableDefinition: [],
Variable: [],
SelectionSet: ['selections'],
Field: ['selectionSet'],
Argument: [],

FragmentSpread: [],
InlineFragment: ['selectionSet'],
FragmentDefinition: ['selectionSet'],

IntValue: [],
FloatValue: [],
StringValue: [],
BooleanValue: [],
NullValue: [],
EnumValue: [],
ListValue: [],
ObjectValue: [],
ObjectField: [],

Directive: [],

NamedType: [],
ListType: [],
NonNullType: [],

SchemaDefinition: [],
OperationTypeDefinition: [],

ScalarTypeDefinition: [],
ObjectTypeDefinition: [],
FieldDefinition: [],
InputValueDefinition: [],
InterfaceTypeDefinition: [],
UnionTypeDefinition: [],
EnumTypeDefinition: [],
EnumValueDefinition: [],
InputObjectTypeDefinition: [],

DirectiveDefinition: [],

SchemaExtension: [],

ScalarTypeExtension: [],
ObjectTypeExtension: [],
InterfaceTypeExtension: [],
UnionTypeExtension: [],
EnumTypeExtension: [],
InputObjectTypeExtension: [],
}
filteredSelectionSetVisitorKeys as any
);

visit(
Expand All @@ -457,61 +425,7 @@ function finalizeSelectionSet(
// visitorKeys argument usage a la https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js
// empty keys cannot be removed only because of typescript errors
// will hopefully be fixed in future version of graphql-js to be optional
{
Name: [],

Document: [],
OperationDefinition: [],
VariableDefinition: [],
Variable: [],
SelectionSet: ['selections'],
Field: ['arguments', 'selectionSet'],
Argument: ['value'],

FragmentSpread: [],
InlineFragment: ['selectionSet'],
FragmentDefinition: ['selectionSet'],

IntValue: [],
FloatValue: [],
StringValue: [],
BooleanValue: [],
NullValue: [],
EnumValue: [],
ListValue: [],
ObjectValue: [],
ObjectField: [],

Directive: [],

NamedType: [],
ListType: [],
NonNullType: [],

SchemaDefinition: [],
OperationTypeDefinition: [],

ScalarTypeDefinition: [],
ObjectTypeDefinition: [],
FieldDefinition: [],
InputValueDefinition: [],
InterfaceTypeDefinition: [],
UnionTypeDefinition: [],
EnumTypeDefinition: [],
EnumValueDefinition: [],
InputObjectTypeDefinition: [],

DirectiveDefinition: [],

SchemaExtension: [],

ScalarTypeExtension: [],
ObjectTypeExtension: [],
InterfaceTypeExtension: [],
UnionTypeExtension: [],
EnumTypeExtension: [],
InputObjectTypeExtension: [],
}
variablesVisitorKeys as any
);

return {
Expand Down
84 changes: 83 additions & 1 deletion packages/delegate/tests/delegateToSchema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { graphql } from 'graphql';

import { delegateToSchema } from '../src/delegateToSchema';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { wrapSchema } from '@graphql-tools/wrap';
import { stitchSchemas } from '@graphql-tools/stitch';

function assertSome<T>(input: T): asserts input is Exclude<T, null | undefined>{
function assertSome<T>(input: T): asserts input is Exclude<T, null | undefined> {
if (input == null) {
throw new Error("Value should be neither null nor undefined.")
}
Expand Down Expand Up @@ -155,4 +157,84 @@ describe('delegateToSchema', () => {
assertSome(result.data)
expect(result.data['delegateToSchema']).toEqual('test');
});
test('should work even when there are variables for nested fields', async () => {
const innerSchema = makeExecutableSchema({
typeDefs: `
input TestInput {
strings: [String]
}
type Test {
strings: [String]
}
type Query {
test(input: TestInput): Test
}
`,
resolvers: {
Query: {
test: (_root, args) => args.input
},
},
});

const outerSchema = wrapSchema({ schema: innerSchema });

const result = await graphql({
schema: outerSchema,
source: /* GraphQL */ `
query test($strings: [String]) {
test(input: { strings: $strings }) {
strings
}
}
`,
variableValues: {
strings: ['foo', 'bar']
}
});

assertSome(result.data);
expect(result.data).toEqual({
test: {
strings: ['foo', 'bar']
}
});
});
test('should work variables in directives', async () => {
const sourceSchema = makeExecutableSchema({
typeDefs: /* GraphQL */`
type Query {
users(input: UsersInput!): [User!]!
}
type User {
name: String!
age: Int!
}
input UsersInput {
limit: Int
}
`,
resolvers: {
Query: {
users: () => {
return [
{ name: 'ABC', age: 10 },
{ name: 'DEF', age: 20 },
];
},
},
},
});
const stitchedSchema = stitchSchemas({ subschemas: [sourceSchema] });

const result = await graphql({
schema: stitchedSchema,
source: /* GraphQL */`query($input: UsersInput!, $skip_age: Boolean!) { users(input: $input) { name age @skip (if: $skip_age) } }`,
variableValues: { input: { limit: 5 }, skip_age: true },
});

expect(result).toEqual({ "data": { "users": [{ "name": "ABC" }, { "name": "DEF" }] } });
})
});

1 comment on commit 7fdef33

@vercel
Copy link

@vercel vercel bot commented on 7fdef33 Aug 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.