Skip to content

Commit

Permalink
[federation] rename _FieldSet to FieldSet in Federation v2 (#1547) (#…
Browse files Browse the repository at this point in the history
…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]>
  • Loading branch information
hrkfdn and dariuszkuc authored Dec 16, 2022
1 parent f1fb84c commit 16f5eb4
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 139 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ import com.expediagroup.graphql.generator.federation.directives.appliedLinkDirec
import com.expediagroup.graphql.generator.federation.exception.IncorrectFederatedDirectiveUsage
import com.expediagroup.graphql.generator.federation.execution.EntityResolver
import com.expediagroup.graphql.generator.federation.execution.FederatedTypeResolver
import com.expediagroup.graphql.generator.federation.extensions.addDirectivesIfNotPresent
import com.expediagroup.graphql.generator.federation.types.ANY_SCALAR_TYPE
import com.expediagroup.graphql.generator.federation.types.ENTITY_UNION_NAME
import com.expediagroup.graphql.generator.federation.types.FIELD_SET_SCALAR_NAME
import com.expediagroup.graphql.generator.federation.types.FIELD_SET_SCALAR_TYPE
import com.expediagroup.graphql.generator.federation.types.FieldSetTransformer
import com.expediagroup.graphql.generator.federation.types.SERVICE_FIELD_DEFINITION
import com.expediagroup.graphql.generator.federation.types._Service
import com.expediagroup.graphql.generator.federation.types.generateEntityFieldDefinition
Expand All @@ -60,6 +60,7 @@ import graphql.schema.GraphQLDirective
import graphql.schema.GraphQLObjectType
import graphql.schema.GraphQLSchema
import graphql.schema.GraphQLType
import graphql.schema.SchemaTransformer
import kotlin.reflect.KType
import kotlin.reflect.full.findAnnotation

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

override fun willBuildSchema(builder: GraphQLSchema.Builder): GraphQLSchema.Builder {
val originalSchema = builder.build()
val originalQuery = originalSchema.queryType

findMissingFederationDirectives(originalSchema.directives).forEach {
builder.additionalDirective(it)
}
if (optInFederationV2) {
val fed2Imports = federatedDirectiveV2List.map { "@${it.name}" }
.minus("@$LINK_DIRECTIVE_NAME")
val fed2Imports = federatedDirectiveV2List.map { it.name }
.minus(LINK_DIRECTIVE_NAME)
.plus(FIELD_SET_SCALAR_NAME)

builder.withSchemaDirective(LINK_DIRECTIVE_TYPE)
.withSchemaAppliedDirective(appliedLinkDirective(FEDERATION_SPEC_URL, fed2Imports))
}

val originalSchema = builder.build()
val originalQuery = originalSchema.queryType
val federatedCodeRegistry = GraphQLCodeRegistry.newCodeRegistry(originalSchema.codeRegistry)

// Add all the federation directives if they are not present
val federatedSchemaBuilder = originalSchema.addDirectivesIfNotPresent(federatedDirectiveList())

// Register the data fetcher for the _service query
val sdl = getFederatedServiceSdl(originalSchema)
federatedCodeRegistry.dataFetcher(FieldCoordinates.coordinates(originalQuery.name, SERVICE_FIELD_DEFINITION.name), DataFetcher { _Service(sdl) })

// Add the _entities field to the query and register all the _Entity union types
val federatedQuery = GraphQLObjectType.newObject(originalQuery)
val entityTypeNames = getFederatedEntities(originalSchema)
// Add the _entities field to the query and register all the _Entity union types
if (entityTypeNames.isNotEmpty()) {
val federatedQuery = GraphQLObjectType.newObject(originalQuery)

val entityField = generateEntityFieldDefinition(entityTypeNames)
federatedQuery.field(entityField)

federatedCodeRegistry.dataFetcher(FieldCoordinates.coordinates(originalQuery.name, entityField.name), EntityResolver(resolvers))
federatedCodeRegistry.typeResolver(ENTITY_UNION_NAME) { env: TypeResolutionEnvironment -> env.schema.getObjectType(env.getObjectName()) }
federatedSchemaBuilder.additionalType(ANY_SCALAR_TYPE)

builder.query(federatedQuery)
.codeRegistry(federatedCodeRegistry.build())
.additionalType(ANY_SCALAR_TYPE)
}

val federatedBuilder = if (optInFederationV2) {
builder
} else {
// transform schema to rename FieldSet to _FieldSet
GraphQLSchema.newSchema(SchemaTransformer.transformSchema(builder.build(), FieldSetTransformer()))
}

return federatedSchemaBuilder.query(federatedQuery.build())
.codeRegistry(federatedCodeRegistry.build())
// Register the data fetcher for the _service query
val sdl = getFederatedServiceSdl(federatedBuilder.build())
federatedCodeRegistry.dataFetcher(FieldCoordinates.coordinates(originalQuery.name, SERVICE_FIELD_DEFINITION.name), DataFetcher { _Service(sdl) })

return federatedBuilder.codeRegistry(federatedCodeRegistry.build())
}

