diff --git a/lib/query-builder.js b/lib/query-builder.js index faa7f0a..09f8692 100644 --- a/lib/query-builder.js +++ b/lib/query-builder.js @@ -113,7 +113,7 @@ function buildQuery (query, parentResult) { } // get keys to reuse on merge results -function buildQuerySelection (selection, parent, wrap = true) { +function buildQuerySelection (selection, parent, wrap = true, fragment = undefined) { if (!(selection && selection.length > 0)) { return { selection: '', keys: [] } } @@ -132,7 +132,7 @@ function buildQuerySelection (selection, parent, wrap = true) { fields.add(toQuerySelection(keyField)) keys.set('key' + keyId(k), k) } else if (selection[i].selection) { - fields.add(buildQuerySelection(selection[i].selection, null, selection[i].wrap).selection) + fields.add(buildQuerySelection(selection[i].selection, null, selection[i].wrap, selection[i].fragment).selection) } else if (selection[i].nested) { fields.add(selection[i].parentField) for (const nested of selection[i].nested.values()) { @@ -182,7 +182,7 @@ function buildQuerySelection (selection, parent, wrap = true) { } } - const qselection = wrap ? `{ ${Array.from(fields).join(' ')} }` : Array.from(fields).join(' ') + const qselection = wrap ? `${fragment ? `... on ${fragment} ` : ''}{ ${Array.from(fields).join(' ')} }` : Array.from(fields).join(' ') return { selection: qselection, keys: Array.from(keys.values()) } } diff --git a/lib/query-lookup.js b/lib/query-lookup.js index 0a8216c..35e85f7 100644 --- a/lib/query-lookup.js +++ b/lib/query-lookup.js @@ -28,6 +28,7 @@ const { mergeMaps, collectNodeArgs, collectPlainArgs, pathJoin } = require('./ut */ function collectQueries ({ subgraphName, queryFieldNode, path = '', fieldId, parent, args, + fragmentFieldTypeName, // references types, fields, aliases, // root: collect root queries @@ -85,6 +86,7 @@ function collectQueries ({ field, fieldId, queryFieldNode, + // TODO maybe parent and root are redundant parent, root, @@ -104,8 +106,9 @@ function collectQueries ({ return { queries, deferreds, order } } - const fieldTypeName = field?.typeName + const fieldTypeName = fragmentFieldTypeName ?? field?.typeName // const fieldType = field && types[fieldTypeName] + const isUnionTypeType = field.type.src.kind === 'UNION' for (let i = 0; i < queryFieldSelections.length; ++i) { const querySelection = queryFieldSelections[i] @@ -115,6 +118,7 @@ function collectQueries ({ ? context.info.fragments[querySelection.name.value] : querySelection + const fragmentFieldTypeName = isUnionTypeType ? fragment.typeCondition.name.value : undefined // TODO if !field - shouldn't happen const nested = collectNestedQueries({ context, @@ -124,12 +128,17 @@ function collectQueries ({ queryNode, querySelection: fragment, types, + fragmentFieldTypeName, fields }) // unwrap fragment as selection for (const n of nested.queries.values()) { - queryNode.query.selection.push({ selection: n.query.selection, wrap: false }) + queryNode.query.selection.push({ + selection: n.query.selection, + wrap: fragmentFieldTypeName !== undefined, + fragment: fragmentFieldTypeName + }) } collectDeferredQueries(queryNode, nested, deferreds) @@ -161,7 +170,7 @@ function collectQueries ({ context.logger.debug(`deferred query for ${cpath} > ${selectionFieldName} on ${subgraphName}`) - const alias = aliases[fieldId] + const alias = aliases && aliases[fieldId] if (alias && nested) { nesting({ selectionFieldName, @@ -258,7 +267,8 @@ function collectNestedQueries ({ types, fields, aliases, - alias + alias, + fragmentFieldTypeName }) { context.logger.debug({ fieldId, path }, 'query lookup, nested') const a = alias ? Object.values(alias)[0] : null @@ -274,7 +284,8 @@ function collectNestedQueries ({ types, fields, aliases, - typeName: a?.type + typeName: a?.type, + fragmentFieldTypeName }) } diff --git a/test/fixtures/authors.js b/test/fixtures/authors.js index 7be04a5..8a10429 100644 --- a/test/fixtures/authors.js +++ b/test/fixtures/authors.js @@ -53,10 +53,21 @@ const schema = ` list: [Author] } + interface BaseError { + message: String! + } + + type NotFoundError implements BaseError { + message: String! + } + + union UpdateAuthorAddressResponse = Author | NotFoundError + type Mutation { createAuthor(author: AuthorInput!): Author! batchCreateAuthor(authors: [AuthorInput]!): [Author]! publishBlogPost(authorId: ID!): Boolean! + updateAuthorAddress(authorId: ID!, address: AuthorAddressInput!): UpdateAuthorAddressResponse! } ` @@ -145,7 +156,23 @@ const resolvers = { } }) - return { success: true } + return true + }, + + async updateAuthorAddress (_, { authorId, address }, context) { + const author = data.authors[authorId] + if (!author) { + return { + __typename: 'NotFoundError', + message: 'Author not found' + } + } + + // we actually don't update the author, just return it + return { + __typename: 'Author', + ...author + } } }, Author: { diff --git a/test/query.test.js b/test/query.test.js index 229790a..a52427d 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -569,6 +569,106 @@ test('mutations', async (t) => { } ] } + }, + + { + name: 'should run a mutation query with union type response', + query: ` + mutation UpdateAuthorAddress($authorId: ID!, $address: AuthorAddressInput!) { + updateAuthorAddress(authorId: $authorId, address: $address) { + __typename + ...on Author { + id name { firstName lastName } + } + ... on NotFoundError { + message + } + } + } + `, + variables: { + authorId: '1', + address: { + street: 'Johnson Street 5', + city: 'Johnson City', + zip: 4200, + country: 'US', + mainResidence: true + } + }, + result: { + updateAuthorAddress: { + __typename: 'Author', + id: '1', + name: { firstName: 'Peter', lastName: 'Pluck' } + } + } + }, + { + name: 'should run a mutation query with union type response and return NotFoundError', + query: ` + mutation UpdateAuthorAddress($authorId: ID!, $address: AuthorAddressInput!) { + updateAuthorAddress(authorId: $authorId, address: $address) { + __typename + ... on Author { + id name { firstName lastName } + } + ... on NotFoundError { + message + } + } + } + `, + variables: { + authorId: '99', + address: { + street: 'Johnson Street 5', + city: 'Johnson City', + zip: 4200, + country: 'US', + mainResidence: true + } + }, + result: { + updateAuthorAddress: { + __typename: 'NotFoundError', + message: 'Author not found' + } + } + }, + + { + name: 'should run a mutation query with fragment', + query: ` + fragment authorFields on Author { + id name { firstName lastName } + } + mutation UpdateAuthorAddress($authorId: ID!, $address: AuthorAddressInput!) { + updateAuthorAddress(authorId: $authorId, address: $address) { + __typename + ...authorFields + ... on NotFoundError { + message + } + } + } + `, + variables: { + authorId: '99', + address: { + street: 'Johnson Street 5', + city: 'Johnson City', + zip: 4200, + country: 'US', + mainResidence: true + } + }, + result: { + updateAuthorAddress: { + __typename: 'NotFoundError', + message: 'Author not found' + } + } } ]