Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit7b9676c

Browse files
Samjinsjin
and
sjin
authored
feat: add label as optional arg to@OverRide directive (#2088)
Support federation v2.7. Here's [directivedetails](https://www.apollographql.com/docs/graphos/schema-design/federated-schemas/reference/directives)Support [progressive@OverRidedirective](https://www.apollographql.com/docs/graphos/schema-design/federated-schemas/reference/directives#progressive-override).These directives will require specific federation versions to generateschema.1. `@authenticate` directive is generated from `2.5+` 2. `@requiresScopes` directive is generated from `2.5+` 3. `@policy` directive is generated from `2.6+` 4. `@composeDirective` directive is generated from `2.1+` 5. `@interfaceObject ` directive is generated from `2.6+` 6. `@override` directive with 2nd argument of `label` from `2.7`---------Co-authored-by: sjin <sjin@expediagroup.com>
1 parentf86ca93 commit7b9676c

File tree

28 files changed

+677
-50
lines changed

28 files changed

+677
-50
lines changed

‎examples/federation/README.md‎

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,8 @@ See individual projects READMEs for detailed instructions on how to run them.
1818
2. Start router and compose products schema using[rover dev command](https://www.apollographql.com/docs/rover/commands/dev)
1919

2020
```shell
21-
# start up router and compose products schema
22-
rover dev --name products --url http://localhost:8080/graphql
23-
```
24-
25-
3. In**another** shell run`rover dev` to compose reviews schema
26-
27-
```shell
28-
rover dev --name reviews --url http://localhost:8081/graphql
21+
# start up router and compose supergraph schema, assuming
22+
rover dev --supergraph-config<path to supergraph.yaml>
2923
```
3024

3125
4. Open http://localhost:3000for the query editor

‎examples/federation/docker-compose.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
services:
22
router:
3-
image:ghcr.io/apollographql/router:v1.40.0
3+
image:ghcr.io/apollographql/router:v1.49.0
44
volumes:
55
-./router.yaml:/dist/config/router.yaml
66
-./supergraph.graphql:/dist/config/supergraph.graphql
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
packagecom.expediagroup.graphql.examples.federation.reviews.query
2+
3+
importcom.expediagroup.graphql.server.operations.Query
4+
importorg.springframework.stereotype.Component
5+
6+
/**
7+
* Provides a simple dummy query to ensure the schema has a root field.
8+
*/
9+
@Component
10+
classReviewsQuery :Query {
11+
fundummyQuery():String="This is a dummy query for the reviews subgraph"
12+
}

‎examples/federation/supergraph.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
federation_version:=2.6.3
1+
federation_version:=2.7.8
22
subgraphs:
33
products:
44
routing_url:http://products:8080/graphql

‎generator/graphql-kotlin-federation/build.gradle.kts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ tasks {
2626
limit {
2727
counter="INSTRUCTION"
2828
value="COVEREDRATIO"
29-
minimum="0.95".toBigDecimal()
29+
minimum="0.94".toBigDecimal()
3030
}
3131
limit {
3232
counter="BRANCH"
3333
value="COVEREDRATIO"
34-
minimum="0.82".toBigDecimal()
34+
minimum="0.80".toBigDecimal()
3535
}
3636
}
3737
}

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

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright2024 Expedia, Inc
2+
* Copyright2025 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.
@@ -20,6 +20,7 @@ import com.apollographql.federation.graphqljava.printer.ServiceSDLPrinter.genera
2020
importcom.expediagroup.graphql.generator.TopLevelObject
2121
importcom.expediagroup.graphql.generator.annotations.GraphQLName
2222
importcom.expediagroup.graphql.generator.directives.DirectiveMetaInformation
23+
importcom.expediagroup.graphql.generator.federation.directives.AUTHENTICATED_DIRECTIVE_NAME
2324
importcom.expediagroup.graphql.generator.federation.directives.COMPOSE_DIRECTIVE_NAME
2425
importcom.expediagroup.graphql.generator.federation.directives.CONTACT_DIRECTIVE_NAME
2526
importcom.expediagroup.graphql.generator.federation.directives.CONTACT_DIRECTIVE_TYPE
@@ -47,11 +48,13 @@ import com.expediagroup.graphql.generator.federation.directives.SHAREABLE_DIRECT
4748
importcom.expediagroup.graphql.generator.federation.directives.TAG_DIRECTIVE_NAME
4849
importcom.expediagroup.graphql.generator.federation.directives.keyDirectiveDefinition
4950
importcom.expediagroup.graphql.generator.federation.directives.linkDirectiveDefinition
51+
importcom.expediagroup.graphql.generator.federation.directives.overrideDirectiveDefinition
5052
importcom.expediagroup.graphql.generator.federation.directives.policyDirectiveDefinition
5153
importcom.expediagroup.graphql.generator.federation.directives.providesDirectiveDefinition
5254
importcom.expediagroup.graphql.generator.federation.directives.requiresDirectiveDefinition
5355
importcom.expediagroup.graphql.generator.federation.directives.requiresScopesDirectiveType
5456
importcom.expediagroup.graphql.generator.federation.directives.toAppliedLinkDirective
57+
importcom.expediagroup.graphql.generator.federation.directives.toAppliedOverrideDirective
5558
importcom.expediagroup.graphql.generator.federation.directives.toAppliedPolicyDirective
5659
importcom.expediagroup.graphql.generator.federation.directives.toAppliedRequiresScopesDirective
5760
importcom.expediagroup.graphql.generator.federation.exception.DuplicateSpecificationLinkImport
@@ -95,8 +98,13 @@ open class FederatedSchemaGeneratorHooks(
9598
privatevalresolvers:List<FederatedTypeResolver>
9699
) : FlowSubscriptionSchemaGeneratorHooks() {
97100
privateval validator:FederatedSchemaValidator=FederatedSchemaValidator()
98-
data classLinkSpec(valnamespace:String,valimports:Map<String,String>)
99-
privateval linkSpecs:MutableMap<String,LinkSpec>=HashMap()
101+
102+
data classLinkSpec(valnamespace:String,valimports:Map<String,String>,valurl:String? =FEDERATION_SPEC_LATEST_URL)
103+
104+
val linkSpecs:MutableMap<String,LinkSpec>=HashMap()
105+
106+
val federationUrl:String
107+
get()= linkSpecs[FEDERATION_SPEC]?.url?:FEDERATION_SPEC_LATEST_URL
100108

101109
// workaround to https://github.com/ExpediaGroup/graphql-kotlin/issues/1815
102110
// since those scalars can be renamed, we need to ensure we only generate those scalars just once
@@ -172,7 +180,7 @@ open class FederatedSchemaGeneratorHooks(
172180
normalizeImportName(import.name) to normalizeImportName(importedName)
173181
}
174182

175-
val linkSpec=LinkSpec(nameSpace, imports)
183+
val linkSpec=LinkSpec(nameSpace, imports, appliedDirectiveAnnotation.url)
176184
linkSpecs[spec]= linkSpec
177185
}
178186
}
@@ -215,8 +223,16 @@ open class FederatedSchemaGeneratorHooks(
215223
else->super.willGenerateGraphQLType(type)
216224
}
217225

