Skip to content

Commit

Permalink
Fix join__field missing when override is used (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilkisiela authored Dec 12, 2024
1 parent 1cd26c1 commit a3cb724
Show file tree
Hide file tree
Showing 3 changed files with 256 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/hot-panthers-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@theguild/federation-composition': patch
---

Fix a missing `@join__field` on a query field where `@override` is used, but not in all subgraphs.
241 changes: 241 additions & 0 deletions __tests__/supergraph-composition.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -769,4 +769,245 @@ testImplementations(api => {
expect(result.supergraphSdl).not.toMatch('federation__Policy');
expect(result.supergraphSdl).not.toMatch('federation__Scope');
});

test('@override with @shareable in different conditions', () => {
let result = api.composeServices([
{
name: 'a',
typeDefs: graphql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@shareable"])
type Query {
bar: String! @shareable
}
`,
},
{
name: 'b',
typeDefs: graphql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@shareable"])
type Query {
bar: String! @shareable
}
`,
},
{
name: 'c',
typeDefs: graphql`
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.3"
import: ["@shareable", "@override"]
)
type Query {
bar: String! @shareable @override(from: "a")
}
`,
},
]);

assertCompositionSuccess(result);

expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ `
type Query @join__type(graph: A) @join__type(graph: B) @join__type(graph: C) {
bar: String! @join__field(graph: B) @join__field(graph: C, override: "a")
}
`);

result = api.composeServices([
{
name: 'a',
typeDefs: graphql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@shareable"])
type Query {
bar: String! @shareable
}
`,
},
{
name: 'b',
typeDefs: graphql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@shareable"])
type Query {
bar: String! @shareable
}
`,
},
{
name: 'c',
typeDefs: graphql`
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.3"
import: ["@shareable", "@override"]
)
type Query {
bar: String! @shareable @override(from: "a")
}
`,
},
{
name: 'd',
typeDefs: graphql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@shareable"])
type Query {
foo: String!
}
`,
},
]);

assertCompositionSuccess(result);

expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ `
type Query
@join__type(graph: A)
@join__type(graph: B)
@join__type(graph: C)
@join__type(graph: D) {
foo: String! @join__field(graph: D)
bar: String! @join__field(graph: B) @join__field(graph: C, override: "a")
}
`);

result = api.composeServices([
{
name: 'a',
typeDefs: graphql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@shareable"])
type Query {
bar: String! @shareable
}
`,
},
{
name: 'b',
typeDefs: graphql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@shareable"])
type Query {
bar: String! @shareable
}
`,
},
{
name: 'c',
typeDefs: graphql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@shareable"])
type Query {
bar: String! @shareable
}
`,
},
]);

assertCompositionSuccess(result);

expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ `
type Query @join__type(graph: A) @join__type(graph: B) @join__type(graph: C) {
bar: String!
}
`);

result = api.composeServices([
{
name: 'a',
typeDefs: graphql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@shareable"])
type Query {
bar: String! @shareable
}
`,
},
{
name: 'b',
typeDefs: graphql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@shareable"])
type Query {
bar: String! @shareable
}
`,
},
{
name: 'c',
typeDefs: graphql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@shareable"])
type Query {
bar: String! @shareable
}
`,
},
{
name: 'd',
typeDefs: graphql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@shareable"])
type Query {
foo: String! @shareable
}
`,
},
]);

assertCompositionSuccess(result);

expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ `
type Query
@join__type(graph: A)
@join__type(graph: B)
@join__type(graph: C)
@join__type(graph: D) {
foo: String! @join__field(graph: D)
bar: String! @join__field(graph: A) @join__field(graph: B) @join__field(graph: C)
}
`);

result = api.composeServices([
{
name: 'a',
typeDefs: graphql`
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.3"
import: ["@shareable", "@override"]
)
type Query {
bar: String! @shareable @override(from: "non-existent")
}
`,
},
]);

assertCompositionSuccess(result);

expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ `
type Query @join__type(graph: A) {
bar: String!
}
`);
});
});
22 changes: 10 additions & 12 deletions src/supergraph/composition/object-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,20 +467,18 @@ export function objectTypeBuilder(): TypeBuilder<ObjectType, ObjectTypeState> {
// If it's a Query type, we don't need to emit `@join__field` directives when there's only one graph
// We do not have to emit `@join__field` if the field is shareable in every graph as well.

if (differencesBetweenGraphs.override) {
const graphsWithOverride = fieldInGraphs.filter(
([_, meta]) =>
meta.override !== null &&
(objectType.byGraph.size > 1
? // if there's more than one graph
// we want to emit `@join__field` with override even when it's pointing to a non-existing subgraph
true
: // but if there's only one graph,
// we don't want to emit `@join__field` if the override is pointing to a non-existing subgraph
typeof graphNameToId(meta.override) === 'string'),
if (differencesBetweenGraphs.override && graphs.size > 1) {
const overriddenGraphs = fieldInGraphs
.map(([_, meta]) => (meta.override ? graphNameToId(meta.override) : null))
.filter((graphId): graphId is string => typeof graphId === 'string');

const graphsToPrintJoinField = fieldInGraphs.filter(
([graphId, meta]) =>
meta.override !== null ||
(meta.shareable && !overriddenGraphs.includes(graphId)),
);

joinFields = graphsWithOverride.map(([graphId, meta]) => ({
joinFields = graphsToPrintJoinField.map(([graphId, meta]) => ({
graph: graphId,
override: meta.override ?? undefined,
usedOverridden: provideUsedOverriddenValue(
Expand Down

0 comments on commit a3cb724

Please sign in to comment.