Skip to content

Commit 46cb6bc

Browse files
authored
feat: support composing executable directive definitions (#156)
> An executable directive is composed into the supergraph schema only if all of the following conditions are met: > > - The directive is defined in all subgraphs. > - The directive is defined identically in all subgraphs. > - The directive is not included in any [@composeDirective](https://www.apollographql.com/docs/graphos/reference/federation/directives#composedirective) directives. > > *[Source](https://www.apollographql.com/docs/graphos/schema-design/federated-schemas/reference/composition-rules#executable-directives)* The last statement is not true, `@composeDirective` does not prevent a executable directive from being included within the supergraph.
1 parent 182c86f commit 46cb6bc

File tree

9 files changed

+621
-7
lines changed

9 files changed

+621
-7
lines changed

.changeset/happy-suits-flash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@theguild/federation-composition": minor
3+
---
4+
5+
Support composing executable directive definitions within subgraphs into the supergraph.

__tests__/supergraph/base.spec.ts

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,295 @@ testVersions((api, version) => {
160160
]);
161161
}, "detected exact same urls").not.toThrow();
162162
});
163+
164+
test("executable directive definition is retained in supergraph", () => {
165+
const result = api.composeServices([
166+
{
167+
name: "foo",
168+
url: "http://foo.com",
169+
typeDefs: graphql`
170+
extend schema
171+
@link(url: "https://specs.apollo.dev/federation/${version}", import: ["@key"])
172+
173+
directive @a(n: Int) on FIELD
174+
175+
type Query {
176+
a: Int
177+
}
178+
`,
179+
},
180+
]);
181+
expect(result.supergraphSdl).toContain("directive @a(n: Int)");
182+
});
183+
184+
test("executable directive definition is retained if defined identical in all subgraphs", () => {
185+
const result = api.composeServices([
186+
{
187+
name: "a",
188+
url: "http://a.com",
189+
typeDefs: graphql`
190+
extend schema
191+
@link(url: "https://specs.apollo.dev/federation/${version}", import: ["@key"])
192+
193+
directive @a(n: Int) on FIELD
194+
195+
type Query {
196+
a: Int
197+
}
198+
`,
199+
},
200+
{
201+
name: "b",
202+
url: "http://b.com",
203+
typeDefs: graphql`
204+
extend schema
205+
@link(url: "https://specs.apollo.dev/federation/${version}", import: ["@key"])
206+
207+
directive @a(n: Int) on FIELD
208+
209+
type Query {
210+
b: Int
211+
}
212+
`,
213+
},
214+
]);
215+
expect(result.supergraphSdl).toContain("directive @a(n: Int)");
216+
});
217+
218+
test("executable directive definition is omitted if only defined within a single subgraph", () => {
219+
const result = api.composeServices([
220+
{
221+
name: "a",
222+
url: "http://a.com",
223+
typeDefs: graphql`
224+
extend schema
225+
@link(url: "https://specs.apollo.dev/federation/${version}", import: ["@key"])
226+
227+
directive @a(n: Int) on FIELD
228+
229+
type Query {
230+
a: Int
231+
}
232+
`,
233+
},
234+
{
235+
name: "b",
236+
url: "http://b.com",
237+
typeDefs: graphql`
238+
extend schema
239+
@link(url: "https://specs.apollo.dev/federation/${version}", import: ["@key"])
240+
241+
type Query {
242+
b: Int
243+
}
244+
`,
245+
},
246+
]);
247+
expect(result.supergraphSdl).not.toContain("directive @a(n: Int)");
248+
});
249+
250+
test("executable directive only contains locations shared between all subgraphs", () => {
251+
const result = api.composeServices([
252+
{
253+
name: "a",
254+
url: "http://a.com",
255+
typeDefs: graphql`
256+
extend schema
257+
@link(url: "https://specs.apollo.dev/federation/${version}", import: ["@key"])
258+
259+
directive @a(n: Int) on FIELD
260+
261+
type Query {
262+
a: Int
263+
}
264+
`,
265+
},
266+
{
267+
name: "b",
268+
url: "http://b.com",
269+
typeDefs: graphql`
270+
extend schema
271+
@link(url: "https://specs.apollo.dev/federation/${version}", import: ["@key"])
272+
273+
directive @a(n: Int) on FIELD | FRAGMENT_SPREAD
274+
275+
type Query {
276+
b: Int
277+
}
278+
`,
279+
},
280+
]);
281+
expect(result.supergraphSdl).toBeDefined();
282+
expect(result.supergraphSdl).toContainGraphQL(graphql`
283+
directive @a(n: Int) on FIELD
284+
`);
285+
expect(result.supergraphSdl).not.toContainGraphQL(graphql`
286+
directive @a(n: Int) on FIELD | FRAGMENT_SPREAD
287+
`);
288+
});
289+
290+
test("executable directive is removed if no locations are shared between all subgraphs", () => {
291+
const result = api.composeServices([
292+
{
293+
name: "a",
294+
url: "http://a.com",
295+
typeDefs: graphql`
296+
extend schema
297+
@link(url: "https://specs.apollo.dev/federation/${version}", import: ["@key"])
298+
299+
directive @a(n: Int) on FIELD
300+
301+
type Query {
302+
a: Int
303+
}
304+
`,
305+
},
306+
{
307+
name: "b",
308+
url: "http://b.com",
309+
typeDefs: graphql`
310+
extend schema
311+
@link(url: "https://specs.apollo.dev/federation/${version}", import: ["@key"])
312+
313+
directive @a(n: Int) on FRAGMENT_SPREAD
314+
315+
type Query {
316+
b: Int
317+
}
318+
`,
319+
},
320+
]);
321+
322+
expect(result.supergraphSdl).toBeDefined();
323+
expect(result.supergraphSdl).not.toContain("directive @a(n: Int)");
324+
});
325+
326+
test("executable directive is only included with executable definition locations", () => {
327+
const result = api.composeServices([
328+
{
329+
name: "a",
330+
url: "http://a.com",
331+
typeDefs: graphql`
332+
extend schema
333+
@link(url: "https://specs.apollo.dev/federation/${version}", import: ["@key"])
334+
335+
directive @a(n: Int) on FIELD | FIELD_DEFINITION
336+
337+
type Query {
338+
a: Int
339+
}
340+
`,
341+
},
342+
{
343+
name: "b",
344+
url: "http://b.com",
345+
typeDefs: graphql`
346+
extend schema
347+
@link(url: "https://specs.apollo.dev/federation/${version}", import: ["@key"])
348+
349+
directive @a(n: Int) on FIELD | FIELD_DEFINITION
350+
351+
type Query {
352+
b: Int
353+
}
354+
`,
355+
},
356+
]);
357+
358+
expect(result.supergraphSdl).toBeDefined();
359+
expect(result.supergraphSdl).toContainGraphQL(graphql`
360+
directive @a(n: Int) on FIELD
361+
`);
362+
});
363+
364+
if (version !== "v2.0") {
365+
test("executable directive that is also a compose directive is included with executable definition and schema definition locations", () => {
366+
const result = api.composeServices([
367+
{
368+
name: "a",
369+
url: "http://a.com",
370+
typeDefs: graphql`
371+
extend schema
372+
@link(url: "https://specs.apollo.dev/federation/${version}", import: ["@composeDirective"])
373+
@link(url: "https://a.dev/a/v1.0", import: ["@a"])
374+
@composeDirective(name: "@a")
375+
376+
directive @a(n: Int) on FIELD | FIELD_DEFINITION
377+
378+
type Query {
379+
a: Int
380+
}
381+
`,
382+
},
383+
{
384+
name: "b",
385+
url: "http://b.com",
386+
typeDefs: graphql`
387+
extend schema
388+
@link(url: "https://specs.apollo.dev/federation/${version}", import: [ "@composeDirective"])
389+
@link(url: "https://a.dev/a/v1.0", import: ["@a"])
390+
@composeDirective(name: "@a")
391+
392+
directive @a(n: Int) on FIELD | FIELD_DEFINITION
393+
394+
type Query {
395+
b: Int
396+
}
397+
`,
398+
},
399+
]);
400+
401+
expect(result.supergraphSdl).toBeDefined();
402+
expect(result.supergraphSdl).toContainGraphQL(graphql`
403+
directive @a(n: Int) on FIELD | FIELD_DEFINITION
404+
`);
405+
});
406+
407+
test("executable directive that is also a compose directive and used within the subgraph schemas is only included with executable definition locations and schema definition locations", () => {
408+
const result = api.composeServices([
409+
{
410+
name: "a",
411+
url: "http://a.com",
412+
typeDefs: graphql`
413+
extend schema
414+
@link(url: "https://specs.apollo.dev/federation/${version}", import: ["@composeDirective"])
415+
@link(url: "https://a.dev/a/v1.0", import: ["@a"])
416+
@composeDirective(name: "@a")
417+
418+
directive @a(n: Int) on FIELD | FIELD_DEFINITION
419+
420+
type Query {
421+
a: Int @a
422+
}
423+
`,
424+
},
425+
{
426+
name: "b",
427+
url: "http://b.com",
428+
typeDefs: graphql`
429+
extend schema
430+
@link(url: "https://specs.apollo.dev/federation/${version}", import: [ "@composeDirective"])
431+
@link(url: "https://a.dev/a/v1.0", import: ["@a"])
432+
@composeDirective(name: "@a")
433+
434+
directive @a(n: Int) on FIELD | FIELD_DEFINITION
435+
436+
type Query {
437+
b: Int @a
438+
}
439+
`,
440+
},
441+
]);
442+
expect(result.supergraphSdl).toBeDefined();
443+
expect(result.supergraphSdl).toContainGraphQL(graphql`
444+
directive @a(n: Int) on FIELD | FIELD_DEFINITION
445+
`);
446+
expect(result.supergraphSdl).toContainGraphQL(graphql`
447+
type Query @join__type(graph: A) @join__type(graph: B) {
448+
a: Int @a @join__field(graph: A)
449+
b: Int @a @join__field(graph: B)
450+
}
451+
`);
452+
});
453+
}
163454
});

0 commit comments

Comments
 (0)