diff --git a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/Federation.java b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/Federation.java index fba777b1..dad58bfa 100644 --- a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/Federation.java +++ b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/Federation.java @@ -163,15 +163,18 @@ private static RuntimeWiring ensureFederationV2DirectiveDefinitionsExist( if (def instanceof DirectiveDefinition && !typeRegistry.getDirectiveDefinition(def.getName()).isPresent()) { typeRegistry.add(def); - } else if (def instanceof ScalarTypeDefinition - && !typeRegistry.scalars().containsKey(def.getName())) { - typeRegistry.add(def); - scalarTypesToAdd.add( - GraphQLScalarType.newScalar() - .name(def.getName()) - .description(null) - .coercing(_Any.type.getCoercing()) - .build()); + } else if (def instanceof ScalarTypeDefinition) { + if (!typeRegistry.scalars().containsKey(def.getName())) { + typeRegistry.add(def); + } + if (!runtimeWiring.getScalars().containsKey(def.getName())) { + scalarTypesToAdd.add( + GraphQLScalarType.newScalar() + .name(def.getName()) + .description(null) + .coercing(_Any.type.getCoercing()) + .build()); + } } else if (def instanceof EnumTypeDefinition && !typeRegistry.types().containsKey(def.getName())) { typeRegistry.add(def); diff --git a/graphql-java-support/src/test/java/com/apollographql/federation/graphqljava/FederationTest.java b/graphql-java-support/src/test/java/com/apollographql/federation/graphqljava/FederationTest.java index 2c35bcfb..19d4f79c 100644 --- a/graphql-java-support/src/test/java/com/apollographql/federation/graphqljava/FederationTest.java +++ b/graphql-java-support/src/test/java/com/apollographql/federation/graphqljava/FederationTest.java @@ -356,6 +356,11 @@ public void verifyFederationV2Transformation_progressiveOverride() { () -> Federation.transform(schemaSDL).fetchEntities(env -> null).build()); } + @Test + public void verifyFederationV2Transformation_scalarsDefinedInSchemaButNotWired() { + verifyFederationTransformation("schemas/federationV2_defined_scalars.graphql", true); + } + private GraphQLSchema verifyFederationTransformation( String schemaFileName, boolean isFederationV2) { final RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring().build(); diff --git a/graphql-java-support/src/test/resources/schemas/federationV2_defined_scalars.graphql b/graphql-java-support/src/test/resources/schemas/federationV2_defined_scalars.graphql new file mode 100644 index 00000000..b451f004 --- /dev/null +++ b/graphql-java-support/src/test/resources/schemas/federationV2_defined_scalars.graphql @@ -0,0 +1,84 @@ +extend schema + @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") + +scalar federation__FieldSet + +directive @custom on OBJECT +directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + +type Product + @custom + @key(fields: "id") + @key(fields: "sku package") + @key(fields: "sku variation { id }") { + id: ID! + sku: String + package: String + variation: ProductVariation + 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! +} + +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 +} + +type Query { + product(id: ID!): Product + deprecatedProduct(sku: String!, package: String!): DeprecatedProduct @deprecated(reason: "Use product query instead") +} + +type User @key(fields: "email") { + 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_defined_scalars_federated.graphql b/graphql-java-support/src/test/resources/schemas/federationV2_defined_scalars_federated.graphql new file mode 100644 index 00000000..2ffa9871 --- /dev/null +++ b/graphql-java-support/src/test/resources/schemas/federationV2_defined_scalars_federated.graphql @@ -0,0 +1,104 @@ +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 @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, for: link__Purpose, import: [link__Import], url: String!) repeatable on SCHEMA + +directive @override(from: String!) on FIELD_DEFINITION + +directive @provides(fields: federation__FieldSet!) on 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 = 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 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 +} + +type ProductDimension @shareable { + size: String + unit: String @inaccessible + weight: Float +} + +type ProductResearch @key(fields : "study { caseNumber }", resolvable : true) { + outcome: String + study: CaseStudy! +} + +type ProductVariation { + id: ID! +} + +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) { + averageProductsCreatedPerYear: Int @requires(fields : "totalProductsCreated yearsOfEmployment") + email: ID! @external + name: String @override(from : "users") + totalProductsCreated: Int @external + yearsOfEmployment: Int! @external +} + +type _Service { + sdl: String! +} + +enum link__Purpose { + EXECUTION + SECURITY +} + +scalar _Any + +scalar federation__FieldSet + +scalar link__Import