Refine null handling in ClientResponseField#toEntity

closes gh-525
This commit is contained in:
rstoyanchev
2022-11-04 13:53:52 +00:00
parent 05939d5e7e
commit ca90cedf3b
4 changed files with 56 additions and 18 deletions

View File

@@ -20,7 +20,9 @@ package org.springframework.graphql.client;
import java.util.List;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseField;
import org.springframework.lang.Nullable;
/**
* Extends {@link ResponseField} to add options for decoding the field value.
@@ -33,27 +35,41 @@ 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 {@code null} and has
* @return the decoded entity, or {@code null} if the field is {@code null}
* but otherwise there are no errors
* @throws FieldAccessException if the target field is {@code null} and the
* response is not {@link GraphQlResponse#isValid() valid} or the field has
* {@link ResponseField#getErrors() errors}.
*/
@Nullable
<D> D toEntity(Class<D> entityType);
/**
* Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}.
*/
@Nullable
<D> D toEntity(ParameterizedTypeReference<D> entityType);
/**
* Variant of {@link #toEntity(Class)} to decode to a list of entities.
* @param elementType the type of elements in the list
* @return the list of decoded entities, or an empty list if the field is
* {@code null} but otherwise there are no errors
* @throws FieldAccessException if the target field is {@code null} and the
* response is not {@link GraphQlResponse#isValid() valid} or the field has
* {@link ResponseField#getErrors() errors}.
*/
<D> List<D> toEntityList(Class<D> elementType);
/**
* Variant of {@link #toEntity(Class)} to decode to a list of entities.
* @param elementType the type of elements in the list
*/
/**
* Variant of {@link #toEntity(Class)} to decode to a list of entities.
* @param elementType the type of elements in the list
* @return the list of decoded entities, or an empty list if the field is
* {@code null} but otherwise there are no errors
* @throws FieldAccessException if the target field is {@code null} and the
* response is not {@link GraphQlResponse#isValid() valid} or the field has
* {@link ResponseField#getErrors() errors}.
*/
<D> List<D> toEntityList(ParameterizedTypeReference<D> elementType);
}

View File

@@ -68,12 +68,28 @@ final class DefaultClientGraphQlResponse extends ResponseMapGraphQlResponse impl
@Override
public <D> D toEntity(Class<D> type) {
return field("").toEntity(type);
ClientResponseField field = field("");
D entity = field.toEntity(type);
// should never happen because toEntity checks response.isValid
if (entity == null) {
throw new FieldAccessException(getRequest(), this, field);
}
return entity;
}
@Override
public <D> D toEntity(ParameterizedTypeReference<D> type) {
return field("").toEntity(type);
ClientResponseField field = field("");
D entity = field.toEntity(type);
// should never happen because toEntity checks response.isValid
if (entity == null) {
throw new FieldAccessException(getRequest(), this, field);
}
return entity;
}
}

View File

@@ -29,6 +29,7 @@ import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.graphql.ResponseError;
import org.springframework.graphql.ResponseField;
import org.springframework.lang.Nullable;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
@@ -98,17 +99,23 @@ final class DefaultClientResponseField implements ClientResponseField {
@Override
public <D> List<D> toEntityList(Class<D> elementType) {
return toEntity(ResolvableType.forClassWithGenerics(List.class, elementType));
List<D> list = toEntity(ResolvableType.forClassWithGenerics(List.class, elementType));
return (list != null ? list : Collections.emptyList());
}
@Override
public <D> List<D> toEntityList(ParameterizedTypeReference<D> elementType) {
return toEntity(ResolvableType.forClassWithGenerics(List.class, ResolvableType.forType(elementType)));
List<D> list = toEntity(ResolvableType.forClassWithGenerics(List.class, ResolvableType.forType(elementType)));
return (list != null ? list : Collections.emptyList());
}
@SuppressWarnings({"unchecked", "ConstantConditions"})
@SuppressWarnings("unchecked")
@Nullable
private <T> T toEntity(ResolvableType targetType) {
if (getValue() == null) {
if (this.response.isValid() && getErrors().isEmpty()) {
return null;
}
throw new FieldAccessException(this.response.getRequest(), this.response, this);
}

View File

@@ -186,9 +186,8 @@ final class DefaultGraphQlClient implements GraphQlClient {
}
/**
* Return the field or {@code null}, but only if the response is valid
* and there are no field errors, or raise {@link FieldAccessException}
* otherwise.
* Return the field or {@code null}, but only if the response is valid and
* there are no field errors. Raise {@link FieldAccessException} otherwise.
* @throws FieldAccessException in case of an invalid response or any
* field error at, above or below the field path
*/
@@ -216,12 +215,12 @@ final class DefaultGraphQlClient implements GraphQlClient {
@Override
public <D> Mono<D> toEntity(Class<D> entityType) {
return this.responseMono.mapNotNull(this::getValidField).map(field -> field.toEntity(entityType));
return this.responseMono.mapNotNull(this::getValidField).mapNotNull(field -> field.toEntity(entityType));
}
@Override
public <D> Mono<D> toEntity(ParameterizedTypeReference<D> entityType) {
return this.responseMono.mapNotNull(this::getValidField).map(field -> field.toEntity(entityType));
return this.responseMono.mapNotNull(this::getValidField).mapNotNull(field -> field.toEntity(entityType));
}
@Override
@@ -254,12 +253,12 @@ final class DefaultGraphQlClient implements GraphQlClient {
@Override
public <D> Flux<D> toEntity(Class<D> entityType) {
return this.responseFlux.mapNotNull(this::getValidField).map(field -> field.toEntity(entityType));
return this.responseFlux.mapNotNull(this::getValidField).mapNotNull(field -> field.toEntity(entityType));
}
@Override
public <D> Flux<D> toEntity(ParameterizedTypeReference<D> entityType) {
return this.responseFlux.mapNotNull(this::getValidField).map(field -> field.toEntity(entityType));
return this.responseFlux.mapNotNull(this::getValidField).mapNotNull(field -> field.toEntity(entityType));
}
@Override