Improve DataFetcher handling for connection field

The ConnectionDataFetcher now handles a null return value from the
delegate DataFetcher, turning leniently into an empty connection.

Also log a debug message in case of a TrivialDataFetcher mapped to
a connection field, and do not decorate it to begin with.

Closes gh-707
This commit is contained in:
rstoyanchev
2023-05-16 11:39:53 +01:00
parent dceb5781a0
commit c152cd78d9
2 changed files with 66 additions and 4 deletions

View File

@@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletionStage;
import graphql.TrivialDataFetcher;
import graphql.relay.Connection;
import graphql.relay.DefaultConnection;
import graphql.relay.DefaultConnectionCursor;
@@ -40,8 +41,11 @@ import graphql.schema.GraphQLType;
import graphql.schema.GraphQLTypeVisitorStub;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -57,6 +61,9 @@ import org.springframework.util.Assert;
*/
public final class ConnectionFieldTypeVisitor extends GraphQLTypeVisitorStub {
private static Log logger = LogFactory.getLog(ConnectionFieldTypeVisitor.class);
private final ConnectionAdapter adapter;
@@ -79,7 +86,16 @@ public final class ConnectionFieldTypeVisitor extends GraphQLTypeVisitorStub {
}
if (isConnectionField(fieldDefinition)) {
codeRegistry.dataFetcher(parent, fieldDefinition, new ConnectionDataFetcher(dataFetcher, adapter));
if (dataFetcher instanceof TrivialDataFetcher<?>) {
if (logger.isDebugEnabled()) {
logger.debug("Connection field " +
"'" + parent.getName() + ":" + fieldDefinition.getName() + "' " +
"is mapped to trivial data fetcher: " + dataFetcher.getClass().getName());
}
}
else {
codeRegistry.dataFetcher(parent, fieldDefinition, new ConnectionDataFetcher(dataFetcher, adapter));
}
}
return TraversalControl.CONTINUE;
@@ -138,12 +154,14 @@ public final class ConnectionFieldTypeVisitor extends GraphQLTypeVisitorStub {
}
@SuppressWarnings("unchecked")
private <T> Connection<T> adapt(Object container) {
private <T> Connection<T> adapt(@Nullable Object container) {
if (container instanceof Connection<?> connection) {
return (Connection<T>) connection;
}
Collection<T> nodes = this.adapter.getContent(container);
Collection<T> nodes = (container != null ?
this.adapter.getContent(container) : Collections.emptyList());
if (nodes.isEmpty()) {
return (Connection<T>) EMPTY_CONNECTION;
}

View File

@@ -19,6 +19,7 @@ package org.springframework.graphql.data.pagination;
import java.util.Collection;
import java.util.List;
import graphql.schema.PropertyDataFetcher;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
@@ -38,7 +39,7 @@ public class ConnectionFieldTypeVisitorTests {
@Test
void dataFetcherDecoration() {
void paginationDataFetcher() {
String document = BookSource.booksConnectionQuery("");
@@ -73,6 +74,49 @@ public class ConnectionFieldTypeVisitorTests {
);
}
@Test // gh-707
void trivialDataFetcherIsSkipped() {
TestConnectionAdapter adapter = new TestConnectionAdapter();
adapter.setInitialOffset(30);
adapter.setHasNext(true);
Mono<ExecutionGraphQlResponse> response = GraphQlSetup.schemaResource(BookSource.paginationSchema)
.dataFetcher("Query", "books", new PropertyDataFetcher<>("books"))
.typeDefinitionConfigurer(new ConnectionTypeDefinitionConfigurer())
.typeVisitor(ConnectionFieldTypeVisitor.create(List.of(adapter)))
.toGraphQlService()
.execute(TestExecutionRequest.forDocument(BookSource.booksConnectionQuery("")));
ResponseHelper.forResponse(response).assertData("{\"books\":null}");
}
@Test // gh-707
void nullValueTreatedAsEmptyConnection() {
TestConnectionAdapter adapter = new TestConnectionAdapter();
adapter.setInitialOffset(30);
adapter.setHasNext(true);
Mono<ExecutionGraphQlResponse> response = GraphQlSetup.schemaResource(BookSource.paginationSchema)
.dataFetcher("Query", "books", environment -> null)
.typeDefinitionConfigurer(new ConnectionTypeDefinitionConfigurer())
.typeVisitor(ConnectionFieldTypeVisitor.create(List.of(adapter)))
.toGraphQlService()
.execute(TestExecutionRequest.forDocument(BookSource.booksConnectionQuery("")));
ResponseHelper.forResponse(response).assertData(
"{\"books\":{" +
"\"edges\":[]," +
"\"pageInfo\":{" +
"\"startCursor\":null," +
"\"endCursor\":null," +
"\"hasPreviousPage\":false," +
"\"hasNextPage\":false}" +
"}}"
);
}
private static class TestConnectionAdapter implements ConnectionAdapter {