diff --git a/lib/gateway/make-resolver.js b/lib/gateway/make-resolver.js index 0ef22362..f0d5d48c 100644 --- a/lib/gateway/make-resolver.js +++ b/lib/gateway/make-resolver.js @@ -397,8 +397,11 @@ function makeResolver ({ service, createOperation, transformData, isQuery, isRef if (isReference && !parent[fieldName]) return null + // Get the actual type as the returnType can be NonNull or List as well + const type = getNamedType(returnType) + const queryId = generatePathKey(info.path).join('.') - const resolverKey = queryId.replace(/\d/g, '_IDX_') + const resolverKey = `${queryId.replace(/\d/g, '_IDX_')}.${type.toString()}` const { reply, __currentQuery, lruGatewayResolvers, pubsub } = context const cached = lruGatewayResolvers.get(`${__currentQuery}_${resolverKey}`) @@ -407,9 +410,6 @@ function makeResolver ({ service, createOperation, transformData, isQuery, isRef let query let selections - // Get the actual type as the returnType can be NonNull or List as well - const type = getNamedType(returnType) - if (cached) { variableNamesToDefine = cached.variableNamesToDefine query = cached.query diff --git a/test/gateway/with-references-in-unions.js b/test/gateway/with-references-in-unions.js new file mode 100644 index 00000000..a5ab6665 --- /dev/null +++ b/test/gateway/with-references-in-unions.js @@ -0,0 +1,257 @@ +'use strict' + +const { test } = require('tap') +const Fastify = require('fastify') +const GQL = require('../..') + +async function createService (schema, resolvers = {}) { + const service = Fastify() + service.register(GQL, { + schema, + resolvers, + federationMetadata: true + }) + await service.listen(0) + + return [service, service.server.address().port] +} + +const users = { + u1: { + id: 'u1', + name: 'Marie Curie', + fields: ['Physics', 'Chemistry'] + }, + u2: { + id: 'u2', + name: 'ALbert Einstein', + fields: ['Physics', 'Philosophy'] + } +} + +const messages = { + m1: { + id: 'm1', + message: 'Text Message.', + recipients: [{ id: 'u1' }] + }, + m2: { + id: 'm2', + header: 'Email Header', + message: 'Email Message.', + recipients: [{ id: 'u2' }] + } +} + +test('gateway handles reference types in unions at the same schema paths correctly', async (t) => { + t.plan(2) + + const [messageService, messageServicePort] = await createService( + ` + type Query @extends { + getMessages: [Message!]! + } + + union Message = EmailMessage | TextMessage + + type TextMessage { + id: ID! + message: String! + recipients: [TextMessageUser!]! + } + + type EmailMessage { + id: ID! + message: String! + header: String! + recipients: [EmailMessageUser!]! + } + + type TextMessageUser @extends @key(fields: "id") { + id: ID! @external + } + + type EmailMessageUser @extends @key(fields: "id") { + id: ID! @external + } + `, + { + Query: { + getMessages: () => { + return Object.values(messages) + } + }, + Message: { + resolveType: (message) => { + if ('header' in message) { + return 'EmailMessage' + } + return 'TextMessage' + } + } + } + ) + + const [userService, userServicePort] = await createService( + ` + type TextMessageUser @key(fields: "id") { + id: ID! + name: String! + fields: [String!]! + } + + type EmailMessageUser @key(fields: "id") { + id: ID! + name: String! + fields: [String!]! + } + `, + { + TextMessageUser: { + __resolveReference: (user) => { + return users[user.id] + } + }, + EmailMessageUser: { + __resolveReference: (user) => { + return users[user.id] + } + } + } + ) + + const gateway = Fastify() + t.teardown(async () => { + await gateway.close() + await messageService.close() + await userService.close() + }) + gateway.register(GQL, { + gateway: { + services: [ + { + name: 'message', + url: `http://localhost:${messageServicePort}/graphql` + }, + { + name: 'user', + url: `http://localhost:${userServicePort}/graphql` + } + ] + } + }) + + const query = ` + query { + getMessages { + ... on TextMessage { + id + message + recipients { + id + name + fields + } + } + ... on EmailMessage { + id + header + message + recipients { + id + name + fields + } + } + } + } + ` + + // Not cached + { + const res = await gateway.inject({ + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + url: '/graphql', + body: JSON.stringify({ + query + }) + }) + + t.same(JSON.parse(res.body), { + data: { + getMessages: [ + { + id: 'm1', + message: 'Text Message.', + recipients: [ + { + id: 'u1', + name: 'Marie Curie', + fields: ['Physics', 'Chemistry'] + } + ] + }, + { + id: 'm2', + header: 'Email Header', + message: 'Email Message.', + recipients: [ + { + id: 'u2', + name: 'ALbert Einstein', + fields: ['Physics', 'Philosophy'] + } + ] + } + ] + } + }) + } + + // Cached + { + const res = await gateway.inject({ + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + url: '/graphql', + body: JSON.stringify({ + query + }) + }) + + t.same(JSON.parse(res.body), { + data: { + getMessages: [ + { + id: 'm1', + message: 'Text Message.', + recipients: [ + { + id: 'u1', + name: 'Marie Curie', + fields: ['Physics', 'Chemistry'] + } + ] + }, + { + id: 'm2', + header: 'Email Header', + message: 'Email Message.', + recipients: [ + { + id: 'u2', + name: 'ALbert Einstein', + fields: ['Physics', 'Philosophy'] + } + ] + } + ] + } + }) + } +})