Skip to content

Commit e7e6757

Browse files
add connect/v0.2 and allow @connect on OBJECT (#3228)
not making this conditional on spec version because that will be enforced by rust code <!-- https://apollographql.atlassian.net/browse/CNN-614 --> <!-- First, 🌠 thank you 🌠 for taking the time to consider a contribution to Apollo! Here are some important details to follow: * ⏰ Your time is important To save your precious time, if the contribution you are making will take more than an hour, please make sure it has been discussed in an issue first. This is especially true for feature requests! * 💡 Features Feature requests can be created and discussed within a GitHub Issue. Be sure to search for existing feature requests (and related issues!) prior to opening a new request. If an existing issue covers the need, please upvote that issue by using the 👍 emote, rather than opening a new issue. * 🕷 Bug fixes These can be created and discussed in this repository. When fixing a bug, please _try_ to add a test which verifies the fix. If you cannot, you should still submit the PR but we may still ask you (and help you!) to create a test. * Federation versions Please make sure you're targeting the federation version you're opening the PR for. Federation 2 (alpha) is currently located on the `main` branch and prior versions of Federation live on the `version-0.x` branch. * 📖 Contribution guidelines Follow https://github.com/apollographql/federation/blob/HEAD/CONTRIBUTING.md when submitting a pull request. Make sure existing tests still pass, and add tests for all new behavior. * ✏️ Explain your pull request Describe the big picture of your changes here to communicate to what your pull request is meant to accomplish. Provide 🔗 links 🔗 to associated issues! We hope you will find this to be a positive experience! Open source contribution can be intimidating and we hope to alleviate that pain as much as possible. Without following these guidelines, you may be missing context that can help you succeed with your contribution, which is why we encourage discussion first. Ultimately, there is no guarantee that we will be able to merge your pull-request, but by following these guidelines we can try to avoid disappointment. -->
1 parent a520a0c commit e7e6757

File tree

7 files changed

+211
-7
lines changed

7 files changed

+211
-7
lines changed

.changeset/lucky-pillows-rhyme.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@apollo/composition": minor
3+
"@apollo/federation-internals": minor
4+
"apollo-federation-integration-testsuite": minor
5+
"@apollo/gateway": minor
6+
"@apollo/query-graphs": minor
7+
"@apollo/query-planner": minor
8+
"@apollo/subgraph": minor
9+
---
10+
11+
Add connect spec v0.2

.changeset/pre.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"mode": "pre",
3+
"tag": "preview",
4+
"initialVersions": {
5+
"@apollo/composition": "2.10.0",
6+
"apollo-federation-integration-testsuite": "2.10.0",
7+
"@apollo/gateway": "2.10.0",
8+
"@apollo/federation-internals": "2.10.0",
9+
"@apollo/query-graphs": "2.10.0",
10+
"@apollo/query-planner": "2.10.0",
11+
"@apollo/subgraph": "2.10.0"
12+
},
13+
"changesets": []
14+
}

composition-js/src/__tests__/connectors.test.ts

+161
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,167 @@ describe("connect spec and join__directive", () => {
121121
}
122122
});
123123

