Skip to content

Commit 1523807

Browse files
committed
Introduce GraphQlResponse
Replace the use of ExecutionResult on the client side where we are dealing with a response map rather, and also incorporate it into the server-side hierarchy where it wraps an ExecutionResult instead. See gh-10
1 parent 77e9835 commit 1523807

37 files changed

+438
-344
lines changed

Diff for: spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/AbstractDirectTransport.java

+9-7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import reactor.core.publisher.Mono;
2626

2727
import org.springframework.graphql.GraphQlRequest;
28+
import org.springframework.graphql.GraphQlResponse;
2829
import org.springframework.graphql.RequestOutput;
2930
import org.springframework.graphql.client.GraphQlTransport;
3031
import org.springframework.test.util.AssertionErrors;
@@ -45,22 +46,23 @@ abstract class AbstractDirectTransport implements GraphQlTransport {
4546

4647

4748
@Override
48-
public Mono<ExecutionResult> execute(GraphQlRequest request) {
49-
return executeInternal(request).cast(ExecutionResult.class);
49+
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
50+
return executeInternal(request).cast(GraphQlResponse.class);
5051
}
5152

5253
@SuppressWarnings({"ConstantConditions", "unchecked"})
5354
@Override
54-
public Flux<ExecutionResult> executeSubscription(GraphQlRequest request) {
55-
return executeInternal(request).flatMapMany(result -> {
55+
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
56+
return executeInternal(request).flatMapMany(output -> {
5657
try {
57-
Object data = result.getData();
58+
Object data = output.getData();
5859
AssertionErrors.assertTrue("Not a Publisher: " + data, data instanceof Publisher);
5960

60-
List<GraphQLError> errors = result.getErrors();
61+
List<GraphQLError> errors = output.getErrors();
6162
AssertionErrors.assertTrue("Subscription errors: " + errors, CollectionUtils.isEmpty(errors));
6263

63-
return Flux.from((Publisher<ExecutionResult>) data);
64+
return Flux.from((Publisher<ExecutionResult>) data)
65+
.map(result -> new RequestOutput(output.getExecutionInput(), result));
6466
}
6567
catch (AssertionError ex) {
6668
throw new AssertionError(ex.getMessage() + "\nRequest: " + request, ex);

Diff for: spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultGraphQlTester.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@
3131
import com.jayway.jsonpath.DocumentContext;
3232
import com.jayway.jsonpath.JsonPath;
3333
import com.jayway.jsonpath.TypeRef;
34-
import graphql.ExecutionResult;
3534
import graphql.GraphQLError;
3635

3736
import org.springframework.core.ParameterizedTypeReference;
3837
import org.springframework.core.ResolvableType;
3938
import org.springframework.graphql.GraphQlRequest;
39+
import org.springframework.graphql.GraphQlResponse;
4040
import org.springframework.graphql.client.GraphQlTransport;
4141
import org.springframework.graphql.support.DocumentSource;
4242
import org.springframework.lang.Nullable;
@@ -165,7 +165,7 @@ public DefaultRequest variable(String name, @Nullable Object value) {
165165
@SuppressWarnings("ConstantConditions")
166166
@Override
167167
public Response execute() {
168-
return transport.execute(request()).map(result -> response(result, request())).block(responseTimeout);
168+
return transport.execute(request()).map(response -> mapResponse(response, request())).block(responseTimeout);
169169
}
170170

171171
@Override
@@ -175,15 +175,15 @@ public void executeAndVerify() {
175175

176176
@Override
177177
public Subscription executeSubscription() {
178-
return () -> transport.executeSubscription(request()).map(result -> response(result, request()));
178+
return () -> transport.executeSubscription(request()).map(result -> mapResponse(result, request()));
179179
}
180180

181181
private GraphQlRequest request() {
182182
return new GraphQlRequest(this.document, this.operationName, this.variables);
183183
}
184184

185-
private DefaultResponse response(ExecutionResult result, GraphQlRequest request) {
186-
return new DefaultResponse(result, errorFilter, assertDecorator(request), jsonPathConfig);
185+
private DefaultResponse mapResponse(GraphQlResponse response, GraphQlRequest request) {
186+
return new DefaultResponse(response, errorFilter, assertDecorator(request), jsonPathConfig);
187187
}
188188

189189
private Consumer<Runnable> assertDecorator(GraphQlRequest request) {
@@ -217,12 +217,12 @@ private final static class ResponseDelegate {
217217

218218

219219
private ResponseDelegate(
220-
ExecutionResult result, @Nullable Predicate<GraphQLError> errorFilter,
220+
GraphQlResponse response, @Nullable Predicate<GraphQLError> errorFilter,
221221
Consumer<Runnable> assertDecorator, Configuration jsonPathConfig) {
222222

223-
this.jsonDoc = JsonPath.parse(result.toSpecification(), jsonPathConfig);
223+
this.jsonDoc = JsonPath.parse(response.toMap(), jsonPathConfig);
224224
this.jsonContent = this.jsonDoc::jsonString;
225-
this.errors = result.getErrors();
225+
this.errors = response.getErrors();
226226
this.unexpectedErrors = new ArrayList<>(this.errors);
227227
this.assertDecorator = assertDecorator;
228228

@@ -293,10 +293,10 @@ private static final class DefaultResponse implements Response, Errors {
293293
private final ResponseDelegate delegate;
294294

295295
private DefaultResponse(
296-
ExecutionResult result, @Nullable Predicate<GraphQLError> errorFilter,
296+
GraphQlResponse response, @Nullable Predicate<GraphQLError> errorFilter,
297297
Consumer<Runnable> assertDecorator, Configuration jsonPathConfig) {
298298

299-
this.delegate = new ResponseDelegate(result, errorFilter, assertDecorator, jsonPathConfig);
299+
this.delegate = new ResponseDelegate(response, errorFilter, assertDecorator, jsonPathConfig);
300300
}
301301

302302
@Override

Diff for: spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultWebSocketGraphQlTester.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@
2020
import java.net.URI;
2121
import java.util.function.Consumer;
2222

23-
import graphql.ExecutionResult;
2423
import reactor.core.publisher.Flux;
2524
import reactor.core.publisher.Mono;
2625

2726
import org.springframework.graphql.GraphQlRequest;
27+
import org.springframework.graphql.GraphQlResponse;
2828
import org.springframework.graphql.client.CodecMappingProvider;
2929
import org.springframework.graphql.client.GraphQlClient;
3030
import org.springframework.graphql.client.GraphQlTransport;
@@ -164,7 +164,7 @@ private static GraphQlTransport asTransport(GraphQlClient client) {
164164
return new GraphQlTransport() {
165165

166166
@Override
167-
public Mono<ExecutionResult> execute(GraphQlRequest request) {
167+
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
168168
return client
169169
.document(request.getDocument())
170170
.operationName(request.getOperationName())
@@ -174,7 +174,7 @@ public Mono<ExecutionResult> execute(GraphQlRequest request) {
174174
}
175175

176176
@Override
177-
public Flux<ExecutionResult> executeSubscription(GraphQlRequest request) {
177+
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
178178
return client
179179
.document(request.getDocument())
180180
.operationName(request.getOperationName())

Diff for: spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebTestClientTransport.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919
import java.util.Collections;
2020
import java.util.Map;
2121

22-
import graphql.ExecutionResult;
2322
import reactor.core.publisher.Flux;
2423
import reactor.core.publisher.Mono;
2524

2625
import org.springframework.core.ParameterizedTypeReference;
2726
import org.springframework.graphql.GraphQlRequest;
27+
import org.springframework.graphql.GraphQlResponse;
2828
import org.springframework.graphql.client.GraphQlTransport;
29-
import org.springframework.graphql.support.MapExecutionResult;
29+
import org.springframework.graphql.support.MapGraphQlResponse;
3030
import org.springframework.http.MediaType;
3131
import org.springframework.test.web.reactive.server.WebTestClient;
3232
import org.springframework.util.Assert;
@@ -53,9 +53,9 @@ final class WebTestClientTransport implements GraphQlTransport {
5353

5454

5555
@Override
56-
public Mono<ExecutionResult> execute(GraphQlRequest request) {
56+
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
5757

58-
Map<String, Object> resultMap = this.webTestClient.post()
58+
Map<String, Object> responseMap = this.webTestClient.post()
5959
.contentType(MediaType.APPLICATION_JSON)
6060
.accept(MediaType.APPLICATION_JSON)
6161
.bodyValue(request.toMap())
@@ -66,13 +66,13 @@ public Mono<ExecutionResult> execute(GraphQlRequest request) {
6666
.returnResult()
6767
.getResponseBody();
6868

69-
resultMap = (resultMap != null ? resultMap : Collections.emptyMap());
70-
ExecutionResult result = MapExecutionResult.from(resultMap);
71-
return Mono.just(result);
69+
responseMap = (responseMap != null ? responseMap : Collections.emptyMap());
70+
GraphQlResponse response = MapGraphQlResponse.forResponse(responseMap);
71+
return Mono.just(response);
7272
}
7373

7474
@Override
75-
public Flux<ExecutionResult> executeSubscription(GraphQlRequest request) {
75+
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
7676
throw new UnsupportedOperationException("Subscriptions not supported over HTTP");
7777
}
7878

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.graphql;
18+
19+
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import graphql.GraphQLError;
24+
25+
import org.springframework.lang.Nullable;
26+
27+
/**
28+
* Represents a GraphQL response with the result of executing a request operation.
29+
*
30+
* @author Rossen Stoyanchev
31+
* @since 1.0.0
32+
*/
33+
public interface GraphQlResponse {
34+
35+
/**
36+
* Whether the response is valid. A response is invalid in one of the
37+
* following two cases:
38+
* <ul>
39+
* <li>the {@link #toMap() response map} has no "data" entry indicating
40+
* errors before execution, e.g. grammar parse and validation
41+
* <li>the "data" entry has a {@code null} value indicating errors during
42+
* execution that prevented a valid response
43+
* </ul>
44+
* <p>A valid response has a "data" key with a {@code non-null} value, but
45+
* it may still be partial and have some fields set to {@code null} due to
46+
* field errors.
47+
* <p>For more details, see section 7 "Response" in the GraphQL spec.
48+
*/
49+
boolean isValid();
50+
51+
/**
52+
* Return the data part of the response, or {@code null} when the response
53+
* is not {@link #isValid() valid}.
54+
* @param <T> a map or a list
55+
*/
56+
@Nullable
57+
<T> T getData();
58+
59+
/**
60+
* Return errors for the response. This contains "request errors" when the
61+
* response is not {@link #isValid() valid} and/or "field errors" for a
62+
* partial response.
63+
*/
64+
List<GraphQLError> getErrors();
65+
66+
/**
67+
* Return implementor specific, protocol extensions, if any.
68+
*/
69+
Map<Object, Object> getExtensions();
70+
71+
/**
72+
* Return a map representation of the response, formatted as required in the
73+
* "Response" section of the GraphQL spec.
74+
*/
75+
Map<String, Object> toMap();
76+
77+
}

Diff for: spring-graphql/src/main/java/org/springframework/graphql/RequestOutput.java

+37-25
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.graphql;
1717

18+
import java.util.Collections;
1819
import java.util.List;
1920
import java.util.Map;
2021

@@ -26,29 +27,37 @@
2627
import org.springframework.util.Assert;
2728

2829
/**
29-
* Wraps an {@link ExecutionResult} and also exposes the {@link ExecutionInput}
30-
* prepared for the request.
30+
* {@link GraphQlResponse} for server use that wraps the {@link ExecutionResult}
31+
* returned from {@link graphql.GraphQL} and also exposes the actual
32+
* {@link ExecutionInput} instance passed into it.
3133
*
3234
* @author Rossen Stoyanchev
3335
* @since 1.0.0
3436
*/
35-
public class RequestOutput implements ExecutionResult {
37+
public class RequestOutput implements GraphQlResponse {
3638

37-
private final ExecutionInput executionInput;
39+
private final ExecutionInput input;
3840

39-
private final ExecutionResult executionResult;
41+
private final ExecutionResult result;
4042

4143

4244
/**
43-
* Create an instance.
44-
* @param executionInput the input prepared for the request
45-
* @param executionResult the result from performing the request
45+
* Constructor to create initial instance.
4646
*/
47-
public RequestOutput(ExecutionInput executionInput, ExecutionResult executionResult) {
48-
Assert.notNull(executionInput, "ExecutionInput is required.");
49-
Assert.notNull(executionResult, "ExecutionResult is required.");
50-
this.executionInput = executionInput;
51-
this.executionResult = executionResult;
47+
public RequestOutput(ExecutionInput input, ExecutionResult result) {
48+
Assert.notNull(input, "ExecutionInput is required.");
49+
Assert.notNull(result, "ExecutionResult is required.");
50+
this.input = input;
51+
this.result = result;
52+
}
53+
54+
/**
55+
* Constructor to re-wrap from transport specific subclass.
56+
*/
57+
protected RequestOutput(RequestOutput requestOutput) {
58+
Assert.notNull(requestOutput, "RequestOutput is required.");
59+
this.input = requestOutput.getExecutionInput();
60+
this.result = requestOutput.result;
5261
}
5362

5463

@@ -57,37 +66,40 @@ public RequestOutput(ExecutionInput executionInput, ExecutionResult executionRes
5766
* {@link RequestInput} and passed to {@link graphql.GraphQL}.
5867
*/
5968
public ExecutionInput getExecutionInput() {
60-
return this.executionInput;
69+
return this.input;
70+
}
71+
72+
protected ExecutionResult getExecutionResult() {
73+
return this.result;
6174
}
6275

63-
@Nullable
6476
@Override
65-
public <T> T getData() {
66-
return this.executionResult.getData();
77+
public boolean isValid() {
78+
return (this.result.isDataPresent() && this.result.getData() != null);
6779
}
6880

81+
@Nullable
6982
@Override
70-
public boolean isDataPresent() {
71-
return this.executionResult.isDataPresent();
83+
public <T> T getData() {
84+
return this.result.getData();
7285
}
7386

7487
public List<GraphQLError> getErrors() {
75-
return this.executionResult.getErrors();
88+
return this.result.getErrors();
7689
}
7790

78-
@Nullable
7991
public Map<Object, Object> getExtensions() {
80-
return this.executionResult.getExtensions();
92+
return (this.result.getExtensions() != null ? this.result.getExtensions() : Collections.emptyMap());
8193
}
8294

8395
@Override
84-
public Map<String, Object> toSpecification() {
85-
return this.executionResult.toSpecification();
96+
public Map<String, Object> toMap() {
97+
return this.result.toSpecification();
8698
}
8799

88100
@Override
89101
public String toString() {
90-
return this.executionResult.toString();
102+
return this.result.toString();
91103
}
92104

93105
}

Diff for: spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientBuilder.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ private static class Jackson2Configurer {
116116
Configuration.defaultConfiguration().mappingProvider().getClass();
117117

118118
// We only need a MappingProvider:
119-
// GraphQlTransport returns ExecutionResult with JSON parsed to Map/List
119+
// GraphQlTransport returns GraphQlResponse with already parsed JSON
120120

121121
static Configuration configure(Configuration config) {
122122
MappingProvider provider = config.mappingProvider();

0 commit comments

Comments
 (0)