diff --git a/packages/merge/src/typedefs-mergers/directives.ts b/packages/merge/src/typedefs-mergers/directives.ts index 44156ee7171..d0b752880ce 100644 --- a/packages/merge/src/typedefs-mergers/directives.ts +++ b/packages/merge/src/typedefs-mergers/directives.ts @@ -84,6 +84,14 @@ const matchValues = (a: ValueNode, b: ValueNode): boolean => { return false; }; +const isLinkDirective = (directive: DirectiveNode): boolean => directive.name.value === 'link'; +const getLinkDirectiveURL = (directive: DirectiveNode): string | undefined => { + const stringValue = isLinkDirective(directive) + ? directive.arguments?.find(arg => arg.name.value === 'url')?.value + : undefined; + return stringValue?.kind === 'StringValue' ? stringValue.value : undefined; +}; + const matchArguments = (a: ArgumentNode, b: ArgumentNode): boolean => a.name.value === b.name.value && a.value.kind === b.value.kind && matchValues(a.value, b.value); @@ -124,6 +132,15 @@ export function mergeDirectives( // if did not find a directive with this name on the result set already result.push(directive); } else { + if (isLinkDirective(directive) && isLinkDirective(result[firstAt])) { + const url1 = getLinkDirectiveURL(directive); + const url2 = getLinkDirectiveURL(result[firstAt]); + // if both are link directives but with different urls, do not merge them + if (url1 && url2 && url1 !== url2) { + result.push(directive); + continue; + } + } // if not repeatable and found directive with the same name already in the result set, // then merge the arguments of the existing directive and the new directive const mergedArguments = mergeArguments( diff --git a/packages/merge/tests/merge-typedefs.spec.ts b/packages/merge/tests/merge-typedefs.spec.ts index b22a4671712..8bde0b66576 100644 --- a/packages/merge/tests/merge-typedefs.spec.ts +++ b/packages/merge/tests/merge-typedefs.spec.ts @@ -1749,6 +1749,39 @@ describe('Merge TypeDefs', () => { expect(reformulatedGraphQL).toBeSimilarString(schemaWithDescription); }); + it('merges the directives with the same name and same arguments (@link)', () => { + const schema1 = parse(/* GraphQL */ ` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.3" + import: ["@composeDirective", "@external", "@foo"] + ) + `); + + const schema2 = parse(/* GraphQL */ ` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.3" + import: ["@composeDirective", "@external"] + ) + @link(url: "file://foo.org/trackable/v2.3", import: ["@trackable"]) + `); + const typeDefs = [schema1, schema2]; + const merged = mergeTypeDefs(typeDefs); + const prettyOutput = print(merged); + const prettyExpected = print( + parse(/* GraphQL */ ` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.3" + import: ["@composeDirective", "@external", "@foo"] + ) + @link(url: "file://foo.org/trackable/v2.3", import: ["@trackable"]) # unique to schema 2 + `), + ); + expect(prettyOutput).toBeSimilarString(prettyExpected); + }); + it('merges the directives with the same name and same arguments', () => { const directive = parse(/* GraphQL */ ` directive @link( @@ -1762,7 +1795,6 @@ describe('Merge TypeDefs', () => { const merged = mergeTypeDefs(typeDefs); expect(print(merged)).toBeSimilarString(print(directive)); }); - it('does not merge repeatable Federation directives without the same arguments', () => { const ast = parse(/* GraphQL */ ` extend schema