Skip to content

Commit 493b2e7

Browse files
committed
BREAKING: drop Federation v1 support
Federation v2 was released almost 3 years ago. It is an evolution of the Federation spec to make it more powerful, flexible and easier to adopt. It is no longer possible to create Federated Supergraphs targeting v1 composition. Users still using fed v1 shoul migrate ASAP to the fed v2.
1 parent 60879c5 commit 493b2e7

File tree

18 files changed

+241
-749
lines changed

18 files changed

+241
-749
lines changed

generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/FederatedSchemaGeneratorHooks.kt

+94-179
Large diffs are not rendered by default.

generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/directives/ExternalDirective.kt

-10
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ import graphql.introspection.Introspection.DirectiveLocation
2121

2222
/**
2323
* ```graphql
24-
* # federation v1 definition
25-
* directive @external on FIELD_DEFINITION
26-
*
27-
* # federation v2 definition
2824
* directive @external on OBJECT | FIELD_DEFINITION
2925
* ```
3026
*
@@ -73,12 +69,6 @@ internal const val EXTERNAL_DIRECTIVE_NAME = "external"
7369
private const val EXTERNAL_DIRECTIVE_DESCRIPTION = "Marks target field as external meaning it will be resolved by federated schema"
7470

7571
internal val EXTERNAL_DIRECTIVE_TYPE: graphql.schema.GraphQLDirective = graphql.schema.GraphQLDirective.newDirective()
76-
.name(EXTERNAL_DIRECTIVE_NAME)
77-
.description(EXTERNAL_DIRECTIVE_DESCRIPTION)
78-
.validLocations(DirectiveLocation.FIELD_DEFINITION)
79-
.build()
80-
81-
internal val EXTERNAL_DIRECTIVE_TYPE_V2: graphql.schema.GraphQLDirective = graphql.schema.GraphQLDirective.newDirective()
8272
.name(EXTERNAL_DIRECTIVE_NAME)
8373
.description(EXTERNAL_DIRECTIVE_DESCRIPTION)
8474
.validLocations(DirectiveLocation.OBJECT, DirectiveLocation.FIELD_DEFINITION)

generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/types/FieldSet.kt

+1-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 Expedia, Inc
2+
* Copyright 2024 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,10 +29,6 @@ import graphql.schema.CoercingSerializeException
2929
import graphql.schema.GraphQLArgument
3030
import graphql.schema.GraphQLNonNull
3131
import graphql.schema.GraphQLScalarType
32-
import graphql.schema.GraphQLSchemaElement
33-
import graphql.schema.GraphQLTypeVisitorStub
34-
import graphql.util.TraversalControl
35-
import graphql.util.TraverserContext
3632
import java.util.Locale
3733

3834
internal const val FIELD_SET_SCALAR_NAME = "FieldSet"
@@ -91,18 +87,3 @@ private object FieldSetCoercing : Coercing<FieldSet, String> {
9187
else -> throw CoercingValueToLiteralException(FieldSet::class, input)
9288
}
9389
}
94-
95-
/**
96-
* Renames FieldSet scalar (used in Federation V2) to _FieldSet (used in Federation V1).
97-
*/
98-
class FieldSetTransformer : GraphQLTypeVisitorStub() {
99-
override fun visitGraphQLScalarType(node: GraphQLScalarType, context: TraverserContext<GraphQLSchemaElement>): TraversalControl {
100-
if (node.name == "FieldSet") {
101-
val legacyFieldSetScalar = node.transform {
102-
it.name("_FieldSet")
103-
}
104-
return changeNode(context, legacyFieldSetScalar)
105-
}
106-
return super.visitGraphQLScalarType(node, context)
107-
}
108-
}

generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/FederatedSchemaGeneratorTest.kt

