Skip to content

Commit 3ea4b38

Browse files
committed
Support Flux for batched entities query
Closes gh-991
1 parent aae19d3 commit 3ea4b38

File tree

2 files changed

+64
-14
lines changed

2 files changed

+64
-14
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/federation/FederationSchemaFactory.java

+4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import graphql.schema.TypeResolver;
3232
import graphql.schema.idl.RuntimeWiring;
3333
import graphql.schema.idl.TypeDefinitionRegistry;
34+
import reactor.core.publisher.Flux;
3435
import reactor.core.publisher.Mono;
3536

3637
import org.springframework.context.ApplicationContext;
@@ -189,6 +190,9 @@ public record EntityMappingInfo(String typeName, HandlerMethod handlerMethod) {
189190
public boolean isBatchHandlerMethod() {
190191
MethodParameter type = handlerMethod().getReturnType();
191192
Class<?> paramType = type.getParameterType();
193+
if (Flux.class.isAssignableFrom(paramType)) {
194+
return true;
195+
}
192196
if (Mono.class.isAssignableFrom(paramType) || CompletionStage.class.isAssignableFrom(paramType)) {
193197
type = type.nested();
194198
}

spring-graphql/src/test/java/org/springframework/graphql/data/federation/EntityMappingInvocationTests.java

+60-14
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import graphql.GraphqlErrorBuilder;
2525
import graphql.schema.DataFetchingEnvironment;
2626
import org.junit.jupiter.api.Test;
27+
import org.junit.jupiter.params.ParameterizedTest;
28+
import org.junit.jupiter.params.provider.ValueSource;
2729
import reactor.core.publisher.Flux;
2830
import reactor.core.publisher.Mono;
2931

@@ -113,8 +115,9 @@ void fetchEntitiesWithExceptions() {
113115
assertAuthor(6, "George", "Orwell", helper);
114116
}
115117

116-
@Test
117-
void batching() {
118+
@ValueSource(classes = {BookListController.class, BookFluxController.class})
119+
@ParameterizedTest
120+
void batching(Class<?> controllerClass) {
118121
Map<String, Object> variables =
119122
Map.of("representations", List.of(
120123
Map.of("__typename", "Book", "id", "1"),
@@ -123,7 +126,7 @@ void batching() {
123126
Map.of("__typename", "Book", "id", "42"),
124127
Map.of("__typename", "Book", "id", "53")));
125128

126-
ResponseHelper helper = executeWith(BookBatchController.class, variables);
129+
ResponseHelper helper = executeWith(controllerClass, variables);
127130

128131
assertAuthor(0, "George", "Orwell", helper);
129132
assertAuthor(1, "Virginia", "Woolf", helper);
@@ -132,30 +135,32 @@ void batching() {
132135
assertAuthor(4, "Vince", "Gilligan", helper);
133136
}
134137

135-
@Test
136-
void batchingWithError() {
138+
@ValueSource(classes = {BookListController.class, BookFluxController.class})
139+
@ParameterizedTest
140+
void batchingWithError(Class<?> controllerClass) {
137141
Map<String, Object> variables =
138142
Map.of("representations", List.of(
139143
Map.of("__typename", "Book", "id", "-97"),
140144
Map.of("__typename", "Book", "id", "4"),
141145
Map.of("__typename", "Book", "id", "5")));
142146

143-
ResponseHelper helper = executeWith(BookBatchController.class, variables);
147+
ResponseHelper helper = executeWith(controllerClass, variables);
144148

145149
assertError(helper, 0, "BAD_REQUEST", "handled");
146150
assertError(helper, 1, "BAD_REQUEST", "handled");
147151
assertError(helper, 2, "BAD_REQUEST", "handled");
148152
}
149153

150-
@Test
151-
void batchingWithoutResult() {
154+
@ValueSource(classes = {BookListController.class, BookFluxController.class})
155+
@ParameterizedTest
156+
void batchingWithoutResult(Class<?> controllerClass) {
152157
Map<String, Object> variables =
153158
Map.of("representations", List.of(
154159
Map.of("__typename", "Book", "id", "-99"),
155160
Map.of("__typename", "Book", "id", "4"),
156161
Map.of("__typename", "Book", "id", "5")));
157162

158-
ResponseHelper helper = executeWith(BookBatchController.class, variables);
163+
ResponseHelper helper = executeWith(controllerClass, variables);
159164

160165
assertError(helper, 0, "INTERNAL_ERROR", "Entity fetcher returned null or completed empty");
161166
assertError(helper, 1, "INTERNAL_ERROR", "Entity fetcher returned null or completed empty");
@@ -242,10 +247,53 @@ public GraphQLError handle(IllegalArgumentException ex, DataFetchingEnvironment
242247

243248
@SuppressWarnings("unused")
244249
@Controller
245-
private static class BookBatchController {
250+
private static class BookListController {
251+
252+
private final BookBatchService batchService = new BookBatchService();
246253

247254
@EntityMapping
248255
public List<Book> book(@Argument List<Integer> idList, List<Map<String, Object>> representations) {
256+
return this.batchService.book(idList, representations);
257+
}
258+
259+
@BatchMapping
260+
public List<Author> author(List<Book> books) {
261+
return this.batchService.author(books);
262+
}
263+
264+
@GraphQlExceptionHandler
265+
public GraphQLError handle(IllegalArgumentException ex, DataFetchingEnvironment env) {
266+
return this.batchService.handle(ex, env);
267+
}
268+
}
269+
270+
271+
@SuppressWarnings("unused")
272+
@Controller
273+
private static class BookFluxController {
274+
275+
private final BookBatchService batchService = new BookBatchService();
276+
277+
@EntityMapping
278+
public Flux<Book> book(@Argument List<Integer> idList, List<Map<String, Object>> representations) {
279+
return Flux.fromIterable(this.batchService.book(idList, representations));
280+
}
281+
282+
@BatchMapping
283+
public Flux<Author> author(List<Book> books) {
284+
return Flux.fromIterable(this.batchService.author(books));
285+
}
286+
287+
@GraphQlExceptionHandler
288+
public GraphQLError handle(IllegalArgumentException ex, DataFetchingEnvironment env) {
289+
return this.batchService.handle(ex, env);
290+
}
291+
}
292+
293+
294+
private static class BookBatchService {
295+
296+
public List<Book> book(List<Integer> idList, List<Map<String, Object>> representations) {
249297

250298
if (idList.get(0) == -97) {
251299
throw new IllegalArgumentException("handled");
@@ -265,12 +313,10 @@ public List<Book> book(@Argument List<Integer> idList, List<Map<String, Object>>
265313
return idList.stream().map(id -> new Book((long) id, null, (Long) null)).toList();
266314
}
267315

268-
@BatchMapping
269-
public Flux<Author> author(List<Book> books) {
270-
return Flux.fromIterable(books).map(book -> BookSource.getBook(book.getId()).getAuthor());
316+
public List<Author> author(List<Book> books) {
317+
return books.stream().map(book -> BookSource.getBook(book.getId()).getAuthor()).toList();
271318
}
272319

273-
@GraphQlExceptionHandler
274320
public GraphQLError handle(IllegalArgumentException ex, DataFetchingEnvironment env) {
275321
return GraphqlErrorBuilder.newError(env)
276322
.errorType(ErrorType.BAD_REQUEST)

0 commit comments

Comments
 (0)