Skip to content

Commit ca90ced

Browse files
committed
Refine null handling in ClientResponseField#toEntity
closes gh-525
1 parent 05939d5 commit ca90ced

File tree

4 files changed

+56
-18
lines changed

4 files changed

+56
-18
lines changed

spring-graphql/src/main/java/org/springframework/graphql/client/ClientResponseField.java

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import java.util.List;
2121

2222
import org.springframework.core.ParameterizedTypeReference;
23+
import org.springframework.graphql.GraphQlResponse;
2324
import org.springframework.graphql.ResponseField;
25+
import org.springframework.lang.Nullable;
2426

2527
/**
2628
* Extends {@link ResponseField} to add options for decoding the field value.
@@ -33,27 +35,41 @@ public interface ClientResponseField extends ResponseField {
3335
/**
3436
* Decode the field to an entity of the given type.
3537
* @param entityType the type to convert to
36-
* @return the decoded entity, never {@code null}
37-
* @throws FieldAccessException if the target field is {@code null} and has
38+
* @return the decoded entity, or {@code null} if the field is {@code null}
39+
* but otherwise there are no errors
40+
* @throws FieldAccessException if the target field is {@code null} and the
41+
* response is not {@link GraphQlResponse#isValid() valid} or the field has
3842
* {@link ResponseField#getErrors() errors}.
3943
*/
44+
@Nullable
4045
<D> D toEntity(Class<D> entityType);
4146

4247
/**
4348
* Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}.
4449
*/
50+
@Nullable
4551
<D> D toEntity(ParameterizedTypeReference<D> entityType);
4652

4753
/**
4854
* Variant of {@link #toEntity(Class)} to decode to a list of entities.
4955
* @param elementType the type of elements in the list
56+
* @return the list of decoded entities, or an empty list if the field is
57+
* {@code null} but otherwise there are no errors
58+
* @throws FieldAccessException if the target field is {@code null} and the
59+
* response is not {@link GraphQlResponse#isValid() valid} or the field has
60+
* {@link ResponseField#getErrors() errors}.
5061
*/
5162
<D> List<D> toEntityList(Class<D> elementType);
5263

53-
/**
54-
* Variant of {@link #toEntity(Class)} to decode to a list of entities.
55-
* @param elementType the type of elements in the list
56-
*/
64+
/**
65+
* Variant of {@link #toEntity(Class)} to decode to a list of entities.
66+
* @param elementType the type of elements in the list
67+
* @return the list of decoded entities, or an empty list if the field is
68+
* {@code null} but otherwise there are no errors
69+
* @throws FieldAccessException if the target field is {@code null} and the
70+
* response is not {@link GraphQlResponse#isValid() valid} or the field has
71+
* {@link ResponseField#getErrors() errors}.
72+
*/
5773
<D> List<D> toEntityList(ParameterizedTypeReference<D> elementType);
5874

5975
}

spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientGraphQlResponse.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,28 @@ public ClientResponseField field(String path) {
6868

6969
@Override
7070
public <D> D toEntity(Class<D> type) {
71-
return field("").toEntity(type);
71+
ClientResponseField field = field("");
72+
D entity = field.toEntity(type);
73+
74+
// should never happen because toEntity checks response.isValid
75+
if (entity == null) {
76+
throw new FieldAccessException(getRequest(), this, field);
77+
}
78+
79+
return entity;
7280
}
7381

7482
@Override
7583
public <D> D toEntity(ParameterizedTypeReference<D> type) {
76-
return field("").toEntity(type);
84+
ClientResponseField field = field("");
85+
D entity = field.toEntity(type);
86+
87+
// should never happen because toEntity checks response.isValid
88+
if (entity == null) {
89+
throw new FieldAccessException(getRequest(), this, field);
90+
}
91+
92+
return entity;
7793
}
7894

7995
}

spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientResponseField.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
3030
import org.springframework.graphql.ResponseError;
3131
import org.springframework.graphql.ResponseField;
32+
import org.springframework.lang.Nullable;
3233
import org.springframework.util.MimeType;
3334
import org.springframework.util.MimeTypeUtils;
3435

@@ -98,17 +99,23 @@ public <D> D toEntity(ParameterizedTypeReference<D> entityType) {
9899

99100
@Override
100101
public <D> List<D> toEntityList(Class<D> elementType) {
101-
return toEntity(ResolvableType.forClassWithGenerics(List.class, elementType));
102+
List<D> list = toEntity(ResolvableType.forClassWithGenerics(List.class, elementType));
103+
return (list != null ? list : Collections.emptyList());
102104
}
103105

104106
@Override
105107
public <D> List<D> toEntityList(ParameterizedTypeReference<D> elementType) {
106-
return toEntity(ResolvableType.forClassWithGenerics(List.class, ResolvableType.forType(elementType)));
108+
List<D> list = toEntity(ResolvableType.forClassWithGenerics(List.class, ResolvableType.forType(elementType)));
109+
return (list != null ? list : Collections.emptyList());
107110
}
108111

109-
@SuppressWarnings({"unchecked", "ConstantConditions"})
112+
@SuppressWarnings("unchecked")
113+
@Nullable
110114
private <T> T toEntity(ResolvableType targetType) {
111115
if (getValue() == null) {
116+
if (this.response.isValid() && getErrors().isEmpty()) {
117+
return null;
118+
}
112119
throw new FieldAccessException(this.response.getRequest(), this.response, this);
113120
}
114121

spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,8 @@ protected RetrieveSpecSupport(String path) {
186186
}
187187

188188
/**
189-
* Return the field or {@code null}, but only if the response is valid
190-
* and there are no field errors, or raise {@link FieldAccessException}
191-
* otherwise.
189+
* Return the field or {@code null}, but only if the response is valid and
190+
* there are no field errors. Raise {@link FieldAccessException} otherwise.
192191
* @throws FieldAccessException in case of an invalid response or any
193192
* field error at, above or below the field path
194193
*/
@@ -216,12 +215,12 @@ private static class DefaultRetrieveSpec extends RetrieveSpecSupport implements
216215

217216
@Override
218217
public <D> Mono<D> toEntity(Class<D> entityType) {
219-
return this.responseMono.mapNotNull(this::getValidField).map(field -> field.toEntity(entityType));
218+
return this.responseMono.mapNotNull(this::getValidField).mapNotNull(field -> field.toEntity(entityType));
220219
}
221220

222221
@Override
223222
public <D> Mono<D> toEntity(ParameterizedTypeReference<D> entityType) {
224-
return this.responseMono.mapNotNull(this::getValidField).map(field -> field.toEntity(entityType));
223+
return this.responseMono.mapNotNull(this::getValidField).mapNotNull(field -> field.toEntity(entityType));
225224
}
226225

227226
@Override
@@ -254,12 +253,12 @@ private static class DefaultRetrieveSubscriptionSpec extends RetrieveSpecSupport
254253

255254
@Override
256255
public <D> Flux<D> toEntity(Class<D> entityType) {
257-
return this.responseFlux.mapNotNull(this::getValidField).map(field -> field.toEntity(entityType));
256+
return this.responseFlux.mapNotNull(this::getValidField).mapNotNull(field -> field.toEntity(entityType));
258257
}
259258

260259
@Override
261260
public <D> Flux<D> toEntity(ParameterizedTypeReference<D> entityType) {
262-
return this.responseFlux.mapNotNull(this::getValidField).map(field -> field.toEntity(entityType));
261+
return this.responseFlux.mapNotNull(this::getValidField).mapNotNull(field -> field.toEntity(entityType));
263262
}
264263

265264
@Override

0 commit comments

Comments
 (0)