Skip to content

Backport _FieldSet Federation v2 fix to 6.x.x branch #1618

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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