From 6474916e4267cb203f56c5645bc1a20392a2c9d9 Mon Sep 17 00:00:00 2001 From: "Sachin D. Shinde" Date: Thu, 5 Jan 2023 11:21:06 -0500 Subject: [PATCH] Add support for tag spec v0.3 (#2314) This PR: - Adds a tag spec definition for tag spec v0.3. - The only change is to add support for the `SCHEMA` location in the `@tag` directive definition. - This also changes composition to use tag spec v0.3 in output. - Updates `SUPPORTED_FEATURES` to include tag v0.3 (which controls supported features during supergraph schema processing). - Updates the federation spec to use the `@tag` directive definition of tag spec v0.3. - Note this change only uses that directive definition for federation spec v2.3. - For federation spec v2.2 and below, we maintain current behavior (which is to use the `@tag` directive definition of tag spec v0.2). - This is arguably a bug, as we should be using the `@tag` directive definition of tag spec v0.1 for Fed 1 instead of v0.2, but I'm guessing it's benign given no one's complained thus far (and we'd have to run checks if we wanted to change the behavior). - Updates `TagDirective` in `@apollo/subgraph` to use the `@tag` directive definition of tag spec v0.3. - Updates tests. --- composition-js/CHANGELOG.md | 1 + .../compose.composeDirective.test.ts.snap | 4 ++-- .../src/__tests__/compose.composeDirective.test.ts | 4 +++- gateway-js/CHANGELOG.md | 1 + .../src/__tests__/gateway/lifecycle-hooks.test.ts | 2 +- internals-js/src/federationSpec.ts | 7 ++++++- internals-js/src/supergraphs.ts | 1 + internals-js/src/tagSpec.ts | 11 ++++++++++- subgraph-js/CHANGELOG.md | 1 + subgraph-js/src/__tests__/buildSubgraphSchema.test.ts | 5 +++-- subgraph-js/src/directives.ts | 1 + 11 files changed, 30 insertions(+), 8 deletions(-) diff --git a/composition-js/CHANGELOG.md b/composition-js/CHANGELOG.md index e6a4c6c3f..5f689fbaa 100644 --- a/composition-js/CHANGELOG.md +++ b/composition-js/CHANGELOG.md @@ -14,6 +14,7 @@ This CHANGELOG pertains only to Apollo Federation packages in the 2.x range. The - Error on composition when a `@shareable` field runtime types don't intersect between subgraphs: a `@shareable` field must resolve the same way in all the subgraphs, but this is impossible if the concrete runtime types have no intersection at all [PR #1556](https://github.com/apollographql/federation/pull/1556). +- Uses the 0.3 version of the tag spec in the supergraph, which adds `@tag` directive support for the `SCHEMA` location [PR #2314](https://github.com/apollographql/federation/pull/2314). ## 2.2.0 diff --git a/composition-js/src/__tests__/__snapshots__/compose.composeDirective.test.ts.snap b/composition-js/src/__tests__/__snapshots__/compose.composeDirective.test.ts.snap index 4d175f957..6d651f4ec 100644 --- a/composition-js/src/__tests__/__snapshots__/compose.composeDirective.test.ts.snap +++ b/composition-js/src/__tests__/__snapshots__/compose.composeDirective.test.ts.snap @@ -4,7 +4,7 @@ exports[`composing custom core directives custom tag directive works when federa "schema @link(url: \\"https://specs.apollo.dev/link/v1.0\\") @link(url: \\"https://specs.apollo.dev/join/v0.3\\", for: EXECUTION) - @link(url: \\"https://specs.apollo.dev/tag/v0.2\\", as: \\"mytag\\") + @link(url: \\"https://specs.apollo.dev/tag/v0.3\\", as: \\"mytag\\") @link(url: \\"https://custom.dev/tag/v1.0\\", import: [\\"@tag\\"]) { query: Query @@ -24,7 +24,7 @@ directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE -directive @mytag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION +directive @mytag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA directive @tag(name: String!, prop: String!) on FIELD_DEFINITION | OBJECT diff --git a/composition-js/src/__tests__/compose.composeDirective.test.ts b/composition-js/src/__tests__/compose.composeDirective.test.ts index 6ab81079f..8e730b273 100644 --- a/composition-js/src/__tests__/compose.composeDirective.test.ts +++ b/composition-js/src/__tests__/compose.composeDirective.test.ts @@ -864,6 +864,7 @@ describe('composing custom core directives', () => { DirectiveLocation.ENUM_VALUE, DirectiveLocation.INPUT_OBJECT, DirectiveLocation.INPUT_FIELD_DEFINITION, + DirectiveLocation.SCHEMA, ], ['name']); expectDirectiveDefinition(schema, 'mytag', [DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.OBJECT], ['name', 'prop']); @@ -915,6 +916,7 @@ describe('composing custom core directives', () => { DirectiveLocation.ENUM_VALUE, DirectiveLocation.INPUT_OBJECT, DirectiveLocation.INPUT_FIELD_DEFINITION, + DirectiveLocation.SCHEMA, ], ['name']); expectDirectiveDefinition(schema, 'tag', [DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.OBJECT], ['name', 'prop']); @@ -923,7 +925,7 @@ describe('composing custom core directives', () => { expectCoreFeature(schema, 'https://custom.dev/tag', '1.0', [{ name: '@tag' }]); const feature = schema.coreFeatures?.getByIdentity('https://specs.apollo.dev/tag'); - expect(feature?.url.toString()).toBe('https://specs.apollo.dev/tag/v0.2'); + expect(feature?.url.toString()).toBe('https://specs.apollo.dev/tag/v0.3'); expect(feature?.imports).toEqual([]); expect(feature?.nameInSchema).toEqual('mytag'); expect(printSchema(schema)).toMatchSnapshot(); diff --git a/gateway-js/CHANGELOG.md b/gateway-js/CHANGELOG.md index 5d5e4b58b..748e4695e 100644 --- a/gateway-js/CHANGELOG.md +++ b/gateway-js/CHANGELOG.md @@ -16,6 +16,7 @@ This CHANGELOG pertains only to Apollo Federation packages in the 2.x range. The - Error on composition when a `@shareable` field runtime types don't intersect between subgraphs: a `@shareable` field must resolve the same way in all the subgraphs, but this is impossible if the concrete runtime types have no intersection at all [PR #1556](https://github.com/apollographql/federation/pull/1556). +- Adds support for the 0.3 version of the tag spec, which adds `@tag` directive support for the `SCHEMA` location [PR #2314](https://github.com/apollographql/federation/pull/2314). ## 2.2.2 diff --git a/gateway-js/src/__tests__/gateway/lifecycle-hooks.test.ts b/gateway-js/src/__tests__/gateway/lifecycle-hooks.test.ts index 50be709aa..9b5bf0cb2 100644 --- a/gateway-js/src/__tests__/gateway/lifecycle-hooks.test.ts +++ b/gateway-js/src/__tests__/gateway/lifecycle-hooks.test.ts @@ -147,7 +147,7 @@ describe('lifecycle hooks', () => { // the supergraph (even just formatting differences), this ID will change // and this test will have to updated. expect(secondCall[0]!.compositionId).toEqual( - 'ed8cb418d55e7cd069f11d093b2ea29316e1a913a5757f383cc78ed399414104', + '4644102bb30ec7e254fac2577f78137bbab156cb965973ae481f309120738ff6', ); // second call should have previous info in the second arg expect(secondCall[1]!.compositionId).toEqual(expectedFirstId); diff --git a/internals-js/src/federationSpec.ts b/internals-js/src/federationSpec.ts index 9069c193c..cdd6c6d17 100644 --- a/internals-js/src/federationSpec.ts +++ b/internals-js/src/federationSpec.ts @@ -94,7 +94,9 @@ const legacyFederationDirectives = [ requiresDirectiveSpec, providesDirectiveSpec, externalDirectiveSpec, - TAG_VERSIONS.latest().tagDirectiveSpec, + // This should really be v0.1 instead of v0.2, but we can't change this to + // v0.1 without checking whether anyone relied on the v0.2 behavior. + TAG_VERSIONS.find(new FeatureVersion(0, 2))!.tagDirectiveSpec, extendsDirectiveSpec, ]; @@ -160,6 +162,9 @@ export class FederationSpecDefinition extends FeatureDefinition { name: FederationDirectiveName.INTERFACE_OBJECT, locations: [DirectiveLocation.OBJECT], })); + this.registerDirective( + TAG_VERSIONS.find(new FeatureVersion(0, 3))!.tagDirectiveSpec + ); } } diff --git a/internals-js/src/supergraphs.ts b/internals-js/src/supergraphs.ts index ae5bdf64c..758db9675 100644 --- a/internals-js/src/supergraphs.ts +++ b/internals-js/src/supergraphs.ts @@ -14,6 +14,7 @@ const SUPPORTED_FEATURES = new Set([ 'https://specs.apollo.dev/join/v0.3', 'https://specs.apollo.dev/tag/v0.1', 'https://specs.apollo.dev/tag/v0.2', + 'https://specs.apollo.dev/tag/v0.3', 'https://specs.apollo.dev/inaccessible/v0.1', 'https://specs.apollo.dev/inaccessible/v0.2', ]); diff --git a/internals-js/src/tagSpec.ts b/internals-js/src/tagSpec.ts index 61ec52fbc..8412fe002 100644 --- a/internals-js/src/tagSpec.ts +++ b/internals-js/src/tagSpec.ts @@ -32,6 +32,10 @@ export class TagSpecDefinition extends FeatureDefinition { DirectiveLocation.INPUT_FIELD_DEFINITION, ); this.printedTagDefinition = 'directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION'; + if (!this.isV02()) { + this.tagLocations.push(DirectiveLocation.SCHEMA); + this.printedTagDefinition = 'directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA'; + } } this.tagDirectiveSpec = createDirectiveSpecification({ name:'tag', @@ -48,6 +52,10 @@ export class TagSpecDefinition extends FeatureDefinition { return this.version.equals(new FeatureVersion(0, 1)); } + private isV02() { + return this.version.equals(new FeatureVersion(0, 2)) + } + addElementsToSchema(schema: Schema): GraphQLError[] { return this.addDirectiveSpec(schema, this.tagDirectiveSpec); } @@ -76,6 +84,7 @@ export class TagSpecDefinition extends FeatureDefinition { export const TAG_VERSIONS = new FeatureDefinitions(tagIdentity) .add(new TagSpecDefinition(new FeatureVersion(0, 1))) - .add(new TagSpecDefinition(new FeatureVersion(0, 2))); + .add(new TagSpecDefinition(new FeatureVersion(0, 2))) + .add(new TagSpecDefinition(new FeatureVersion(0, 3))); registerKnownFeature(TAG_VERSIONS); diff --git a/subgraph-js/CHANGELOG.md b/subgraph-js/CHANGELOG.md index 9bda4696f..74310c6f5 100644 --- a/subgraph-js/CHANGELOG.md +++ b/subgraph-js/CHANGELOG.md @@ -8,6 +8,7 @@ This CHANGELOG pertains only to Apollo Federation packages in the 2.x range. The - Adds support for the 2.3 version of the federation spec (that is, `@link(url: "https://specs.apollo.dev/federation/v2.3")`), with: - New `@interfaceObject` directive and support for keys on interfaces. + - `@tag` directive support for the `SCHEMA` location [PR #2314](https://github.com/apollographql/federation/pull/2314). ## 2.2.0 diff --git a/subgraph-js/src/__tests__/buildSubgraphSchema.test.ts b/subgraph-js/src/__tests__/buildSubgraphSchema.test.ts index 9b1a5448d..6ae3ce1ee 100644 --- a/subgraph-js/src/__tests__/buildSubgraphSchema.test.ts +++ b/subgraph-js/src/__tests__/buildSubgraphSchema.test.ts @@ -810,7 +810,7 @@ describe('buildSubgraphSchema', () => { const { data, errors } = await graphql({ schema, source: query }); expect(errors).toBeUndefined(); expect((data?._service as any).sdl).toMatchString( - (header.length === 0 + (header.length === 0 ? '' : ` ${header.trim()} @@ -1226,6 +1226,7 @@ describe('buildSubgraphSchema', () => { it('expands federation 2.3 correctly', async () => { // For 2.3, we expect in everything from 2.2 plus: // - the @interfaceObject directive + // - the @tag directive to additionally have the SCHEMA location await testVersion('2.3', ` schema @link(url: \"https://specs.apollo.dev/link/v1.0\") @@ -1246,7 +1247,7 @@ describe('buildSubgraphSchema', () => { directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION - directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA directive @federation__extends on OBJECT | INTERFACE diff --git a/subgraph-js/src/directives.ts b/subgraph-js/src/directives.ts index f9c9c9e3e..5e106cbed 100644 --- a/subgraph-js/src/directives.ts +++ b/subgraph-js/src/directives.ts @@ -84,6 +84,7 @@ export const TagDirective = new GraphQLDirective({ DirectiveLocation.ENUM_VALUE, DirectiveLocation.INPUT_OBJECT, DirectiveLocation.INPUT_FIELD_DEFINITION, + DirectiveLocation.SCHEMA, ], isRepeatable: true, args: {