Skip to content

Commit

Permalink
ensure scalars are wired up if no existing wiring (#382)
Browse files Browse the repository at this point in the history
Fixes #380 

This restructures the scalar transformation logic to ensure that both
the type definition and wiring are added correctly if necessary. This
allows consumers to define the scalars in their schema, but not assign
runtime wiring. This can be nice to support other tools like linters or
graphql-faker that require the schema to be a valid schema file.
  • Loading branch information
craig-day authored Feb 16, 2024
1 parent 149e4c5 commit d730ff5
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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!]!
}
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit d730ff5

Please sign in to comment.