Skip to content

Commit 16f5eb4

Browse files
hrkfdndariuszkuc
andauthored
[federation] rename _FieldSet to FieldSet in Federation v2 (#1547) (#1618)
* [federation] rename _FieldSet to FieldSet in Federation v2 Per [Apollo Specification](https://www.apollographql.com/docs/federation/federation-spec), `_FieldSet` was renamed to `FieldSet` in Federation v2. Resolves: #1512 * ktlint - unused imports Co-authored-by: Dariusz Kuc <[email protected]>
1 parent f1fb84c commit 16f5eb4

File tree

8 files changed

+104
-139
lines changed

8 files changed

+104
-139
lines changed

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

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ import com.expediagroup.graphql.generator.federation.directives.appliedLinkDirec
4242
import com.expediagroup.graphql.generator.federation.exception.IncorrectFederatedDirectiveUsage
4343
import com.expediagroup.graphql.generator.federation.execution.EntityResolver
4444
import com.expediagroup.graphql.generator.federation.execution.FederatedTypeResolver
45-
import com.expediagroup.graphql.generator.federation.extensions.addDirectivesIfNotPresent
4645
import com.expediagroup.graphql.generator.federation.types.ANY_SCALAR_TYPE
4746
import com.expediagroup.graphql.generator.federation.types.ENTITY_UNION_NAME
4847
import com.expediagroup.graphql.generator.federation.types.FIELD_SET_SCALAR_NAME
4948
import com.expediagroup.graphql.generator.federation.types.FIELD_SET_SCALAR_TYPE
49+
import com.expediagroup.graphql.generator.federation.types.FieldSetTransformer
5050
import com.expediagroup.graphql.generator.federation.types.SERVICE_FIELD_DEFINITION
5151
import com.expediagroup.graphql.generator.federation.types._Service
5252
import com.expediagroup.graphql.generator.federation.types.generateEntityFieldDefinition
@@ -60,6 +60,7 @@ import graphql.schema.GraphQLDirective
6060
import graphql.schema.GraphQLObjectType
6161
import graphql.schema.GraphQLSchema
6262
import graphql.schema.GraphQLType
63+
import graphql.schema.SchemaTransformer
6364
import kotlin.reflect.KType
6465
import kotlin.reflect.full.findAnnotation
6566

@@ -133,40 +134,57 @@ open class FederatedSchemaGeneratorHooks(private val resolvers: List<FederatedTy
133134
}
134135

135136
override fun willBuildSchema(builder: GraphQLSchema.Builder): GraphQLSchema.Builder {
137+
val originalSchema = builder.build()
138+
val originalQuery = originalSchema.queryType
139+
140+
findMissingFederationDirectives(originalSchema.directives).forEach {
141+
builder.additionalDirective(it)
142+
}
136143
if (optInFederationV2) {
137-
val fed2Imports = federatedDirectiveV2List.map { "@${it.name}" }
138-
.minus("@$LINK_DIRECTIVE_NAME")
144+
val fed2Imports = federatedDirectiveV2List.map { it.name }
145+
.minus(LINK_DIRECTIVE_NAME)
139146
.plus(FIELD_SET_SCALAR_NAME)
140147

141148
builder.withSchemaDirective(LINK_DIRECTIVE_TYPE)
142149
.withSchemaAppliedDirective(appliedLinkDirective(FEDERATION_SPEC_URL, fed2Imports))
143150
}
144151

145-
val originalSchema = builder.build()
146-
val originalQuery = originalSchema.queryType
147152
val federatedCodeRegistry = GraphQLCodeRegistry.newCodeRegistry(originalSchema.codeRegistry)
148-
149-
// Add all the federation directives if they are not present
150-
val federatedSchemaBuilder = originalSchema.addDirectivesIfNotPresent(federatedDirectiveList())
151-
152-
// Register the data fetcher for the _service query
153-
val sdl = getFederatedServiceSdl(originalSchema)
154-
federatedCodeRegistry.dataFetcher(FieldCoordinates.coordinates(originalQuery.name, SERVICE_FIELD_DEFINITION.name), DataFetcher { _Service(sdl) })
155-
156-
// Add the _entities field to the query and register all the _Entity union types
157-
val federatedQuery = GraphQLObjectType.newObject(originalQuery)
158153
val entityTypeNames = getFederatedEntities(originalSchema)
154+
// Add the _entities field to the query and register all the _Entity union types
159155
if (entityTypeNames.isNotEmpty()) {
156+
val federatedQuery = GraphQLObjectType.newObject(originalQuery)
157+
160158
val entityField = generateEntityFieldDefinition(entityTypeNames)
161159
federatedQuery.field(entityField)
162160

163161
federatedCodeRegistry.dataFetcher(FieldCoordinates.coordinates(originalQuery.name, entityField.name), EntityResolver(resolvers))
164162
federatedCodeRegistry.typeResolver(ENTITY_UNION_NAME) { env: TypeResolutionEnvironment -> env.schema.getObjectType(env.getObjectName()) }
165-
federatedSchemaBuilder.additionalType(ANY_SCALAR_TYPE)
163+
164+
builder.query(federatedQuery)
165+
.codeRegistry(federatedCodeRegistry.build())
166+
.additionalType(ANY_SCALAR_TYPE)
167+
}
168+
169+
val federatedBuilder = if (optInFederationV2) {
170+
builder
171+
} else {
172+
// transform schema to rename FieldSet to _FieldSet
173+
GraphQLSchema.newSchema(SchemaTransformer.transformSchema(builder.build(), FieldSetTransformer()))
166174
}
167175

168-
return federatedSchemaBuilder.query(federatedQuery.build())
169-
.codeRegistry(federatedCodeRegistry.build())
176+
// Register the data fetcher for the _service query
177+
val sdl = getFederatedServiceSdl(federatedBuilder.build())
178+
federatedCodeRegistry.dataFetcher(FieldCoordinates.coordinates(originalQuery.name, SERVICE_FIELD_DEFINITION.name), DataFetcher { _Service(sdl) })
179+
180+
return federatedBuilder.codeRegistry(federatedCodeRegistry.build())
181+
}
182+
183+
private fun findMissingFederationDirectives(existingDirectives: List<GraphQLDirective>): List<GraphQLDirective> {
184+
val existingDirectiveNames = existingDirectives.map { it.name }
185+
return federatedDirectiveList().filter {
186+
!existingDirectiveNames.contains(it.name)
187+
}
170188
}
171189

172190
private fun federatedDirectiveList(): List<GraphQLDirective> = if (optInFederationV2) {

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

Lines changed: 0 additions & 32 deletions
This file was deleted.
Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ import graphql.schema.CoercingSerializeException
2828
import graphql.schema.GraphQLArgument
2929
import graphql.schema.GraphQLNonNull
3030
import graphql.schema.GraphQLScalarType
31+
import graphql.schema.GraphQLSchemaElement
32+
import graphql.schema.GraphQLTypeVisitorStub
33+
import graphql.util.TraversalControl
34+
import graphql.util.TraverserContext
3135

32-
internal const val FIELD_SET_SCALAR_NAME = "_FieldSet"
36+
internal const val FIELD_SET_SCALAR_NAME = "FieldSet"
3337
internal const val FIELD_SET_ARGUMENT_NAME = "fields"
3438

3539
/**
@@ -72,3 +76,18 @@ private object FieldSetCoercing : Coercing<FieldSet, String> {
7276
throw CoercingValueToLiteralException(_FieldSet::class, input)
7377
}
7478
}
79+
80+
/**
81+
* Renames FieldSet scalar (used in Federation V2) to _FieldSet (used in Federation V1).
82+
*/
83+
class FieldSetTransformer : GraphQLTypeVisitorStub() {
84+
override fun visitGraphQLScalarType(node: GraphQLScalarType, context: TraverserContext<GraphQLSchemaElement>): TraversalControl {
85+
if (node.name == "FieldSet") {
86+
val legacyFieldSetScalar = node.transform {
87+
it.name("_FieldSet")
88+
}
89+
return changeNode(context, legacyFieldSetScalar)
90+
}
91+
return super.visitGraphQLScalarType(node, context)
92+
}
93+
}

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class FederatedSchemaV2GeneratorTest {
3030
fun `verify can generate federated schema`() {
3131
val expectedSchema =
3232
"""
33-
schema @link(import : ["@extends", "@external", "@inaccessible", "@key", "@override", "@provides", "@requires", "@shareable", "@tag", "_FieldSet"], url : "https://specs.apollo.dev/federation/v2.0"){
33+
schema @link(import : ["extends", "external", "inaccessible", "key", "override", "provides", "requires", "shareable", "tag", "FieldSet"], url : "https://specs.apollo.dev/federation/v2.0"){
3434
query: Query
3535
}
3636
@@ -58,7 +58,7 @@ class FederatedSchemaV2GeneratorTest {
5858
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
5959
6060
"Space separated list of primary keys needed to access federated object"
61-
directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
61+
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
6262
6363
"Links definitions within the document to external schemas."
6464
directive @link(import: [String], url: String) repeatable on SCHEMA
@@ -67,10 +67,10 @@ class FederatedSchemaV2GeneratorTest {
6767
directive @override(from: String!) on FIELD_DEFINITION
6868
6969
"Specifies the base type field set that will be selectable by the gateway"
70-
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
70+
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
7171
7272
"Specifies required input field set from the base type for a resolver"
73-
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
73+
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
7474
7575
"Indicates that given object and/or field can be resolved by multiple subgraphs"
7676
directive @shareable on OBJECT | FIELD_DEFINITION
@@ -133,11 +133,11 @@ class FederatedSchemaV2GeneratorTest {
133133
sdl: String!
134134
}
135135
136+
"Federation type representing set of fields"
137+
scalar FieldSet
138+
136139
"Federation scalar type used to represent any external entities passed to _entities query."
137140
scalar _Any
138-
139-
"Federation type representing set of fields"
140-
scalar _FieldSet
141141
""".trimIndent()
142142

143143
val config = FederatedSchemaGeneratorConfig(

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

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ type SelfReferenceObject {
9999

100100
const val FEDERATED_SERVICE_SDL_V2 =
101101
"""
102-
schema @link(import : ["@extends", "@external", "@inaccessible", "@key", "@override", "@provides", "@requires", "@shareable", "@tag", "_FieldSet"], url : "https://specs.apollo.dev/federation/v2.0"){
102+
schema @link(import : ["extends", "external", "inaccessible", "key", "override", "provides", "requires", "shareable", "tag", "FieldSet"], url : "https://specs.apollo.dev/federation/v2.0"){
103103
query: Query
104104
}
105105
@@ -111,21 +111,38 @@ directive @extends on OBJECT | INTERFACE
111111
"Marks target field as external meaning it will be resolved by federated schema"
112112
directive @external on FIELD_DEFINITION
113113
114+
"Marks location within schema as inaccessible from the GraphQL Gateway"
115+
directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
116+
114117
"Space separated list of primary keys needed to access federated object"
115-
directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
118+
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
119+
120+
"Links definitions within the document to external schemas."
121+
directive @link(import: [String], url: String) repeatable on SCHEMA
122+
123+
"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
124+
directive @override(from: String!) on FIELD_DEFINITION
116125
117126
"Specifies the base type field set that will be selectable by the gateway"
118-
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
127+
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
119128
120129
"Specifies required input field set from the base type for a resolver"
121-
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
130+
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
131+
132+
"Indicates that given object and/or field can be resolved by multiple subgraphs"
133+
directive @shareable on OBJECT | FIELD_DEFINITION
134+
135+
"Allows users to annotate fields and types with additional metadata information"
136+
directive @tag(name: String!) repeatable on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
122137
123138
interface Product @extends @key(fields : "id", resolvable : true) @key(fields : "upc", resolvable : true) {
124139
id: String! @external
125140
reviews: [Review!]!
126141
upc: String! @external
127142
}
128143
144+
union _Entity = Book | User
145+
129146
type Book implements Product @extends @key(fields : "id", resolvable : true) @key(fields : "upc", resolvable : true) {
130147
author: User! @provides(fields : "name")
131148
id: String! @external
@@ -140,6 +157,8 @@ type CustomScalar {
140157
}
141158
142159
type Query @extends {
160+
"Union of all types that use the @key directive, including both types native to the schema and extended types"
161+
_entities(representations: [_Any!]!): [_Entity]!
143162
_service: _Service!
144163
}
145164
@@ -160,7 +179,10 @@ type _Service {
160179
}
161180
162181
"Federation type representing set of fields"
163-
scalar _FieldSet
182+
scalar FieldSet
183+
184+
"Federation scalar type used to represent any external entities passed to _entities query."
185+
scalar _Any
164186
"""
165187

166188
class ServiceQueryResolverTest {

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

Lines changed: 0 additions & 70 deletions
This file was deleted.

website/docs/schema-generator/federation/apollo-federation.mdx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class Query {
105105

106106
val config = FederatedSchemaGeneratorConfig(
107107
supportedPackages = "com.example",
108-
hooks = FederatedSchemaGeneratorHooks(emptyList())
108+
hooks = FederatedSchemaGeneratorHooks(emptyList(), optInFederationV2 = true)
109109
)
110110

111111
toFederatedSchema(
@@ -119,7 +119,7 @@ will generate
119119
```graphql
120120
# Federation spec types
121121
scalar _Any
122-
scalar _FieldSet
122+
scalar FieldSet
123123

124124
union _Entity
125125

@@ -128,9 +128,9 @@ type _Service {
128128
}
129129

130130
directive @external on FIELD_DEFINITION
131-
directive @requires(fields: _FieldSet) on FIELD_DEFINITION
132-
directive @provides(fields: _FieldSet) on FIELD_DEFINITION
133-
directive @key(fields: _FieldSet) on OBJECT | INTERFACE
131+
directive @requires(fields: FieldSet) on FIELD_DEFINITION
132+
directive @provides(fields: FieldSet) on FIELD_DEFINITION
133+
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
134134
directive @extends on OBJECT | INTERFACE
135135

136136
# Schema types

0 commit comments

Comments
 (0)