Skip to content

Commit fff35ea

Browse files
committed
Move object conversion into response object
1 parent 7dc1ebf commit fff35ea

File tree

8 files changed

+129
-96
lines changed

8 files changed

+129
-96
lines changed
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package graphql.kickstart.spring.webclient.boot;
22

3-
public class GraphQLClientException extends RuntimeException {
3+
import lombok.NoArgsConstructor;
44

5+
@NoArgsConstructor
6+
public class GraphQLClientException extends RuntimeException {
57

8+
GraphQLClientException(String message, Throwable cause) {
9+
super(message, cause);
10+
}
611

712
}

graphql-webclient/src/main/java/graphql/kickstart/spring/webclient/boot/GraphQLRequest.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,14 @@
22

33
import org.springframework.http.HttpHeaders;
44

5-
public interface GraphQLRequest<T> {
5+
public interface GraphQLRequest {
66

7-
static GraphQLRequestBuilder<Object> builder() {
8-
return new GraphQLRequestBuilder<>(Object.class);
9-
}
10-
11-
static <T> GraphQLRequestBuilder<T> builder(Class<T> returnType) {
12-
return new GraphQLRequestBuilder<>(returnType);
7+
static GraphQLRequestBuilder builder() {
8+
return new GraphQLRequestBuilder();
139
}
1410

1511
GraphQLRequestBody getRequestBody();
1612

1713
HttpHeaders getHeaders();
1814

19-
Class<T> getReturnType();
20-
2115
}

graphql-webclient/src/main/java/graphql/kickstart/spring/webclient/boot/GraphQLRequestBuilder.java

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,25 @@
1010
import org.springframework.http.HttpHeaders;
1111
import org.springframework.util.StreamUtils;
1212

13-
public class GraphQLRequestBuilder<T> {
13+
public class GraphQLRequestBuilder {
1414

1515
private final HttpHeaders headers = new HttpHeaders();
1616
private final GraphQLRequestBody.GraphQLRequestBodyBuilder bodyBuilder = GraphQLRequestBody.builder();
17-
private final Class<T> returnType;
1817

19-
GraphQLRequestBuilder(Class<T> returnType) {
20-
this.returnType = returnType;
18+
GraphQLRequestBuilder() {
2119
}
2220

23-
public GraphQLRequestBuilder<T> header(String name, String value) {
21+
public GraphQLRequestBuilder header(String name, String value) {
2422
headers.add(name, value);
2523
return this;
2624
}
2725

28-
public GraphQLRequestBuilder<T> header(String name, String... values) {
26+
public GraphQLRequestBuilder header(String name, String... values) {
2927
headers.addAll(name, Arrays.asList(values));
3028
return this;
3129
}
3230

33-
public GraphQLRequestBuilder<T> resource(String resource) {
31+
public GraphQLRequestBuilder resource(String resource) {
3432
return query(loadQuery(resource));
3533
}
3634

@@ -45,23 +43,23 @@ private String loadResource(Resource resource) throws IOException {
4543
}
4644
}
4745

48-
public GraphQLRequestBuilder<T> query(String query) {
46+
public GraphQLRequestBuilder query(String query) {
4947
bodyBuilder.query(query);
5048
return this;
5149
}
5250

53-
public GraphQLRequestBuilder<T> variables(Object variables) {
51+
public GraphQLRequestBuilder variables(Object variables) {
5452
bodyBuilder.variables(variables);
5553
return this;
5654
}
5755

58-
public GraphQLRequestBuilder<T> operationName(String operationName) {
56+
public GraphQLRequestBuilder operationName(String operationName) {
5957
bodyBuilder.operationName(operationName);
6058
return this;
6159
}
6260

63-
public GraphQLRequest<T> build() {
64-
return new GraphQLRequestImpl<>(headers, bodyBuilder.build(), returnType);
61+
public GraphQLRequest build() {
62+
return new GraphQLRequestImpl(headers, bodyBuilder.build());
6563
}
6664

6765
}

graphql-webclient/src/main/java/graphql/kickstart/spring/webclient/boot/GraphQLRequestImpl.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
import org.springframework.http.HttpHeaders;
55

66
@Value
7-
class GraphQLRequestImpl<T> implements GraphQLRequest<T> {
7+
class GraphQLRequestImpl implements GraphQLRequest {
88

99
HttpHeaders headers;
1010
GraphQLRequestBody requestBody;
11-
Class<T> returnType;
1211

1312
}

graphql-webclient/src/main/java/graphql/kickstart/spring/webclient/boot/GraphQLResponse.java

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,86 @@
11
package graphql.kickstart.spring.webclient.boot;
22

3+
import static java.util.Collections.emptyList;
4+
5+
import com.fasterxml.jackson.core.JsonProcessingException;
6+
import com.fasterxml.jackson.databind.JavaType;
7+
import com.fasterxml.jackson.databind.JsonNode;
8+
import com.fasterxml.jackson.databind.ObjectMapper;
9+
import java.util.Collections;
310
import java.util.List;
4-
import java.util.Map;
5-
import java.util.Map.Entry;
11+
import java.util.Optional;
612
import lombok.Data;
713

814
@Data
915
public class GraphQLResponse {
1016

11-
private Map<String, Object> data;
12-
private List<GraphQLError> errors;
17+
private final JsonNode data;
18+
private final List<GraphQLError> errors;
19+
private final String rawResponse;
20+
private final ObjectMapper objectMapper;
1321

14-
Object getFirstObject() {
15-
validateNoErrors();
22+
GraphQLResponse(String rawResponse, ObjectMapper objectMapper) {
23+
this.rawResponse = rawResponse;
24+
this.objectMapper = objectMapper;
1625

17-
if (data != null && !data.isEmpty()) {
18-
return data.entrySet().stream().findFirst().map(Entry::getValue).orElse(null);
26+
JsonNode tree = readTree(rawResponse);
27+
errors = readErrors(tree);
28+
data = tree.get("data");
29+
}
30+
31+
private JsonNode readTree(String rawResponse) {
32+
try {
33+
return objectMapper.readTree(rawResponse);
34+
} catch (JsonProcessingException e) {
35+
throw new GraphQLClientException("Cannot read response '" + rawResponse + "'", e);
36+
}
37+
}
38+
39+
private List<GraphQLError> readErrors(JsonNode tree) {
40+
if (tree.has("errors")) {
41+
return objectMapper.convertValue(tree.get("errors"), constructListType(GraphQLError.class));
42+
}
43+
return emptyList();
44+
}
45+
46+
public <T> T get(String fieldName, Class<T> type) {
47+
if (data != null && data.has(fieldName) && data.get(fieldName) != null) {
48+
return objectMapper.convertValue(data.get(fieldName), type);
1949
}
2050
return null;
2151
}
2252

23-
private void validateNoErrors() {
53+
public <T> T getFirstObject(Class<T> type) {
54+
return getFirstDataEntry().map(it -> objectMapper.convertValue(it, type)).orElse(null);
55+
}
56+
57+
private Optional<JsonNode> getFirstDataEntry() {
58+
if (data != null && !data.isEmpty()) {
59+
return Optional.ofNullable(data.fields().next().getValue());
60+
}
61+
return Optional.empty();
62+
}
63+
64+
public <T> List<T> getList(String fieldName, Class<T> type) {
65+
if (data.has(fieldName) && data.get(fieldName) != null) {
66+
return objectMapper.convertValue(data.get(fieldName), constructListType(type));
67+
}
68+
return emptyList();
69+
}
70+
71+
@SuppressWarnings("unchecked")
72+
public <T> List<T> getFirstList(Class<T> type) {
73+
return getFirstDataEntry()
74+
.map(it -> objectMapper.convertValue(it, constructListType(type)))
75+
.map(List.class::cast)
76+
.orElseGet(Collections::emptyList);
77+
}
78+
79+
private JavaType constructListType(Class<?> type) {
80+
return objectMapper.getTypeFactory().constructCollectionType(List.class, type);
81+
}
82+
83+
public void validateNoErrors() {
2484
if (errors != null && !errors.isEmpty()) {
2585
throw new GraphQLErrorsException(errors);
2686
}

graphql-webclient/src/main/java/graphql/kickstart/spring/webclient/boot/GraphQLWebClient.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,10 @@ static GraphQLWebClient newInstance(WebClient webClient, ObjectMapper objectMapp
1616

1717
<T> Mono<T> post(String resource, Map<String, Object> variables, Class<T> returnType);
1818

19-
Mono<GraphQLResponse> post(GraphQLRequest<?> request);
19+
Mono<GraphQLResponse> post(GraphQLRequest request);
2020

2121
<T> Flux<T> flux(String resource, Class<T> returnType);
2222

2323
<T> Flux<T> flux(String resource, Map<String, Object> variables, Class<T> returnType);
2424

25-
@SuppressWarnings("unchecked")
26-
<T> Flux<T> flux(GraphQLRequest<T> request);
2725
}
Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package graphql.kickstart.spring.webclient.boot;
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
4-
import java.util.List;
54
import java.util.Map;
65
import lombok.RequiredArgsConstructor;
7-
import lombok.SneakyThrows;
86
import lombok.extern.slf4j.Slf4j;
97
import org.springframework.http.MediaType;
108
import org.springframework.web.reactive.function.client.WebClient;
@@ -25,23 +23,22 @@ public <T> Mono<T> post(String resource, Class<T> returnType) {
2523

2624
@Override
2725
public <T> Mono<T> post(String resource, Map<String, Object> variables, Class<T> returnType) {
28-
return execute(resource, variables).map(it -> readValue(it, returnType));
29-
}
30-
31-
@SneakyThrows
32-
private <T> T readValue(Object value, Class<T> returnType) {
33-
log.trace("Read value: {}", value);
34-
return objectMapper.convertValue(value, returnType);
26+
return post(resource, variables)
27+
.flatMap(it -> {
28+
it.validateNoErrors();
29+
return Mono.justOrEmpty(it.getFirstObject(returnType));
30+
});
3531
}
3632

3733
@Override
38-
public Mono<GraphQLResponse> post(GraphQLRequest<?> request) {
39-
WebClient.RequestBodySpec spec = webClient.post()
40-
.contentType(MediaType.APPLICATION_JSON);
41-
request.getHeaders().forEach((header, values) -> spec.header(header, values.toArray(new String[0])));
34+
public Mono<GraphQLResponse> post(GraphQLRequest request) {
35+
WebClient.RequestBodySpec spec = webClient.post().contentType(MediaType.APPLICATION_JSON);
36+
request.getHeaders()
37+
.forEach((header, values) -> spec.header(header, values.toArray(new String[0])));
4238
return spec.bodyValue(request.getRequestBody())
43-
.retrieve()
44-
.bodyToMono(GraphQLResponse.class);
39+
.retrieve()
40+
.bodyToMono(String.class)
41+
.map(it -> new GraphQLResponse(it, objectMapper));
4542
}
4643

4744
@Override
@@ -52,38 +49,17 @@ public <T> Flux<T> flux(String resource, Class<T> returnType) {
5249
@Override
5350
@SuppressWarnings("unchecked")
5451
public <T> Flux<T> flux(String resource, Map<String, Object> variables, Class<T> returnType) {
55-
Mono<Object> responseObject = execute(resource, variables);
56-
57-
return responseObject.map(List.class::cast)
58-
.flatMapMany(Flux::fromIterable)
59-
.map(it -> readValue(it, returnType));
60-
}
61-
62-
@Override
63-
@SuppressWarnings("unchecked")
64-
public <T> Flux<T> flux(GraphQLRequest<T> request) {
65-
Mono<Object> responseObject = execute(request);
66-
return responseObject.map(List.class::cast)
67-
.flatMapMany(Flux::fromIterable)
68-
.map(it -> readValue(it, request.getReturnType()));
52+
return post(resource, variables)
53+
.map(it -> it.getFirstList(returnType))
54+
.flatMapMany(Flux::fromIterable);
6955
}
7056

71-
private Mono<Object> execute(String resource, Map<String, Object> variables) {
72-
GraphQLRequest<?> request = GraphQLRequest.builder(Object.class)
57+
private Mono<GraphQLResponse> post(String resource, Map<String, Object> variables) {
58+
GraphQLRequest request = GraphQLRequest.builder()
7359
.resource(resource)
7460
.variables(variables)
7561
.build();
76-
return execute(request);
77-
}
78-
79-
private Mono<Object> execute(GraphQLRequest<?> request) {
80-
WebClient.RequestBodySpec spec = webClient.post()
81-
.contentType(MediaType.APPLICATION_JSON);
82-
request.getHeaders().forEach((header, values) -> spec.header(header, values.toArray(new String[0])));
83-
return spec.bodyValue(request.getRequestBody())
84-
.retrieve()
85-
.bodyToMono(GraphQLResponse.class)
86-
.flatMap(it -> Mono.justOrEmpty(it.getFirstObject()));
62+
return post(request);
8763
}
8864

8965
}

0 commit comments

Comments
 (0)