Skip to content

Commit

Permalink
feat: add support for fed v2.6 and v2.7 (#377)
Browse files Browse the repository at this point in the history
* federation v2.6 - new `@policy` directive, see
[docs](https://www.apollographql.com/docs/federation/federated-types/federated-directives#policy)
for details

```graphql
directive @Policy(policies: [[federation__Policy!]!]!) on
  | FIELD_DEFINITION
  | OBJECT
  | INTERFACE
  | SCALAR
  | ENUM

scalar Policy
```

* federation v2.7 - update to `@override` definition (new `label`
argument), see
[docs](https://www.apollographql.com/docs/federation/federated-types/federated-directives#progressive-override)
for details

```graphql
directive @OverRide(from: String!, label: String) on FIELD_DEFINITION
```

---------

Co-authored-by: Trevor Scheer <trevor.scheer@gmail.com>
  • Loading branch information
dariuszkuc and trevor-scheer committed Oct 10, 2024
1 parent 4b750b8 commit 42dcf10
Show file tree
Hide file tree
Showing 13 changed files with 461 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public final class Federation {
public static final String FEDERATION_SPEC_V2_3 = "https://specs.apollo.dev/federation/v2.3";
public static final String FEDERATION_SPEC_V2_4 = "https://specs.apollo.dev/federation/v2.4";
public static final String FEDERATION_SPEC_V2_5 = "https://specs.apollo.dev/federation/v2.5";
public static final String FEDERATION_SPEC_V2_6 = "https://specs.apollo.dev/federation/v2.6";
public static final String FEDERATION_SPEC_V2_7 = "https://specs.apollo.dev/federation/v2.7";

private static final SchemaGenerator.Options generatorOptions =
SchemaGenerator.Options.defaultOptions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import static com.apollographql.federation.graphqljava.Federation.FEDERATION_SPEC_V2_3;
import static com.apollographql.federation.graphqljava.Federation.FEDERATION_SPEC_V2_4;
import static com.apollographql.federation.graphqljava.Federation.FEDERATION_SPEC_V2_5;
import static com.apollographql.federation.graphqljava.Federation.FEDERATION_SPEC_V2_6;
import static com.apollographql.federation.graphqljava.Federation.FEDERATION_SPEC_V2_7;
import static graphql.introspection.Introspection.DirectiveLocation.FIELD_DEFINITION;
import static graphql.introspection.Introspection.DirectiveLocation.INTERFACE;
import static graphql.introspection.Introspection.DirectiveLocation.OBJECT;
Expand Down Expand Up @@ -223,6 +225,10 @@ public static List<SDLNamedDefinition> loadFederationSpecDefinitions(String fede
return loadFed2Definitions("definitions_fed2_3.graphqls");
case FEDERATION_SPEC_V2_5:
return loadFed2Definitions("definitions_fed2_5.graphqls");
case FEDERATION_SPEC_V2_6:
return loadFed2Definitions("definitions_fed2_6.graphqls");
case FEDERATION_SPEC_V2_7:
return loadFed2Definitions("definitions_fed2_7.graphqls");
default:
throw new UnsupportedFederationVersionException(federationSpec);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@

public final class LinkDirectiveProcessor {

private static final Map<String, Integer> DIRECTIVES_BY_MIN_SUPPORTED_VERSION =
Map.of(
"@composeDirective", 21,
"@interfaceObject", 23,
"@authenticated", 25,
"@requiresScopes", 25,
"@policy", 26);

private LinkDirectiveProcessor() {}

/**
Expand Down Expand Up @@ -68,21 +76,10 @@ private static Stream<SDLNamedDefinition> loadDefinitions(Directive linkDirectiv
final String specLink = ((StringValue) urlArgument.getValue()).getValue();

final int federationVersion = parseFederationVersion(specLink);
if (imports.containsKey("@composeDirective")
&& !isComposeDirectiveSupported(federationVersion)) {
throw new UnsupportedLinkImportException("@composeDirective");
}

if (imports.containsKey("@interfaceObject") && !isInterfaceObjectSupported(federationVersion)) {
throw new UnsupportedLinkImportException("@interfaceObject");
}

if (imports.containsKey("@authenticated") && !isAuthorizationSupported(federationVersion)) {
throw new UnsupportedLinkImportException("@authenticated");
}

if (imports.containsKey("@requiresScopes") && !isAuthorizationSupported(federationVersion)) {
throw new UnsupportedLinkImportException("@requiresScopes");
for (Map.Entry<String, Integer> directiveInfo :
DIRECTIVES_BY_MIN_SUPPORTED_VERSION.entrySet()) {
validateDirectiveSupport(
imports, federationVersion, directiveInfo.getKey(), directiveInfo.getValue());
}

return loadFederationSpecDefinitions(specLink).stream()
Expand All @@ -102,16 +99,11 @@ private static int parseFederationVersion(String specLink) {
}
}

private static boolean isComposeDirectiveSupported(int federationVersion) {
return federationVersion >= 21;
}

private static boolean isInterfaceObjectSupported(int federationVersion) {
return federationVersion >= 23;
}

private static boolean isAuthorizationSupported(int federationVersion) {
return federationVersion >= 25;
private static void validateDirectiveSupport(
Map<String, String> imports, int version, String directiveName, int minVersion) {
if (imports.containsKey(directiveName) && version < minVersion) {
throw new UnsupportedLinkImportException(directiveName, minVersion, version);
}
}

private static Stream<Directive> getFederationLinkDirectives(SchemaDefinition schemaDefinition) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ public UnsupportedLinkImportException(Value importedDefinition) {
super("Unsupported import: " + importedDefinition);
}

public UnsupportedLinkImportException(String importedDefinition) {
public UnsupportedLinkImportException(String importedDefinition, int minVersion, int version) {
super(
"New Federation feature " + importedDefinition + " imported using old Federation version");
String.format(
"Federation v%.1f feature %s imported using old Federation v%.1f version",
minVersion / 10.0, importedDefinition, version / 10.0));
}
}
102 changes: 102 additions & 0 deletions graphql-java-support/src/main/resources/definitions_fed2_6.graphqls
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#
# https://specs.apollo.dev/federation/v2.0/federation-v2.0.graphql
#

directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
directive @external on OBJECT | FIELD_DEFINITION
directive @extends on OBJECT | INTERFACE
directive @override(from: String!) on FIELD_DEFINITION
directive @inaccessible on
| FIELD_DEFINITION
| OBJECT
| INTERFACE
| UNION
| ENUM
| ENUM_VALUE
| SCALAR
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
| ARGUMENT_DEFINITION
directive @tag(name: String!) repeatable on
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| UNION
| ARGUMENT_DEFINITION
| SCALAR
| ENUM
| ENUM_VALUE
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
scalar FieldSet

#
# https://specs.apollo.dev/link/v1.0/link-v1.0.graphql
#

directive @link(
url: String!,
as: String,
import: [Import],
for: Purpose)
repeatable on SCHEMA

scalar Import

enum Purpose {
SECURITY
EXECUTION
}

#
# federation-v2.1
#

directive @composeDirective(name: String!) repeatable on SCHEMA

#
# federation-v2.2
#

directive @shareable repeatable on FIELD_DEFINITION | OBJECT

#
# federation-v2.3
#

directive @interfaceObject on OBJECT

#
# federation-v2.5
#

directive @authenticated on
ENUM
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| SCALAR

directive @requiresScopes(scopes: [[Scope!]!]!) on
ENUM
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| SCALAR

scalar Scope

#
# federation-v2.6
#

directive @policy(policies: [[Policy!]!]!) on
ENUM
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| SCALAR

scalar Policy
107 changes: 107 additions & 0 deletions graphql-java-support/src/main/resources/definitions_fed2_7.graphqls
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#
# https://specs.apollo.dev/federation/v2.0/federation-v2.0.graphql
#

directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
directive @external on OBJECT | FIELD_DEFINITION
directive @extends on OBJECT | INTERFACE
directive @inaccessible on
| FIELD_DEFINITION
| OBJECT
| INTERFACE
| UNION
| ENUM
| ENUM_VALUE
| SCALAR
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
| ARGUMENT_DEFINITION
directive @tag(name: String!) repeatable on
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| UNION
| ARGUMENT_DEFINITION
| SCALAR
| ENUM
| ENUM_VALUE
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
scalar FieldSet

#
# https://specs.apollo.dev/link/v1.0/link-v1.0.graphql
#

directive @link(
url: String!,
as: String,
import: [Import],
for: Purpose)
repeatable on SCHEMA

scalar Import

enum Purpose {
SECURITY
EXECUTION
}

#
# federation-v2.1
#

directive @composeDirective(name: String!) repeatable on SCHEMA

#
# federation-v2.2
#

directive @shareable repeatable on FIELD_DEFINITION | OBJECT

#
# federation-v2.3
#

directive @interfaceObject on OBJECT

#
# federation-v2.5
#

directive @authenticated on
ENUM
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| SCALAR

directive @requiresScopes(scopes: [[Scope!]!]!) on
ENUM
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| SCALAR

scalar Scope

#
# federation-v2.6
#

directive @policy(policies: [[Policy!]!]!) on
ENUM
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| SCALAR

scalar Policy

#
# federation-v2.7
#

directive @override(from: String!, label: String) on FIELD_DEFINITION
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,11 @@ public void verifyFederationV2Transformation_authorizedFromUnsupportedVersion_th
() -> Federation.transform(schemaSDL).fetchEntities(env -> null).build());
}

@Test
public void verifyFederationV2Transformation_policy() {
verifyFederationTransformation("schemas/policy.graphql", true);
}

@Test
public void
verifyFederationV2Transformation_requiresScopesFromUnsupportedVersion_throwsException() {
Expand All @@ -328,6 +333,29 @@ public void verifyFederationV2Transformation_authorizedFromUnsupportedVersion_th
() -> Federation.transform(schemaSDL).fetchEntities(env -> null).build());
}

@Test
public void verifyFederationV2Transformation_policyFromUnsupportedVersion_throwsException() {
final String schemaSDL = FileUtils.readResource("schemas/policyUnsupportedSpecVersion.graphql");
assertThrows(
UnsupportedLinkImportException.class,
() -> Federation.transform(schemaSDL).fetchEntities(env -> null).build());
}

@Test
public void verifyFederationV2Transformation_progressiveOverride() {
verifyFederationTransformation("schemas/progressiveOverride.graphql", true);
}

@Test
public void
verifyFederationV2Transformation_progressiveOverrideFromUnsupportedVersion_throwsException() {
final String schemaSDL =
FileUtils.readResource("schemas/progressiveOverrideUnsupportedSpecVersion.graphql");
assertThrows(
SchemaProblem.class,
() -> Federation.transform(schemaSDL).fetchEntities(env -> null).build());
}

private GraphQLSchema verifyFederationTransformation(
String schemaFileName, boolean isFederationV2) {
final RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring().build();
Expand Down
11 changes: 11 additions & 0 deletions graphql-java-support/src/test/resources/schemas/policy.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
extend schema @link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@key", "@policy", "Policy", "FieldSet"])

type Product @key(fields: "id") {
id: ID!
name: String!
supplier: String @policy(policies: [["policyA"]])
}

type Query {
product(id: ID!): Product
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@policy", "Policy", "FieldSet"])

type Product @key(fields: "id") {
id: ID!
name: String!
supplier: String @policy(policies: [["policy"]])
}

type Query {
product(id: ID!): Product
}
Loading

0 comments on commit 42dcf10

Please sign in to comment.