Skip to content

Commit 9102bdb

Browse files
committed
Replace isValid with hasValue in ResponseField
hasValue is more meaningful because it means valid and with some value that could be decoded, which in turn allows removing Nullable from toEntity/List. Or if there is no value, getError() can be used to differentiate between a failed field vs a null field declared optional. See gh-10
1 parent 5e95fcf commit 9102bdb

File tree

7 files changed

+51
-75
lines changed

7 files changed

+51
-75
lines changed

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import org.springframework.core.ParameterizedTypeReference;
2121
import org.springframework.graphql.GraphQlRequest;
2222
import org.springframework.graphql.GraphQlResponse;
23-
import org.springframework.lang.Nullable;
2423

2524
/**
2625
* {@link GraphQlResponse} for client use, with further options to handle the
@@ -49,27 +48,25 @@ public interface ClientGraphQlResponse extends GraphQlResponse {
4948
* </pre>
5049
* @param path relative to the "data" key
5150
* @return representation for the field with further options to inspect or
52-
* decode its value; use {@link ResponseField#isValid()} to check if the
53-
* field actually exists, has a value, or field errors
51+
* decode its value; use {@link ResponseField#hasValue()} to check if the
52+
* field actually exists and has a value.
5453
*/
5554
ResponseField field(String path);
5655

5756
/**
5857
* Decode the full response map to the given target type.
5958
* @param type the target class
60-
* @return the decoded value, or {@code null} if the "data" is {@code null}
59+
* @return the decoded value, or never {@code null}
6160
* @throws FieldAccessException if the response is not {@link #isValid() valid}
6261
*/
63-
@Nullable
6462
<D> D toEntity(Class<D> type);
6563

6664
/**
6765
* Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}.
6866
* @param type the target type
69-
* @return the decoded value, or {@code null} if the "data" is {@code null}
67+
* @return the decoded value, or never {@code null}
7068
* @throws FieldAccessException if the response is not {@link #isValid() valid}
7169
*/
72-
@Nullable
7370
<D> D toEntity(ParameterizedTypeReference<D> type);
7471

7572
}

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

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,7 @@ public ResponseField field(String path) {
7575
Object value = getFieldValue(dataPath);
7676
List<GraphQLError> errors = getFieldErrors(dataPath);
7777

78-
return new DefaultField(
79-
path, dataPath, (value != NO_VALUE), (value != NO_VALUE ? value : null), errors);
78+
return new DefaultField(path, dataPath, (value != NO_VALUE ? value : null), errors);
8079
}
8180

