Skip to content

Commit 8674135

Browse files
authored
[federation] fix @link definition and doc cleanup (#1588)
Fixed `@link` directive definition from: ```graphql directive @link(url: String, import: [Import]) repeatable on SCHEMA ``` to ```graphql directive @link(url: String!, import: [Import]) repeatable on SCHEMA ``` Cleaned up some federation docs as well.
1 parent 325a6c7 commit 8674135

File tree

18 files changed

+90
-81
lines changed

18 files changed

+90
-81
lines changed

examples/client/src/integration/wiremock/__files/schema.graphql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
schema @link(url : "https://specs.apollo.dev/link/v1.0/") @link(import : ["extends", "external", "inaccessible", "key", "link", "override", "provides", "requires", "shareable", "tag", "_FieldSet"], url : "https://www.apollographql.com/docs/federation/federation-spec/"){
1+
schema @link(import : ["extends", "external", "inaccessible", "key", "link", "override", "provides", "requires", "shareable", "tag", "_FieldSet"], url : "https://www.apollographql.com/docs/federation/federation-spec/"){
22
query: Query
33
mutation: Mutation
44
}
@@ -28,7 +28,7 @@ directive @include(
2828
directive @key(fields: _FieldSet!, resolvable: Boolean) repeatable on OBJECT | INTERFACE
2929

3030
"Links definitions within the document to external schemas."
31-
directive @link(import: [String], url: String) repeatable on SCHEMA
31+
directive @link(import: [String], url: String!) repeatable on SCHEMA
3232

3333
"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
3434
directive @override(from: String!) repeatable on FIELD_DEFINITION

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import com.expediagroup.graphql.generator.annotations.GraphQLDirective
2020
import graphql.introspection.Introspection.DirectiveLocation
2121

2222
/**
23+
* **`@extends` directive is deprecated**. Federation v2 no longer requires `@extends` directive due to the smart entity type
24+
* merging. All usage of `@extends` directive should be removed from your Federation v2 schemas.
25+
*
2326
* ```graphql
2427
* directive @extends on OBJECT | INTERFACE
2528
* ```

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,29 @@ import graphql.introspection.Introspection.DirectiveLocation
2727
* The @external directive is used to mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. @external
2828
* directive is only applicable on federated extended types. All the external fields should either be referenced from the @key, @requires or @provides directives field sets.
2929
*
30-
* Due to the smart merging of entity types, `@external` directive is no longer required on `@key` fields and can be omitted from the schema. `@external` directive is only required on fields
31-
* referenced by the `@requires` and `@provides` directive.
30+
* Due to the smart merging of entity types, Federation v2 no longer requires `@external` directive on `@key` fields and can be safely omitted from the schema. `@external` directive is only required
31+
* on fields referenced by the `@requires` and `@provides` directive.
3232
*
3333
* Example:
3434
* Given
3535
*
3636
* ```kotlin
3737
* @KeyDirective(FieldSet("id"))
38-
* @ExtendsDirective
39-
* class Product(@ExternalDirective val id: String) {
40-
* fun newFunctionality(): String = "whatever"
38+
* class Product(val id: String) {
39+
* @ExternalDirective
40+
* var externalField: String by Delegates.notNull()
41+
*
42+
* @RequiresDirective(FieldSet("externalField"))
43+
* fun newFunctionality(): String { ... }
4144
* }
4245
* ```
4346
*
4447
* should generate
4548
*
4649
* ```graphql
47-
* type Product @extends @key(fields : "id") {
48-
* id: String! @external
50+
* type Product @key(fields : "id") {
51+
* externalField: String! @external
52+
* id: String!
4953
* newFunctionality: String!
5054
* }
5155
* ```

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ import graphql.introspection.Introspection.DirectiveLocation
3333
* | ARGUMENT_DEFINITION
3434
* ```
3535
*
36-
* Inaccessible directive marks location within schema as inaccessible from the GraphQL Gateway. This allows you to incrementally add schema elements (e.g. fields) to multiple subgraphs without breaking composition.
36+
* Inaccessible directive marks location within schema as inaccessible from the GraphQL Gateway. While `@inaccessible` fields are not exposed by the gateway to the clients, they are still available for
37+
* query plans and can be referenced from `@key` and `@requires` directives. This allows you to not expose sensitive fields to your clients but still make them available for computations. Inaccessible
38+
* can also be used to incrementally add schema elements (e.g. fields) to multiple subgraphs without breaking composition.
3739
*
3840
* > NOTE: Location within schema will be inaccessible from the GraphQL Gateway as long as **any** of the subgraphs marks that location as `@inacessible`.
3941
*

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ import graphql.schema.GraphQLArgument
3131
* directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
3232
* ```
3333
*
34-
* The @key directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface. Key directive should be specified on the root entity type as
35-
* well as all the corresponding federated (i.e. extended) types. Key fields specified in the directive field set should correspond to a valid field on the underlying GraphQL interface/object.
36-
* Federated extended types should also instrument all the referenced key fields with @external directive.
34+
* The `@key` directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface. The specified field set can represent single field (e.g. `"id"`),
35+
* multiple fields (e.g. `"id name"`) or nested selection sets (e.g. `"id user { name }"`). Multiple keys can be specified on a target type.
36+
*
37+
* Key directives should be specified on all entities (objects that can resolve its fields across multiple subgraphs). Key fields specified in the directive field set should correspond to a valid field
38+
* on the underlying GraphQL interface/object.
3739
*
3840
* Example:
3941
* Given following entity type definition

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ import graphql.Scalars
2121
import graphql.introspection.Introspection.DirectiveLocation
2222
import graphql.schema.GraphQLArgument
2323
import graphql.schema.GraphQLList
24+
import graphql.schema.GraphQLNonNull
2425

2526
const val LINK_SPEC_URL = "https://specs.apollo.dev/link/v1.0/"
2627
const val FEDERATION_SPEC_URL = "https://specs.apollo.dev/federation/v2.0"
2728

2829
/**
2930
* ```graphql
30-
* directive @link(url: String, import: [Import]) repeatable on SCHEMA
31+
* directive @link(url: String!, import: [Import]) repeatable on SCHEMA
3132
* ```
3233
*
3334
* The `@link` directive links definitions within the document to external schemas.
@@ -62,7 +63,7 @@ internal val LINK_DIRECTIVE_TYPE: graphql.schema.GraphQLDirective = graphql.sche
6263
.argument(
6364
GraphQLArgument.newArgument()
6465
.name("url")
65-
.type(Scalars.GraphQLString)
66+
.type(GraphQLNonNull.nonNull(Scalars.GraphQLString))
6667
)
6768
.argument(
6869
GraphQLArgument

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@ import graphql.introspection.Introspection.DirectiveLocation
2525
* directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
2626
* ```
2727
*
28-
* The @provides directive is used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. This allows you to expose only a subset
29-
* of fields from the underlying federated object type to be selectable from the federated schema without the need to call other subgraphs. Provided fields specified in the directive field set should
30-
* correspond to a valid field on the underlying GraphQL interface/object type. @provides directive can only be used on fields returning federated extended objects.
28+
* The `@provides` directive is a router optimization hint specifying field set that can be resolved locally at the given subgraph through this particular query path. This allows you to expose only a
29+
* subset of fields from the underlying entity type to be selectable from the federated schema without the need to call other subgraphs. Provided fields specified in the directive field set should
30+
* correspond to a valid field on the underlying GraphQL interface/object type. `@provides` directive can only be used on fields returning entities.
31+
*
32+
* >NOTE: Federation v2 does not require `@provides` directive if field can **always** be resolved locally. `@provides` should be omitted in this situation.
3133
*
3234
* Example 1:
3335
* We might want to expose only name of the user that submitted a review.
3436
*
35-
* >NOTE: Due to the smart entity type merging, Federation v2 does not require `@provides` directive if field can always be resolved locally.
3637
*
3738
* ```kotlin
3839
* @KeyDirective(FieldSet("id"))

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,15 @@ import graphql.introspection.Introspection.DirectiveLocation
2525
* directive @requires(fields: _FieldSet!) on FIELD_DEFINITON
2626
* ```
2727
*
28-
* The @requires directive is used to annotate the required input field set from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the
29-
* client, but the service may need additional information from other services. Required fields specified in the directive field set should correspond to a valid field on the underlying GraphQL
30-
* interface/object and should be instrumented with @external directive. Since @requires directive specifies additional fields (besides the one specified in @key directive) that are required to
31-
* resolve federated type fields, this directive can only be specified on federated extended objects fields.
28+
* The `@requires` directive is used to specify external (provided by other subgraphs) entity fields that are needed to resolve target field. It is used to develop a query plan where
29+
* the required fields may not be needed by the client, but the service may need additional information from other subgraphs. Required fields specified in the directive field set should
30+
* correspond to a valid field on the underlying GraphQL interface/object and should be instrumented with `@external` directive.
3231
*
33-
* NOTE: fields specified in the @requires directive will only be specified in the queries that reference those fields. This is problematic for Kotlin as the non nullable primitive properties have
34-
* to be initialized when they are declared. Simplest workaround for this problem is to initialize the underlying property to some dummy value that will be used if it is not specified. This approach
35-
* might become problematic though as it might be impossible to determine whether fields was initialized with the default value or the invalid/default value was provided by the federated query.
36-
* Another potential workaround is to rely on delegation to initialize the property after the object gets created. This will ensure that exception will be thrown if queries attempt to resolve fields
37-
* that reference the uninitialized property.
32+
* Fields specified in the `@requires` directive will only be specified in the queries that reference those fields. This is problematic for Kotlin as the non-nullable primitive properties
33+
* have to be initialized when they are declared. Simplest workaround for this problem is to initialize the underlying property to some default value (e.g. null) that will be used if
34+
* it is not specified. This approach might become problematic though as it might be impossible to determine whether fields was initialized with the default value or the invalid/default
35+
* value was provided by the federated query. Another potential workaround is to rely on delegation to initialize the property after the object gets created. This will ensure that exception
36+
* will be thrown if queries attempt to resolve fields that reference the uninitialized property.
3837
*
3938
* Example:
4039
* Given

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class FederatedSchemaV2GeneratorTest {
6161
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
6262
6363
"Links definitions within the document to external schemas."
64-
directive @link(import: [String], url: String) repeatable on SCHEMA
64+
directive @link(import: [String], url: String!) repeatable on SCHEMA
6565
6666
"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
6767
directive @override(from: String!) on FIELD_DEFINITION

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINIT
103103
directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE
104104
105105
"Links definitions within the document to external schemas."
106-
directive @link(import: [String], url: String) repeatable on SCHEMA
106+
directive @link(import: [String], url: String!) repeatable on SCHEMA
107107
108108
"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
109109
directive @override(from: String!) on FIELD_DEFINITION
@@ -161,7 +161,7 @@ directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINIT
161161
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
162162
163163
"Links definitions within the document to external schemas."
164-
directive @link(import: [String], url: String) repeatable on SCHEMA
164+
directive @link(import: [String], url: String!) repeatable on SCHEMA
165165
166166
"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
167167
directive @override(from: String!) on FIELD_DEFINITION

plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLGradlePluginIT.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ class GraphQLGradlePluginIT : GraphQLGradlePluginAbstractIT() {
545545
directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE
546546
547547
"Links definitions within the document to external schemas."
548-
directive @link(import: [String], url: String) repeatable on SCHEMA
548+
directive @link(import: [String], url: String!) repeatable on SCHEMA
549549
550550
"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
551551
directive @override(from: String!) on FIELD_DEFINITION

plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLGenerateSDLTaskIT.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ internal val FEDERATED_SCHEMA =
9494
directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE
9595
9696
"Links definitions within the document to external schemas."
97-
directive @link(import: [String], url: String) repeatable on SCHEMA
97+
directive @link(import: [String], url: String!) repeatable on SCHEMA
9898
9999
"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
100100
directive @override(from: String!) on FIELD_DEFINITION

plugins/graphql-kotlin-maven-plugin/src/integration/generate-sdl-federated/src/test/kotlin/com/expediagroup/graphql/plugin/maven/GenerateSDLMojoTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class GenerateSDLMojoTest {
6565
directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE
6666
6767
"Links definitions within the document to external schemas."
68-
directive @link(import: [String], url: String) repeatable on SCHEMA
68+
directive @link(import: [String], url: String!) repeatable on SCHEMA
6969
7070
"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
7171
directive @override(from: String!) on FIELD_DEFINITION

plugins/graphql-kotlin-maven-plugin/src/integration/generate-sdl-hooks/src/test/kotlin/com/expediagroup/graphql/plugin/maven/GenerateSDLMojoTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class GenerateSDLMojoTest {
6565
directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE
6666
6767
"Links definitions within the document to external schemas."
68-
directive @link(import: [String], url: String) repeatable on SCHEMA
68+
directive @link(import: [String], url: String!) repeatable on SCHEMA
6969
7070
"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
7171
directive @override(from: String!) on FIELD_DEFINITION

plugins/schema/graphql-kotlin-sdl-generator/src/integrationTest/kotlin/com/expediagroup/graphql/plugin/schema/GenerateCustomSDLTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class GenerateCustomSDLTest {
5454
directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE
5555
5656
"Links definitions within the document to external schemas."
57-
directive @link(import: [String], url: String) repeatable on SCHEMA
57+
directive @link(import: [String], url: String!) repeatable on SCHEMA
5858
5959
"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
6060
directive @override(from: String!) on FIELD_DEFINITION

website/docs/plugins/gradle-plugin-usage.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ Separate class is generated for each provided GraphQL query and are saved under
335335

336336
```kotlin
337337
// build.gradle.kts
338-
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask
338+
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateTestClientTask
339339

340340
val graphqlGenerateTestClient by tasks.getting(GraphQLGenerateTestClientTask::class) {
341341
packageName.set("com.example.generated")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ directive @extends on OBJECT | INTERFACE
127127
directive @external on FIELD_DEFINITION
128128
directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
129129
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
130-
directive @link(import: [String], url: String) repeatable on SCHEMA
130+
directive @link(import: [String], url: String!) repeatable on SCHEMA
131131
directive @override(from: String!) on FIELD_DEFINITION
132132
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
133133
directive @requires(fields: FieldSet!) on FIELD_DEFINITION

0 commit comments

Comments
 (0)