+70-52
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.expediagroup.graphql.generator.federation.data.queries.simple.SimpleQ
2323
import com.expediagroup.graphql.generator.federation.directives.KEY_DIRECTIVE_NAME
2424
import com.expediagroup.graphql.generator.federation.types.ENTITY_UNION_NAME
2525
import graphql.schema.GraphQLUnionType
26+
import org.junit.jupiter.api.Assertions
2627
import org.junit.jupiter.api.Assertions.assertEquals
2728
import org.junit.jupiter.api.Test
2829
import kotlin.test.assertNotNull
@@ -33,7 +34,7 @@ class FederatedSchemaGeneratorTest {
3334
fun `verify can generate federated schema`() {
3435
val expectedSchema =
3536
"""
36-
schema {
37+
schema @link(import : ["@external", "@key", "@provides", "@requires", "FieldSet"], url : "https://specs.apollo.dev/federation/v2.6"){
3738
query: Query
3839
}
3940
@@ -45,11 +46,8 @@ class FederatedSchemaGeneratorTest {
4546
reason: String = "No longer supported"
4647
) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION
4748
48-
"Marks target object as extending part of the federated schema"
49-
directive @extends on OBJECT | INTERFACE
50-
5149
"Marks target field as external meaning it will be resolved by federated schema"
52-
directive @external on FIELD_DEFINITION
50+
directive @external on OBJECT | FIELD_DEFINITION
5351
5452
"Directs the executor to include this field or fragment only when the `if` argument is true"
5553
directive @include(
@@ -58,16 +56,19 @@ class FederatedSchemaGeneratorTest {
5856
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
5957
6058
"Space separated list of primary keys needed to access federated object"
61-
directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
59+
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
60+
61+
"Links definitions within the document to external schemas."
62+
directive @link(as: String, import: [link__Import], url: String!) repeatable on SCHEMA
6263
6364
"Indicates an Input Object is a OneOf Input Object."
6465
directive @oneOf on INPUT_OBJECT
6566
6667
"Specifies the base type field set that will be selectable by the gateway"
67-
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
68+
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
6869
6970
"Specifies required input field set from the base type for a resolver"
70-
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
71+
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
7172
7273
"Directs the executor to skip this field or fragment when the `if` argument is true."
7374
directive @skip(
@@ -81,33 +82,33 @@ class FederatedSchemaGeneratorTest {
8182
url: String!
8283
) on SCALAR
8384
84-
interface Product @extends @key(fields : "id") @key(fields : "upc") {
85-
id: String! @external
85+
interface Product @key(fields : "id", resolvable : true) @key(fields : "upc", resolvable : true) {
86+
id: String!
8687
reviews: [Review!]!
87-
upc: String! @external
88+
upc: String!
8889
}
8990
9091
union _Entity = Author | Book | User
9192
92-
type Author @extends @key(fields : "authorId") {
93-
authorId: Int! @external
94-
name: String! @external
93+
type Author @key(fields : "authorId", resolvable : true) {
94+
authorId: Int!
95+
name: String!
9596
}
9697
97-
type Book implements Product @extends @key(fields : "id") @key(fields : "upc") {
98+
type Book implements Product @key(fields : "id", resolvable : true) @key(fields : "upc", resolvable : true) {
9899
author: User! @provides(fields : "name")
99-
id: String! @external
100+
id: String!
100101
reviews: [Review!]!
101102
shippingCost: String! @requires(fields : "weight")
102-
upc: String! @external
103+
upc: String!
103104
weight: Float! @external
104105
}
105106
106107
type CustomScalar {
107108
value: String!
108109
}
109110
110-
type Query @extends {
111+
type Query {
111112
"Union of all types that use the @key directive, including both types native to the schema and extended types"
112113
_entities(representations: [_Any!]!): [_Entity]!
113114
_service: _Service!
@@ -120,30 +121,31 @@ class FederatedSchemaGeneratorTest {
120121
id: String!
121122
}
122123
123-
type User @extends @key(fields : "userId") {
124-
name: String! @external
125-
userId: Int! @external
124+
type User @key(fields : "userId", resolvable : true) {
125+
name: String!
126+
userId: Int!
126127
}
127128
128129
type _Service {
129130
sdl: String!
130131
}
131132
133+
"Federation type representing set of fields"
134+
scalar FieldSet
135+
132136
"Federation scalar type used to represent any external entities passed to _entities query."
133137
scalar _Any
134138
135-
"Federation type representing set of fields"
136-
scalar _FieldSet
139+
scalar link__Import
137140
""".trimIndent()
138141

139142
val config = FederatedSchemaGeneratorConfig(
140-
supportedPackages = listOf("com.expediagroup.graphql.generator.federation.data.queries.federated.v1"),
141-
hooks = FederatedSchemaGeneratorHooks(emptyList(), optInFederationV2 = false)
143+
supportedPackages = listOf("com.expediagroup.graphql.generator.federation.data.queries.federated"),
144+
hooks = FederatedSchemaGeneratorHooks(emptyList())
142145
)
143146

144147
val schema = toFederatedSchema(config = config)
145-
146-
assertEquals(expectedSchema, schema.print().trim())
148+
Assertions.assertEquals(expectedSchema, schema.print().trim())
147149
val productType = schema.getObjectType("Book")
148150
assertNotNull(productType)
149151
assertNotNull(productType.hasAppliedDirective(KEY_DIRECTIVE_NAME))
@@ -157,7 +159,7 @@ class FederatedSchemaGeneratorTest {
157159
fun `verify generator does not add federation queries for non-federated schemas`() {
158160
val expectedSchema =
159161
"""
160-
schema {
162+
schema @link(url : "https://specs.apollo.dev/federation/v2.6"){
161163
query: Query
162164
}
163165
@@ -167,30 +169,18 @@ class FederatedSchemaGeneratorTest {
167169
reason: String = "No longer supported"
168170
) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION
169171
170-
"Marks target object as extending part of the federated schema"
171-
directive @extends on OBJECT | INTERFACE
172-
173-
"Marks target field as external meaning it will be resolved by federated schema"
174-
directive @external on FIELD_DEFINITION
175-
176172
"Directs the executor to include this field or fragment only when the `if` argument is true"
177173
directive @include(
178174
"Included when true."
179175
if: Boolean!
180176
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
181177
182-
"Space separated list of primary keys needed to access federated object"
183-
directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
178+
"Links definitions within the document to external schemas."
179+
directive @link(as: String, import: [link__Import], url: String!) repeatable on SCHEMA
184180
185181
"Indicates an Input Object is a OneOf Input Object."
186182
directive @oneOf on INPUT_OBJECT
187183
188-
"Specifies the base type field set that will be selectable by the gateway"
189-
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
190-
191-
"Specifies required input field set from the base type for a resolver"
192-
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
193-
194184
"Directs the executor to skip this field or fragment when the `if` argument is true."
195185
directive @skip(
196186
"Skipped when true."
@@ -203,7 +193,7 @@ class FederatedSchemaGeneratorTest {
203193
url: String!
204194
) on SCALAR
205195
206-
type Query @extends {
196+
type Query {
207197
_service: _Service!
208198
hello(name: String!): String!
209199
}
@@ -212,27 +202,56 @@ class FederatedSchemaGeneratorTest {
212202
sdl: String!
213203
}
214204
215-
"Federation type representing set of fields"
216-
scalar _FieldSet
205+
scalar link__Import
217206
""".trimIndent()
218207

219208
val config = FederatedSchemaGeneratorConfig(
220209
supportedPackages = listOf("com.expediagroup.graphql.generator.federation.data.queries.simple"),
221-
hooks = FederatedSchemaGeneratorHooks(emptyList(), optInFederationV2 = false)
210+
hooks = FederatedSchemaGeneratorHooks(emptyList())
222211
)
223212

224213
val schema = toFederatedSchema(config, listOf(TopLevelObject(SimpleQuery())))
225214
assertEquals(expectedSchema, schema.print().trim())
226215
}
227216

228217
@Test
229-
fun `verify a nested federated schema still works`() {
218+
fun `verify a schema with self nested query still works`() {
230219
val expectedSchema =
231220
"""
232-
schema {
221+
schema @link(url : "https://specs.apollo.dev/federation/v2.6"){
233222
query: Query
234223
}
235224
225+
"Marks the field, argument, input field or enum value as deprecated"
226+
directive @deprecated(
227+
"The reason for the deprecation"
228+
reason: String = "No longer supported"
229+
) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION
230+
231+
"Directs the executor to include this field or fragment only when the `if` argument is true"
232+
directive @include(
233+
"Included when true."
234+
if: Boolean!
235+
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
236+
237+
"Links definitions within the document to external schemas."
238+
directive @link(as: String, import: [link__Import], url: String!) repeatable on SCHEMA
239+
240+
"Indicates an Input Object is a OneOf Input Object."
241+
directive @oneOf on INPUT_OBJECT
242+
243+
"Directs the executor to skip this field or fragment when the `if` argument is true."
244+
directive @skip(
245+
"Skipped when true."
246+
if: Boolean!
247+
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
248+
249+
"Exposes a URL that specifies the behaviour of this scalar."
250+
directive @specifiedBy(
251+
"The URL that specifies the behaviour of this scalar."
252+
url: String!
253+
) on SCALAR
254+
236255
type Query {
237256
_service: _Service!
238257
getSimpleNestedObject: [SelfReferenceObject]!
@@ -248,16 +267,15 @@ class FederatedSchemaGeneratorTest {
248267
sdl: String!
249268
}
250269
251-
"Federation type representing set of fields"
252-
scalar _FieldSet
270+
scalar link__Import
253271
""".trimIndent()
254272

255273
val config = FederatedSchemaGeneratorConfig(
256274
supportedPackages = listOf("com.expediagroup.graphql.generator.federation.data.queries.simple"),
257-
hooks = FederatedSchemaGeneratorHooks(emptyList(), optInFederationV2 = false)
275+
hooks = FederatedSchemaGeneratorHooks(emptyList())
258276
)
259277

260278
val schema = toFederatedSchema(config, listOf(TopLevelObject(NestedQuery())))
261-
assertEquals(expectedSchema, schema.print(includeDirectives = false).trim())
279+
assertEquals(expectedSchema, schema.print(includeDirectives = true).trim())
262280
}
263281
}

0 commit comments

Comments
 (0)