From a076d7dc19d6dcfaee564461317991ef7330625a Mon Sep 17 00:00:00 2001 From: Roman Lovakov Date: Mon, 30 Sep 2024 15:06:20 +0300 Subject: [PATCH] Added resolver for federation queries --- .../smallrye/graphql/schema/Annotations.java | 1 + .../graphql/schema/SchemaBuilder.java | 14 ++ .../schema/creator/OperationCreator.java | 2 + .../graphql/schema/model/OperationType.java | 3 +- .../smallrye/graphql/schema/model/Schema.java | 20 ++- docs/federation.md | 119 ++++++++++++++ .../graphql/api/federation/Resolver.java | 15 ++ .../graphql/entry/http/IndexInitializer.java | 2 + .../smallrye/graphql/bootstrap/Bootstrap.java | 46 +++++- .../bootstrap/FederationDataFetcher.java | 16 +- .../graphql/execution/ResolverTest.java | 155 ++++++++++++++++++ .../graphql/test/resolver/ExtendedApi.java | 25 +++ .../graphql/test/resolver/ExtendedType.java | 55 +++++++ .../mavenplugin/GenerateSchemaMojo.java | 4 +- 14 files changed, 469 insertions(+), 8 deletions(-) create mode 100644 server/api/src/main/java/io/smallrye/graphql/api/federation/Resolver.java create mode 100644 server/implementation/src/test/java/io/smallrye/graphql/execution/ResolverTest.java create mode 100644 server/implementation/src/test/java/io/smallrye/graphql/test/resolver/ExtendedApi.java create mode 100644 server/implementation/src/test/java/io/smallrye/graphql/test/resolver/ExtendedType.java diff --git a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Annotations.java b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Annotations.java index 00b41ddc4..6d32044b5 100644 --- a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Annotations.java +++ b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Annotations.java @@ -590,6 +590,7 @@ private static Map getAnnotationsWithFilter(org.jbo public static final DotName ERROR_CODE = DotName.createSimple("io.smallrye.graphql.api.ErrorCode"); public static final DotName DATAFETCHER = DotName.createSimple("io.smallrye.graphql.api.DataFetcher"); public static final DotName SUBCRIPTION = DotName.createSimple("io.smallrye.graphql.api.Subscription"); + public static final DotName RESOLVER = DotName.createSimple("io.smallrye.graphql.api.federation.Resolver"); public static final DotName DIRECTIVE = DotName.createSimple("io.smallrye.graphql.api.Directive"); public static final DotName DEFAULT_NON_NULL = DotName.createSimple("io.smallrye.graphql.api.DefaultNonNull"); public static final DotName NULLABLE = DotName.createSimple("io.smallrye.graphql.api.Nullable"); diff --git a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/SchemaBuilder.java b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/SchemaBuilder.java index e4e2ad13c..c57b113c7 100644 --- a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/SchemaBuilder.java +++ b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/SchemaBuilder.java @@ -153,6 +153,7 @@ private Schema generateSchema() { for (AnnotationInstance graphQLApiAnnotation : graphQLApiAnnotations) { ClassInfo apiClass = graphQLApiAnnotation.target().asClass(); List methods = getAllMethodsIncludingFromSuperClasses(apiClass); + addResolvers(schema, methods); NamespaceHelper.getNamespace(graphQLApiAnnotation).ifPresentOrElse( namespace -> addNamespacedOperations(namespace, schema, methods), () -> addOperations(schema, methods)); @@ -456,6 +457,19 @@ private void addOperations(Schema schema, List methodInfoList) { } } + private void addResolvers(Schema schema, List methodInfoList) { + for (MethodInfo methodInfo : methodInfoList) { + Annotations annotationsForMethod = Annotations.getAnnotationsForMethod(methodInfo); + if (annotationsForMethod.containsOneOfTheseAnnotations(Annotations.RESOLVER)) { + Operation resolver = operationCreator.createOperation(methodInfo, OperationType.RESOLVER, null); + String className = resolver.getClassName(); + String resolverClassName = className.substring(className.lastIndexOf(".") + 1); + resolver.setName(resolverClassName + resolver.getName()); + schema.addResolver(resolver); + } + } + } + private void setUpSchemaDirectivesAndDescription(Schema schema, Collection graphQLApiAnnotations, Directives directivesHelper) { diff --git a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/OperationCreator.java b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/OperationCreator.java index ffa064f38..2f2a84634 100644 --- a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/OperationCreator.java +++ b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/OperationCreator.java @@ -304,6 +304,8 @@ private static DotName getOperationAnnotation(OperationType operationType) { return Annotations.MUTATION; case SUBSCRIPTION: return Annotations.SUBCRIPTION; + case RESOLVER: + return Annotations.RESOLVER; default: break; } diff --git a/common/schema-model/src/main/java/io/smallrye/graphql/schema/model/OperationType.java b/common/schema-model/src/main/java/io/smallrye/graphql/schema/model/OperationType.java index 004c4428f..bf81a6385 100644 --- a/common/schema-model/src/main/java/io/smallrye/graphql/schema/model/OperationType.java +++ b/common/schema-model/src/main/java/io/smallrye/graphql/schema/model/OperationType.java @@ -8,5 +8,6 @@ public enum OperationType { QUERY, MUTATION, - SUBSCRIPTION + SUBSCRIPTION, + RESOLVER, } diff --git a/common/schema-model/src/main/java/io/smallrye/graphql/schema/model/Schema.java b/common/schema-model/src/main/java/io/smallrye/graphql/schema/model/Schema.java index 714b8a7ae..5e284134d 100644 --- a/common/schema-model/src/main/java/io/smallrye/graphql/schema/model/Schema.java +++ b/common/schema-model/src/main/java/io/smallrye/graphql/schema/model/Schema.java @@ -23,6 +23,7 @@ public final class Schema implements Serializable { private Set queries = new HashSet<>(); private Set mutations = new HashSet<>(); private Set subscriptions = new HashSet<>(); + private Set resolvers = new HashSet<>(); private Map namespacedQueries = new HashMap<>(); private Map namespacedMutations = new HashMap<>(); @@ -99,7 +100,7 @@ public void addQuery(Operation query) { public boolean hasOperations() { return hasQueries() || hasNamespaceQueries() || hasMutations() || hasNamespaceMutations() - || hasSubscriptions(); + || hasSubscriptions() || hasResolvers(); } public boolean hasQueries() { @@ -138,6 +139,22 @@ public boolean hasSubscriptions() { return !this.subscriptions.isEmpty(); } + public Set getResolvers() { + return resolvers; + } + + public void setResolvers(Set resolvers) { + this.resolvers = resolvers; + } + + public void addResolver(Operation resolver) { + this.resolvers.add(resolver); + } + + public boolean hasResolvers() { + return !this.resolvers.isEmpty(); + } + public Map getInputs() { return inputs; } @@ -342,6 +359,7 @@ public String toString() { ", queries=" + queries + ", mutations=" + mutations + ", subscriptions=" + subscriptions + + ", resolvers=" + resolvers + ", namespacedQueries=" + namespacedQueries + ", namespacedMutations=" + namespacedMutations + ", directiveTypes=" + directiveTypes + diff --git a/docs/federation.md b/docs/federation.md index 7f09c763f..3ed10fcf7 100644 --- a/docs/federation.md +++ b/docs/federation.md @@ -84,3 +84,122 @@ public class Prices { It is crucial that the sequence of argument list matches with the order of result list. Currently, the name of the Argument `id` must match with the property name in the type. +## Federation Reference Resolver + +In federation you also may want extend external type by some fields, without publishing queries into schema. You can do it using @Resolver + +```java +@Extends +@Key(fields = @FieldSet("upc")) +public final class Product { + @External + @NonNull + private String upc; + @External + private Integer weight; + @External + private Integer price; + private Boolean inStock; + @Requires(fields = @FieldSet("price weight")) + private Integer shippingPrice; +} + +@GraphQLApi +public class Api { + @Query // 0 query, that will be added into schema + public Product findByUPC(String upc) { + return new Product(upc , ...etc); + } + + @Resolver // 1 You dont receive external fields price weight here, just key + public Product resolveByUPC(String upc) { + return new Product(upc , ...etc); + } + + @Resolver // 2 The order of variables doesn't matter + public Product resolveByUPCForShipping(int price, String upc, @Name("weight") int someWeight) { + return new Product(upc , someWeight, price, (price * someWeight) /*calculate shippingPrice */, ...etc); + } + + @Resolver // 3 + public Product resolveByUPCForSource(int price, String upc) { + return new Product(upc, price, ...etc); + } + + @Requires(fields = @FieldSet("price")) + public int anotherWeight(@Source Product product) { + return product.price() * 2; + } +} +``` + +Will be generated next schema +``` +type Product @extends @key(fields : "upc") { + anotherWeight: Int! @requires(fields : "price") + inStock: Boolean + price: Int @external + shippingPrice: Int @requires(fields : "price weight") + upc: String! @external + weight: Int @external +} + +type Query { + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! +} +``` + +These methods will only be available to the federation router, which send next request +``` +// request 1 +query { + _entities(representations: [{ + "__typename": "Product", + "upc": "1" // just id key + }]) { + __typename + ... on Product { + inStock + } + } +} + +// request 2 +query { + _entities(representations: [{ + "__typename": "Product", + "upc": "1", // id key + "price": 100, // shippingPrice requires this field + "weight": 100 // shippingPrice requires this field + }]) { + __typename + ... on Product { + inStock + shippingPrice + } + } +} + +// request 3 +query { + _entities(representations: [{ + "__typename": "Product", + "upc": "2", + "price": 1299 // anotherWeight requires this field + } + ]) { + __typename + ... on Product { + anotherWeight + } + } +} +``` + +Unfortunately, you will have to make separate methods with different `@External` parameters. + +It is not currently possible to combine them into one separate type. + +You also can using @Query (if you want add queries into schema) or @Resolver (requests 0 and 1). +And if it was request `_entities` - @Resolvers methods are checked first (they have higher priority). diff --git a/server/api/src/main/java/io/smallrye/graphql/api/federation/Resolver.java b/server/api/src/main/java/io/smallrye/graphql/api/federation/Resolver.java new file mode 100644 index 000000000..98e617a14 --- /dev/null +++ b/server/api/src/main/java/io/smallrye/graphql/api/federation/Resolver.java @@ -0,0 +1,15 @@ +package io.smallrye.graphql.api.federation; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import io.smallrye.common.annotation.Experimental; + +@Target(ElementType.METHOD) +@Retention(RUNTIME) +@Experimental("Resolver method without creating query method") +public @interface Resolver { +} diff --git a/server/implementation-servlet/src/main/java/io/smallrye/graphql/entry/http/IndexInitializer.java b/server/implementation-servlet/src/main/java/io/smallrye/graphql/entry/http/IndexInitializer.java index 65491090e..48e02047d 100644 --- a/server/implementation-servlet/src/main/java/io/smallrye/graphql/entry/http/IndexInitializer.java +++ b/server/implementation-servlet/src/main/java/io/smallrye/graphql/entry/http/IndexInitializer.java @@ -42,6 +42,7 @@ import io.smallrye.graphql.api.federation.Override; import io.smallrye.graphql.api.federation.Provides; import io.smallrye.graphql.api.federation.Requires; +import io.smallrye.graphql.api.federation.Resolver; import io.smallrye.graphql.api.federation.Shareable; import io.smallrye.graphql.api.federation.Tag; import io.smallrye.graphql.api.federation.link.Import; @@ -127,6 +128,7 @@ private IndexView createCustomIndex() { indexer.index(convertClassToInputStream(Shareable.class)); indexer.index(convertClassToInputStream(Tag.class)); indexer.index(convertClassToInputStream(Namespace.class)); + indexer.index(convertClassToInputStream(Resolver.class)); } catch (IOException ex) { throw new RuntimeException(ex); } diff --git a/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/Bootstrap.java b/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/Bootstrap.java index b72ffb1b0..73045f716 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/Bootstrap.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/Bootstrap.java @@ -29,6 +29,7 @@ import com.apollographql.federation.graphqljava.Federation; +import graphql.Scalars; import graphql.introspection.Introspection.DirectiveLocation; import graphql.schema.Coercing; import graphql.schema.DataFetcher; @@ -180,7 +181,7 @@ private void generateGraphQLSchema() { createGraphQLObjectTypes(); createGraphQLInputObjectTypes(); - addQueries(schemaBuilder); + GraphQLObjectType queryRootType = addQueries(schemaBuilder); addMutations(schemaBuilder); addSubscriptions(schemaBuilder); schemaBuilder.withSchemaAppliedDirectives(Arrays.stream( @@ -209,9 +210,18 @@ private void generateGraphQLSchema() { if (Config.get().isFederationEnabled()) { log.enableFederation(); + + // hack! For schema build success if queries are empty. + // It will be overrides in Federation transformation + addDummySdlQuery(schemaBuilder, queryRootType); + + // Build reference resolvers type, without adding to schema (just for federation) + GraphQLObjectType resolversType = buildResolvers(); + GraphQLSchema rawSchema = schemaBuilder.build(); this.graphQLSchema = Federation.transform(rawSchema) - .fetchEntities(new FederationDataFetcher(rawSchema.getQueryType(), rawSchema.getCodeRegistry())) + .fetchEntities( + new FederationDataFetcher(resolversType, rawSchema.getQueryType(), rawSchema.getCodeRegistry())) .resolveEntityType(fetchEntityType()) .setFederation2(true) .build(); @@ -220,6 +230,35 @@ private void generateGraphQLSchema() { } } + private void addDummySdlQuery(GraphQLSchema.Builder schemaBuilder, GraphQLObjectType queryRootType) { + GraphQLObjectType type = GraphQLObjectType.newObject() + .name("_Service") + .field(GraphQLFieldDefinition + .newFieldDefinition().name("sdl") + .type(new GraphQLNonNull(Scalars.GraphQLString)) + .build()) + .build(); + + GraphQLFieldDefinition field = GraphQLFieldDefinition.newFieldDefinition() + .name("_service") + .type(GraphQLNonNull.nonNull(type)) + .build(); + + GraphQLObjectType.Builder newQueryType = GraphQLObjectType.newObject(queryRootType); + + newQueryType.field(field); + schemaBuilder.query(newQueryType.build()); + } + + private GraphQLObjectType buildResolvers() { + GraphQLObjectType.Builder queryBuilder = GraphQLObjectType.newObject() + .name("Resolver"); + if (schema.hasResolvers()) { + addRootObject(queryBuilder, schema.getResolvers(), "Resolver"); + } + return queryBuilder.build(); + } + private TypeResolver fetchEntityType() { return env -> { Object src = env.getObject(); @@ -321,7 +360,7 @@ private void createGraphQLDirectiveType(DirectiveType directiveType) { directiveTypes.add(directiveBuilder.build()); } - private void addQueries(GraphQLSchema.Builder schemaBuilder) { + private GraphQLObjectType addQueries(GraphQLSchema.Builder schemaBuilder) { GraphQLObjectType.Builder queryBuilder = GraphQLObjectType.newObject() .name(QUERY) .description(QUERY_DESCRIPTION); @@ -335,6 +374,7 @@ private void addQueries(GraphQLSchema.Builder schemaBuilder) { GraphQLObjectType query = queryBuilder.build(); schemaBuilder.query(query); + return query; } private void addMutations(GraphQLSchema.Builder schemaBuilder) { diff --git a/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/FederationDataFetcher.java b/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/FederationDataFetcher.java index 312a90285..61a8c6277 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/FederationDataFetcher.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/FederationDataFetcher.java @@ -39,10 +39,13 @@ public class FederationDataFetcher implements DataFetcher cache = new ConcurrentHashMap<>(); - public FederationDataFetcher(GraphQLObjectType queryType, GraphQLCodeRegistry codeRegistry) { + public FederationDataFetcher(GraphQLObjectType resolversType, GraphQLObjectType queryType, + GraphQLCodeRegistry codeRegistry) { + this.resolversType = resolversType; this.queryType = queryType; this.codeRegistry = codeRegistry; } @@ -104,6 +107,11 @@ && matchesArguments(typeAndArgumentNames, definition)) { } private TypeFieldWrapper findBatchFieldDefinition(TypeAndArgumentNames typeAndArgumentNames) { + for (GraphQLFieldDefinition field : resolversType.getFields()) { + if (matchesReturnTypeList(field, typeAndArgumentNames.type) && matchesArguments(typeAndArgumentNames, field)) { + return new TypeFieldWrapper(resolversType, field); + } + } for (GraphQLFieldDefinition field : queryType.getFields()) { if (matchesReturnTypeList(field, typeAndArgumentNames.type) && matchesArguments(typeAndArgumentNames, field)) { return new TypeFieldWrapper(queryType, field); @@ -120,6 +128,11 @@ private TypeFieldWrapper findBatchFieldDefinition(TypeAndArgumentNames typeAndAr } private TypeFieldWrapper findFieldDefinition(TypeAndArgumentNames typeAndArgumentNames) { + for (GraphQLFieldDefinition field : resolversType.getFields()) { + if (matchesReturnType(field, typeAndArgumentNames.type) && matchesArguments(typeAndArgumentNames, field)) { + return new TypeFieldWrapper(resolversType, field); + } + } for (GraphQLFieldDefinition field : queryType.getFields()) { if (matchesReturnType(field, typeAndArgumentNames.type) && matchesArguments(typeAndArgumentNames, field)) { return new TypeFieldWrapper(queryType, field); @@ -132,7 +145,6 @@ private TypeFieldWrapper findFieldDefinition(TypeAndArgumentNames typeAndArgumen return typeFieldWrapper; } } - throw new RuntimeException( "no query found for " + typeAndArgumentNames.type + " by " + typeAndArgumentNames.argumentNames); } diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/ResolverTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/ResolverTest.java new file mode 100644 index 000000000..278f0585f --- /dev/null +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/ResolverTest.java @@ -0,0 +1,155 @@ +package io.smallrye.graphql.execution; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.IOException; +import java.io.InputStream; +import java.util.stream.Stream; + +import jakarta.json.JsonObject; +import jakarta.json.JsonString; +import jakarta.json.JsonValue; + +import org.jboss.jandex.IndexView; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import graphql.schema.GraphQLSchema; +import io.smallrye.graphql.api.Directive; +import io.smallrye.graphql.api.federation.Extends; +import io.smallrye.graphql.api.federation.External; +import io.smallrye.graphql.api.federation.Key; +import io.smallrye.graphql.api.federation.Requires; +import io.smallrye.graphql.bootstrap.Bootstrap; +import io.smallrye.graphql.schema.SchemaBuilder; +import io.smallrye.graphql.schema.model.Schema; +import io.smallrye.graphql.spi.config.Config; +import io.smallrye.graphql.test.resolver.ExtendedApi; +import io.smallrye.graphql.test.resolver.ExtendedType; + +/** + * Test for Federated namespaces + */ +public class ResolverTest { + private static final TestConfig config = (TestConfig) Config.get(); + private static ExecutionService executionService; + + @AfterAll + static void afterAll() { + config.reset(); + config.federationEnabled = false; + System.setProperty("smallrye.graphql.federation.enabled", "false"); + } + + @BeforeAll + static void beforeAll() { + config.federationEnabled = true; + System.setProperty("smallrye.graphql.federation.enabled", "true"); + + IndexView index = buildIndex(Directive.class, Key.class, External.class, Key.Keys.class, + Extends.class, Requires.class, ExtendedType.class, ExtendedApi.class); + + GraphQLSchema graphQLSchema = createGraphQLSchema(index); + Schema schema = SchemaBuilder.build(index); + executionService = new ExecutionService(graphQLSchema, schema); + } + + private static IndexView buildIndex(Class... classes) { + org.jboss.jandex.Indexer indexer = new org.jboss.jandex.Indexer(); + Stream.of(classes).forEach(cls -> index(indexer, cls)); + return indexer.complete(); + } + + private static InputStream getResourceStream(Class type) { + String name = type.getName().replace(".", "/") + ".class"; + return Thread.currentThread().getContextClassLoader().getResourceAsStream(name); + } + + private static void index(org.jboss.jandex.Indexer indexer, Class cls) { + try { + indexer.index(getResourceStream(cls)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static GraphQLSchema createGraphQLSchema(IndexView index) { + Schema schema = SchemaBuilder.build(index); + assertNotNull(schema, "Schema should not be null"); + GraphQLSchema graphQLSchema = Bootstrap.bootstrap(schema, true); + assertNotNull(graphQLSchema, "GraphQLSchema should not be null"); + return graphQLSchema; + } + + private static JsonObject executeAndGetResult(String graphQL) { + JsonObjectResponseWriter jsonObjectResponseWriter = new JsonObjectResponseWriter(graphQL); + jsonObjectResponseWriter.logInput(); + executionService.executeSync(jsonObjectResponseWriter.getInput(), jsonObjectResponseWriter); + jsonObjectResponseWriter.logOutput(); + return jsonObjectResponseWriter.getOutput(); + } + + @Test + public void findByIdTest() { + JsonObject jsonObject = executeAndGetResult(TEST_ID_QUERY); + assertNotNull(jsonObject); + + JsonValue jsonValue = jsonObject.getJsonObject("data") + .getJsonArray("_entities") + .getJsonObject(0) + .get("id"); + assertEquals(((JsonString) jsonValue).getString(), "id"); + + jsonValue = jsonObject.getJsonObject("data") + .getJsonArray("_entities") + .getJsonObject(0) + .get("value"); + assertNull(jsonValue); + } + + @Test + public void extendsTest() { + JsonObject jsonObject = executeAndGetResult(TEST_ID_NAME_KEY_QUERY); + assertNotNull(jsonObject); + + JsonValue jsonValue = jsonObject.getJsonObject("data") + .getJsonArray("_entities") + .getJsonObject(0) + .get("id"); + assertEquals(((JsonString) jsonValue).getString(), "id"); + + jsonValue = jsonObject.getJsonObject("data") + .getJsonArray("_entities") + .getJsonObject(0) + .get("value"); + assertEquals(((JsonString) jsonValue).getString(), "idnamekey"); + } + + private static final String TEST_ID_QUERY = "query {\n" + + "_entities(\n" + + " representations: { id: \"id\", __typename: \"ExtendedType\" }\n" + + ") {\n" + + " __typename\n" + + " ... on ExtendedType {\n" + + " id\n" + + " }\n" + + " }\n" + + "}"; + + private static final String TEST_ID_NAME_KEY_QUERY = "query {\n" + + "_entities(\n" + + " representations: { id: \"id\", name: \"name\", key: \"key\", __typename: \"ExtendedType\" }\n" + + ") {\n" + + " __typename\n" + + " ... on ExtendedType {\n" + + " id\n" + + " name\n" + + " key\n" + + " value\n" + + " }\n" + + " }\n" + + "}"; +} diff --git a/server/implementation/src/test/java/io/smallrye/graphql/test/resolver/ExtendedApi.java b/server/implementation/src/test/java/io/smallrye/graphql/test/resolver/ExtendedApi.java new file mode 100644 index 000000000..e224c570e --- /dev/null +++ b/server/implementation/src/test/java/io/smallrye/graphql/test/resolver/ExtendedApi.java @@ -0,0 +1,25 @@ +package io.smallrye.graphql.test.resolver; + +import org.eclipse.microprofile.graphql.GraphQLApi; + +import io.smallrye.graphql.api.federation.Resolver; + +@GraphQLApi +public class ExtendedApi { + @Resolver + public ExtendedType extendedTypeById(String id) { + ExtendedType extendedType = new ExtendedType(); + extendedType.setId(id); + return extendedType; + } + + @Resolver + public ExtendedType extendedTypeByIdNameKey(String id, String name, String key) { + ExtendedType extendedType = new ExtendedType(); + extendedType.setId(id); + extendedType.setName(name); + extendedType.setKey(key); + extendedType.setValue(id + name + key); + return extendedType; + } +} diff --git a/server/implementation/src/test/java/io/smallrye/graphql/test/resolver/ExtendedType.java b/server/implementation/src/test/java/io/smallrye/graphql/test/resolver/ExtendedType.java new file mode 100644 index 000000000..0d183f209 --- /dev/null +++ b/server/implementation/src/test/java/io/smallrye/graphql/test/resolver/ExtendedType.java @@ -0,0 +1,55 @@ +package io.smallrye.graphql.test.resolver; + +import io.smallrye.graphql.api.federation.Extends; +import io.smallrye.graphql.api.federation.External; +import io.smallrye.graphql.api.federation.FieldSet; +import io.smallrye.graphql.api.federation.Key; +import io.smallrye.graphql.api.federation.Requires; + +@Extends +@Key(fields = @FieldSet("id")) +public class ExtendedType { + @External + private String id; + + @External + private String name; + + @External + private String key; + + @Requires(fields = @FieldSet("name key")) + private String value; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/tools/maven-plugin/src/main/java/io/smallrye/graphql/mavenplugin/GenerateSchemaMojo.java b/tools/maven-plugin/src/main/java/io/smallrye/graphql/mavenplugin/GenerateSchemaMojo.java index f6fb2633a..90d2ad4de 100644 --- a/tools/maven-plugin/src/main/java/io/smallrye/graphql/mavenplugin/GenerateSchemaMojo.java +++ b/tools/maven-plugin/src/main/java/io/smallrye/graphql/mavenplugin/GenerateSchemaMojo.java @@ -254,7 +254,9 @@ private String generateSchema(IndexView index, boolean enableFederation) { GraphQLSchema graphQLSchema = Bootstrap.bootstrap(internalSchema, true); if (graphQLSchema != null && enableFederation) { graphQLSchema = Federation.transform(graphQLSchema) - .fetchEntities(new FederationDataFetcher(graphQLSchema.getQueryType(), graphQLSchema.getCodeRegistry())) + .fetchEntities(new FederationDataFetcher( + GraphQLObjectType.newObject().name("Resolver").build(), + graphQLSchema.getQueryType(), graphQLSchema.getCodeRegistry())) .resolveEntityType(fetchEntityType()) .setFederation2(true) .build();