Skip to content

Commit 4a14aa7

Browse files
author
James Bodkin
committed
Allow non-nullable types in graphql relay
1 parent 9180a7e commit 4a14aa7

File tree

3 files changed

+88
-5
lines changed

3 files changed

+88
-5
lines changed

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ private static GraphQLObjectType getAsObjectType(@Nullable GraphQLFieldDefinitio
153153
@Nullable
154154
private static GraphQLObjectType getEdgeType(@Nullable GraphQLFieldDefinition field) {
155155
if (getType(field) instanceof GraphQLList listType) {
156-
if (listType.getWrappedType() instanceof GraphQLObjectType type) {
156+
if (getType(listType.getWrappedType()) instanceof GraphQLObjectType type) {
157157
return type;
158158
}
159159
}
@@ -169,6 +169,14 @@ private static GraphQLType getType(@Nullable GraphQLFieldDefinition field) {
169169
return (type instanceof GraphQLNonNull nonNullType) ? nonNullType.getWrappedType() : type;
170170
}
171171

172+
@Nullable
173+
private static GraphQLType getType(@Nullable GraphQLType type) {
174+
if (type == null) {
175+
return null;
176+
}
177+
return (type instanceof GraphQLNonNull nonNullType) ? nonNullType.getWrappedType() : type;
178+
}
179+
172180

173181
/**
174182
* Create a {@code ConnectionTypeVisitor} instance that delegates to the
@@ -185,13 +193,13 @@ public static ConnectionFieldTypeVisitor create(List<ConnectionAdapter> adapters
185193
/**
186194
* {@code DataFetcher} decorator that adapts return values with an adapter.
187195
*/
188-
private record ConnectionDataFetcher(DataFetcher<?> delegate, ConnectionAdapter adapter) implements DataFetcher<Object> {
196+
record ConnectionDataFetcher(DataFetcher<?> delegate, ConnectionAdapter adapter) implements DataFetcher<Object> {
189197

190198
private static final Connection<?> EMPTY_CONNECTION =
191199
new DefaultConnection<>(Collections.emptyList(), new DefaultPageInfo(null, null, false, false));
192200

193201

194-
private ConnectionDataFetcher {
202+
ConnectionDataFetcher {
195203
Assert.notNull(delegate, "DataFetcher delegate is required");
196204
Assert.notNull(adapter, "ConnectionAdapter is required");
197205
}

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

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,44 @@ void connectionTypeWithoutEdgesIsNotDecorated() throws Exception {
205205
assertThat(actual).isSameAs(dataFetcher);
206206
}
207207

208+
@Test
209+
void connectionTypeWithNonNullEdgesIsDecorated() throws Exception {
210+
String schemaContent = """
211+
type Query {
212+
libraries(first: Int, after: String, last: Int, before: String): LibraryConnection!
213+
}
214+
215+
type LibraryConnection {
216+
edges: [LibraryEdge!]!
217+
pageInfo: PageInfo!
218+
}
219+
220+
type LibraryEdge {
221+
node: Library!
222+
cursor: String!
223+
}
224+
225+
type Library {
226+
name: String
227+
}
228+
229+
type PageInfo {
230+
hasPreviousPage: Boolean!
231+
hasNextPage: Boolean!
232+
startCursor: String
233+
endCursor: String
234+
}
235+
""";
236+
237+
FieldCoordinates coordinates = FieldCoordinates.coordinates("Query", "libraries");
238+
DataFetcher<?> dataFetcher = env -> null;
239+
240+
DataFetcher<?> actual =
241+
applyConnectionFieldTypeVisitor(schemaContent, coordinates, dataFetcher);
242+
243+
assertThat(actual).isInstanceOf(ConnectionFieldTypeVisitor.ConnectionDataFetcher.class);
244+
}
245+
208246
private static DataFetcher<?> applyConnectionFieldTypeVisitor(
209247
Object schemaSource, FieldCoordinates coordinates, DataFetcher<?> fetcher) throws Exception {
210248

@@ -291,8 +329,6 @@ public TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node,
291329
}
292330
}
293331

294-
295-
296332
private static class ListConnectionAdapter implements ConnectionAdapter {
297333

298334
private int initialOffset = 0;

spring-graphql/src/test/java/org/springframework/graphql/execution/BatchLoadingTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,43 @@ void batchLoader() {
8888
assertThat(author.getLastName()).isEqualTo("Orwell");
8989
}
9090

91+
@Test
92+
void batchLoaderWithNullValue() {
93+
String document = "{ " +
94+
" booksByCriteria(criteria: {author:\"Orwell\"}) { " +
95+
" author {" +
96+
" firstName, " +
97+
" lastName " +
98+
" }" +
99+
" }" +
100+
"}";
101+
102+
this.registry.forTypePair(Long.class, Author.class)
103+
.registerBatchLoader((ids, env) -> Flux.fromIterable(ids.stream().<Author>map(id -> null).toList()));
104+
105+
TestExecutionGraphQlService service = GraphQlSetup.schemaResource(BookSource.schema)
106+
.queryFetcher("booksByCriteria", env -> {
107+
Map<String, Object> criteria = env.getArgument("criteria");
108+
String authorName = (String) criteria.get("author");
109+
return BookSource.findBooksByAuthor(authorName).stream()
110+
.map(book -> new Book(book.getId(), book.getName(), book.getAuthorId()))
111+
.collect(Collectors.toList());
112+
})
113+
.dataFetcher("Book", "author", env -> {
114+
Book book = env.getSource();
115+
DataLoader<Long, Author> dataLoader = env.getDataLoader(Author.class.getName());
116+
return dataLoader.load(book.getAuthorId());
117+
})
118+
.dataLoaders(this.registry)
119+
.toGraphQlService();
120+
121+
Mono<ExecutionGraphQlResponse> responseMono = service.execute(document);
122+
123+
List<Book> books = ResponseHelper.forResponse(responseMono).toList("booksByCriteria", Book.class);
124+
assertThat(books).hasSize(2);
125+
126+
Author author = books.get(0).getAuthor();
127+
assertThat(author).isNull();
128+
}
129+
91130
}

0 commit comments

Comments
 (0)