diff --git a/.changeset/old-berries-fry.md b/.changeset/old-berries-fry.md new file mode 100644 index 0000000..ad26fce --- /dev/null +++ b/.changeset/old-berries-fry.md @@ -0,0 +1,5 @@ +--- +"@theguild/federation-composition": patch +--- + +Fix: do not expose `federation__Scope` and `federation__Policy` scalar definitions to a supergraph diff --git a/__tests__/supergraph-composition.spec.ts b/__tests__/supergraph-composition.spec.ts index 87e6df6..b1d8bee 100644 --- a/__tests__/supergraph-composition.spec.ts +++ b/__tests__/supergraph-composition.spec.ts @@ -674,4 +674,101 @@ testImplementations(api => { } `); }); + + test('removes federation__Policy and federation__Scope scalars', () => { + const result = api.composeServices( + [ + { + name: 'feed', + typeDefs: graphql` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.6" + import: ["@requiresScopes", "@policy", "@key", "@shareable"] + ) + + scalar federation__Scope + scalar federation__Policy + + directive @federation__requiresScopes( + scopes: [[federation__Scope!]!]! + ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + directive @federation__policy( + policies: [[federation__Policy!]!]! + ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + + type Query { + feed: [Post!]! + @shareable + @requiresScopes(scopes: [["read:posts"]]) + @policy(policies: [["read_posts"]]) + } + + type Post @key(fields: "id") { + id: ID! + title: String! + } + `, + }, + { + name: 'comments', + typeDefs: graphql` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.6" + import: [ + "@authenticated" + "@requiresScopes" + "@policy" + "@key" + "@external" + "@shareable" + ] + ) + + scalar federation__Scope + scalar federation__Policy + + directive @federation__requiresScopes( + scopes: [[federation__Scope!]!]! + ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + directive @federation__policy( + policies: [[federation__Policy!]!]! + ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + + extend type Post @key(fields: "id") { + id: ID! @external + comments: [Comment!]! @policy(policies: [["read_post_comments"]]) + } + + extend type Query { + feed: [Post!]! + @shareable + @policy(policies: [["read_post_comments"], ["read_post_comments"]]) + @requiresScopes(scopes: [["read:posts"]]) + } + + type Mutation { + commentPost(input: CommentPostInput!): Comment! @authenticated + } + + input CommentPostInput { + postId: ID! + comment: String! + } + + type Comment { + id: ID! + body: String! + } + `, + }, + ], + ); + + assertCompositionSuccess(result); + + expect(result.supergraphSdl).not.toMatch('federation__Policy'); + expect(result.supergraphSdl).not.toMatch('federation__Scope'); + }); }); diff --git a/src/specifications/federation.ts b/src/specifications/federation.ts index e20d81b..6bd967b 100644 --- a/src/specifications/federation.ts +++ b/src/specifications/federation.ts @@ -228,7 +228,7 @@ const federationSpecFactory = { ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @requiresScopes( - scopes: [[Scope!]!]! + scopes: [[federation__Scope!]!]! ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE @@ -247,7 +247,8 @@ const federationSpecFactory = { name: String! ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION scalar FieldSet - scalar Policy + scalar federation__Policy + scalar federation__Scope `, prefix, imports, @@ -274,6 +275,14 @@ function createTypeDefinitions(doc: string, prefix: string, imports?: readonly L toInclude.add('federation__FieldSet'); } + if (toInclude.has('requiresScopes')) { + toInclude.add('federation__Scope'); + } + + if (toInclude.has('policy')) { + toInclude.add('federation__Policy'); + } + const directives: DirectiveDefinitionNode[] = []; const types: TypeDefinitionNode[] = []; diff --git a/src/subgraph/state.ts b/src/subgraph/state.ts index 4f1e493..9c84b69 100644 --- a/src/subgraph/state.ts +++ b/src/subgraph/state.ts @@ -1098,6 +1098,8 @@ function directiveFactory(state: SubgraphState) { export function cleanSubgraphStateFromFederationSpec(state: SubgraphState): SubgraphState { state.types.delete('_FieldSet'); state.types.delete('federation__FieldSet'); + state.types.delete('federation__Policy'); + state.types.delete('federation__Scope'); return state; } diff --git a/src/subgraph/validation/validation-context.ts b/src/subgraph/validation/validation-context.ts index 1d65174..32b024c 100644 --- a/src/subgraph/validation/validation-context.ts +++ b/src/subgraph/validation/validation-context.ts @@ -223,6 +223,7 @@ export function createSubgraphValidationContext( } setOfNames.add(alias ? alias.replace(/^@/, '') : specDirective.name.value); + setOfNames.add(`federation__${specDirective.name.value}`); } } @@ -251,6 +252,7 @@ export function createSubgraphValidationContext( } setOfNames.add(alias ? alias : specType.name.value); + setOfNames.add(`federation__${specType.name.value}`); } }