Skip to content

Commit 5905f02

Browse files
committed
Deprecate ResponseField getError and hasValue
See gh-499
1 parent 6004937 commit 5905f02

File tree

9 files changed

+94
-64
lines changed

9 files changed

+94
-64
lines changed

spring-graphql/src/main/java/org/springframework/graphql/GraphQlResponse.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ public interface GraphQlResponse {
7878
* </pre>
7979
* @param path relative to the "data" key
8080
* @return representation for the field with further options to inspect or
81-
* decode its value; use {@link ResponseField#hasValue()} to check if
82-
* the field actually exists and has a value.
81+
* decode its value; use {@link ResponseField#getValue()} to check if
82+
* the field actually has a value.
8383
*/
8484
ResponseField field(String path);
8585

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

+49-10
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,13 @@ public interface ResponseField {
3737
* <li>{@code "true"} means the field is not {@code null}, and therefore valid,
3838
* although it may be partial with nested field {@link #getErrors() errors}.
3939
* <li>{@code "false"} means the field is {@code null} or doesn't exist; use
40-
* {@link #getError()} to check if the field is {@code null} due to an error.
40+
* {@link #getErrors()} to check for field errors, and
41+
* {@link GraphQlResponse#isValid()} to check if the entire response is
42+
* valid or not.
4143
* </ul>
44+
* @deprecated as of 1.0.3 in favor of checking via {@link #getValue()}
4245
*/
46+
@Deprecated
4347
boolean hasValue();
4448

4549
/**
@@ -65,10 +69,10 @@ public interface ResponseField {
6569

6670
/**
6771
* Return the error that provides the reason for a failed field.
68-
* <p>When the field <strong>does not</strong> {@link #hasValue() have} a
69-
* value, this method looks for the first field error. According to the
70-
* GraphQL spec, section 6.4.4, "Handling Field Errors", there should be
71-
* only one error per field. The returned field error may be:
72+
* <p>When the field is {@code null}, this method looks for the first field
73+
* error. According to the GraphQL spec, section 6.4.4, "Handling Field
74+
* Errors", there should be only one error per field. The returned field
75+
* error may be:
7276
* <ul>
7377
* <li>on the field
7478
* <li>on a parent field, when the field is not present
@@ -83,16 +87,51 @@ public interface ResponseField {
8387
* partial and contain {@link #getErrors() errors} on nested fields.
8488
* @return return the error for this field, or {@code null} if there is no
8589
* error with the same path as the field path
90+
* @deprecated since 1.0.3 in favor of {@link #getErrors()}
8691
*/
8792
@Nullable
93+
@Deprecated
8894
ResponseError getError();
8995

9096
/**
91-
* Return all field errors including errors above, at, and below this field.
92-
* <p>In practice, when the field <strong>does have</strong> a value, it is
93-
* considered valid but possibly partial with nested field errors. When the
94-
* field <strong>does not have</strong> a value, there should be only one
95-
* field error, and in that case it is better to use {@link #getError()}.
97+
* Return all errors that have a path, and it is at above, or below the field path.
98+
* <p>According to the GraphQL spec, section 6.4.4, "Handling Field Errors"
99+
* if a field has an error it is set to {@code null}. That means a field
100+
* has either a value or an error, and there is only one error per field.
101+
* <p>Errors may also occur at paths above or below the field path. Consider
102+
* the following cases:
103+
* <table>
104+
* <tr>
105+
* <th>Value</th>
106+
* <th>Errors</th>
107+
* <th>Case</th>
108+
* </tr>
109+
* <tr>
110+
* <td>Non-{@code null}</td>
111+
* <td>Empty</td>
112+
* <td>Success</td>
113+
* </tr>
114+
* <tr>
115+
* <td>Non-{@code null}</td>
116+
* <td>Errors below</td>
117+
* <td>Partial with errors on nested fields</td>
118+
* </tr>
119+
* <tr>
120+
* <td>{@code null}</td>
121+
* <td>Error at field</td>
122+
* <td>Field failure</td>
123+
* </tr>
124+
* <tr>
125+
* <td>{@code null}</td>
126+
* <td>Error above field</td>
127+
* <td>Parent field failure</td>
128+
* </tr>
129+
* <tr>
130+
* <td>{@code null}</td>
131+
* <td>Error below field</td>
132+
* <td>Nested field failure bubbles up because field is required</td>
133+
* </tr>
134+
* </table>
96135
*/
97136
List<ResponseError> getErrors();
98137

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

+5-7
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ public interface ClientResponseField extends ResponseField {
3434
* Decode the field to an entity of the given type.
3535
* @param entityType the type to convert to
3636
* @return the decoded entity, never {@code null}
37-
* @throws FieldAccessException if the target field is not present or
38-
* has no value, checked via {@link #hasValue()}.
37+
* @throws FieldAccessException if the target field is {@code null} and has
38+
* {@link ResponseField#getErrors() errors}.
3939
*/
4040
<D> D toEntity(Class<D> entityType);
4141

@@ -45,16 +45,14 @@ public interface ClientResponseField extends ResponseField {
4545
<D> D toEntity(ParameterizedTypeReference<D> entityType);
4646

4747
/**
48-
* Decode the field to a list of entities with the given type.
48+
* Variant of {@link #toEntity(Class)} to decode to a list of entities.
4949
* @param elementType the type of elements in the list
50-
* @return the decoded list of entities, possibly empty
51-
* @throws FieldAccessException if the target field is not present or
52-
* has no value, checked via {@link #hasValue()}.
5350
*/
5451
<D> List<D> toEntityList(Class<D> elementType);
5552

5653
/**
57-
* Variant of {@link #toEntityList(Class)} with {@link ParameterizedTypeReference}.
54+
* Variant of {@link #toEntity(Class)} to decode to a list of entities.
55+
* @param elementType the type of elements in the list
5856
*/
5957
<D> List<D> toEntityList(ParameterizedTypeReference<D> elementType);
6058

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ final class DefaultClientResponseField implements ClientResponseField {
5454
}
5555

5656

57+
@SuppressWarnings("deprecation")
5758
@Override
5859
public boolean hasValue() {
59-
return this.field.hasValue();
60+
return (this.field.getValue() != null);
6061
}
6162

6263
@Override
@@ -74,6 +75,7 @@ public <T> T getValue() {
7475
return this.field.getValue();
7576
}
7677

78+
@SuppressWarnings("deprecation")
7779
@Override
7880
public ResponseError getError() {
7981
return this.field.getError();
@@ -106,7 +108,7 @@ public <D> List<D> toEntityList(ParameterizedTypeReference<D> elementType) {
106108

107109
@SuppressWarnings({"unchecked", "ConstantConditions"})
108110
private <T> T toEntity(ResolvableType targetType) {
109-
if (!hasValue()) {
111+
if (getValue() == null) {
110112
throw new FieldAccessException(this.response.getRequest(), this.response, this);
111113
}
112114

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ protected ClientResponseField getValidField(ClientGraphQlResponse response) {
199199
throw new FieldAccessException(
200200
((DefaultClientGraphQlResponse) response).getRequest(), response, field);
201201
}
202-
return (field.hasValue() ? field : null);
202+
return (field.getValue() != null ? field : null);
203203
}
204204

205205
}

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

+3-4
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@
2121
import org.springframework.graphql.ResponseField;
2222

2323
/**
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
27-
* {@link ResponseField#hasValue()}.
24+
* An exception raised when an attempt is made to decode data from a response
25+
* that is not {@link GraphQlResponse#isValid() valid} or where field value
26+
* is {@code null}, or there field {@link ResponseField#getErrors() errors}.
2827
*
2928
* @author Rossen Stoyanchev
3029
* @since 1.0.0

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

+15-26
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,11 @@ interface RetrieveSpec {
243243
/**
244244
* Decode the field to an entity of the given type.
245245
* @param entityType the type to convert to
246-
* @return {@code Mono} with the decoded entity. Completes empty when
247-
* the field is {@code null} without errors, or ends with
248-
* {@link FieldAccessException} for an invalid response or a failed field
249-
* @see GraphQlResponse#isValid()
250-
* @see ResponseField#getError()
246+
* @return {@code Mono} with the decoded entity; completes with
247+
* {@link FieldAccessException} in case of {@link ResponseField field
248+
* errors} or an {@link GraphQlResponse#isValid() invalid} response;
249+
* completes empty if the field is {@code null} but has no errors.
250+
* @see ResponseField#getErrors()
251251
*/
252252
<D> Mono<D> toEntity(Class<D> entityType);
253253

@@ -257,18 +257,14 @@ interface RetrieveSpec {
257257
<D> Mono<D> toEntity(ParameterizedTypeReference<D> entityType);
258258

259259
/**
260-
* Decode the field to a list of entities with the given type.
260+
* Variant of {@link #toEntity(Class)} to decode to a List of entities.
261261
* @param elementType the type of elements in the list
262-
* @return {@code Mono} with a list of decoded entities, possibly an
263-
* empty list, or ends with {@link FieldAccessException} if the target
264-
* field is not present or has no value.
265-
* @see GraphQlResponse#isValid()
266-
* @see ResponseField#getError()
267262
*/
268263
<D> Mono<List<D>> toEntityList(Class<D> elementType);
269264

270265
/**
271-
* Variant of {@link #toEntityList(Class)} with {@link ParameterizedTypeReference}.
266+
* Variant of {@link #toEntity(Class)} to decode to a List of entities.
267+
* @param elementType the type of elements in the list
272268
*/
273269
<D> Mono<List<D>> toEntityList(ParameterizedTypeReference<D> elementType);
274270

@@ -283,12 +279,11 @@ interface RetrieveSubscriptionSpec {
283279
/**
284280
* Decode the field to an entity of the given type.
285281
* @param entityType the type to convert to
286-
* @return a stream of decoded entities, one for each response, excluding
287-
* responses in which the field is {@code null} without errors. Ends with
288-
* {@link FieldAccessException} for an invalid response or a failed field.
289-
* May also end with a {@link GraphQlTransportException}.
290-
* @see GraphQlResponse#isValid()
291-
* @see ResponseField#getError()
282+
* @return {@code Mono} with the decoded entity; completes with
283+
* {@link FieldAccessException} in case of {@link ResponseField field
284+
* errors} or an {@link GraphQlResponse#isValid() invalid} response;
285+
* completes empty if the field is {@code null} but has no errors.
286+
* @see ResponseField#getErrors()
292287
*/
293288
<D> Flux<D> toEntity(Class<D> entityType);
294289

@@ -298,19 +293,13 @@ interface RetrieveSubscriptionSpec {
298293
<D> Flux<D> toEntity(ParameterizedTypeReference<D> entityType);
299294

300295
/**
301-
* Decode the field to a list of entities with the given type.
296+
* Variant of {@link #toEntity(Class)} to decode each response to a List of entities.
302297
* @param elementType the type of elements in the list
303-
* @return lists of decoded entities, one for each response, excluding
304-
* responses in which the field is {@code null} without errors. Ends with
305-
* {@link FieldAccessException} for an invalid response or a failed field.
306-
* May also end with a {@link GraphQlTransportException}.
307-
* @see GraphQlResponse#isValid()
308-
* @see ResponseField#getError()
309298
*/
310299
<D> Flux<List<D>> toEntityList(Class<D> elementType);
311300

312301
/**
313-
* Variant of {@link #toEntityList(Class)} with {@link ParameterizedTypeReference}.
302+
* Variant of {@link #toEntity(Class)} to decode each response to a List of entities.
314303
*/
315304
<D> Flux<List<D>> toEntityList(ParameterizedTypeReference<D> elementType);
316305

spring-graphql/src/main/java/org/springframework/graphql/support/AbstractGraphQlResponse.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ public List<Object> getParsedPath() {
160160
return this.parsedPath;
161161
}
162162

163+
@SuppressWarnings("deprecation")
163164
@Override
164165
public boolean hasValue() {
165166
return (this.value != null);
@@ -171,9 +172,10 @@ public <T> T getValue() {
171172
return (T) this.value;
172173
}
173174

175+
@SuppressWarnings("deprecation")
174176
@Override
175177
public ResponseError getError() {
176-
if (!hasValue()) {
178+
if (getValue() != null) {
177179
if (!this.fieldErrors.isEmpty()) {
178180
return this.fieldErrors.get(0);
179181
}

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

+12-11
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ private void testExecuteFailedResponse(String document) {
179179

180180
assertThat(response).isNotNull();
181181
assertThat(response.isValid()).isFalse();
182-
assertThat(response.field("me").hasValue()).isFalse();
182+
assertThat(response.field("me")).isNotNull();
183183

184184
assertThatThrownBy(() -> response.field("me").toEntity(MovieCharacter.class))
185185
.isInstanceOf(FieldAccessException.class);
@@ -201,26 +201,27 @@ void executePartialResponse() {
201201
.isTrue();
202202

203203
ClientResponseField field = response.field("me");
204-
assertThat(field.hasValue()).isTrue();
204+
assertThat((Object) field.getValue()).isNotNull();
205205
assertThat(field.getErrors()).hasSize(1);
206206
assertThat(field.getErrors().get(0).getParsedPath()).containsExactly("me", "name");
207207
assertThat(field.toEntity(MovieCharacter.class))
208208
.as("Decoding with nested field error should not be precluded")
209209
.isNotNull();
210210

211-
ClientResponseField nameField = response.field("me.name");
212-
assertThat(nameField.hasValue()).isFalse();
213-
assertThat(nameField.getError()).isNotNull();
214-
assertThat(nameField.getError().getParsedPath()).containsExactly("me", "name");
215-
assertThatThrownBy(() -> nameField.toEntity(String.class))
211+
field = response.field("me.name");
212+
assertThat((Object) field.getValue()).isNull();
213+
assertThat(field.getErrors()).isNotEmpty();
214+
assertThat(field.getErrors().get(0).getParsedPath()).containsExactly("me", "name");
215+
ClientResponseField theField = field;
216+
assertThatThrownBy(() -> theField.toEntity(String.class))
216217
.as("Decoding field null with direct field error should be rejected")
217218
.isInstanceOf(FieldAccessException.class)
218219
.hasMessageContaining("Test error");
219220

220-
ClientResponseField nonExistingField = response.field("me.name.other");
221-
assertThat(nonExistingField.hasValue()).isFalse();
222-
assertThat(nameField.getError()).isNotNull();
223-
assertThat(nameField.getError().getParsedPath()).containsExactly("me", "name");
221+
field = response.field("me.name.other");
222+
assertThat((Object) field.getValue()).isNull();
223+
assertThat(field.getErrors()).isNotEmpty();
224+
assertThat(field.getErrors().get(0).getParsedPath()).containsExactly("me", "name");
224225
}
225226

226227
private GraphQLError errorForPath(String errorPath) {

0 commit comments

Comments
 (0)