From fcb7f941c38f2ea3b0a8be4bbb62db408bcff910 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 22 May 2023 13:23:46 +0100 Subject: [PATCH] Improve "Connection" Java type checks ConnectionFieldTypeVisitor now also checks if the container type ends on "Connection", and if so it lets it pass through. See gh-709 --- .../CompositeConnectionAdapter.java | 6 +-- .../ConnectionFieldTypeVisitor.java | 23 ++++++---- .../ConnectionFieldTypeVisitorTests.java | 46 ++++++++++++++++++- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/pagination/CompositeConnectionAdapter.java b/spring-graphql/src/main/java/org/springframework/graphql/data/pagination/CompositeConnectionAdapter.java index 69e8300e..06e05e74 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/pagination/CompositeConnectionAdapter.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/pagination/CompositeConnectionAdapter.java @@ -62,15 +62,15 @@ final class CompositeConnectionAdapter implements ConnectionAdapter { } private ConnectionAdapter getRequiredAdapter(Object container) { - ConnectionAdapter adapter = getAdapter(container); + ConnectionAdapter adapter = getAdapter(container.getClass()); Assert.notNull(adapter, "No ConnectionAdapter for: " + container.getClass().getName()); return adapter; } @Nullable - private ConnectionAdapter getAdapter(Object container) { + private ConnectionAdapter getAdapter(Class containerType) { for (ConnectionAdapter adapter : this.adapters) { - if (adapter.supports(container.getClass())) { + if (adapter.supports(containerType)) { return adapter; } } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/pagination/ConnectionFieldTypeVisitor.java b/spring-graphql/src/main/java/org/springframework/graphql/data/pagination/ConnectionFieldTypeVisitor.java index fad3259c..5318777e 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/pagination/ConnectionFieldTypeVisitor.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/pagination/ConnectionFieldTypeVisitor.java @@ -195,19 +195,24 @@ public final class ConnectionFieldTypeVisitor extends GraphQLTypeVisitorStub { } } - @SuppressWarnings("unchecked") - private Connection adapt(@Nullable Object container) { - if (container instanceof Connection connection) { - return (Connection) connection; + private Object adapt(@Nullable Object container) { + if (container == null) { + return EMPTY_CONNECTION; } - Collection nodes = (container != null ? - this.adapter.getContent(container) : Collections.emptyList()); - - if (nodes.isEmpty()) { - return (Connection) EMPTY_CONNECTION; + if (container instanceof Connection) { + return container; } + if (!this.adapter.supports(container.getClass())) { + if (container.getClass().getName().endsWith("Connection")) { + return container; + } + throw new IllegalStateException( + "No ConnectionAdapter for: " + container.getClass().getName()); + } + + Collection nodes = this.adapter.getContent(container); int index = 0; List> edges = new ArrayList<>(nodes.size()); for (T node : nodes) { diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/pagination/ConnectionFieldTypeVisitorTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/pagination/ConnectionFieldTypeVisitorTests.java index b480c8f0..6412b8fd 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/pagination/ConnectionFieldTypeVisitorTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/pagination/ConnectionFieldTypeVisitorTests.java @@ -34,6 +34,7 @@ import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import org.springframework.core.io.Resource; +import org.springframework.graphql.Book; import org.springframework.graphql.BookSource; import org.springframework.graphql.ExecutionGraphQlResponse; import org.springframework.graphql.GraphQlSetup; @@ -51,7 +52,7 @@ public class ConnectionFieldTypeVisitorTests { @Test - void paginationDataFetcher() { + void paginatedTypeIsAdapted() { ListConnectionAdapter adapter = new ListConnectionAdapter(); adapter.setInitialOffset(30); @@ -83,6 +84,39 @@ public class ConnectionFieldTypeVisitorTests { ); } + @Test // gh-709 + void customConnectionTypeIsPassedThrough() { + + List> edges = BookSource.books().stream().map(book -> new MyEdge<>("0_" + book.getId(), book)).toList(); + MyPageInfo pageInfo = new MyPageInfo(edges.get(0).cursor(), edges.get(edges.size() - 1).cursor, true, true); + MyConnection connection = new MyConnection<>(edges, pageInfo); + + Mono response = GraphQlSetup.schemaResource(BookSource.paginationSchema) + .dataFetcher("Query", "books", env -> connection) + .connectionSupport(new ListConnectionAdapter()) + .toGraphQlService() + .execute(BookSource.booksConnectionQuery(null)); + + ResponseHelper.forResponse(response).assertData( + "{\"books\":{" + + "\"edges\":[" + + "{\"cursor\":\"0_1\",\"node\":{\"id\":\"1\",\"name\":\"Nineteen Eighty-Four\"}}," + + "{\"cursor\":\"0_2\",\"node\":{\"id\":\"2\",\"name\":\"The Great Gatsby\"}}," + + "{\"cursor\":\"0_3\",\"node\":{\"id\":\"3\",\"name\":\"Catch-22\"}}," + + "{\"cursor\":\"0_4\",\"node\":{\"id\":\"4\",\"name\":\"To The Lighthouse\"}}," + + "{\"cursor\":\"0_5\",\"node\":{\"id\":\"5\",\"name\":\"Animal Farm\"}}," + + "{\"cursor\":\"0_53\",\"node\":{\"id\":\"53\",\"name\":\"Breaking Bad\"}}," + + "{\"cursor\":\"0_42\",\"node\":{\"id\":\"42\",\"name\":\"Hitchhiker's Guide to the Galaxy\"}}" + + "]," + + "\"pageInfo\":{" + + "\"startCursor\":\"0_1\"," + + "\"endCursor\":\"0_42\"," + + "\"hasPreviousPage\":true," + + "\"hasNextPage\":true}" + + "}}" + ); + } + @Test // gh-707 void nullValueTreatedAsEmptyConnection() { @@ -226,4 +260,14 @@ public class ConnectionFieldTypeVisitorTests { } + + private record MyConnection(List> edges, MyPageInfo pageInfo) { + } + + private record MyEdge(String cursor, T node) { + } + + private record MyPageInfo(String startCursor, String endCursor, boolean hasPreviousPage, boolean hasNextPage) { + } + }