Skip to content

Commit

Permalink
Merge pull request apollographql/apollo-server#2841 from apollographq…
Browse files Browse the repository at this point in the history
…l/jackson/federation-list-type-keys

Allow list-type entity @key's 
Apollo-Orig-Commit-AS: apollographql/apollo-server@4e4aa81
  • Loading branch information
James Baxley authored Jun 14, 2019
2 parents 4b9e819 + 02f266b commit 0d6d036
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,53 +42,6 @@ describe('keyFieldsSelectInvalidType', () => {
expect(warnings).toHaveLength(0);
});

it('warns if @key references fields of a list type', () => {
const serviceA = {
typeDefs: gql`
type Product @key(fields: "myList myOptionalList") {
sku: String!
myList: [String]!
myOptionalList: [String]
upc: String!
color: Color!
}
type Color {
id: ID!
value: String!
}
`,
name: 'serviceA',
};

const serviceB = {
typeDefs: gql`
extend type Product {
sku: String! @external
price: Int! @requires(fields: "sku")
}
`,
name: 'serviceB',
};

const { schema, errors } = composeServices([serviceA, serviceB]);
expect(errors).toHaveLength(0);

const warnings = validateKeyFieldsSelectInvalidType(schema);
expect(warnings).toMatchInlineSnapshot(`
Array [
Object {
"code": "KEY_FIELDS_SELECT_INVALID_TYPE",
"message": "[serviceA] Product -> A @key selects Product.myList, which is a list type. Keys cannot select lists.",
},
Object {
"code": "KEY_FIELDS_SELECT_INVALID_TYPE",
"message": "[serviceA] Product -> A @key selects Product.myOptionalList, which is a list type. Keys cannot select lists.",
},
]
`);
});

it('warns if @key references fields of an interface type', () => {
const serviceA = {
typeDefs: gql`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
GraphQLSchema,
isObjectType,
FieldNode,
isListType,
isInterfaceType,
isNonNullType,
getNullableType,
Expand Down Expand Up @@ -46,19 +45,6 @@ export const keyFieldsSelectInvalidType = (schema: GraphQLSchema) => {
}

if (matchingField) {
if (
isListType(matchingField.type) ||
(isNonNullType(matchingField.type) &&
isListType(getNullableType(matchingField.type)))
) {
errors.push(
errorWithCode(
'KEY_FIELDS_SELECT_INVALID_TYPE',
logServiceAndType(serviceName, typeName) +
`A @key selects ${typeName}.${name}, which is a list type. Keys cannot select lists.`,
),
);
}
if (
isInterfaceType(matchingField.type) ||
(isNonNullType(matchingField.type) &&
Expand Down
125 changes: 125 additions & 0 deletions gateway-js/src/__tests__/integration/list-key.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import gql from 'graphql-tag';
import { execute, ServiceDefinitionModule } from '../execution-utils';
import { astSerializer, queryPlanSerializer } from '../../snapshotSerializers';

expect.addSnapshotSerializer(astSerializer);
expect.addSnapshotSerializer(queryPlanSerializer);

const users = [
{ id: ['1', '1'], name: 'Trevor Scheer', __typename: 'User' },
{ id: ['2', '2'], name: 'James Baxley', __typename: 'User' },
];

const reviews = [
{ id: '1', authorId: ['1', '1'], body: 'Good', __typename: 'Review' },
{ id: '2', authorId: ['2', '2'], body: 'Bad', __typename: 'Review' },
];

const reviewService: ServiceDefinitionModule = {
name: 'review',
typeDefs: gql`
type Query {
reviews: [Review!]!
}
type Review {
id: ID!
author: User!
body: String!
}
extend type User @key(fields: "id") {
id: [ID!]! @external
}
`,
resolvers: {
Query: {
reviews() {
return reviews;
},
},
Review: {
author(review) {
return {
id: review.authorId,
};
},
},
},
};

const listsAreEqual = <T>(as: T[], bs: T[]) =>
as.length === bs.length && as.every((a, i) => bs[i] === as[i]);

const userService: ServiceDefinitionModule = {
name: 'user',
typeDefs: gql`
type User @key(fields: "id") {
id: [ID!]!
name: String!
}
`,
resolvers: {
User: {
__resolveReference(reference) {
return users.find(user => listsAreEqual(user.id, reference.id));
},
},
},
};

it('fetches data correctly list type @key fields', async () => {
const query = gql`
query Reviews {
reviews {
body
author {
name
}
}
}
`;

const { data, queryPlan } = await execute([userService, reviewService], {
query,
});

expect(data).toEqual({
reviews: [
{ body: 'Good', author: { name: 'Trevor Scheer' } },
{ body: 'Bad', author: { name: 'James Baxley' } },
],
});
expect(queryPlan).toMatchInlineSnapshot(`
QueryPlan {
Sequence {
Fetch(service: "review") {
{
reviews {
body
author {
__typename
id
}
}
}
},
Flatten(path: "reviews.@.author") {
Fetch(service: "user") {
{
... on User {
__typename
id
}
} =>
{
... on User {
name
}
}
},
},
},
}
`);
});

0 comments on commit 0d6d036

Please sign in to comment.