8281
@Override
@@ -101,18 +100,14 @@ private class DefaultField implements ResponseField {
101100

102101
private final List<GraphQLError> errors;
103102

104-
private final boolean exists;
105-
106103
@Nullable
107104
private final Object value;
108105

109106
public DefaultField(
110-
String path, List<Object> parsedPath, boolean exists, @Nullable Object value,
111-
List<GraphQLError> errors) {
107+
String path, List<Object> parsedPath, @Nullable Object value, List<GraphQLError> errors) {
112108

113109
this.path = path;
114110
this.parsedPath = parsedPath;
115-
this.exists = exists;
116111
this.value = value;
117112
this.errors = errors;
118113
}
@@ -123,8 +118,8 @@ public String getPath() {
123118
}
124119

125120
@Override
126-
public boolean isValid() {
127-
return (this.exists && (this.value != null || this.errors.isEmpty()));
121+
public boolean hasValue() {
122+
return (this.value != null);
128123
}
129124

130125
@SuppressWarnings("unchecked")
@@ -160,25 +155,18 @@ public <D> D toEntity(ParameterizedTypeReference<D> entityType) {
160155

161156
@Override
162157
public <D> List<D> toEntityList(Class<D> elementType) {
163-
List<D> list = toEntity(ResolvableType.forClassWithGenerics(List.class, elementType));
164-
return (list != null ? list : Collections.emptyList());
158+
return toEntity(ResolvableType.forClassWithGenerics(List.class, elementType));
165159
}
166160

167161
@Override
168162
public <D> List<D> toEntityList(ParameterizedTypeReference<D> elementType) {
169-
List<D> list = toEntity(ResolvableType.forClassWithGenerics(List.class, ResolvableType.forType(elementType)));
170-
return (list != null ? list : Collections.emptyList());
163+
return toEntity(ResolvableType.forClassWithGenerics(List.class, ResolvableType.forType(elementType)));
171164
}
172165

173-
@SuppressWarnings("unchecked")
174-
@Nullable
166+
@SuppressWarnings({"unchecked", "ConstantConditions"})
175167
private <T> T toEntity(ResolvableType targetType) {
176-
if (!isValid()) {
177-
throw new FieldAccessException(request, DefaultClientGraphQlResponse.this, this);
178-
}
179-
180168
if (this.value == null) {
181-
return null;
169+
throw new FieldAccessException(request, DefaultClientGraphQlResponse.this, this);
182170
}
183171

184172
DataBufferFactory bufferFactory = DefaultDataBufferFactory.sharedInstance;

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ protected RetrieveSpecSupport(String path) {
199199

200200
protected ResponseField getField(ClientGraphQlResponse response) {
201201
ResponseField field = response.field(this.path);
202-
if (!field.isValid() || !field.getErrors().isEmpty()) {
202+
if (!field.hasValue() || !field.getErrors().isEmpty()) {
203203
GraphQlRequest request = response.getRequest();
204204
throw new FieldAccessException(request, response, field);
205205
}
@@ -220,12 +220,12 @@ private static class DefaultRetrieveSpec extends RetrieveSpecSupport implements
220220

221221
@Override
222222
public <D> Mono<D> toEntity(Class<D> entityType) {
223-
return this.responseMono.map(this::getField).mapNotNull(field -> field.toEntity(entityType));
223+
return this.responseMono.map(this::getField).map(field -> field.toEntity(entityType));
224224
}
225225

226226
@Override
227227
public <D> Mono<D> toEntity(ParameterizedTypeReference<D> entityType) {
228-
return this.responseMono.map(this::getField).mapNotNull(field -> field.toEntity(entityType));
228+
return this.responseMono.map(this::getField).map(field -> field.toEntity(entityType));
229229
}
230230

231231
@Override
@@ -252,12 +252,12 @@ private static class DefaultRetrieveSubscriptionSpec extends RetrieveSpecSupport
252252

253253
@Override
254254
public <D> Flux<D> toEntity(Class<D> entityType) {
255-
return this.responseFlux.map(this::getField).mapNotNull(field -> field.toEntity(entityType));
255+
return this.responseFlux.map(this::getField).map(field -> field.toEntity(entityType));
256256
}
257257

258258
@Override
259259
public <D> Flux<D> toEntity(ParameterizedTypeReference<D> entityType) {
260-
return this.responseFlux.map(this::getField).mapNotNull(field -> field.toEntity(entityType));
260+
return this.responseFlux.map(this::getField).map(field -> field.toEntity(entityType));
261261
}
262262

263263
@Override

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
import org.springframework.graphql.GraphQlResponse;
2222

2323
/**
24-
* An exception raised on an attempt to decode data from an
25-
* {@link GraphQlResponse#isValid() invalid response} or an
26-
* {@link ResponseField#isValid() invalid field}.
24+
* An exception raised on an attempt to decode data from a
25+
* {@link GraphQlResponse#isValid() failed response} or a field is not present,
26+
* or has no value, checked via {@link ResponseField#hasValue()}.
2727
*
2828
* @author Rossen Stoyanchev
2929
* @since 1.0.0

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

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,9 @@ interface RetrieveSpec {
180180
/**
181181
* Decode the field to an entity of the given type.
182182
* @param entityType the type to convert to
183-
* @return {@code Mono} with the decoded entity, possibly empty if the field
184-
* {@link ResponseField#getValue() value} is {@code null}
185-
* @throws FieldAccessException if the target field is not
186-
* {@link ResponseField#isValid() valid} or has any errors, including
187-
* nested errors.
183+
* @return {@code Mono} with the decoded entity, or a
184+
* {@link FieldAccessException} if the target field is not present or
185+
* has no value, checked via {@link ResponseField#hasValue()}.
188186
*/
189187
<D> Mono<D> toEntity(Class<D> entityType);
190188

@@ -196,12 +194,9 @@ interface RetrieveSpec {
196194
/**
197195
* Decode the field to a list of entities with the given type.
198196
* @param elementType the type of elements in the list
199-
* @return {@code Mono} with the list of decoded entities, possibly an
200-
* empty list if the field {@link ResponseField#getValue() value} is
201-
* {@code null} or empty
202-
* @throws FieldAccessException if the target field is not
203-
* {@link ResponseField#isValid() valid} or has any errors, including
204-
* nested errors.
197+
* @return {@code Mono} with a list of decoded entities, possibly empty, or
198+
* a {@link FieldAccessException} if the target field is not present or
199+
* has no value, checked via {@link ResponseField#hasValue()}; the stream
205200
*/
206201
<D> Mono<List<D>> toEntityList(Class<D> elementType);
207202

@@ -221,11 +216,9 @@ interface RetrieveSubscriptionSpec {
221216
/**
222217
* Decode the field to an entity of the given type.
223218
* @param entityType the type to convert to
224-
* @return {@code Mono} with the decoded entity, possibly empty if the field
225-
* {@link ResponseField#getValue() value} is {@code null}
226-
* @throws FieldAccessException if the target field is not
227-
* {@link ResponseField#isValid() valid} or has any errors, including
228-
* nested errors.
219+
* @return {@code Mono} with the decoded entity, or a
220+
* {@link FieldAccessException} if the target field is not present or
221+
* has no value, checked via {@link ResponseField#hasValue()}.
229222
*/
230223
<D> Flux<D> toEntity(Class<D> entityType);
231224

@@ -237,12 +230,10 @@ interface RetrieveSubscriptionSpec {
237230
/**
238231
* Decode the field to a list of entities with the given type.
239232
* @param elementType the type of elements in the list
240-
* @return {@code Mono} with the list of decoded entities, possibly an
241-
* empty list if the field {@link ResponseField#getValue() value} is
242-
* {@code null} or empty
243-
* @throws FieldAccessException if the target field is not
244-
* {@link ResponseField#isValid() valid} or has any errors, including
245-
* nested errors.
233+
* @return lists of decoded entities, possibly empty, or a
234+
* {@link FieldAccessException} if the target field is not present or
235+
* has no value, checked via {@link ResponseField#hasValue()}; the stream
236+
* may also end with a range of {@link GraphQlTransportException} types.
246237
*/
247238
<D> Flux<List<D>> toEntityList(Class<D> elementType);
248239

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,18 @@
3434
public interface ResponseField {
3535

3636
/**
37-
* Whether the field is valid. A field is invalid if:
37+
* Whether the field is valid and has a value.
3838
* <ul>
39-
* <li>the path doesn't exist
40-
* <li>it is {@code null} AND has a field error
39+
* <li>{@code "true"} means the field is not {@code null} in which case there
40+
* is no field {@link #getError() error}. The field may still be partial and
41+
* have nested, field {@link #getErrors() errors}.
42+
* <li>{@code "false"} means the field is {@code null} or does not exist.
43+
* Check for a field {@link #getError()}, which may be on the field or on a
44+
* parent field. The field may also be {@code null} because it is defined as
45+
* optional in the schema.
4146
* </ul>
42-
* <p>A field that is not {@code null} is valid but may still be partial
43-
* with some fields below it set to {@code null} due to field errors.
44-
* A valid field may be {@code null} if the schema allows it, but in that
45-
* case it will not have any field errors.
4647
*/
47-
boolean isValid();
48+
boolean hasValue();
4849

4950
/**
5051
* Return the path for the field under the "data" key in the response map.
@@ -84,24 +85,23 @@ public interface ResponseField {
8485
/**
8586
* Decode the field to an entity of the given type.
8687
* @param entityType the type to convert to
87-
* @return the decoded entity, possibly {@code null} if the field
88-
* {@link #getValue() value} is {@code null}
89-
* @throws FieldAccessException if "this" field is not {@link #isValid() valid}
88+
* @return the decoded entity, never {@code null}
89+
* @throws FieldAccessException if the target field is not present or
90+
* has no value, checked via {@link #hasValue()}.
9091
*/
91-
@Nullable
9292
<D> D toEntity(Class<D> entityType);
9393

9494
/**
9595
* Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}.
9696
*/
97-
@Nullable
9897
<D> D toEntity(ParameterizedTypeReference<D> entityType);
9998

10099
/**
101100
* Decode the field to a list of entities with the given type.
102101
* @param elementType the type of elements in the list
103102
* @return the decoded list of entities, possibly empty
104-
* @throws FieldAccessException if "this" field is not {@link #isValid() valid}
103+
* @throws FieldAccessException if the target field is not present or
104+
* has no value, checked via {@link #hasValue()}.
105105
*/
106106
<D> List<D> toEntityList(Class<D> elementType);
107107

spring-graphql/src/test/java/org/springframework/graphql/client/GraphQlClientTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ private void testExecuteFailedResponse(String document) {
165165

166166
assertThat(response).isNotNull();
167167
assertThat(response.isValid()).isFalse();
168-
assertThat(response.field("me").isValid()).isFalse();
168+
assertThat(response.field("me").hasValue()).isFalse();
169169

170170
assertThatThrownBy(() -> response.field("me").toEntity(MovieCharacter.class))
171171
.isInstanceOf(FieldAccessException.class);
@@ -187,23 +187,23 @@ void executePartialResponse() {
187187
.isTrue();
188188

189189
ResponseField field = response.field("me");
190-
assertThat(field.isValid()).isTrue();
190+
assertThat(field.hasValue()).isTrue();
191191
assertThat(field.getErrors()).hasSize(1);
192192
assertThat(field.getErrors().get(0).getPath()).containsExactly("me", "name");
193193
assertThat(field.toEntity(MovieCharacter.class))
194194
.as("Decoding with nested field error should not be precluded")
195195
.isNotNull();
196196

197197
ResponseField nameField = response.field("me.name");
198-
assertThat(nameField.isValid()).isFalse();
198+
assertThat(nameField.hasValue()).isFalse();
199199
assertThat(nameField.getError()).isNotNull();
200200
assertThat(nameField.getError().getPath()).containsExactly("me", "name");
201201
assertThatThrownBy(() -> nameField.toEntity(String.class))
202202
.as("Decoding field null with direct field error should be rejected")
203203
.isInstanceOf(FieldAccessException.class);
204204

205205
ResponseField nonExistingField = response.field("me.name.other");
206-
assertThat(nonExistingField.isValid()).isFalse();
206+
assertThat(nonExistingField.hasValue()).isFalse();
207207
assertThat(nameField.getError()).isNotNull();
208208
assertThat(nameField.getError().getPath()).containsExactly("me", "name");
209209
}

0 commit comments

Comments
 (0)