Skip to content

Transform interfaces into possible types only #3257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
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
1 change: 1 addition & 0 deletions packages/apollo-gateway/src/FieldSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface Field<TParent = GraphQLCompositeType> {
parentType: TParent;
fieldNode: FieldNode;
fieldDef: GraphQLField<any, any>;
parentTypesArray?: TParent[];
}
export type FieldSet = Field[];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ export const typeDefs = gql`
sku: String!
name: String
price: String
details: ProductDetails
}

interface ProductDetails {
country: String
}

type ProductDetailsFurniture implements ProductDetails {
country: String
color: String
}

type ProductDetailsBook implements ProductDetails {
country: String
pages: Int
}

type Furniture implements Product @key(fields: "upc") @key(fields: "sku") {
Expand All @@ -33,6 +48,7 @@ export const typeDefs = gql`
price: String
brand: Brand
metadata: [MetadataOrError]
details: ProductDetailsFurniture
}

extend type Book implements Product @key(fields: "isbn") {
Expand All @@ -43,6 +59,7 @@ export const typeDefs = gql`
sku: String!
name(delimeter: String = " "): String @requires(fields: "title year")
price: String
details: ProductDetailsBook
}

type Car @key(fields: "id") {
Expand Down
87 changes: 87 additions & 0 deletions packages/apollo-gateway/src/__tests__/buildQueryPlan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -790,4 +790,91 @@ describe('buildQueryPlan', () => {
}
`);
});

it(`interface fragments should expand into possible types only`, () => {
const query = gql`
query {
books {
... on Product {
name
... on Furniture {
upc
}
}
}
}
`;

const queryPlan = buildQueryPlan(buildOperationContext(schema, query));

expect(queryPlan).toMatchInlineSnapshot(`
QueryPlan {
Sequence {
Fetch(service: "books") {
{
books {
__typename
isbn
title
year
}
}
},
Flatten(path: "books.@") {
Fetch(service: "product") {
{
... on Book {
__typename
isbn
title
year
}
} =>
{
... on Book {
name
}
}
},
},
},
}
`);
});

it(`interface inside interface should expand into possible types only`, () => {
const query = gql`
query {
product(upc: "") {
details {
country
}
}
}
`;

const queryPlan = buildQueryPlan(buildOperationContext(schema, query));

expect(queryPlan).toMatchInlineSnapshot(`
QueryPlan {
Fetch(service: "product") {
{
product(upc: "") {
__typename
... on Book {
details {
country
}
}
... on Furniture {
details {
country
}
}
}
}
},
}
`);
});
});
51 changes: 47 additions & 4 deletions packages/apollo-gateway/src/buildQueryPlan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
isListType,
isNamedType,
isObjectType,
isInterfaceType,
Kind,
OperationDefinitionNode,
SelectionSetNode,
Expand Down Expand Up @@ -402,6 +403,17 @@ function splitFields(
if (isObjectType(parentType)) {
// If parent type is an object type, we can directly look for the right
// group.

// ParentTypes collected from fragments. Multiple values mean nested fragments
if (field.parentTypesArray) {
let possibleParentTypes = field.parentTypesArray.map(type =>
context.getPossibleTypes(type),
);
if (!possibleParentTypes.every(arr => arr.includes(parentType))) {
continue;
}
}

const group = groupForField(field as Field<GraphQLObjectType>);
group.fields.push(
completeField(
Expand All @@ -422,7 +434,19 @@ function splitFields(
GraphQLObjectType
>();

for (const runtimeParentType of context.getPossibleTypes(parentType)) {
let possibleTypes = context.getPossibleTypes(parentType);

// ParentTypes collected from fragments. Multiple values mean nested fragments
if (field.parentTypesArray) {
let possibleParentTypes = field.parentTypesArray.map(type =>
context.getPossibleTypes(type),
);
possibleTypes = possibleTypes.filter(type =>
possibleParentTypes.every(arr => arr.includes(type)),
);
}

for (const runtimeParentType of possibleTypes) {
const fieldDef = context.getFieldDef(
runtimeParentType,
field.fieldNode,
Expand Down Expand Up @@ -464,15 +488,26 @@ function completeField(
fields: FieldSet,
): Field {
const { fieldNode, fieldDef } = fields[0];
const returnType = getNamedType(fieldDef.type);

let returnType = getNamedType(fieldDef.type);

if (isObjectType(parentType) || isInterfaceType(parentType)) {
// We should find field type by name, because it may be changed after
// replacing parent type inside splitFields function.
// For union only __typename may be requested and it's type
// will be taken from fieldDef
const realField = parentType.getFields()[fieldDef.name];
if (realField) {
returnType = getNamedType(realField.type);
}
}

if (!isCompositeType(returnType)) {
// FIXME: We should look at all field nodes to make sure we take directives
// into account (or remove directives for the time being).
return { parentType, fieldNode, fieldDef };
} else {
// For composite types, we need to recurse.

const fieldPath = addPath(path, getResponseName(fieldNode), fieldDef.type);

const subGroup = new FetchGroup(parentGroup.serviceName);
Expand Down Expand Up @@ -516,12 +551,18 @@ function collectFields(
visitedFragmentNames: { [fragmentName: string]: boolean } = Object.create(
null,
),
parentTypesArray: GraphQLCompositeType[] = [],
): FieldSet {
for (const selection of selectionSet.selections) {
switch (selection.kind) {
case Kind.FIELD:
const fieldDef = context.getFieldDef(parentType, selection);
fields.push({ parentType, fieldNode: selection, fieldDef });
fields.push({
parentType,
fieldNode: selection,
fieldDef,
parentTypesArray,
});
break;
case Kind.INLINE_FRAGMENT:
collectFields(
Expand All @@ -530,6 +571,7 @@ function collectFields(
selection.selectionSet,
fields,
visitedFragmentNames,
[...parentTypesArray, parentType],
);
break;
case Kind.FRAGMENT_SPREAD:
Expand All @@ -551,6 +593,7 @@ function collectFields(
fragment.selectionSet,
fields,
visitedFragmentNames,
[...parentTypesArray, parentType],
);
break;
}
Expand Down