From 3bf838ecdfeee7a83f84f9646c210115bd48c4a5 Mon Sep 17 00:00:00 2001 From: dariuszkuc <9501705+dariuszkuc@users.noreply.github.com> Date: Tue, 14 Feb 2023 21:52:38 -0600 Subject: [PATCH] fix: fixed logic that checked whether new federation features are supported Old logic was just comparing imported spec to a specific version instead of a range. --- .../directives/LinkDirectiveProcessor.java | 26 +++++-- ...UnsupportedFederationVersionException.java | 4 ++ .../resources/schemas/federationV2.graphql | 70 ++++++++++++++++--- .../schemas/federationV2_federated.graphql | 48 ++++++++++--- 4 files changed, 123 insertions(+), 25 deletions(-) diff --git a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/directives/LinkDirectiveProcessor.java b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/directives/LinkDirectiveProcessor.java index ecd1272d..52bb2e6a 100644 --- a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/directives/LinkDirectiveProcessor.java +++ b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/directives/LinkDirectiveProcessor.java @@ -1,10 +1,9 @@ package com.apollographql.federation.graphqljava.directives; -import static com.apollographql.federation.graphqljava.Federation.FEDERATION_SPEC_V2_1; -import static com.apollographql.federation.graphqljava.Federation.FEDERATION_SPEC_V2_3; import static com.apollographql.federation.graphqljava.FederationDirectives.loadFederationSpecDefinitions; import com.apollographql.federation.graphqljava.exceptions.MultipleFederationLinksException; +import com.apollographql.federation.graphqljava.exceptions.UnsupportedFederationVersionException; import com.apollographql.federation.graphqljava.exceptions.UnsupportedLinkImportException; import graphql.language.Argument; import graphql.language.ArrayValue; @@ -67,14 +66,13 @@ private static Stream loadDefinitions(Directive linkDirectiv final Argument urlArgument = linkDirective.getArgument("url"); final String specLink = ((StringValue) urlArgument.getValue()).getValue(); - final boolean allowComposeableDirective = FEDERATION_SPEC_V2_1.equals(specLink); - final boolean allowInterfaceObjectDirective = FEDERATION_SPEC_V2_3.equals(specLink); - if (!allowComposeableDirective && imports.containsKey("@composeDirective")) { + final int federationVersion = parseFederationVersion(specLink); + if (imports.containsKey("@composeDirective") && !isComposeDirectiveSupported(federationVersion)) { throw new UnsupportedLinkImportException("@composeDirective"); } - if (!allowInterfaceObjectDirective && imports.containsKey("@interfaceObject")) { + if (imports.containsKey("@interfaceObject") && !isInterfaceObjectSupported(federationVersion)) { throw new UnsupportedLinkImportException("@interfaceObject"); } @@ -86,6 +84,22 @@ private static Stream loadDefinitions(Directive linkDirectiv .transform(definition, new LinkImportsRenamingVisitor(imports))); } + private static int parseFederationVersion(String specLink) { + final String versionString = specLink.substring(specLink.length() - 3); + try { + return Math.round(Float.parseFloat(versionString) * 10); + } catch (Exception e) { + throw new UnsupportedFederationVersionException(specLink); + } + } + private static boolean isComposeDirectiveSupported(int federationVersion) { + return federationVersion >= 21; + } + + private static boolean isInterfaceObjectSupported(int federationVersion) { + return federationVersion >= 23; + } + private static Stream getFederationLinkDirectives(SchemaDefinition schemaDefinition) { return schemaDefinition.getDirectives("link").stream() .filter( diff --git a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/exceptions/UnsupportedFederationVersionException.java b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/exceptions/UnsupportedFederationVersionException.java index 5d309169..951b5e80 100644 --- a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/exceptions/UnsupportedFederationVersionException.java +++ b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/exceptions/UnsupportedFederationVersionException.java @@ -5,4 +5,8 @@ public class UnsupportedFederationVersionException extends RuntimeException { public UnsupportedFederationVersionException(String federationSpec) { super("Specified federation spec = " + federationSpec + " is currently not supported"); } + + public UnsupportedFederationVersionException(String federationSpec, Exception e) { + super("Specified federation spec = " + federationSpec + " is currently not supported", e); + } } diff --git a/graphql-java-support/src/test/resources/schemas/federationV2.graphql b/graphql-java-support/src/test/resources/schemas/federationV2.graphql index 297f103f..3c59731d 100644 --- a/graphql-java-support/src/test/resources/schemas/federationV2.graphql +++ b/graphql-java-support/src/test/resources/schemas/federationV2.graphql @@ -1,8 +1,30 @@ extend schema -@link(url: "https://specs.apollo.dev/federation/v2.0", - import: ["@key", "@shareable", "@provides", "@external", "@tag", "@extends", "@override", "@inaccessible"]) + @link( + url: "https://specs.apollo.dev/federation/v2.3" + import: [ + "@composeDirective" + "@extends" + "@external" + "@key" + "@inaccessible" + "@interfaceObject" + "@override" + "@provides" + "@requires" + "@shareable" + "@tag" + ] + ) + @link(url: "https://myspecs.dev/myCustomDirective/v1.0", import: ["@custom"]) + @composeDirective(name: "@custom") -type Product @key(fields: "id") @key(fields: "sku package") @key(fields: "sku variation { id }") { + directive @custom on OBJECT + +type Product + @custom + @key(fields: "id") + @key(fields: "sku package") + @key(fields: "sku variation { id }") { id: ID! sku: String package: String @@ -10,24 +32,50 @@ type Product @key(fields: "id") @key(fields: "sku package") @key(fields: "sku va dimensions: ProductDimension createdBy: User @provides(fields: "totalProductsCreated") notes: String @tag(name: "internal") + research: [ProductResearch!]! +} + +type DeprecatedProduct @key(fields: "sku package") { + sku: String! + package: String! + reason: String + createdBy: User } type ProductVariation { - id: ID! + id: ID! +} + +type ProductResearch @key(fields: "study { caseNumber }") { + study: CaseStudy! + outcome: String +} + +type CaseStudy { + caseNumber: ID! + description: String } type ProductDimension @shareable { - size: String - weight: Float - unit: String @inaccessible + size: String + weight: Float + unit: String @inaccessible } type Query { - product(id: ID!): Product + product(id: ID!): Product + deprecatedProduct(sku: String!, package: String!): DeprecatedProduct @deprecated(reason: "Use product query instead") } type User @key(fields: "email") { - email: ID! - name: String @shareable @override(from: "users") - totalProductsCreated: Int @external + averageProductsCreatedPerYear: Int @requires(fields: "totalProductsCreated yearsOfEmployment") + email: ID! @external + name: String @override(from: "users") + totalProductsCreated: Int @external + yearsOfEmployment: Int! @external +} + +type Inventory @interfaceObject @key(fields: "id") { + id: ID! + deprecatedProducts: [DeprecatedProduct!]! } diff --git a/graphql-java-support/src/test/resources/schemas/federationV2_federated.graphql b/graphql-java-support/src/test/resources/schemas/federationV2_federated.graphql index a947ec43..d0707b74 100644 --- a/graphql-java-support/src/test/resources/schemas/federationV2_federated.graphql +++ b/graphql-java-support/src/test/resources/schemas/federationV2_federated.graphql @@ -1,15 +1,19 @@ -schema @link(import : ["@key", "@shareable", "@provides", "@external", "@tag", "@extends", "@override", "@inaccessible"], url : "https://specs.apollo.dev/federation/v2.0"){ +schema @composeDirective(name : "@custom") @link(import : ["@composeDirective", "@extends", "@external", "@key", "@inaccessible", "@interfaceObject", "@override", "@provides", "@requires", "@shareable", "@tag"], url : "https://specs.apollo.dev/federation/v2.3") @link(import : ["@custom"], url : "https://myspecs.dev/myCustomDirective/v1.0"){ query: Query } +directive @composeDirective(name: String!) repeatable on SCHEMA + +directive @custom on OBJECT + directive @extends on OBJECT | INTERFACE directive @external on OBJECT | FIELD_DEFINITION -directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION - directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION +directive @interfaceObject on OBJECT + directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE directive @link(as: String, import: [link__Import], url: String!) repeatable on SCHEMA @@ -18,18 +22,38 @@ directive @override(from: String!) on FIELD_DEFINITION directive @provides(fields: federation__FieldSet!) on FIELD_DEFINITION -directive @shareable on OBJECT | FIELD_DEFINITION +directive @requires(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @shareable repeatable on OBJECT | FIELD_DEFINITION directive @tag(name: String!) repeatable on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION -union _Entity = Product | User +union _Entity = DeprecatedProduct | Inventory | Product | ProductResearch | User + +type CaseStudy { + caseNumber: ID! + description: String +} + +type DeprecatedProduct @key(fields : "sku package", resolvable : true) { + createdBy: User + package: String! + reason: String + sku: String! +} -type Product @key(fields : "id", resolvable : true) @key(fields : "sku package", resolvable : true) @key(fields : "sku variation { id }", resolvable : true) { +type Inventory @interfaceObject @key(fields : "id", resolvable : true) { + deprecatedProducts: [DeprecatedProduct!]! + id: ID! +} + +type Product @custom @key(fields : "id", resolvable : true) @key(fields : "sku package", resolvable : true) @key(fields : "sku variation { id }", resolvable : true) { createdBy: User @provides(fields : "totalProductsCreated") dimensions: ProductDimension id: ID! notes: String @tag(name : "internal") package: String + research: [ProductResearch!]! sku: String variation: ProductVariation } @@ -40,6 +64,11 @@ type ProductDimension @shareable { weight: Float } +type ProductResearch @key(fields : "study { caseNumber }", resolvable : true) { + outcome: String + study: CaseStudy! +} + type ProductVariation { id: ID! } @@ -47,13 +76,16 @@ type ProductVariation { type Query { _entities(representations: [_Any!]!): [_Entity]! _service: _Service! + deprecatedProduct(package: String!, sku: String!): DeprecatedProduct @deprecated(reason : "Use product query instead") product(id: ID!): Product } type User @key(fields : "email", resolvable : true) { - email: ID! - name: String @override(from : "users") @shareable + averageProductsCreatedPerYear: Int @requires(fields : "totalProductsCreated yearsOfEmployment") + email: ID! @external + name: String @override(from : "users") totalProductsCreated: Int @external + yearsOfEmployment: Int! @external } type _Service {