Merge branch '1.0.x'
This commit is contained in:
@@ -78,8 +78,8 @@ public interface GraphQlResponse {
|
||||
* </pre>
|
||||
* @param path relative to the "data" key
|
||||
* @return representation for the field with further options to inspect or
|
||||
* decode its value; use {@link ResponseField#hasValue()} to check if
|
||||
* the field actually exists and has a value.
|
||||
* decode its value; use {@link ResponseField#getValue()} to check if
|
||||
* the field actually has a value.
|
||||
*/
|
||||
ResponseField field(String path);
|
||||
|
||||
|
||||
@@ -37,9 +37,13 @@ public interface ResponseField {
|
||||
* <li>{@code "true"} means the field is not {@code null}, and therefore valid,
|
||||
* although it may be partial with nested field {@link #getErrors() errors}.
|
||||
* <li>{@code "false"} means the field is {@code null} or doesn't exist; use
|
||||
* {@link #getError()} to check if the field is {@code null} due to an error.
|
||||
* {@link #getErrors()} to check for field errors, and
|
||||
* {@link GraphQlResponse#isValid()} to check if the entire response is
|
||||
* valid or not.
|
||||
* </ul>
|
||||
* @deprecated as of 1.0.3 in favor of checking via {@link #getValue()}
|
||||
*/
|
||||
@Deprecated
|
||||
boolean hasValue();
|
||||
|
||||
/**
|
||||
@@ -65,10 +69,10 @@ public interface ResponseField {
|
||||
|
||||
/**
|
||||
* Return the error that provides the reason for a failed field.
|
||||
* <p>When the field <strong>does not</strong> {@link #hasValue() have} a
|
||||
* value, this method looks for the first field error. According to the
|
||||
* GraphQL spec, section 6.4.4, "Handling Field Errors", there should be
|
||||
* only one error per field. The returned field error may be:
|
||||
* <p>When the field is {@code null}, this method looks for the first field
|
||||
* error. According to the GraphQL spec, section 6.4.4, "Handling Field
|
||||
* Errors", there should be only one error per field. The returned field
|
||||
* error may be:
|
||||
* <ul>
|
||||
* <li>on the field
|
||||
* <li>on a parent field, when the field is not present
|
||||
@@ -83,16 +87,51 @@ public interface ResponseField {
|
||||
* partial and contain {@link #getErrors() errors} on nested fields.
|
||||
* @return return the error for this field, or {@code null} if there is no
|
||||
* error with the same path as the field path
|
||||
* @deprecated since 1.0.3 in favor of {@link #getErrors()}
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated
|
||||
ResponseError getError();
|
||||
|
||||
/**
|
||||
* Return all field errors including errors above, at, and below this field.
|
||||
* <p>In practice, when the field <strong>does have</strong> a value, it is
|
||||
* considered valid but possibly partial with nested field errors. When the
|
||||
* field <strong>does not have</strong> a value, there should be only one
|
||||
* field error, and in that case it is better to use {@link #getError()}.
|
||||
* Return all errors that have a path, and it is at above, or below the field path.
|
||||
* <p>According to the GraphQL spec, section 6.4.4, "Handling Field Errors"
|
||||
* if a field has an error it is set to {@code null}. That means a field
|
||||
* has either a value or an error, and there is only one error per field.
|
||||
* <p>Errors may also occur at paths above or below the field path. Consider
|
||||
* the following cases:
|
||||
* <table>
|
||||
* <tr>
|
||||
* <th>Value</th>
|
||||
* <th>Errors</th>
|
||||
* <th>Case</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Non-{@code null}</td>
|
||||
* <td>Empty</td>
|
||||
* <td>Success</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Non-{@code null}</td>
|
||||
* <td>Errors below</td>
|
||||
* <td>Partial with errors on nested fields</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code null}</td>
|
||||
* <td>Error at field</td>
|
||||
* <td>Field failure</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code null}</td>
|
||||
* <td>Error above field</td>
|
||||
* <td>Parent field failure</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code null}</td>
|
||||
* <td>Error below field</td>
|
||||
* <td>Nested field failure bubbles up because field is required</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*/
|
||||
List<ResponseError> getErrors();
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ public interface ClientResponseField extends ResponseField {
|
||||
* Decode the field to an entity of the given type.
|
||||
* @param entityType the type to convert to
|
||||
* @return the decoded entity, never {@code null}
|
||||
* @throws FieldAccessException if the target field is not present or
|
||||
* has no value, checked via {@link #hasValue()}.
|
||||
* @throws FieldAccessException if the target field is {@code null} and has
|
||||
* {@link ResponseField#getErrors() errors}.
|
||||
*/
|
||||
<D> D toEntity(Class<D> entityType);
|
||||
|
||||
@@ -45,16 +45,14 @@ public interface ClientResponseField extends ResponseField {
|
||||
<D> D toEntity(ParameterizedTypeReference<D> entityType);
|
||||
|
||||
/**
|
||||
* Decode the field to a list of entities with the given type.
|
||||
* Variant of {@link #toEntity(Class)} to decode to a list of entities.
|
||||
* @param elementType the type of elements in the list
|
||||
* @return the decoded list of entities, possibly empty
|
||||
* @throws FieldAccessException if the target field is not present or
|
||||
* has no value, checked via {@link #hasValue()}.
|
||||
*/
|
||||
<D> List<D> toEntityList(Class<D> elementType);
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntityList(Class)} with {@link ParameterizedTypeReference}.
|
||||
* Variant of {@link #toEntity(Class)} to decode to a list of entities.
|
||||
* @param elementType the type of elements in the list
|
||||
*/
|
||||
<D> List<D> toEntityList(ParameterizedTypeReference<D> elementType);
|
||||
|
||||
|
||||
@@ -54,9 +54,10 @@ final class DefaultClientResponseField implements ClientResponseField {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public boolean hasValue() {
|
||||
return this.field.hasValue();
|
||||
return (this.field.getValue() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,6 +75,7 @@ final class DefaultClientResponseField implements ClientResponseField {
|
||||
return this.field.getValue();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public ResponseError getError() {
|
||||
return this.field.getError();
|
||||
@@ -106,7 +108,7 @@ final class DefaultClientResponseField implements ClientResponseField {
|
||||
|
||||
@SuppressWarnings({"unchecked", "ConstantConditions"})
|
||||
private <T> T toEntity(ResolvableType targetType) {
|
||||
if (!hasValue()) {
|
||||
if (getValue() == null) {
|
||||
throw new FieldAccessException(this.response.getRequest(), this.response, this);
|
||||
}
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ final class DefaultGraphQlClient implements GraphQlClient {
|
||||
throw new FieldAccessException(
|
||||
((DefaultClientGraphQlResponse) response).getRequest(), response, field);
|
||||
}
|
||||
return (field.hasValue() ? field : null);
|
||||
return (field.getValue() != null ? field : null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,10 +21,9 @@ import org.springframework.graphql.GraphQlResponse;
|
||||
import org.springframework.graphql.ResponseField;
|
||||
|
||||
/**
|
||||
* An exception raised on an attempt to decode data from a
|
||||
* {@link GraphQlResponse#isValid() failed response} or a field is not present,
|
||||
* or has no value, checked via
|
||||
* {@link ResponseField#hasValue()}.
|
||||
* An exception raised when an attempt is made to decode data from a response
|
||||
* that is not {@link GraphQlResponse#isValid() valid} or where field value
|
||||
* is {@code null}, or there field {@link ResponseField#getErrors() errors}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
|
||||
@@ -243,11 +243,11 @@ public interface GraphQlClient {
|
||||
/**
|
||||
* Decode the field to an entity of the given type.
|
||||
* @param entityType the type to convert to
|
||||
* @return {@code Mono} with the decoded entity. Completes empty when
|
||||
* the field is {@code null} without errors, or ends with
|
||||
* {@link FieldAccessException} for an invalid response or a failed field
|
||||
* @see GraphQlResponse#isValid()
|
||||
* @see ResponseField#getError()
|
||||
* @return {@code Mono} with the decoded entity; completes with
|
||||
* {@link FieldAccessException} in case of {@link ResponseField field
|
||||
* errors} or an {@link GraphQlResponse#isValid() invalid} response;
|
||||
* completes empty if the field is {@code null} but has no errors.
|
||||
* @see ResponseField#getErrors()
|
||||
*/
|
||||
<D> Mono<D> toEntity(Class<D> entityType);
|
||||
|
||||
@@ -257,18 +257,14 @@ public interface GraphQlClient {
|
||||
<D> Mono<D> toEntity(ParameterizedTypeReference<D> entityType);
|
||||
|
||||
/**
|
||||
* Decode the field to a list of entities with the given type.
|
||||
* Variant of {@link #toEntity(Class)} to decode to a List of entities.
|
||||
* @param elementType the type of elements in the list
|
||||
* @return {@code Mono} with a list of decoded entities, possibly an
|
||||
* empty list, or ends with {@link FieldAccessException} if the target
|
||||
* field is not present or has no value.
|
||||
* @see GraphQlResponse#isValid()
|
||||
* @see ResponseField#getError()
|
||||
*/
|
||||
<D> Mono<List<D>> toEntityList(Class<D> elementType);
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntityList(Class)} with {@link ParameterizedTypeReference}.
|
||||
* Variant of {@link #toEntity(Class)} to decode to a List of entities.
|
||||
* @param elementType the type of elements in the list
|
||||
*/
|
||||
<D> Mono<List<D>> toEntityList(ParameterizedTypeReference<D> elementType);
|
||||
|
||||
@@ -283,12 +279,11 @@ public interface GraphQlClient {
|
||||
/**
|
||||
* Decode the field to an entity of the given type.
|
||||
* @param entityType the type to convert to
|
||||
* @return a stream of decoded entities, one for each response, excluding
|
||||
* responses in which the field is {@code null} without errors. Ends with
|
||||
* {@link FieldAccessException} for an invalid response or a failed field.
|
||||
* May also end with a {@link GraphQlTransportException}.
|
||||
* @see GraphQlResponse#isValid()
|
||||
* @see ResponseField#getError()
|
||||
* @return {@code Mono} with the decoded entity; completes with
|
||||
* {@link FieldAccessException} in case of {@link ResponseField field
|
||||
* errors} or an {@link GraphQlResponse#isValid() invalid} response;
|
||||
* completes empty if the field is {@code null} but has no errors.
|
||||
* @see ResponseField#getErrors()
|
||||
*/
|
||||
<D> Flux<D> toEntity(Class<D> entityType);
|
||||
|
||||
@@ -298,19 +293,13 @@ public interface GraphQlClient {
|
||||
<D> Flux<D> toEntity(ParameterizedTypeReference<D> entityType);
|
||||
|
||||
/**
|
||||
* Decode the field to a list of entities with the given type.
|
||||
* Variant of {@link #toEntity(Class)} to decode each response to a List of entities.
|
||||
* @param elementType the type of elements in the list
|
||||
* @return lists of decoded entities, one for each response, excluding
|
||||
* responses in which the field is {@code null} without errors. Ends with
|
||||
* {@link FieldAccessException} for an invalid response or a failed field.
|
||||
* May also end with a {@link GraphQlTransportException}.
|
||||
* @see GraphQlResponse#isValid()
|
||||
* @see ResponseField#getError()
|
||||
*/
|
||||
<D> Flux<List<D>> toEntityList(Class<D> elementType);
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntityList(Class)} with {@link ParameterizedTypeReference}.
|
||||
* Variant of {@link #toEntity(Class)} to decode each response to a List of entities.
|
||||
*/
|
||||
<D> Flux<List<D>> toEntityList(ParameterizedTypeReference<D> elementType);
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ public abstract class AbstractGraphQlResponse implements GraphQlResponse {
|
||||
return this.parsedPath;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public boolean hasValue() {
|
||||
return (this.value != null);
|
||||
@@ -171,9 +172,10 @@ public abstract class AbstractGraphQlResponse implements GraphQlResponse {
|
||||
return (T) this.value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public ResponseError getError() {
|
||||
if (!hasValue()) {
|
||||
if (getValue() != null) {
|
||||
if (!this.fieldErrors.isEmpty()) {
|
||||
return this.fieldErrors.get(0);
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ public class GraphQlClientTests extends GraphQlClientTestSupport {
|
||||
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.isValid()).isFalse();
|
||||
assertThat(response.field("me").hasValue()).isFalse();
|
||||
assertThat(response.field("me")).isNotNull();
|
||||
|
||||
assertThatThrownBy(() -> response.field("me").toEntity(MovieCharacter.class))
|
||||
.isInstanceOf(FieldAccessException.class);
|
||||
@@ -201,26 +201,27 @@ public class GraphQlClientTests extends GraphQlClientTestSupport {
|
||||
.isTrue();
|
||||
|
||||
ClientResponseField field = response.field("me");
|
||||
assertThat(field.hasValue()).isTrue();
|
||||
assertThat((Object) field.getValue()).isNotNull();
|
||||
assertThat(field.getErrors()).hasSize(1);
|
||||
assertThat(field.getErrors().get(0).getParsedPath()).containsExactly("me", "name");
|
||||
assertThat(field.toEntity(MovieCharacter.class))
|
||||
.as("Decoding with nested field error should not be precluded")
|
||||
.isNotNull();
|
||||
|
||||
ClientResponseField nameField = response.field("me.name");
|
||||
assertThat(nameField.hasValue()).isFalse();
|
||||
assertThat(nameField.getError()).isNotNull();
|
||||
assertThat(nameField.getError().getParsedPath()).containsExactly("me", "name");
|
||||
assertThatThrownBy(() -> nameField.toEntity(String.class))
|
||||
field = response.field("me.name");
|
||||
assertThat((Object) field.getValue()).isNull();
|
||||
assertThat(field.getErrors()).isNotEmpty();
|
||||
assertThat(field.getErrors().get(0).getParsedPath()).containsExactly("me", "name");
|
||||
ClientResponseField theField = field;
|
||||
assertThatThrownBy(() -> theField.toEntity(String.class))
|
||||
.as("Decoding field null with direct field error should be rejected")
|
||||
.isInstanceOf(FieldAccessException.class)
|
||||
.hasMessageContaining("Test error");
|
||||
|
||||
ClientResponseField nonExistingField = response.field("me.name.other");
|
||||
assertThat(nonExistingField.hasValue()).isFalse();
|
||||
assertThat(nameField.getError()).isNotNull();
|
||||
assertThat(nameField.getError().getParsedPath()).containsExactly("me", "name");
|
||||
field = response.field("me.name.other");
|
||||
assertThat((Object) field.getValue()).isNull();
|
||||
assertThat(field.getErrors()).isNotEmpty();
|
||||
assertThat(field.getErrors().get(0).getParsedPath()).containsExactly("me", "name");
|
||||
}
|
||||
|
||||
private GraphQLError errorForPath(String errorPath) {
|
||||
|
||||
Reference in New Issue
Block a user