Skip to content
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

Reduce interface expansion for types contained to a single service #3582

Merged
merged 2 commits into from
Jan 21, 2020
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
1 change: 1 addition & 0 deletions packages/apollo-gateway/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
> The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the the appropriate changes within that release will be moved into the new section.

* Fix onSchemaChange callbacks for unmanaged configs [#3605](https://github.com/apollographql/apollo-server/pull/3605)
* Reduce interface expansion for types contained to a single service [#3582](https://github.com/apollographql/apollo-server/pull/3582)

# v0.11.4

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const typeDefs = gql`

extend type Query {
product(upc: String!): Product
vehicle(id: String!): Vehicle
topProducts(first: Int = 5): [Product]
topCars(first: Int = 5): [Car]
}
Expand Down Expand Up @@ -65,11 +66,32 @@ export const typeDefs = gql`
details: ProductDetailsBook
}

type Car @key(fields: "id") {
interface Vehicle {
id: String!
description: String
price: String
}

type Car implements Vehicle @key(fields: "id") {
id: String!
description: String
price: String
}

type Van implements Vehicle @key(fields: "id") {
id: String!
description: String
price: String
}

union Thing = Car | Ikea

extend type User @key(fields: "id") {
id: ID! @external
vehicle: Vehicle
thing: Thing
}

# Value type
type KeyValue {
key: String!
Expand Down Expand Up @@ -131,17 +153,25 @@ const products = [
{ __typename: 'Book', isbn: '0987654321', price: 29 },
];

const cars = [
const vehicles = [
{
__typename: 'Car',
id: '1',
description: 'Humble Toyota',
price: 9990,
},
{
__typename: 'Car',
id: '2',
description: 'Awesome Tesla',
price: 12990,
},
{
__typename: 'Van',
id: '3',
description: 'Just a van...',
price: 15990,
},
];

export const resolvers: GraphQLResolverMap<any> = {
Expand Down Expand Up @@ -176,13 +206,34 @@ export const resolvers: GraphQLResolverMap<any> = {
},
Car: {
__resolveReference(object) {
return cars.find(car => car.id === object.id);
return vehicles.find(vehicles => vehicles.id === object.id);
},
},
Van: {
__resolveReference(object) {
return vehicles.find(vehicles => vehicles.id === object.id);
},
},
Thing: {
__resolveType(object) {
return 'id' in object ? 'Car' : 'Ikea';
},
},
User: {
vehicle(user) {
return vehicles.find(vehicles => vehicles.id === user.id);
},
thing(user) {
return vehicles.find(vehicles => vehicles.id === user.id);
},
},
Query: {
product(_, args) {
return products.find(product => product.upc === args.upc);
},
vehicle(_, args) {
return vehicles.find(vehicles => vehicles.id === args.id);
},
topProducts(_, args) {
return products.slice(0, args.first);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,17 @@ export const typeDefs = gql`
relatedReviews: [Review!]! @requires(fields: "similarBooks { isbn }")
}

extend type Car @key(fields: "id") {
extend interface Vehicle {
retailPrice: String
}

extend type Car implements Vehicle @key(fields: "id") {
id: String! @external
price: String @external
retailPrice: String @requires(fields: "price")
}

extend type Van implements Vehicle @key(fields: "id") {
id: String! @external
price: String @external
retailPrice: String @requires(fields: "price")
Expand Down Expand Up @@ -217,6 +227,11 @@ export const resolvers: GraphQLResolverMap<any> = {
return car.price;
},
},
Van: {
retailPrice(van) {
return van.price;
},
},
MetadataOrError: {
__resolveType(object) {
return 'key' in object ? 'KeyValue' : 'Error';
Expand Down
117 changes: 117 additions & 0 deletions packages/apollo-gateway/src/__tests__/executeQueryPlan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import { buildQueryPlan, buildOperationContext } from '../buildQueryPlan';
import { executeQueryPlan } from '../executeQueryPlan';
import { LocalGraphQLDataSource } from '../datasources/LocalGraphQLDataSource';

import { astSerializer, queryPlanSerializer } from '../snapshotSerializers';
expect.addSnapshotSerializer(astSerializer);
expect.addSnapshotSerializer(queryPlanSerializer);

function buildLocalService(modules: GraphQLSchemaModule[]) {
const schema = buildFederatedSchema(modules);
return new LocalGraphQLDataSource(schema);
Expand Down Expand Up @@ -434,6 +438,119 @@ describe('executeQueryPlan', () => {
expect(response.errors).toBeUndefined();
});

it(`can execute queries on interface types`, async () => {
const query = gql`
query {
vehicle(id: "1") {
description
price
retailPrice
}
}
`;

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

const response = await executeQueryPlan(
queryPlan,
serviceMap,
buildRequestContext(),
operationContext,
);

expect(response.data).toMatchInlineSnapshot(`
Object {
"vehicle": Object {
"description": "Humble Toyota",
"price": "9990",
"retailPrice": "9990",
},
}
`);
});

it(`can execute queries whose fields are interface types`, async () => {
const query = gql`
query {
user(id: "1") {
name
vehicle {
description
price
retailPrice
}
}
}
`;

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

const response = await executeQueryPlan(
queryPlan,
serviceMap,
buildRequestContext(),
operationContext,
);

expect(response.data).toMatchInlineSnapshot(`
Object {
"user": Object {
"name": "Ada Lovelace",
"vehicle": Object {
"description": "Humble Toyota",
"price": "9990",
"retailPrice": "9990",
},
},
}
`);
});

it(`can execute queries whose fields are union types`, async () => {
const query = gql`
query {
user(id: "1") {
name
thing {
... on Vehicle {
description
price
retailPrice
}
... on Ikea {
asile
}
}
}
}
`;

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

const response = await executeQueryPlan(
queryPlan,
serviceMap,
buildRequestContext(),
operationContext,
);

expect(response.data).toMatchInlineSnapshot(`
Object {
"user": Object {
"name": "Ada Lovelace",
"thing": Object {
"description": "Humble Toyota",
"price": "9990",
"retailPrice": "9990",
},
},
}
`);
});

it('can execute queries with falsey @requires (except undefined)', async () => {
const query = gql`
query {
Expand Down
Loading