218-
overridefunwillGenerateDirective(directiveInfo:DirectiveMetaInformation):GraphQLDirective?=
226+
overridefunwillGenerateDirective(directiveInfo:DirectiveMetaInformation):GraphQLDirective?{
219227
when (directiveInfo.effectiveName) {
228+
COMPOSE_DIRECTIVE_NAME-> checkDirectiveVersionCompatibility(directiveInfo.effectiveName,Pair(2,1))
229+
INTERFACE_OBJECT_DIRECTIVE_NAME-> checkDirectiveVersionCompatibility(directiveInfo.effectiveName,Pair(2,3))
230+
AUTHENTICATED_DIRECTIVE_NAME-> checkDirectiveVersionCompatibility(directiveInfo.effectiveName,Pair(2,5))
231+
REQUIRES_SCOPE_DIRECTIVE_NAME-> checkDirectiveVersionCompatibility(directiveInfo.effectiveName,Pair(2,5))
232+
POLICY_DIRECTIVE_NAME-> checkDirectiveVersionCompatibility(directiveInfo.effectiveName,Pair(2,6))
233+
}
234+
235+
returnwhen (directiveInfo.effectiveName) {
220236
CONTACT_DIRECTIVE_NAME->CONTACT_DIRECTIVE_TYPE
221237
EXTERNAL_DIRECTIVE_NAME->EXTERNAL_DIRECTIVE_TYPE
222238
KEY_DIRECTIVE_NAME-> keyDirectiveDefinition(fieldSetScalar)
@@ -225,17 +241,25 @@ open class FederatedSchemaGeneratorHooks(
225241
PROVIDES_DIRECTIVE_NAME-> providesDirectiveDefinition(fieldSetScalar)
226242
REQUIRES_DIRECTIVE_NAME-> requiresDirectiveDefinition(fieldSetScalar)
227243
REQUIRES_SCOPE_DIRECTIVE_NAME-> requiresScopesDirectiveType(scopesScalar)
244+
OVERRIDE_DIRECTIVE_NAME-> overrideDirectiveDefinition(federationUrl)
228245
else->super.willGenerateDirective(directiveInfo)
229246
}
247+
}
230248

231249
overridefunwillApplyDirective(directiveInfo:DirectiveMetaInformation,directive:GraphQLDirective):GraphQLAppliedDirective? {
232250
returnwhen (directiveInfo.effectiveName) {
233251
REQUIRES_SCOPE_DIRECTIVE_NAME-> {
234252
directive.toAppliedRequiresScopesDirective(directiveInfo)
235253
}
254+
236255
POLICY_DIRECTIVE_NAME-> {
237256
directive.toAppliedPolicyDirective(directiveInfo)
238257
}
258+
259+
OVERRIDE_DIRECTIVE_NAME-> {
260+
directive.toAppliedOverrideDirective(directiveInfo)
261+
}
262+
239263
else-> {
240264
super.willApplyDirective(directiveInfo, directive)
241265
}
@@ -293,7 +317,7 @@ open class FederatedSchemaGeneratorHooks(
293317
// only add @link directive definition if it doesn't exist yet
294318
builder.additionalDirective(linkDirective)
295319
}
296-
builder.withSchemaAppliedDirective(linkDirective.toAppliedLinkDirective(FEDERATION_SPEC_LATEST_URL,null, fed2Imports))
320+
builder.withSchemaAppliedDirective(linkDirective.toAppliedLinkDirective(federationUrl,null, fed2Imports))
297321
}
298322

299323
val federatedCodeRegistry=GraphQLCodeRegistry.newCodeRegistry(originalSchema.codeRegistry)
@@ -369,4 +393,10 @@ open class FederatedSchemaGeneratorHooks(
369393
return kClass.findAnnotation<GraphQLName>()?.value
370394
?: kClass.simpleName
371395
}
396+
397+
privatefuncheckDirectiveVersionCompatibility(directiveName:String,requiredVersion:Pair<Int,Int>) {
398+
if (!isFederationVersionAtLeast(federationUrl, requiredVersion.first, requiredVersion.second)) {
399+
throwIllegalArgumentException("@$directiveName directive requires Federation${requiredVersion.first}.${requiredVersion.second} or later, but version$federationUrl was specified")
400+
}
401+
}
372402
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2025 Expedia, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
packagecom.expediagroup.graphql.generator.federation
18+
19+
/**
20+
* Checks if the federation version from the URL meets or exceeds the specified version.
21+
*
22+
* @param federationUrl The federation specification URL (e.g., "https://specs.apollo.dev/federation/v2.7")
23+
* @param major The major version to check against
24+
* @param minor The minor version to check against
25+
* @return True if the URL's version is at least the specified major.minor version
26+
*/
27+
internalfunisFederationVersionAtLeast(federationUrl:String,major:Int,minor:Int):Boolean {
28+
val versionRegex=""".*?/v?(\d+)\.(\d+).*""".toRegex()
29+
val matchResult= versionRegex.find(federationUrl)
30+
31+
returnif (matchResult!=null) {
32+
val (majorStr, minorStr)= matchResult.destructured
33+
val fedMajor= majorStr.toIntOrNull()?:0
34+
val fedMinor= minorStr.toIntOrNull()?:0
35+
36+
fedMajor> major|| (fedMajor== major&& fedMinor>= minor)
37+
}else {
38+
false
39+
}
40+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright2024 Expedia, Inc
2+
* Copyright2025 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.
@@ -32,7 +32,7 @@ const val LINK_SPEC_URL_PREFIX = "$APOLLO_SPEC_URL/$LINK_SPEC"
3232
constvalLINK_SPEC_LATEST_URL="$LINK_SPEC_URL_PREFIX/v$LINK_SPEC_LATEST_VERSION"
3333

3434
constvalFEDERATION_SPEC="federation"
35-
constvalFEDERATION_SPEC_LATEST_VERSION="2.6"
35+
constvalFEDERATION_SPEC_LATEST_VERSION="2.7"
3636
constvalFEDERATION_SPEC_URL_PREFIX="$APOLLO_SPEC_URL/$FEDERATION_SPEC"
3737
constvalFEDERATION_SPEC_LATEST_URL="$FEDERATION_SPEC_URL_PREFIX/v$FEDERATION_SPEC_LATEST_VERSION"
3838

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

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright2023 Expedia, Inc
2+
* Copyright2025 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.
@@ -17,7 +17,14 @@
1717
packagecom.expediagroup.graphql.generator.federation.directives
1818

1919
importcom.expediagroup.graphql.generator.annotations.GraphQLDirective
20+
importcom.expediagroup.graphql.generator.directives.DirectiveMetaInformation
21+
importcom.expediagroup.graphql.generator.federation.isFederationVersionAtLeast
22+
importgraphql.Scalars
2023
importgraphql.introspection.Introspection.DirectiveLocation
24+
importgraphql.schema.GraphQLAppliedDirective
25+
importgraphql.schema.GraphQLAppliedDirectiveArgument
26+
importgraphql.schema.GraphQLArgument
27+
importgraphql.schema.GraphQLNonNull
2128

2229
/**
2330
* ```graphql
@@ -30,6 +37,7 @@ import graphql.introspection.Introspection.DirectiveLocation
3037
* >NOTE: Only one subgraph can `@override` any given field. If multiple subgraphs attempt to `@override` the same field, a composition error occurs.
3138
*
3239
* @param from name of the subgraph to override field resolution
40+
* @param label optional string containing migration parameters (e.g. "percent(number)"). Enterprise feature available in Federation 2.7+.
3341
*
3442
* @see <a href="https://www.apollographql.com/docs/rover/subgraphs/#publishing-a-subgraph-schema-to-apollo-studio">Publishing schema to Apollo Studio</a>
3543
*/
@@ -39,7 +47,86 @@ import graphql.introspection.Introspection.DirectiveLocation
3947
description=OVERRIDE_DIRECTIVE_DESCRIPTION,
4048
locations= [DirectiveLocation.FIELD_DEFINITION]
4149
)
42-
annotationclassOverrideDirective(valfrom:String)
50+
annotationclassOverrideDirective(valfrom:String,vallabel:String ="")
4351

4452
internalconstvalOVERRIDE_DIRECTIVE_NAME="override"
53+
internalconstvalOVERRIDE_DIRECTIVE_FROM_PARAM="from"
54+
internalconstvalOVERRIDE_DIRECTIVE_LABEL_PARAM="label"
4555
privateconstvalOVERRIDE_DIRECTIVE_DESCRIPTION="Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
56+
57+
/**
58+
* Creates the override directive definition
59+
*/
60+
internalfunoverrideDirectiveDefinition(federationVersion:String =FEDERATION_SPEC_LATEST_URL): graphql.schema.GraphQLDirective {
61+
val builder= graphql.schema.GraphQLDirective.newDirective()
62+
.name(OVERRIDE_DIRECTIVE_NAME)
63+
.description(OVERRIDE_DIRECTIVE_DESCRIPTION)
64+
.validLocation(DirectiveLocation.FIELD_DEFINITION)
65+
.argument(
66+
GraphQLArgument.newArgument()
67+
.name(OVERRIDE_DIRECTIVE_FROM_PARAM)
68+
.description("Name of the subgraph to override field resolution")
69+
.type(GraphQLNonNull(Scalars.GraphQLString))
70+
)
71+
72+
if (isFederationVersionAtLeast(federationVersion,2,7)) {
73+
builder.argument(
74+
GraphQLArgument.newArgument()
75+
.name(OVERRIDE_DIRECTIVE_LABEL_PARAM)
76+
.description("The value must follow the format of 'percent(number)'")
77+
.type(Scalars.GraphQLString)
78+
)
79+
}
80+
81+
return builder.build()
82+
}
83+
84+
/**
85+
* Converts a GraphQL directive to an applied override directive with proper validation
86+
* and handling of optional label argument.
87+
*/
88+
internalfun graphql.schema.GraphQLDirective.toAppliedOverrideDirective(directiveInfo:DirectiveMetaInformation,federationVersion:String =FEDERATION_SPEC_LATEST_URL):GraphQLAppliedDirective {
89+
val overrideDirective= directiveInfo.directiveasOverrideDirective
90+
val label= overrideDirective.label.takeIf { it.isNotEmpty() }
91+
92+
if (!label.isNullOrEmpty()&&!isFederationVersionAtLeast(federationVersion,2,7)) {
93+
throwIllegalArgumentException("@override directive 'label' parameter requires Federation 2.7+")
94+
}
95+
96+
if (!label.isNullOrEmpty()&&!validateLabel(label)) {
97+
throwIllegalArgumentException("@override label must follow the format 'percent(number)', got:$label")
98+
}
99+
100+
val builder=GraphQLAppliedDirective.newDirective()
101+
.name(this.name)
102+
.argument(
103+
GraphQLAppliedDirectiveArgument.newArgument()
104+
.name(OVERRIDE_DIRECTIVE_FROM_PARAM)
105+
.type(GraphQLNonNull(Scalars.GraphQLString))
106+
.valueProgrammatic(overrideDirective.from)
107+
.build()
108+
)
109+
110+
if (!label.isNullOrEmpty()) {
111+
builder.argument(
112+
GraphQLAppliedDirectiveArgument.newArgument()
113+
.name(OVERRIDE_DIRECTIVE_LABEL_PARAM)
114+
.type(Scalars.GraphQLString)
115+
.valueProgrammatic(label)
116+
.build()
117+
)
118+
}
119+
120+
return builder.build()
121+
}
122+
123+
/**
124+
* Validates that the label follows the format 'percent(number)'
125+
* Returns true if the label is valid or null/empty
126+
*/
127+
internalfunvalidateLabel(label:String?):Boolean {
128+
if (label.isNullOrEmpty())returntrue
129+
130+
val percentPattern="""^percent\(\d+\)$""".toRegex()
131+
return percentPattern.matches(label)
132+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp