Skip to content

Commit 662df7e

Browse files
authored
Merge pull request #15 from graphql-java-kickstart/object-conversion-in-response
Move object conversion into response object
2 parents 7dc1ebf + 44b37f1 commit 662df7e

File tree

12 files changed

+259
-110
lines changed

12 files changed

+259
-110
lines changed

.github/workflows/pull-request.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
name: "Pull request"
22
on:
3-
push:
4-
branches-ignore:
5-
- master
63
pull_request:
74
types: [opened, synchronize, reopened]
85

README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ When using Maven:
1515
<dependency>
1616
<groupId>com.graphql-java-kickstart</groupId>
1717
<artifactId>graphql-webclient-spring-boot-starter</artifactId>
18-
<version>0.2.0</version>
18+
<version>0.3.0</version>
1919
</dependency>
2020
```
2121

2222
When using gradle:
2323
```groovy
24-
implementation "com.graphql-java-kickstart:graphql-webclient-spring-boot-starter:0.2.0"
24+
implementation "com.graphql-java-kickstart:graphql-webclient-spring-boot-starter:0.3.0"
2525
```
2626

2727
Configure at least the URL of the GraphQL API to consume:
@@ -31,6 +31,27 @@ graphql:
3131
url: https://graphql.github.com/graphql
3232
```
3333
34+
The starter creates a Spring bean of type `GraphQLWebClient` that you can use in your
35+
classes to send queries. A simplified example might look like this:
36+
37+
```java
38+
@Component
39+
class MyClass {
40+
41+
private final GraphQLWebClient graphQLWebClient;
42+
43+
MyClass(GraphQLWebClient graphQLWebClient) {
44+
this.graphQLWebClient = graphQLWebClient;
45+
}
46+
47+
void executeSomeQuery() {
48+
GraphQLRequest request = GraphQLRequest.builder().query("query { hello }").build();
49+
GraphQLResponse response = graphQLWebClient.post(request).block();
50+
String value = response.get("hello", String.class);
51+
}
52+
}
53+
```
54+
3455
### Using latest Snapshots
3556

3657
You can use the latest Snapshots by configuring the Snapshot repository, see https://www.graphql-java-kickstart.com/servlet/#using-the-latest-development-build.

gradle.properties

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version=0.3.1-SNAPSHOT
1+
version=1.0.0-SNAPSHOT
22

33
PROJECT_GROUP = com.graphql-java-kickstart
44
PROJECT_NAME = graphql-spring-webclient
@@ -15,13 +15,13 @@ LIB_PROJECT_REACTOR_VER = 1.0.1
1515

1616
### Test libraries
1717

18-
LIB_GRAPHQL_TOOLS_VER = 6.0.0
19-
LIB_GRAPHQL_SPRING_VER = 7.0.0
20-
LIB_GRAPHQL_JAVA_VER = 14.0
21-
LIB_GRAPHQL_SERVLET_VER = 9.0.1
18+
LIB_GRAPHQL_TOOLS_VER = 6.3.0
19+
LIB_GRAPHQL_SPRING_VER = 8.1.1
20+
LIB_GRAPHQL_JAVA_VER = 15.0
21+
LIB_GRAPHQL_SERVLET_VER = 10.1.0
2222

2323
### Gradle Plugins
2424

25-
LIB_SPRING_BOOT_VER = 2.2.5.RELEASE
25+
LIB_SPRING_BOOT_VER = 2.3.6.RELEASE
2626
LIB_BINTRAY_PLUGIN_VER = 1.8.4
2727
LIB_RELEASE_PLUGIN_VER = 2.8.1

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
public class GraphQLClientException extends RuntimeException {
44

5-
5+
GraphQLClientException(String message, Throwable cause) {
6+
super(message, cause);
7+
}
68

79
}

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: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,91 @@
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;
6-
import lombok.Data;
11+
import java.util.Optional;
12+
import lombok.Getter;
713

8-
@Data
914
public class GraphQLResponse {
1015

11-
private Map<String, Object> data;
12-
private List<GraphQLError> errors;
16+
public static final String ERRORS_FIELD = "errors";
1317

14-
Object getFirstObject() {
15-
validateNoErrors();
18+
private final JsonNode data;
19+
@Getter
20+
private final List<GraphQLError> errors;
21+
private final ObjectMapper objectMapper;
1622

17-
if (data != null && !data.isEmpty()) {
18-
return data.entrySet().stream().findFirst().map(Entry::getValue).orElse(null);
23+
GraphQLResponse(String rawResponse, ObjectMapper objectMapper) {
24+
this.objectMapper = objectMapper;
25+
26+
JsonNode tree = readTree(rawResponse);
27+
errors = readErrors(tree);
28+
data = tree.hasNonNull("data") ? tree.get("data") : null;
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.hasNonNull(ERRORS_FIELD)) {
41+
return convertList(tree.get(ERRORS_FIELD), GraphQLError.class);
42+
}
43+
return emptyList();
44+
}
45+
46+
private <T> List<T> convertList(JsonNode node, Class<T> type) {
47+
return objectMapper.convertValue(node, constructListType(type));
48+
}
49+
50+
private JavaType constructListType(Class<?> type) {
51+
return objectMapper.getTypeFactory().constructCollectionType(List.class, type);
52+
}
53+
54+
public <T> T get(String fieldName, Class<T> type) {
55+
if (data != null && data.hasNonNull(fieldName)) {
56+
return objectMapper.convertValue(data.get(fieldName), type);
1957
}
2058
return null;
2159
}
2260

23-
private void validateNoErrors() {
24-
if (errors != null && !errors.isEmpty()) {
61+
public <T> T getFirst(Class<T> type) {
62+
return getFirstDataEntry().map(it -> objectMapper.convertValue(it, type)).orElse(null);
63+
}
64+
65+
private Optional<JsonNode> getFirstDataEntry() {
66+
if (data != null && !data.isEmpty()) {
67+
return Optional.ofNullable(data.fields().next().getValue());
68+
}
69+
return Optional.empty();
70+
}
71+
72+
public <T> List<T> getList(String fieldName, Class<T> type) {
73+
if (data != null && data.hasNonNull(fieldName)) {
74+
return convertList(data.get(fieldName), type);
75+
}
76+
return emptyList();
77+
}
78+
79+
@SuppressWarnings("unchecked")
80+
public <T> List<T> getFirstList(Class<T> type) {
81+
return getFirstDataEntry()
82+
.map(it -> convertList(it, type))
83+
.map(List.class::cast)
84+
.orElseGet(Collections::emptyList);
85+
}
86+
87+
public void validateNoErrors() {
88+
if (!errors.isEmpty()) {
2589
throw new GraphQLErrorsException(errors);
2690
}
2791
}

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.getFirst(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)