private fun findMissingFederationDirectives(existingDirectives: List<GraphQLDirective>): List<GraphQLDirective> {
val existingDirectiveNames = existingDirectives.map { it.name }
return federatedDirectiveList().filter {
!existingDirectiveNames.contains(it.name)
}
}

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

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ import graphql.schema.CoercingSerializeException
import graphql.schema.GraphQLArgument
import graphql.schema.GraphQLNonNull
import graphql.schema.GraphQLScalarType
import graphql.schema.GraphQLSchemaElement
import graphql.schema.GraphQLTypeVisitorStub
import graphql.util.TraversalControl
import graphql.util.TraverserContext

internal const val FIELD_SET_SCALAR_NAME = "_FieldSet"
internal const val FIELD_SET_SCALAR_NAME = "FieldSet"
internal const val FIELD_SET_ARGUMENT_NAME = "fields"

/**
Expand Down Expand Up @@ -72,3 +76,18 @@ private object FieldSetCoercing : Coercing<FieldSet, String> {
throw CoercingValueToLiteralException(_FieldSet::class, input)
}
}

/**
* Renames FieldSet scalar (used in Federation V2) to _FieldSet (used in Federation V1).
*/
class FieldSetTransformer : GraphQLTypeVisitorStub() {
override fun visitGraphQLScalarType(node: GraphQLScalarType, context: TraverserContext<GraphQLSchemaElement>): TraversalControl {
if (node.name == "FieldSet") {
val legacyFieldSetScalar = node.transform {
it.name("_FieldSet")
}
return changeNode(context, legacyFieldSetScalar)
}
return super.visitGraphQLScalarType(node, context)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class FederatedSchemaV2GeneratorTest {
fun `verify can generate federated schema`() {
val expectedSchema =
"""
schema @link(import : ["@extends", "@external", "@inaccessible", "@key", "@override", "@provides", "@requires", "@shareable", "@tag", "_FieldSet"], url : "https://specs.apollo.dev/federation/v2.0"){
schema @link(import : ["extends", "external", "inaccessible", "key", "override", "provides", "requires", "shareable", "tag", "FieldSet"], url : "https://specs.apollo.dev/federation/v2.0"){
query: Query
}
Expand Down Expand Up @@ -58,7 +58,7 @@ class FederatedSchemaV2GeneratorTest {
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"Space separated list of primary keys needed to access federated object"
directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
"Links definitions within the document to external schemas."
directive @link(import: [String], url: String) repeatable on SCHEMA
Expand All @@ -67,10 +67,10 @@ class FederatedSchemaV2GeneratorTest {
directive @override(from: String!) on FIELD_DEFINITION
"Specifies the base type field set that will be selectable by the gateway"
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
"Specifies required input field set from the base type for a resolver"
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
"Indicates that given object and/or field can be resolved by multiple subgraphs"
directive @shareable on OBJECT | FIELD_DEFINITION
Expand Down Expand Up @@ -133,11 +133,11 @@ class FederatedSchemaV2GeneratorTest {
sdl: String!
}
"Federation type representing set of fields"
scalar FieldSet
"Federation scalar type used to represent any external entities passed to _entities query."
scalar _Any
"Federation type representing set of fields"
scalar _FieldSet
""".trimIndent()

val config = FederatedSchemaGeneratorConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ type SelfReferenceObject {

const val FEDERATED_SERVICE_SDL_V2 =
"""
schema @link(import : ["@extends", "@external", "@inaccessible", "@key", "@override", "@provides", "@requires", "@shareable", "@tag", "_FieldSet"], url : "https://specs.apollo.dev/federation/v2.0"){
schema @link(import : ["extends", "external", "inaccessible", "key", "override", "provides", "requires", "shareable", "tag", "FieldSet"], url : "https://specs.apollo.dev/federation/v2.0"){
query: Query
}
Expand All @@ -111,21 +111,38 @@ directive @extends on OBJECT | INTERFACE
"Marks target field as external meaning it will be resolved by federated schema"
directive @external on FIELD_DEFINITION
"Marks location within schema as inaccessible from the GraphQL Gateway"
directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
"Space separated list of primary keys needed to access federated object"
directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
"Links definitions within the document to external schemas."
directive @link(import: [String], url: String) repeatable on SCHEMA
"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
directive @override(from: String!) on FIELD_DEFINITION
"Specifies the base type field set that will be selectable by the gateway"
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
"Specifies required input field set from the base type for a resolver"
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
"Indicates that given object and/or field can be resolved by multiple subgraphs"
directive @shareable on OBJECT | FIELD_DEFINITION
"Allows users to annotate fields and types with additional metadata information"
directive @tag(name: String!) repeatable on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
interface Product @extends @key(fields : "id", resolvable : true) @key(fields : "upc", resolvable : true) {
id: String! @external
reviews: [Review!]!
upc: String! @external
}
union _Entity = Book | User
type Book implements Product @extends @key(fields : "id", resolvable : true) @key(fields : "upc", resolvable : true) {
author: User! @provides(fields : "name")
id: String! @external
Expand All @@ -140,6 +157,8 @@ type CustomScalar {
}
type Query @extends {
"Union of all types that use the @key directive, including both types native to the schema and extended types"
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service!
}
Expand All @@ -160,7 +179,10 @@ type _Service {
}
"Federation type representing set of fields"
scalar _FieldSet
scalar FieldSet
"Federation scalar type used to represent any external entities passed to _entities query."
scalar _Any
"""

class ServiceQueryResolverTest {
Expand Down

This file was deleted.

10 changes: 5 additions & 5 deletions website/docs/schema-generator/federation/apollo-federation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class Query {

val config = FederatedSchemaGeneratorConfig(
supportedPackages = "com.example",
hooks = FederatedSchemaGeneratorHooks(emptyList())
hooks = FederatedSchemaGeneratorHooks(emptyList(), optInFederationV2 = true)
)

toFederatedSchema(
Expand All @@ -119,7 +119,7 @@ will generate
```graphql
# Federation spec types
scalar _Any
scalar _FieldSet
scalar FieldSet

union _Entity

Expand All @@ -128,9 +128,9 @@ type _Service {
}

directive @external on FIELD_DEFINITION
directive @requires(fields: _FieldSet) on FIELD_DEFINITION
directive @provides(fields: _FieldSet) on FIELD_DEFINITION
directive @key(fields: _FieldSet) on OBJECT | INTERFACE
directive @requires(fields: FieldSet) on FIELD_DEFINITION
directive @provides(fields: FieldSet) on FIELD_DEFINITION
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @extends on OBJECT | INTERFACE

# Schema types
Expand Down
Loading

0 comments on commit 16f5eb4

Please sign in to comment.