Skip to content

Commit fcb7f94

Browse files
committed
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
1 parent d113831 commit fcb7f94

File tree

3 files changed

+61
-12
lines changed

3 files changed

+61
-12
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/pagination/CompositeConnectionAdapter.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,15 @@ public String cursorAt(Object container, int index) {
6262
}
6363

6464
private ConnectionAdapter getRequiredAdapter(Object container) {
65-
ConnectionAdapter adapter = getAdapter(container);
65+
ConnectionAdapter adapter = getAdapter(container.getClass());
6666
Assert.notNull(adapter, "No ConnectionAdapter for: " + container.getClass().getName());
6767
return adapter;
6868
}
6969

7070
@Nullable
71-
private ConnectionAdapter getAdapter(Object container) {
71+
private ConnectionAdapter getAdapter(Class<?> containerType) {
7272
for (ConnectionAdapter adapter : this.adapters) {
73-
if (adapter.supports(container.getClass())) {
73+
if (adapter.supports(containerType)) {
7474
return adapter;
7575
}
7676
}

spring-graphql/src/main/java/org/springframework/graphql/data/pagination/ConnectionFieldTypeVisitor.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,19 +195,24 @@ else if (result instanceof CompletionStage<?> stage) {
195195
}
196196
}
197197

198-
@SuppressWarnings("unchecked")
199-
private <T> Connection<T> adapt(@Nullable Object container) {
200-
if (container instanceof Connection<?> connection) {
201-
return (Connection<T>) connection;
198+
private <T> Object adapt(@Nullable Object container) {
199+
if (container == null) {
200+
return EMPTY_CONNECTION;
202201
}
203202

204-
Collection<T> nodes = (container != null ?
205-
this.adapter.getContent(container) : Collections.emptyList());
203+
if (container instanceof Connection<?>) {
204+
return container;
205+
}
206206

207-
if (nodes.isEmpty()) {
208-
return (Connection<T>) EMPTY_CONNECTION;
207+
if (!this.adapter.supports(container.getClass())) {
208+
if (container.getClass().getName().endsWith("Connection")) {
209+
return container;
210+
}
211+
throw new IllegalStateException(
212+
"No ConnectionAdapter for: " + container.getClass().getName());
209213
}
210214

215+
Collection<T> nodes = this.adapter.getContent(container);
211216
int index = 0;
212217
List<Edge<T>> edges = new ArrayList<>(nodes.size());
213218
for (T node : nodes) {

spring-graphql/src/test/java/org/springframework/graphql/data/pagination/ConnectionFieldTypeVisitorTests.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import reactor.core.publisher.Mono;
3535

3636
import org.springframework.core.io.Resource;
37+
import org.springframework.graphql.Book;
3738
import org.springframework.graphql.BookSource;
3839
import org.springframework.graphql.ExecutionGraphQlResponse;
3940
import org.springframework.graphql.GraphQlSetup;
@@ -51,7 +52,7 @@ public class ConnectionFieldTypeVisitorTests {
5152

5253

5354
@Test
54-
void paginationDataFetcher() {
55+
void paginatedTypeIsAdapted() {
5556

5657
ListConnectionAdapter adapter = new ListConnectionAdapter();
5758
adapter.setInitialOffset(30);
@@ -83,6 +84,39 @@ void paginationDataFetcher() {
8384
);
8485
}
8586

87+
@Test // gh-709
88+
void customConnectionTypeIsPassedThrough() {
89+
90+
List<MyEdge<Book>> edges = BookSource.books().stream().map(book -> new MyEdge<>("0_" + book.getId(), book)).toList();
91+
MyPageInfo pageInfo = new MyPageInfo(edges.get(0).cursor(), edges.get(edges.size() - 1).cursor, true, true);
92+
MyConnection<Book> connection = new MyConnection<>(edges, pageInfo);
93+
94+
Mono<ExecutionGraphQlResponse> response = GraphQlSetup.schemaResource(BookSource.paginationSchema)
95+
.dataFetcher("Query", "books", env -> connection)
96+
.connectionSupport(new ListConnectionAdapter())
97+
.toGraphQlService()
98+
.execute(BookSource.booksConnectionQuery(null));
99+
100+
ResponseHelper.forResponse(response).assertData(
101+
"{\"books\":{" +
102+
"\"edges\":[" +
103+
"{\"cursor\":\"0_1\",\"node\":{\"id\":\"1\",\"name\":\"Nineteen Eighty-Four\"}}," +
104+
"{\"cursor\":\"0_2\",\"node\":{\"id\":\"2\",\"name\":\"The Great Gatsby\"}}," +
105+
"{\"cursor\":\"0_3\",\"node\":{\"id\":\"3\",\"name\":\"Catch-22\"}}," +
106+
"{\"cursor\":\"0_4\",\"node\":{\"id\":\"4\",\"name\":\"To The Lighthouse\"}}," +
107+
"{\"cursor\":\"0_5\",\"node\":{\"id\":\"5\",\"name\":\"Animal Farm\"}}," +
108+
"{\"cursor\":\"0_53\",\"node\":{\"id\":\"53\",\"name\":\"Breaking Bad\"}}," +
109+
"{\"cursor\":\"0_42\",\"node\":{\"id\":\"42\",\"name\":\"Hitchhiker's Guide to the Galaxy\"}}" +
110+
"]," +
111+
"\"pageInfo\":{" +
112+
"\"startCursor\":\"0_1\"," +
113+
"\"endCursor\":\"0_42\"," +
114+
"\"hasPreviousPage\":true," +
115+
"\"hasNextPage\":true}" +
116+
"}}"
117+
);
118+
}
119+
86120
@Test // gh-707
87121
void nullValueTreatedAsEmptyConnection() {
88122

@@ -226,4 +260,14 @@ public String cursorAt(Object container, int index) {
226260

227261
}
228262

263+
264+
private record MyConnection<T>(List<MyEdge<T>> edges, MyPageInfo pageInfo) {
265+
}
266+
267+
private record MyEdge<T>(String cursor, T node) {
268+
}
269+
270+
private record MyPageInfo(String startCursor, String endCursor, boolean hasPreviousPage, boolean hasNextPage) {
271+
}
272+
229273
}

0 commit comments

Comments
 (0)