124+
it("composes v0.2", () => {
125+
const subgraphs = [
126+
{
127+
name: "with-connectors-v0_2",
128+
typeDefs: parse(`
129+
extend schema
130+
@link(
131+
url: "https://specs.apollo.dev/federation/v2.11"
132+
import: ["@key"]
133+
)
134+
@link(
135+
url: "https://specs.apollo.dev/connect/v0.2"
136+
import: ["@connect", "@source"]
137+
)
138+
@source(name: "v1", http: { baseURL: "http://v1" })
139+
140+
type Query {
141+
resources: [Resource!]!
142+
@connect(source: "v1", http: { GET: "/resources" }, selection: "")
143+
}
144+
145+
type Resource @key(fields: "id") @connect(source: "v1", http: { GET: "/resources" }, selection: "") {
146+
id: ID!
147+
name: String!
148+
}
149+
`),
150+
},
151+
{
152+
name: "with-connectors-v0_1",
153+
typeDefs: parse(`
154+
extend schema
155+
@link(
156+
url: "https://specs.apollo.dev/federation/v2.10"
157+
import: ["@key"]
158+
)
159+
@link(
160+
url: "https://specs.apollo.dev/connect/v0.1"
161+
import: ["@connect", "@source"]
162+
)
163+
@source(name: "v1", http: { baseURL: "http://v1" })
164+
165+
type Query {
166+
widgets: [Widget!]!
167+
@connect(source: "v1", http: { GET: "/widgets" }, selection: "")
168+
}
169+
170+
type Widget @key(fields: "id") {
171+
id: ID!
172+
name: String!
173+
}
174+
`),
175+
},
176+
];
177+
178+
const result = composeServices(subgraphs);
179+
expect(result.errors ?? []).toEqual([]);
180+
const printed = printSchema(result.schema!);
181+
expect(printed).toMatchInlineSnapshot(`
182+
"schema
183+
@link(url: \\"https://specs.apollo.dev/link/v1.0\\")
184+
@link(url: \\"https://specs.apollo.dev/join/v0.5\\", for: EXECUTION)
185+
@link(url: \\"https://specs.apollo.dev/connect/v0.2\\", for: EXECUTION)
186+
@join__directive(graphs: [WITH_CONNECTORS_V0_1_], name: \\"link\\", args: {url: \\"https://specs.apollo.dev/connect/v0.1\\", import: [\\"@connect\\", \\"@source\\"]})
187+
@join__directive(graphs: [WITH_CONNECTORS_V0_2_], name: \\"link\\", args: {url: \\"https://specs.apollo.dev/connect/v0.2\\", import: [\\"@connect\\", \\"@source\\"]})
188+
@join__directive(graphs: [WITH_CONNECTORS_V0_1_, WITH_CONNECTORS_V0_2_], name: \\"source\\", args: {name: \\"v1\\", http: {baseURL: \\"http://v1\\"}})
189+
{
190+
query: Query
191+
}
192+
193+
directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
194+
195+
directive @join__graph(name: String!, url: String!) on ENUM_VALUE
196+
197+
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
198+
199+
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
200+
201+
directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE
202+
203+
directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION
204+
205+
directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
206+
207+
directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION
208+
209+
enum link__Purpose {
210+
\\"\\"\\"
211+
\`SECURITY\` features provide metadata necessary to securely resolve fields.
212+
\\"\\"\\"
213+
SECURITY
214+
215+
\\"\\"\\"
216+
\`EXECUTION\` features provide metadata necessary for operation execution.
217+
\\"\\"\\"
218+
EXECUTION
219+
}
220+
221+
scalar link__Import
222+
223+
enum join__Graph {
224+
WITH_CONNECTORS_V0_1_ @join__graph(name: \\"with-connectors-v0_1\\", url: \\"\\")
225+
WITH_CONNECTORS_V0_2_ @join__graph(name: \\"with-connectors-v0_2\\", url: \\"\\")
226+
}
227+
228+
scalar join__FieldSet
229+
230+
scalar join__DirectiveArguments
231+
232+
scalar join__FieldValue
233+
234+
input join__ContextArgument {
235+
name: String!
236+
type: String!
237+
context: String!
238+
selection: join__FieldValue!
239+
}
240+
241+
type Query
242+
@join__type(graph: WITH_CONNECTORS_V0_1_)
243+
@join__type(graph: WITH_CONNECTORS_V0_2_)
244+
{
245+
widgets: [Widget!]! @join__field(graph: WITH_CONNECTORS_V0_1_) @join__directive(graphs: [WITH_CONNECTORS_V0_1_], name: \\"connect\\", args: {source: \\"v1\\", http: {GET: \\"/widgets\\"}, selection: \\"\\"})
246+
resources: [Resource!]! @join__field(graph: WITH_CONNECTORS_V0_2_) @join__directive(graphs: [WITH_CONNECTORS_V0_2_], name: \\"connect\\", args: {source: \\"v1\\", http: {GET: \\"/resources\\"}, selection: \\"\\"})
247+
}
248+
249+
type Widget
250+
@join__type(graph: WITH_CONNECTORS_V0_1_, key: \\"id\\")
251+
{
252+
id: ID!
253+
name: String!
254+
}
255+
256+
type Resource
257+
@join__type(graph: WITH_CONNECTORS_V0_2_, key: \\"id\\")
258+
@join__directive(graphs: [WITH_CONNECTORS_V0_2_], name: \\"connect\\", args: {source: \\"v1\\", http: {GET: \\"/resources\\"}, selection: \\"\\"})
259+
{
260+
id: ID!
261+
name: String!
262+
}"
263+
`);
264+
265+
if (result.schema) {
266+
expect(printSchema(result.schema.toAPISchema())).toMatchInlineSnapshot(`
267+
"type Query {
268+
widgets: [Widget!]!
269+
resources: [Resource!]!
270+
}
271+
272+
type Widget {
273+
id: ID!
274+
name: String!
275+
}
276+
277+
type Resource {
278+
id: ID!
279+
name: String!
280+
}"
281+
`);
282+
}
283+
});
284+
124285
it("composes with renames", () => {
125286
const subgraphs = [
126287
{

composition-js/src/merging/merge.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -1102,7 +1102,7 @@ class Merger {
11021102
if (!source) {
11031103
continue;
11041104
}
1105-
1105+
11061106
const sourceMetadata = this.subgraphs.values()[idx].metadata();
11071107
const keyDirective = sourceMetadata.keyDirective();
11081108
if (source.hasAppliedDirective(keyDirective)) {
@@ -3208,7 +3208,22 @@ class Merger {
32083208
}
32093209

32103210
const linkDirective = this.linkSpec.coreDirective(this.merged);
3211-
for (const link of linksToPersist) {
3211+
3212+
// When persisting features as @link directives in the supergraph, we can't
3213+
// repeat features that have the same identity, but different versions. This
3214+
// chooses the highest version of each feature to persist.
3215+
//
3216+
// (The original feature version is still recorded in a @join__directive
3217+
// so we're not losing any information.)
3218+
const highestLinkByIdentity = [...linksToPersist].reduce((map, link) => {
3219+
const existing = map.get(link.identity);
3220+
if (!existing || existing.version.lt(link.version)) {
3221+
map.set(link.identity, link);
3222+
}
3223+
return map;
3224+
}, new Map<string, FeatureDefinition>());
3225+
3226+
for (const [_, link] of highestLinkByIdentity) {
32123227
dest.applyDirective(linkDirective, {
32133228
url: link.toString(),
32143229
for: link.defaultCorePurpose,

internals-js/src/federation.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1954,9 +1954,9 @@ export function setSchemaAsFed2Subgraph(schema: Schema, useLatest: boolean = fal
19541954

19551955
// This is the full @link declaration as added by `asFed2SubgraphDocument`. It's here primarily for uses by tests that print and match
19561956
// subgraph schema to avoid having to update 20+ tests every time we use a new directive or the order of import changes ...
1957-
export const FEDERATION2_LINK_WITH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.10", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresScopes", "@policy", "@context", "@fromContext", "@cost", "@listSize"])';
1957+
export const FEDERATION2_LINK_WITH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.11", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresScopes", "@policy", "@context", "@fromContext", "@cost", "@listSize"])';
19581958
// This is the full @link declaration that is added when upgrading fed v1 subgraphs to v2 version. It should only be used by tests.
1959-
export const FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.10", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])';
1959+
export const FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.11", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])';
19601960

19611961
// This is the federation @link for tests that go through the SchemaUpgrader.
19621962
export const FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS_UPGRADED = '@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])';

internals-js/src/specs/connectSpec.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,9 @@ export class ConnectSpecDefinition extends FeatureDefinition {
6262
selection: JSONSelection!
6363
entity: Boolean = false
6464
) repeatable on FIELD_DEFINITION
65+
| OBJECT # added in v0.2, validation enforced in rust
6566
*/
66-
const connect = this.addDirective(schema, CONNECT).addLocations(DirectiveLocation.FIELD_DEFINITION);
67+
const connect = this.addDirective(schema, CONNECT).addLocations(DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.OBJECT);
6768
connect.repeatable = true;
6869

6970
connect.addArgument(SOURCE, schema.stringType());
@@ -143,6 +144,7 @@ export class ConnectSpecDefinition extends FeatureDefinition {
143144
}
144145

145146
export const CONNECT_VERSIONS = new FeatureDefinitions<ConnectSpecDefinition>(connectIdentity)
146-
.add(new ConnectSpecDefinition(new FeatureVersion(0, 1), new FeatureVersion(2, 10)));
147+
.add(new ConnectSpecDefinition(new FeatureVersion(0, 1), new FeatureVersion(2, 10)))
148+
.add(new ConnectSpecDefinition(new FeatureVersion(0, 2), new FeatureVersion(2, 11)));
147149

148150
registerKnownFeature(CONNECT_VERSIONS);

internals-js/src/specs/federationSpec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ export const FEDERATION_VERSIONS = new FeatureDefinitions<FederationSpecDefiniti
195195
.add(new FederationSpecDefinition(new FeatureVersion(2, 7)))
196196
.add(new FederationSpecDefinition(new FeatureVersion(2, 8)))
197197
.add(new FederationSpecDefinition(new FeatureVersion(2, 9)))
198-
.add(new FederationSpecDefinition(new FeatureVersion(2, 10)));
198+
.add(new FederationSpecDefinition(new FeatureVersion(2, 10)))
199+
.add(new FederationSpecDefinition(new FeatureVersion(2, 11)));
199200

200201
registerKnownFeature(FEDERATION_VERSIONS);

0 commit comments

Comments
 (0)