diff --git a/composition-js/src/__tests__/connectors.test.ts b/composition-js/src/__tests__/connectors.test.ts index a4d34874c..ac3e4d837 100644 --- a/composition-js/src/__tests__/connectors.test.ts +++ b/composition-js/src/__tests__/connectors.test.ts @@ -1,13 +1,13 @@ -import {composeServices} from "../compose"; -import {printSchema} from "@apollo/federation-internals"; -import {parse} from "graphql/index"; +import { composeServices } from "../compose"; +import { printSchema } from "@apollo/federation-internals"; +import { parse } from "graphql/index"; describe("connect spec and join__directive", () => { - it("composes", () => { - const subgraphs = [ - { - name: "with-connectors", - typeDefs: parse(` + it("composes", () => { + const subgraphs = [ + { + name: "with-connectors", + typeDefs: parse(` extend schema @link( url: "https://specs.apollo.dev/federation/v2.7" @@ -29,93 +29,102 @@ describe("connect spec and join__directive", () => { name: String! } `), - }, - ]; - - const result = composeServices(subgraphs); - expect(result.errors ?? []).toEqual([]); - const printed = printSchema(result.schema!); - expect(printed).toMatchInlineSnapshot(` - "schema - @link(url: \\"https://specs.apollo.dev/link/v1.0\\") - @link(url: \\"https://specs.apollo.dev/join/v0.4\\", for: EXECUTION) - @join__directive(graphs: [WITH_CONNECTORS], name: \\"link\\", args: {url: \\"https://specs.apollo.dev/connect/v0.1\\", import: [\\"@connect\\", \\"@source\\"]}) - @join__directive(graphs: [WITH_CONNECTORS], name: \\"source\\", args: {name: \\"v1\\", http: {baseURL: \\"http://v1\\"}}) - { - query: Query - } + }, + ]; + + const result = composeServices(subgraphs); + expect(result.errors ?? []).toEqual([]); + const printed = printSchema(result.schema!); + expect(printed).toMatchInlineSnapshot(` + "schema + @link(url: \\"https://specs.apollo.dev/link/v1.0\\") + @link(url: \\"https://specs.apollo.dev/join/v0.5\\", for: EXECUTION) + @join__directive(graphs: [WITH_CONNECTORS], name: \\"link\\", args: {url: \\"https://specs.apollo.dev/connect/v0.1\\", import: [\\"@connect\\", \\"@source\\"]}) + @join__directive(graphs: [WITH_CONNECTORS], name: \\"source\\", args: {name: \\"v1\\", http: {baseURL: \\"http://v1\\"}}) + { + query: Query + } + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + directive @join__graph(name: String!, url: String!) on ENUM_VALUE - directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR - directive @join__graph(name: String!, url: String!) on ENUM_VALUE + directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean, overrideLabel: String, contextArguments: [join__ContextArgument!]) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION - directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE - directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean, overrideLabel: String) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION - directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE - directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION - directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + enum link__Purpose { + \\"\\"\\" + \`SECURITY\` features provide metadata necessary to securely resolve fields. + \\"\\"\\" + SECURITY - directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION + \\"\\"\\" + \`EXECUTION\` features provide metadata necessary for operation execution. + \\"\\"\\" + EXECUTION + } - enum link__Purpose { - \\"\\"\\" - \`SECURITY\` features provide metadata necessary to securely resolve fields. - \\"\\"\\" - SECURITY + scalar link__Import - \\"\\"\\" - \`EXECUTION\` features provide metadata necessary for operation execution. - \\"\\"\\" - EXECUTION - } + enum join__Graph { + WITH_CONNECTORS @join__graph(name: \\"with-connectors\\", url: \\"\\") + } - scalar link__Import + scalar join__FieldSet - enum join__Graph { - WITH_CONNECTORS @join__graph(name: \\"with-connectors\\", url: \\"\\") - } + scalar join__DirectiveArguments - scalar join__FieldSet + scalar join__FieldValue - scalar join__DirectiveArguments + input join__ContextArgument { + name: String! + type: String! + context: String! + selection: join__FieldValue! + } - type Query - @join__type(graph: WITH_CONNECTORS) - { - resources: [Resource!]! @join__directive(graphs: [WITH_CONNECTORS], name: \\"connect\\", args: {source: \\"v1\\", http: {GET: \\"/resources\\"}, selection: \\"\\"}) - } + type Query + @join__type(graph: WITH_CONNECTORS) + { + resources: [Resource!]! @join__directive(graphs: [WITH_CONNECTORS], name: \\"connect\\", args: {source: \\"v1\\", http: {GET: \\"/resources\\"}, selection: \\"\\"}) + } + + type Resource + @join__type(graph: WITH_CONNECTORS, key: \\"id\\") + { + id: ID! + name: String! + }" + `); + + if (result.schema) { + expect(printSchema(result.schema.toAPISchema())).toMatchInlineSnapshot(` + "type Query { + resources: [Resource!]! + } + + type Resource { + id: ID! + name: String! + }" + `); + } + }); - type Resource - @join__type(graph: WITH_CONNECTORS, key: \\"id\\") + it("composes with renames", () => { + const subgraphs = [ { - id: ID! - name: String! - }" - `); - - if (result.schema) { - expect(printSchema(result.schema.toAPISchema())).toMatchInlineSnapshot(` - "type Query { - resources: [Resource!]! - } - - type Resource { - id: ID! - name: String! - }" - `); - } - }); - - it("composes with renames", () => { - const subgraphs = [ - { - name: "with-connectors", - typeDefs: parse(` + name: "with-connectors", + typeDefs: parse(` extend schema @link( url: "https://specs.apollo.dev/federation/v2.7" @@ -141,93 +150,102 @@ describe("connect spec and join__directive", () => { name: String! } `), - }, - ]; - - const result = composeServices(subgraphs); - expect(result.errors ?? []).toEqual([]); - const printed = printSchema(result.schema!); - expect(printed).toMatchInlineSnapshot(` - "schema - @link(url: \\"https://specs.apollo.dev/link/v1.0\\") - @link(url: \\"https://specs.apollo.dev/join/v0.4\\", for: EXECUTION) - @join__directive(graphs: [WITH_CONNECTORS], name: \\"link\\", args: {url: \\"https://specs.apollo.dev/connect/v0.1\\", as: \\"http\\", import: [{name: \\"@connect\\", as: \\"@http\\"}, {name: \\"@source\\", as: \\"@api\\"}]}) - @join__directive(graphs: [WITH_CONNECTORS], name: \\"api\\", args: {name: \\"v1\\", http: {baseURL: \\"http://v1\\"}}) - { - query: Query - } + }, + ]; + + const result = composeServices(subgraphs); + expect(result.errors ?? []).toEqual([]); + const printed = printSchema(result.schema!); + expect(printed).toMatchInlineSnapshot(` + "schema + @link(url: \\"https://specs.apollo.dev/link/v1.0\\") + @link(url: \\"https://specs.apollo.dev/join/v0.5\\", for: EXECUTION) + @join__directive(graphs: [WITH_CONNECTORS], name: \\"link\\", args: {url: \\"https://specs.apollo.dev/connect/v0.1\\", as: \\"http\\", import: [{name: \\"@connect\\", as: \\"@http\\"}, {name: \\"@source\\", as: \\"@api\\"}]}) + @join__directive(graphs: [WITH_CONNECTORS], name: \\"api\\", args: {name: \\"v1\\", http: {baseURL: \\"http://v1\\"}}) + { + query: Query + } - directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA - directive @join__graph(name: String!, url: String!) on ENUM_VALUE + directive @join__graph(name: String!, url: String!) on ENUM_VALUE - directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR - directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean, overrideLabel: String) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean, overrideLabel: String, contextArguments: [join__ContextArgument!]) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION - directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE - directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION - directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE - directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION + directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION - enum link__Purpose { - \\"\\"\\" - \`SECURITY\` features provide metadata necessary to securely resolve fields. - \\"\\"\\" - SECURITY + enum link__Purpose { + \\"\\"\\" + \`SECURITY\` features provide metadata necessary to securely resolve fields. + \\"\\"\\" + SECURITY - \\"\\"\\" - \`EXECUTION\` features provide metadata necessary for operation execution. - \\"\\"\\" - EXECUTION - } + \\"\\"\\" + \`EXECUTION\` features provide metadata necessary for operation execution. + \\"\\"\\" + EXECUTION + } - scalar link__Import + scalar link__Import - enum join__Graph { - WITH_CONNECTORS @join__graph(name: \\"with-connectors\\", url: \\"\\") - } + enum join__Graph { + WITH_CONNECTORS @join__graph(name: \\"with-connectors\\", url: \\"\\") + } - scalar join__FieldSet + scalar join__FieldSet - scalar join__DirectiveArguments + scalar join__DirectiveArguments - type Query - @join__type(graph: WITH_CONNECTORS) - { - resources: [Resource!]! @join__directive(graphs: [WITH_CONNECTORS], name: \\"http\\", args: {source: \\"v1\\", http: {GET: \\"/resources\\"}, selection: \\"\\"}) - } + scalar join__FieldValue + + input join__ContextArgument { + name: String! + type: String! + context: String! + selection: join__FieldValue! + } + + type Query + @join__type(graph: WITH_CONNECTORS) + { + resources: [Resource!]! @join__directive(graphs: [WITH_CONNECTORS], name: \\"http\\", args: {source: \\"v1\\", http: {GET: \\"/resources\\"}, selection: \\"\\"}) + } - type Resource - @join__type(graph: WITH_CONNECTORS, key: \\"id\\") + type Resource + @join__type(graph: WITH_CONNECTORS, key: \\"id\\") + { + id: ID! + name: String! + }" + `); + + if (result.schema) { + expect(printSchema(result.schema.toAPISchema())).toMatchInlineSnapshot(` + "type Query { + resources: [Resource!]! + } + + type Resource { + id: ID! + name: String! + }" + `); + } + }); + + it("requires the http arg for @source", () => { + const subgraphs = [ { - id: ID! - name: String! - }" - `); - - if (result.schema) { - expect(printSchema(result.schema.toAPISchema())).toMatchInlineSnapshot(` - "type Query { - resources: [Resource!]! - } - - type Resource { - id: ID! - name: String! - }" - `); - } - }); - - it("requires the http arg for @source", () => { - const subgraphs = [ - { - name: "with-connectors", - typeDefs: parse(` + name: "with-connectors", + typeDefs: parse(` extend schema @link( url: "https://specs.apollo.dev/federation/v2.7" @@ -249,21 +267,23 @@ describe("connect spec and join__directive", () => { name: String! } `), - }, - ]; - - const result = composeServices(subgraphs); - expect(result.errors?.length).toBe(1); - const error = result.errors![0]; - expect(error.message).toEqual('[with-connectors] Directive "@source" argument "http" of type "connect__SourceHTTP!" is required, but it was not provided.'); - expect(error.extensions.code).toEqual("INVALID_GRAPHQL"); - }) - - it("requires the http arg for @connect", () => { - const subgraphs = [ - { - name: "with-connectors", - typeDefs: parse(` + }, + ]; + + const result = composeServices(subgraphs); + expect(result.errors?.length).toBe(1); + const error = result.errors![0]; + expect(error.message).toEqual( + '[with-connectors] Directive "@source" argument "http" of type "connect__SourceHTTP!" is required, but it was not provided.' + ); + expect(error.extensions.code).toEqual("INVALID_GRAPHQL"); + }); + + it("requires the http arg for @connect", () => { + const subgraphs = [ + { + name: "with-connectors", + typeDefs: parse(` extend schema @link( url: "https://specs.apollo.dev/federation/v2.7" @@ -285,13 +305,15 @@ describe("connect spec and join__directive", () => { name: String! } `), - }, - ]; - - const result = composeServices(subgraphs); - expect(result.errors?.length).toBe(1); - const error = result.errors![0]; - expect(error.message).toEqual('[with-connectors] Directive "@connect" argument "http" of type "connect__ConnectHTTP!" is required, but it was not provided.'); - expect(error.extensions.code).toEqual("INVALID_GRAPHQL"); - }) + }, + ]; + + const result = composeServices(subgraphs); + expect(result.errors?.length).toBe(1); + const error = result.errors![0]; + expect(error.message).toEqual( + '[with-connectors] Directive "@connect" argument "http" of type "connect__ConnectHTTP!" is required, but it was not provided.' + ); + expect(error.extensions.code).toEqual("INVALID_GRAPHQL"); + }); }); diff --git a/composition-js/src/__tests__/hints.test.ts b/composition-js/src/__tests__/hints.test.ts index b31ba0662..4013b2e03 100644 --- a/composition-js/src/__tests__/hints.test.ts +++ b/composition-js/src/__tests__/hints.test.ts @@ -1298,7 +1298,7 @@ describe('when a directive causes an implicit federation version upgrade', () => assertCompositionSuccess(result); expect(result).toRaiseHint( HINTS.IMPLICITLY_UPGRADED_FEDERATION_VERSION, - 'Subgraph upgraded has been implicitly upgraded from federation v2.5 to v2.7', + 'Subgraph upgraded has been implicitly upgraded from federation v2.5 to v2.9', '@link' ); }); @@ -1318,12 +1318,12 @@ describe('when a directive causes an implicit federation version upgrade', () => assertCompositionSuccess(result); expect(result).toRaiseHint( HINTS.IMPLICITLY_UPGRADED_FEDERATION_VERSION, - 'Subgraph upgraded-1 has been implicitly upgraded from federation v2.5 to v2.7', + 'Subgraph upgraded-1 has been implicitly upgraded from federation v2.5 to v2.9', '@link' ); expect(result).toRaiseHint( HINTS.IMPLICITLY_UPGRADED_FEDERATION_VERSION, - 'Subgraph upgraded-2 has been implicitly upgraded from federation v2.5 to v2.7', + 'Subgraph upgraded-2 has been implicitly upgraded from federation v2.5 to v2.9', '@link' ); }); diff --git a/internals-js/src/specs/connectSpec.ts b/internals-js/src/specs/connectSpec.ts index 91f7b1a50..a8e98d78e 100644 --- a/internals-js/src/specs/connectSpec.ts +++ b/internals-js/src/specs/connectSpec.ts @@ -178,6 +178,6 @@ export type ConnectDirectiveHTTP = { }; export const CONNECT_VERSIONS = new FeatureDefinitions(connectIdentity) - .add(new ConnectSpecDefinition(new FeatureVersion(0, 1), new FeatureVersion(2, 7))); + .add(new ConnectSpecDefinition(new FeatureVersion(0, 1), new FeatureVersion(2, 9))); registerKnownFeature(CONNECT_VERSIONS);