diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/federation/FederationSchemaFactory.java b/spring-graphql/src/main/java/org/springframework/graphql/data/federation/FederationSchemaFactory.java index c61829176..095d55fd1 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/federation/FederationSchemaFactory.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/federation/FederationSchemaFactory.java @@ -26,6 +26,9 @@ import com.apollographql.federation.graphqljava.Federation; import com.apollographql.federation.graphqljava.SchemaTransformer; +import graphql.language.Argument; +import graphql.language.BooleanValue; +import graphql.language.Directive; import graphql.language.TypeDefinition; import graphql.schema.DataFetcher; import graphql.schema.GraphQLSchema; @@ -190,12 +193,9 @@ public SchemaTransformer createSchemaTransformer(TypeDefinitionRegistry registry private void checkEntityMappings(TypeDefinitionRegistry registry) { List unmappedEntities = new ArrayList<>(); for (TypeDefinition type : registry.types().values()) { - type.getDirectives().forEach((directive) -> { - boolean isEntityType = directive.getName().equalsIgnoreCase("key"); - if (isEntityType && !this.handlerMethods.containsKey(type.getName())) { - unmappedEntities.add(type.getName()); - } - }); + if (isEntityMappingExpected(type) && !this.handlerMethods.containsKey(type.getName())) { + unmappedEntities.add(type.getName()); + } } if (!unmappedEntities.isEmpty()) { throw new IllegalStateException("Unmapped entity types: " + @@ -203,6 +203,21 @@ private void checkEntityMappings(TypeDefinitionRegistry registry) { } } + /** + * Determine if a handler method is expected for this type: there is at least one '@key' directive + * whose 'resolvable' argument resolves to true (either explicitly, or if the argument is not set). + * @param type the type to inspect. + * @return true if a handler method is expected for this type + */ + private boolean isEntityMappingExpected(TypeDefinition type) { + List keyDirectives = type.getDirectives("key"); + return !keyDirectives.isEmpty() && keyDirectives.stream() + .anyMatch((keyDirective) -> { + Argument resolvableArg = keyDirective.getArgument("resolvable"); + return resolvableArg == null || + (resolvableArg.getValue() instanceof BooleanValue) && ((BooleanValue) resolvableArg.getValue()).isValue(); + }); + } public record EntityMappingInfo(String typeName, HandlerMethod handlerMethod) { diff --git a/spring-graphql/src/test/resources/books/federation-schema.graphqls b/spring-graphql/src/test/resources/books/federation-schema.graphqls index 251dca335..0f8918595 100644 --- a/spring-graphql/src/test/resources/books/federation-schema.graphqls +++ b/spring-graphql/src/test/resources/books/federation-schema.graphqls @@ -1,6 +1,9 @@ +extend schema @link(url: "https://specs.apollo.dev/federation/v2.9", import: ["@key", "@extends", "@external"] ) + type Book @key(fields: "id") @extends { id: ID! @external author: Author + publisher: Publisher } type Author { @@ -8,3 +11,7 @@ type Author { firstName: String lastName: String } + +type Publisher @key(fields: "id", resolvable: false) { + id: ID! @external +}