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
This commit is contained in:
rstoyanchev
2022-03-11 13:02:47 +00:00
parent 77e9835d3a
commit 15238072bb
37 changed files with 439 additions and 345 deletions

View File

@@ -25,6 +25,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.client.GraphQlTransport;
import org.springframework.test.util.AssertionErrors;
@@ -45,22 +46,23 @@ abstract class AbstractDirectTransport implements GraphQlTransport {
@Override
public Mono<ExecutionResult> execute(GraphQlRequest request) {
return executeInternal(request).cast(ExecutionResult.class);
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
return executeInternal(request).cast(GraphQlResponse.class);
}
@SuppressWarnings({"ConstantConditions", "unchecked"})
@Override
public Flux<ExecutionResult> executeSubscription(GraphQlRequest request) {
return executeInternal(request).flatMapMany(result -> {
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
return executeInternal(request).flatMapMany(output -> {
try {
Object data = result.getData();
Object data = output.getData();
AssertionErrors.assertTrue("Not a Publisher: " + data, data instanceof Publisher);
List<GraphQLError> errors = result.getErrors();
List<GraphQLError> errors = output.getErrors();
AssertionErrors.assertTrue("Subscription errors: " + errors, CollectionUtils.isEmpty(errors));
return Flux.from((Publisher<ExecutionResult>) data);
return Flux.from((Publisher<ExecutionResult>) data)
.map(result -> new RequestOutput(output.getExecutionInput(), result));
}
catch (AssertionError ex) {
throw new AssertionError(ex.getMessage() + "\nRequest: " + request, ex);

View File

@@ -31,12 +31,12 @@ import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.TypeRef;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.client.GraphQlTransport;
import org.springframework.graphql.support.DocumentSource;
import org.springframework.lang.Nullable;
@@ -165,7 +165,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
@SuppressWarnings("ConstantConditions")
@Override
public Response execute() {
return transport.execute(request()).map(result -> response(result, request())).block(responseTimeout);
return transport.execute(request()).map(response -> mapResponse(response, request())).block(responseTimeout);
}
@Override
@@ -175,15 +175,15 @@ final class DefaultGraphQlTester implements GraphQlTester {
@Override
public Subscription executeSubscription() {
return () -> transport.executeSubscription(request()).map(result -> response(result, request()));
return () -> transport.executeSubscription(request()).map(result -> mapResponse(result, request()));
}
private GraphQlRequest request() {
return new GraphQlRequest(this.document, this.operationName, this.variables);
}
private DefaultResponse response(ExecutionResult result, GraphQlRequest request) {
return new DefaultResponse(result, errorFilter, assertDecorator(request), jsonPathConfig);
private DefaultResponse mapResponse(GraphQlResponse response, GraphQlRequest request) {
return new DefaultResponse(response, errorFilter, assertDecorator(request), jsonPathConfig);
}
private Consumer<Runnable> assertDecorator(GraphQlRequest request) {
@@ -217,12 +217,12 @@ final class DefaultGraphQlTester implements GraphQlTester {
private ResponseDelegate(
ExecutionResult result, @Nullable Predicate<GraphQLError> errorFilter,
GraphQlResponse response, @Nullable Predicate<GraphQLError> errorFilter,
Consumer<Runnable> assertDecorator, Configuration jsonPathConfig) {
this.jsonDoc = JsonPath.parse(result.toSpecification(), jsonPathConfig);
this.jsonDoc = JsonPath.parse(response.toMap(), jsonPathConfig);
this.jsonContent = this.jsonDoc::jsonString;
this.errors = result.getErrors();
this.errors = response.getErrors();
this.unexpectedErrors = new ArrayList<>(this.errors);
this.assertDecorator = assertDecorator;
@@ -293,10 +293,10 @@ final class DefaultGraphQlTester implements GraphQlTester {
private final ResponseDelegate delegate;
private DefaultResponse(
ExecutionResult result, @Nullable Predicate<GraphQLError> errorFilter,
GraphQlResponse response, @Nullable Predicate<GraphQLError> errorFilter,
Consumer<Runnable> assertDecorator, Configuration jsonPathConfig) {
this.delegate = new ResponseDelegate(result, errorFilter, assertDecorator, jsonPathConfig);
this.delegate = new ResponseDelegate(response, errorFilter, assertDecorator, jsonPathConfig);
}
@Override

View File

@@ -20,11 +20,11 @@ package org.springframework.graphql.test.tester;
import java.net.URI;
import java.util.function.Consumer;
import graphql.ExecutionResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.client.CodecMappingProvider;
import org.springframework.graphql.client.GraphQlClient;
import org.springframework.graphql.client.GraphQlTransport;
@@ -164,7 +164,7 @@ final class DefaultWebSocketGraphQlTester extends AbstractDelegatingGraphQlTeste
return new GraphQlTransport() {
@Override
public Mono<ExecutionResult> execute(GraphQlRequest request) {
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
return client
.document(request.getDocument())
.operationName(request.getOperationName())
@@ -174,7 +174,7 @@ final class DefaultWebSocketGraphQlTester extends AbstractDelegatingGraphQlTeste
}
@Override
public Flux<ExecutionResult> executeSubscription(GraphQlRequest request) {
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
return client
.document(request.getDocument())
.operationName(request.getOperationName())

View File

@@ -19,14 +19,14 @@ package org.springframework.graphql.test.tester;
import java.util.Collections;
import java.util.Map;
import graphql.ExecutionResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.client.GraphQlTransport;
import org.springframework.graphql.support.MapExecutionResult;
import org.springframework.graphql.support.MapGraphQlResponse;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.Assert;
@@ -53,9 +53,9 @@ final class WebTestClientTransport implements GraphQlTransport {
@Override
public Mono<ExecutionResult> execute(GraphQlRequest request) {
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
Map<String, Object> resultMap = this.webTestClient.post()
Map<String, Object> responseMap = this.webTestClient.post()
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(request.toMap())
@@ -66,13 +66,13 @@ final class WebTestClientTransport implements GraphQlTransport {
.returnResult()
.getResponseBody();
resultMap = (resultMap != null ? resultMap : Collections.emptyMap());
ExecutionResult result = MapExecutionResult.from(resultMap);
return Mono.just(result);
responseMap = (responseMap != null ? responseMap : Collections.emptyMap());
GraphQlResponse response = MapGraphQlResponse.forResponse(responseMap);
return Mono.just(response);
}
@Override
public Flux<ExecutionResult> executeSubscription(GraphQlRequest request) {
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
throw new UnsupportedOperationException("Subscriptions not supported over HTTP");
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql;
import java.util.List;
import java.util.Map;
import graphql.GraphQLError;
import org.springframework.lang.Nullable;
/**
* Represents a GraphQL response with the result of executing a request operation.
*
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public interface GraphQlResponse {
/**
* Whether the response is valid. A response is invalid in one of the
* following two cases:
* <ul>
* <li>the {@link #toMap() response map} has no "data" entry indicating
* errors before execution, e.g. grammar parse and validation
* <li>the "data" entry has a {@code null} value indicating errors during
* execution that prevented a valid response
* </ul>
* <p>A valid response has a "data" key with a {@code non-null} value, but
* it may still be partial and have some fields set to {@code null} due to
* field errors.
* <p>For more details, see section 7 "Response" in the GraphQL spec.
*/
boolean isValid();
/**
* Return the data part of the response, or {@code null} when the response
* is not {@link #isValid() valid}.
* @param <T> a map or a list
*/
@Nullable
<T> T getData();
/**
* Return errors for the response. This contains "request errors" when the
* response is not {@link #isValid() valid} and/or "field errors" for a
* partial response.
*/
List<GraphQLError> getErrors();
/**
* Return implementor specific, protocol extensions, if any.
*/
Map<Object, Object> getExtensions();
/**
* Return a map representation of the response, formatted as required in the
* "Response" section of the GraphQL spec.
*/
Map<String, Object> toMap();
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.graphql;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -26,29 +27,37 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Wraps an {@link ExecutionResult} and also exposes the {@link ExecutionInput}
* prepared for the request.
* {@link GraphQlResponse} for server use that wraps the {@link ExecutionResult}
* returned from {@link graphql.GraphQL} and also exposes the actual
* {@link ExecutionInput} instance passed into it.
*
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public class RequestOutput implements ExecutionResult {
public class RequestOutput implements GraphQlResponse {
private final ExecutionInput executionInput;
private final ExecutionInput input;
private final ExecutionResult executionResult;
private final ExecutionResult result;
/**
* Create an instance.
* @param executionInput the input prepared for the request
* @param executionResult the result from performing the request
* Constructor to create initial instance.
*/
public RequestOutput(ExecutionInput executionInput, ExecutionResult executionResult) {
Assert.notNull(executionInput, "ExecutionInput is required.");
Assert.notNull(executionResult, "ExecutionResult is required.");
this.executionInput = executionInput;
this.executionResult = executionResult;
public RequestOutput(ExecutionInput input, ExecutionResult result) {
Assert.notNull(input, "ExecutionInput is required.");
Assert.notNull(result, "ExecutionResult is required.");
this.input = input;
this.result = result;
}
/**
* Constructor to re-wrap from transport specific subclass.
*/
protected RequestOutput(RequestOutput requestOutput) {
Assert.notNull(requestOutput, "RequestOutput is required.");
this.input = requestOutput.getExecutionInput();
this.result = requestOutput.result;
}
@@ -57,37 +66,40 @@ public class RequestOutput implements ExecutionResult {
* {@link RequestInput} and passed to {@link graphql.GraphQL}.
*/
public ExecutionInput getExecutionInput() {
return this.executionInput;
return this.input;
}
protected ExecutionResult getExecutionResult() {
return this.result;
}
@Override
public boolean isValid() {
return (this.result.isDataPresent() && this.result.getData() != null);
}
@Nullable
@Override
public <T> T getData() {
return this.executionResult.getData();
}
@Override
public boolean isDataPresent() {
return this.executionResult.isDataPresent();
return this.result.getData();
}
public List<GraphQLError> getErrors() {
return this.executionResult.getErrors();
return this.result.getErrors();
}
@Nullable
public Map<Object, Object> getExtensions() {
return this.executionResult.getExtensions();
return (this.result.getExtensions() != null ? this.result.getExtensions() : Collections.emptyMap());
}
@Override
public Map<String, Object> toSpecification() {
return this.executionResult.toSpecification();
public Map<String, Object> toMap() {
return this.result.toSpecification();
}
@Override
public String toString() {
return this.executionResult.toString();
return this.result.toString();
}
}

View File

@@ -116,7 +116,7 @@ public abstract class AbstractGraphQlClientBuilder<B extends AbstractGraphQlClie
Configuration.defaultConfiguration().mappingProvider().getClass();
// We only need a MappingProvider:
// GraphQlTransport returns ExecutionResult with JSON parsed to Map/List
// GraphQlTransport returns GraphQlResponse with already parsed JSON
static Configuration configure(Configuration config) {
MappingProvider provider = config.mappingProvider();

View File

@@ -25,7 +25,6 @@ import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.TypeRef;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -33,6 +32,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.support.DocumentSource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -178,16 +178,16 @@ final class DefaultGraphQlClient implements GraphQlClient {
*/
private static class DefaultResponse implements Response {
private final ExecutionResult result;
private final GraphQlResponse response;
private final DocumentContext jsonPathDoc;
private final List<GraphQLError> errors;
private DefaultResponse(ExecutionResult result, Configuration jsonPathConfig) {
this.result = result;
this.jsonPathDoc = JsonPath.parse(result.toSpecification(), jsonPathConfig);
this.errors = result.getErrors();
private DefaultResponse(GraphQlResponse response, Configuration jsonPathConfig) {
this.response = response;
this.jsonPathDoc = JsonPath.parse(response.toMap(), jsonPathConfig);
this.errors = response.getErrors();
}
@Override
@@ -226,8 +226,8 @@ final class DefaultGraphQlClient implements GraphQlClient {
}
@Override
public ExecutionResult andReturn() {
return this.result;
public GraphQlResponse andReturn() {
return this.response;
}
}

View File

@@ -18,12 +18,12 @@ package org.springframework.graphql.client;
import java.util.List;
import java.util.Map;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.support.DocumentSource;
import org.springframework.graphql.support.ResourceDocumentSource;
import org.springframework.lang.Nullable;
@@ -218,9 +218,9 @@ public interface GraphQlClient {
List<GraphQLError> errors();
/**
* Return the underlying {@link ExecutionResult} for the response.
* Return the underlying {@link GraphQlResponse}.
*/
ExecutionResult andReturn();
GraphQlResponse andReturn();
}

View File

@@ -16,11 +16,11 @@
package org.springframework.graphql.client;
import graphql.ExecutionResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
/**
@@ -34,16 +34,16 @@ public interface GraphQlTransport {
/**
* Execute a request with a single response such as a "query" or "mutation".
* @param request the request to execute
* @return a {@code Mono} with the {@code ExecutionResult} for the response.
* @return a {@code Mono} with the {@code GraphQlResponse} for the response.
* The {@code Mono} may end wth an error due to transport or other issues
* such as failures to encode the request or decode the response.
*/
Mono<ExecutionResult> execute(GraphQlRequest request);
Mono<GraphQlResponse> execute(GraphQlRequest request);
/**
* Execute a "subscription" request with a stream of responses.
* @param request the request to execute
* @return a {@code Flux} of {@code ExecutionResult} responses.
* @return a {@code Flux} of {@code GraphQlResponse} responses.
* The {@code Flux} may terminate as follows:
* <ul>
* <li>Completes if the subscription completes before the connection is closed.
@@ -55,6 +55,6 @@ public interface GraphQlTransport {
* <p>The {@code Flux} may be cancelled to notify the server to end the
* subscription stream.
*/
Flux<ExecutionResult> executeSubscription(GraphQlRequest request);
Flux<GraphQlResponse> executeSubscription(GraphQlRequest request);
}

View File

@@ -18,13 +18,13 @@ package org.springframework.graphql.client;
import java.util.Map;
import graphql.ExecutionResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.support.MapExecutionResult;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.support.MapGraphQlResponse;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
@@ -54,18 +54,18 @@ final class HttpGraphQlTransport implements GraphQlTransport {
@Override
public Mono<ExecutionResult> execute(GraphQlRequest request) {
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
return this.webClient.post()
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(request.toMap())
.retrieve()
.bodyToMono(MAP_TYPE)
.map(MapExecutionResult::from);
.map(MapGraphQlResponse::forResponse);
}
@Override
public Flux<ExecutionResult> executeSubscription(GraphQlRequest request) {
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
throw new UnsupportedOperationException("Subscriptions not supported over HTTP");
}

View File

@@ -24,7 +24,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -34,8 +33,9 @@ import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.support.MapExecutionResult;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.support.MapGraphQlError;
import org.springframework.graphql.support.MapGraphQlResponse;
import org.springframework.graphql.web.support.GraphQlMessage;
import org.springframework.graphql.web.support.GraphQlMessageType;
import org.springframework.http.HttpHeaders;
@@ -145,12 +145,12 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
}
@Override
public Mono<ExecutionResult> execute(GraphQlRequest request) {
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
return this.graphQlSessionMono.flatMap(session -> session.execute(request));
}
@Override
public Flux<ExecutionResult> executeSubscription(GraphQlRequest request) {
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
return this.graphQlSessionMono.flatMapMany(session -> session.executeSubscription(request));
}
@@ -372,9 +372,9 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
private final Sinks.Many<GraphQlMessage> requestSink = Sinks.many().unicast().onBackpressureBuffer();
private final Map<String, Sinks.One<ExecutionResult>> resultSinks = new ConcurrentHashMap<>();
private final Map<String, Sinks.One<GraphQlResponse>> responseSinks = new ConcurrentHashMap<>();
private final Map<String, Sinks.Many<ExecutionResult>> streamingSinks = new ConcurrentHashMap<>();
private final Map<String, Sinks.Many<GraphQlResponse>> streamSinks = new ConcurrentHashMap<>();
GraphQlSession(WebSocketSession webSocketSession) {
@@ -389,32 +389,32 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
return this.requestSink.asFlux();
}
public Mono<ExecutionResult> execute(GraphQlRequest request) {
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
String id = String.valueOf(this.requestIndex.incrementAndGet());
try {
GraphQlMessage message = GraphQlMessage.subscribe(id, request);
Sinks.One<ExecutionResult> sink = Sinks.one();
this.resultSinks.put(id, sink);
Sinks.One<GraphQlResponse> sink = Sinks.one();
this.responseSinks.put(id, sink);
trySend(message);
return sink.asMono().doOnCancel(() -> this.resultSinks.remove(id));
return sink.asMono().doOnCancel(() -> this.responseSinks.remove(id));
}
catch (Exception ex) {
this.resultSinks.remove(id);
this.responseSinks.remove(id);
return Mono.error(ex);
}
}
public Flux<ExecutionResult> executeSubscription(GraphQlRequest request) {
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
String id = String.valueOf(this.requestIndex.incrementAndGet());
try {
GraphQlMessage message = GraphQlMessage.subscribe(id, request);
Sinks.Many<ExecutionResult> sink = Sinks.many().unicast().onBackpressureBuffer();
this.streamingSinks.put(id, sink);
Sinks.Many<GraphQlResponse> sink = Sinks.many().unicast().onBackpressureBuffer();
this.streamSinks.put(id, sink);
trySend(message);
return sink.asFlux().doOnCancel(() -> cancelStream(id));
}
catch (Exception ex) {
this.streamingSinks.remove(id);
this.streamSinks.remove(id);
return Flux.error(ex);
}
}
@@ -438,7 +438,7 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
}
private void cancelStream(String id) {
Sinks.Many<ExecutionResult> streamSink = this.streamingSinks.remove(id);
Sinks.Many<GraphQlResponse> streamSink = this.streamSinks.remove(id);
if (streamSink != null) {
try {
trySend(GraphQlMessage.complete(id));
@@ -458,8 +458,8 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
*/
public void handleNext(GraphQlMessage message) {
String id = message.getId();
Sinks.One<ExecutionResult> sink = this.resultSinks.remove(id);
Sinks.Many<ExecutionResult> streamingSink = this.streamingSinks.get(id);
Sinks.One<GraphQlResponse> sink = this.responseSinks.remove(id);
Sinks.Many<GraphQlResponse> streamingSink = this.streamSinks.get(id);
if (sink == null && streamingSink == null) {
if (logger.isDebugEnabled()) {
@@ -468,10 +468,10 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
return;
}
Map<String, Object> resultMap = message.getPayload();
ExecutionResult result = MapExecutionResult.from(resultMap);
Map<String, Object> responseMap = message.getPayload();
GraphQlResponse response = MapGraphQlResponse.forResponse(responseMap);
Sinks.EmitResult emitResult = (sink != null ? sink.tryEmitValue(result) : streamingSink.tryEmitNext(result));
Sinks.EmitResult emitResult = (sink != null ? sink.tryEmitValue(response) : streamingSink.tryEmitNext(response));
if (emitResult.isFailure()) {
// Just log: cannot overflow, is serialized, and cancel is handled in doOnCancel
if (logger.isDebugEnabled()) {
@@ -481,13 +481,13 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
}
/**
* Handle an "error" message, turning it into an {@link ExecutionResult}
* for a single result response, or signaling an error to streams.
* Handle an "error" message, turning it into an {@link GraphQlResponse}
* for single responses, or signaling an error for streams.
*/
public void handleError(GraphQlMessage message) {
String id = message.getId();
Sinks.One<ExecutionResult> sink = this.resultSinks.remove(id);
Sinks.Many<ExecutionResult> streamingSink = this.streamingSinks.remove(id);
Sinks.One<GraphQlResponse> sink = this.responseSinks.remove(id);
Sinks.Many<GraphQlResponse> streamingSink = this.streamSinks.remove(id);
if (sink == null && streamingSink == null ) {
if (logger.isDebugEnabled()) {
@@ -500,8 +500,8 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
Sinks.EmitResult emitResult;
if (sink != null) {
ExecutionResult result = MapExecutionResult.forErrorsOnly(payload);
emitResult = sink.tryEmitValue(result);
GraphQlResponse response = MapGraphQlResponse.forErrorsOnly(payload);
emitResult = sink.tryEmitValue(response);
}
else {
List<GraphQLError> graphQLErrors = MapGraphQlError.from(payload);
@@ -518,14 +518,14 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
* Handle a "complete" message.
*/
public void handleComplete(GraphQlMessage message) {
Sinks.One<ExecutionResult> resultSink = this.resultSinks.remove(message.getId());
Sinks.Many<ExecutionResult> streamingResultSink = this.streamingSinks.remove(message.getId());
Sinks.One<GraphQlResponse> sink = this.responseSinks.remove(message.getId());
Sinks.Many<GraphQlResponse> streamSink = this.streamSinks.remove(message.getId());
if (resultSink != null) {
resultSink.tryEmitEmpty();
if (sink != null) {
sink.tryEmitEmpty();
}
else if (streamingResultSink != null) {
streamingResultSink.tryEmitComplete();
else if (streamSink != null) {
streamSink.tryEmitComplete();
}
}
@@ -548,10 +548,10 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
* Terminate and clean all in-progress requests with the given error.
*/
public void terminateRequests(Exception ex) {
this.resultSinks.values().forEach(sink -> sink.tryEmitError(ex));
this.streamingSinks.values().forEach(sink -> sink.tryEmitError(ex));
this.resultSinks.clear();
this.streamingSinks.clear();
this.responseSinks.values().forEach(sink -> sink.tryEmitError(ex));
this.streamSinks.values().forEach(sink -> sink.tryEmitError(ex));
this.responseSinks.clear();
this.streamSinks.clear();
}
@Override

View File

@@ -21,18 +21,18 @@ import java.util.List;
import java.util.Map;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.GraphQLError;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.util.Assert;
/**
* Implementation of {@link ExecutionResult} backed by a {@link Map}.
* {@link GraphQlResponse} for client use that wraps the GraphQL response map.
*
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public final class MapExecutionResult implements ExecutionResult {
public final class MapGraphQlResponse implements GraphQlResponse {
private final Map<String, Object> resultMap;
@@ -40,13 +40,18 @@ public final class MapExecutionResult implements ExecutionResult {
@SuppressWarnings("unchecked")
private MapExecutionResult(Map<String, Object> resultMap) {
private MapGraphQlResponse(Map<String, Object> resultMap) {
Assert.notNull(resultMap, "'resultMap' is required");
this.resultMap = resultMap;
this.errors = MapGraphQlError.from((List<Map<String, Object>>) resultMap.get("errors"));
}
@Override
public boolean isValid() {
return (this.resultMap.containsKey("data") && this.resultMap.get("data") != null);
}
@Override
public List<GraphQLError> getErrors() {
return this.errors;
@@ -58,26 +63,21 @@ public final class MapExecutionResult implements ExecutionResult {
return (T) this.resultMap.get("data");
}
@Override
public boolean isDataPresent() {
return (this.resultMap.get("data") != null);
}
@SuppressWarnings("unchecked")
@Override
public Map<Object, Object> getExtensions() {
return (Map<Object, Object>) this.resultMap.get("extensions");
return (Map<Object, Object>) this.resultMap.getOrDefault("extensions", Collections.emptyMap());
}
@Override
public Map<String, Object> toSpecification() {
return ExecutionResultImpl.newExecutionResult().from(this).build().toSpecification();
public Map<String, Object> toMap() {
return this.resultMap;
}
@Override
public boolean equals(Object other) {
return (other instanceof MapExecutionResult &&
this.resultMap.equals(((MapExecutionResult) other).resultMap));
return (other instanceof MapGraphQlResponse &&
this.resultMap.equals(((MapGraphQlResponse) other).resultMap));
}
@Override
@@ -95,24 +95,24 @@ public final class MapExecutionResult implements ExecutionResult {
* Create an instance from an {@code ExecutionResult} serialized to map via
* {@link ExecutionResult#toSpecification()}.
*/
public static ExecutionResult from(Map<String, Object> map) {
return new MapExecutionResult(map);
public static GraphQlResponse forResponse(Map<String, Object> map) {
return new MapGraphQlResponse(map);
}
/**
* Create an {@code ExecutionResult} with a "data" key that returns the
* given map.
*/
public static ExecutionResult forDataOnly(Map<String, Object> map) {
return new MapExecutionResult(Collections.singletonMap("data", map));
public static GraphQlResponse forDataOnly(Map<String, Object> map) {
return new MapGraphQlResponse(Collections.singletonMap("data", map));
}
/**
* Create an {@code ExecutionResult} with an "errors" key that returns the
* given serialized errors.
*/
public static ExecutionResult forErrorsOnly(List<Map<String, Object>> errors) {
return new MapExecutionResult(Collections.singletonMap("errors", errors));
public static GraphQlResponse forErrorsOnly(List<Map<String, Object>> errors) {
return new MapGraphQlResponse(Collections.singletonMap("errors", errors));
}
}

View File

@@ -16,7 +16,6 @@
package org.springframework.graphql.web;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@@ -49,15 +48,14 @@ public class WebOutput extends RequestOutput {
* @param requestOutput the output from an executed request
*/
public WebOutput(RequestOutput requestOutput) {
this(requestOutput.getExecutionInput(), requestOutput, new HttpHeaders());
super(requestOutput);
this.responseHeaders = new HttpHeaders();
}
private WebOutput(ExecutionInput executionInput, ExecutionResult executionResult,
HttpHeaders responseHeaders) {
private WebOutput(ExecutionInput executionInput, ExecutionResult executionResult, HttpHeaders headers) {
super(executionInput, executionResult);
Assert.notNull(responseHeaders, "HttpHeaders is required");
this.responseHeaders = responseHeaders;
Assert.notNull(headers, "HttpHeaders is required");
this.responseHeaders = headers;
}
@@ -90,21 +88,13 @@ public class WebOutput extends RequestOutput {
*/
public static final class Builder {
private final WebOutput webOutput;
private final WebOutput original;
@Nullable
private Object data;
private final ExecutionResultImpl.Builder builder;
private List<GraphQLError> errors;
@Nullable
private Map<Object, Object> extensions;
private Builder(WebOutput output) {
this.webOutput = output;
this.data = output.getData();
this.errors = output.getErrors();
this.extensions = output.getExtensions();
private Builder(WebOutput original) {
this.original = original;
this.builder = ExecutionResultImpl.newExecutionResult().from(original.getExecutionResult());
}
/**
@@ -112,8 +102,8 @@ public class WebOutput extends RequestOutput {
* @param data the execution result data
* @return the current builder
*/
public Builder data(@Nullable Object data) {
this.data = data;
public Builder data(Object data) {
this.builder.data(data);
return this;
}
@@ -124,7 +114,7 @@ public class WebOutput extends RequestOutput {
* @return the current builder
*/
public Builder errors(@Nullable List<GraphQLError> errors) {
this.errors = (errors != null) ? errors : Collections.emptyList();
this.builder.errors(errors);
return this;
}
@@ -135,13 +125,13 @@ public class WebOutput extends RequestOutput {
* @return the current builder
*/
public Builder extensions(@Nullable Map<Object, Object> extensions) {
this.extensions = extensions;
this.builder.extensions(extensions);
return this;
}
public WebOutput build() {
ExecutionResult result = new ExecutionResultImpl(this.data, this.errors, this.extensions);
return new WebOutput(this.webOutput.getExecutionInput(), result, this.webOutput.getResponseHeaders());
return new WebOutput(this.original.getExecutionInput(), this.builder.build(),
this.original.getResponseHeaders());
}
}

View File

@@ -20,10 +20,10 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.RequestOutput;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -63,6 +63,7 @@ public class GraphQlMessage {
/**
* Constructor for deserialization.
*/
@SuppressWarnings("unused")
GraphQlMessage() {
this.type = GraphQlMessageType.NOT_SPECIFIED;
}
@@ -176,11 +177,21 @@ public class GraphQlMessage {
/**
* Create a {@code "next"} server message.
* @param id unique request id
* @param result the result from request execution to add as the message payload
* @param output the output to obtain the result map from
*/
public static GraphQlMessage next(String id, ExecutionResult result) {
Assert.notNull(result, "ExecutionResult is required");
return new GraphQlMessage(id, GraphQlMessageType.NEXT, result.toSpecification());
public static GraphQlMessage next(String id, RequestOutput output) {
Assert.notNull(output, "'RequestOutput' is required");
return next(id, output.toMap());
}
/**
* Create a {@code "next"} server message.
* @param id unique request id
* @param responseMap the response map
*/
public static GraphQlMessage next(String id, Map<String, Object> responseMap) {
Assert.notNull(responseMap, "'responseMap' is required");
return new GraphQlMessage(id, GraphQlMessageType.NEXT, responseMap);
}
/**

View File

@@ -15,7 +15,8 @@
*/
package org.springframework.graphql.web.webflux;
import graphql.ExecutionResult;
import java.util.Map;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
@@ -93,8 +94,8 @@ final class CodecDelegate {
return encode(session, GraphQlMessage.connectionAck(ackPayload));
}
public WebSocketMessage encodeNext(WebSocketSession session, String id, ExecutionResult result) {
return encode(session, GraphQlMessage.next(id, result));
public WebSocketMessage encodeNext(WebSocketSession session, String id, Map<String, Object> responseMap) {
return encode(session, GraphQlMessage.next(id, responseMap));
}
public WebSocketMessage encodeError(WebSocketSession session, String id, Throwable ex) {

View File

@@ -72,14 +72,13 @@ public class GraphQlHttpHandler {
}
return this.graphQlHandler.handleRequest(input);
})
.flatMap((output) -> {
Map<String, Object> spec = output.toSpecification();
.flatMap(output -> {
if (logger.isDebugEnabled()) {
logger.debug("Execution complete");
}
ServerResponse.BodyBuilder builder = ServerResponse.ok();
builder.headers(headers -> headers.putAll(output.getResponseHeaders()));
return builder.bodyValue(spec);
return builder.bodyValue(output.toMap());
});
}

View File

@@ -185,10 +185,11 @@ public class GraphQlWebSocketHandler implements WebSocketHandler {
+ ".");
}
Flux<ExecutionResult> outputFlux;
Flux<Map<String, Object>> responseFlux;
if (output.getData() instanceof Publisher) {
// Subscription
outputFlux = Flux.from((Publisher<ExecutionResult>) output.getData())
responseFlux = Flux.from((Publisher<ExecutionResult>) output.getData())
.map(ExecutionResult::toSpecification)
.doOnSubscribe((subscription) -> {
Subscription previous = subscriptions.putIfAbsent(id, subscription);
if (previous != null) {
@@ -198,11 +199,11 @@ public class GraphQlWebSocketHandler implements WebSocketHandler {
}
else {
// Single response (query or mutation) that may contain errors
outputFlux = Flux.just(output);
responseFlux = Flux.just(output.toMap());
}
return outputFlux
.map(result -> this.codecDelegate.encodeNext(session, id, result))
return responseFlux
.map(responseMap -> this.codecDelegate.encodeNext(session, id, responseMap))
.concatWith(Mono.fromCallable(() -> this.codecDelegate.encodeComplete(session, id)))
.onErrorResume(ex -> {
if (ex instanceof SubscriptionExistsException) {

View File

@@ -82,14 +82,15 @@ public class GraphQlHttpHandler {
logger.debug("Executing: " + input);
}
Mono<ServerResponse> responseMono = this.graphQlHandler.handleRequest(input).map((output) -> {
if (logger.isDebugEnabled()) {
logger.debug("Execution complete");
}
ServerResponse.BodyBuilder builder = ServerResponse.ok();
builder.headers(headers -> headers.putAll(output.getResponseHeaders()));
return builder.body(output.toSpecification());
});
Mono<ServerResponse> responseMono = this.graphQlHandler.handleRequest(input)
.map(output -> {
if (logger.isDebugEnabled()) {
logger.debug("Execution complete");
}
ServerResponse.BodyBuilder builder = ServerResponse.ok();
builder.headers(headers -> headers.putAll(output.getResponseHeaders()));
return builder.body(output.toMap());
});
return ServerResponse.async(responseMono);
}

View File

@@ -48,7 +48,6 @@ import org.springframework.graphql.web.WebInput;
import org.springframework.graphql.web.WebOutput;
import org.springframework.graphql.web.WebSocketInterceptor;
import org.springframework.graphql.web.support.GraphQlMessage;
import org.springframework.graphql.web.support.GraphQlMessageType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
@@ -226,10 +225,11 @@ public class GraphQlWebSocketHandler extends TextWebSocketHandler implements Sub
+ (!CollectionUtils.isEmpty(output.getErrors()) ? " with errors: " + output.getErrors() : "")
+ ".");
}
Flux<ExecutionResult> outputFlux;
Flux<Map<String, Object>> responseFlux;
if (output.getData() instanceof Publisher) {
// Subscription
outputFlux = Flux.from((Publisher<ExecutionResult>) output.getData())
responseFlux = Flux.from((Publisher<ExecutionResult>) output.getData())
.map(ExecutionResult::toSpecification)
.doOnSubscribe((subscription) -> {
Subscription prev = getSessionInfo(session).getSubscriptions().putIfAbsent(id, subscription);
if (prev != null) {
@@ -239,11 +239,11 @@ public class GraphQlWebSocketHandler extends TextWebSocketHandler implements Sub
}
else {
// Single response (query or mutation) that may contain errors
outputFlux = Flux.just(output);
responseFlux = Flux.just(output.toMap());
}
return outputFlux
.map(result -> encode(GraphQlMessage.next(id, result)))
return responseFlux
.map(responseMap -> encode(GraphQlMessage.next(id, responseMap)))
.concatWith(Mono.fromCallable(() -> encode(GraphQlMessage.complete(id))))
.onErrorResume((ex) -> {
if (ex instanceof SubscriptionExistsException) {

View File

@@ -48,9 +48,9 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Rossen Stoyanchev
*/
public class GraphQlResponse {
public class ResponseHelper {
private static final Log logger = LogFactory.getLog(GraphQlResponse.class);
private static final Log logger = LogFactory.getLog(ResponseHelper.class);
private final DocumentContext documentContext;
@@ -60,9 +60,9 @@ public class GraphQlResponse {
private boolean errorsChecked;
private GraphQlResponse(ExecutionResult result) {
this.documentContext = JsonPath.parse(result.toSpecification(), initJsonPathConfig());
this.errors = result.getErrors();
private ResponseHelper(Map<String, Object> responseMap, List<GraphQLError> errors) {
this.documentContext = JsonPath.parse(responseMap, initJsonPathConfig());
this.errors = errors;
}
private static Configuration initJsonPathConfig() {
@@ -73,7 +73,7 @@ public class GraphQlResponse {
}
public GraphQlResponse log() {
public ResponseHelper log() {
logger.debug("GraphQlResponse: " + this.documentContext.jsonString());
return this;
}
@@ -132,27 +132,33 @@ public class GraphQlResponse {
}
public static GraphQlResponse from(ExecutionResult result) {
return new GraphQlResponse(result);
public static ResponseHelper forResult(ExecutionResult result) {
return new ResponseHelper(result.toSpecification(), result.getErrors());
}
public static GraphQlResponse from(Mono<? extends ExecutionResult> resultMono) {
public static ResponseHelper forResult(Mono<? extends ExecutionResult> resultMono) {
ExecutionResult result = resultMono.block(Duration.ofSeconds(5));
assertThat(result).isNotNull();
return from(result);
return forResult(result);
}
public static Flux<GraphQlResponse> forSubscription(ExecutionResult result) {
public static ResponseHelper forResponse(Mono<? extends RequestOutput> outputMono) {
RequestOutput output = outputMono.block(Duration.ofSeconds(5));
assertThat(output).isNotNull();
return forResult(output.getExecutionResult());
}
public static Flux<ResponseHelper> forSubscription(ExecutionResult result) {
assertThat(result.getErrors()).as("Errors present in GraphQL response").isEmpty();
Publisher<ExecutionResult> publisher = result.getData();
return Flux.from(publisher).map(GraphQlResponse::from);
return Flux.from(publisher).map(ResponseHelper::forResult);
}
@SuppressWarnings("BlockingMethodInNonBlockingContext")
public static Flux<GraphQlResponse> forSubscription(Mono<? extends ExecutionResult> resultMono) {
ExecutionResult result = resultMono.block(Duration.ofSeconds(5));
assertThat(result).isNotNull();
return forSubscription(result);
public static Flux<ResponseHelper> forSubscription(Mono<? extends RequestOutput> resultMono) {
RequestOutput output = resultMono.block(Duration.ofSeconds(5));
assertThat(output).isNotNull();
return forSubscription(output.getExecutionResult());
}
@@ -165,15 +171,15 @@ public class GraphQlResponse {
}
public String message() {
return GraphQlResponse.this.errors.get(index).getMessage();
return ResponseHelper.this.errors.get(index).getMessage();
}
public String errorType() {
return GraphQlResponse.this.errors.get(index).getErrorType().toString();
return ResponseHelper.this.errors.get(index).getErrorType().toString();
}
public Map<String, Object> extensions() {
return GraphQlResponse.this.errors.get(index).getExtensions();
return ResponseHelper.this.errors.get(index).getExtensions();
}
}

View File

@@ -23,7 +23,6 @@ import java.util.function.Consumer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.GraphQLError;
@@ -31,7 +30,8 @@ import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.support.MapGraphQlResponse;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -80,14 +80,11 @@ public class GraphQlClientTestSupport {
}
private void setMockResponse(Consumer<ExecutionResultImpl.Builder> consumer) {
ExecutionResultImpl.Builder builder = new ExecutionResultImpl.Builder();
consumer.accept(builder);
ExecutionInput executionInput = ExecutionInput.newExecutionInput("{}").build();
ExecutionResult result = builder.build();
when(this.transport.execute(this.requestCaptor.capture()))
.thenReturn(Mono.just(new RequestOutput(executionInput, result)));
GraphQlResponse response = MapGraphQlResponse.forResponse(result.toSpecification());
when(this.transport.execute(this.requestCaptor.capture())).thenReturn(Mono.just(response));
}
private void serialize(String data, ExecutionResultImpl.Builder builder) {

View File

@@ -20,7 +20,6 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -29,6 +28,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.web.support.GraphQlMessage;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.socket.WebSocketHandler;
@@ -100,7 +100,7 @@ public final class MockGraphQlWebSocketServer implements WebSocketHandler {
return Flux.error(new IllegalStateException("Unexpected request: " + message));
}
return request.getResponseFlux()
.map(result -> GraphQlMessage.next(id, result))
.map(response -> GraphQlMessage.next(id, response.toMap()))
.concatWithValues(
request.getError() != null ?
GraphQlMessage.error(id, request.getError()) :
@@ -118,12 +118,12 @@ public final class MockGraphQlWebSocketServer implements WebSocketHandler {
/**
* Respond with the given a single result.
*/
GraphQlRequest andRespond(ExecutionResult result);
GraphQlRequest andRespond(GraphQlResponse response);
/**
* Respond with the given a single result {@code Mono}.
*/
GraphQlRequest andRespond(Mono<ExecutionResult> resultMono);
GraphQlRequest andRespond(Mono<GraphQlResponse> responseMono);
/**
* Respond with a GraphQL over WebSocket "error" message.
@@ -133,12 +133,12 @@ public final class MockGraphQlWebSocketServer implements WebSocketHandler {
/**
* Respond with the given stream of responses.
*/
GraphQlRequest andStream(Flux<ExecutionResult> resultFlux);
GraphQlRequest andStream(Flux<GraphQlResponse> responseFlux);
/**
* Respond with the given stream of responses and terminate with an error.
*/
GraphQlRequest andStreamWithError(Flux<ExecutionResult> resultFlux, GraphQLError error);
GraphQlRequest andStreamWithError(Flux<GraphQlResponse> responseFlux, GraphQLError error);
}
@@ -147,7 +147,7 @@ public final class MockGraphQlWebSocketServer implements WebSocketHandler {
private final GraphQlRequest request;
private Flux<ExecutionResult> responseFlux = Flux.empty();
private Flux<GraphQlResponse> responseFlux = Flux.empty();
@Nullable
private GraphQLError error;
@@ -158,13 +158,13 @@ public final class MockGraphQlWebSocketServer implements WebSocketHandler {
}
@Override
public GraphQlRequest andRespond(ExecutionResult result) {
return addResponse(Flux.just(result), null);
public GraphQlRequest andRespond(GraphQlResponse response) {
return addResponse(Flux.just(response), null);
}
@Override
public GraphQlRequest andRespond(Mono<ExecutionResult> resultMono) {
return addResponse(Flux.from(resultMono), null);
public GraphQlRequest andRespond(Mono<GraphQlResponse> responseMono) {
return addResponse(Flux.from(responseMono), null);
}
@Override
@@ -173,17 +173,17 @@ public final class MockGraphQlWebSocketServer implements WebSocketHandler {
}
@Override
public GraphQlRequest andStream(Flux<ExecutionResult> resultFlux) {
return addResponse(resultFlux, null);
public GraphQlRequest andStream(Flux<GraphQlResponse> responseFlux) {
return addResponse(responseFlux, null);
}
@Override
public GraphQlRequest andStreamWithError(Flux<ExecutionResult> resultFlux, GraphQLError error) {
return addResponse(resultFlux, error);
public GraphQlRequest andStreamWithError(Flux<GraphQlResponse> responseFlux, GraphQLError error) {
return addResponse(responseFlux, error);
}
private GraphQlRequest addResponse(Flux<ExecutionResult> resultFlux, @Nullable GraphQLError error) {
this.responseFlux = resultFlux;
private GraphQlRequest addResponse(Flux<GraphQlResponse> responseFlux, @Nullable GraphQLError error) {
this.responseFlux = responseFlux;
this.error = error;
return this.request;
}
@@ -192,7 +192,7 @@ public final class MockGraphQlWebSocketServer implements WebSocketHandler {
return this.request;
}
public Flux<ExecutionResult> getResponseFlux() {
public Flux<GraphQlResponse> getResponseFlux() {
return this.responseFlux;
}

View File

@@ -25,7 +25,6 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import org.junit.jupiter.api.Test;
@@ -34,7 +33,8 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.support.MapExecutionResult;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.support.MapGraphQlResponse;
import org.springframework.graphql.web.TestWebSocketClient;
import org.springframework.graphql.web.TestWebSocketConnection;
import org.springframework.graphql.web.support.GraphQlMessage;
@@ -70,17 +70,17 @@ public class MockWebSocketGraphQlTransportTests {
private final WebSocketGraphQlTransport transport = createTransport(this.webSocketClient);
private final ExecutionResult result1 = MapExecutionResult.forDataOnly(Collections.singletonMap("key1", "value1"));
private final GraphQlResponse response1 = MapGraphQlResponse.forDataOnly(Collections.singletonMap("key1", "value1"));
private final ExecutionResult result2 = MapExecutionResult.forDataOnly(Collections.singletonMap("key2", "value2"));
private final GraphQlResponse response2 = MapGraphQlResponse.forDataOnly(Collections.singletonMap("key2", "value2"));
@Test
void request() {
GraphQlRequest request = this.mockServer.expectOperation("{Query1}").andRespond(this.result1);
GraphQlRequest request = this.mockServer.expectOperation("{Query1}").andRespond(this.response1);
StepVerifier.create(this.transport.execute(request))
.expectNext(this.result1).expectComplete()
.expectNext(this.response1).expectComplete()
.verify(TIMEOUT);
assertActualClientMessages(
@@ -90,10 +90,10 @@ public class MockWebSocketGraphQlTransportTests {
@Test
void requestStream() {
GraphQlRequest request = this.mockServer.expectOperation("{Sub1}").andStream(Flux.just(this.result1, result2));
GraphQlRequest request = this.mockServer.expectOperation("{Sub1}").andStream(Flux.just(this.response1, response2));
StepVerifier.create(this.transport.executeSubscription(request))
.expectNext(this.result1, result2).expectComplete()
.expectNext(this.response1, response2).expectComplete()
.verify(TIMEOUT);
assertActualClientMessages(
@@ -108,7 +108,7 @@ public class MockWebSocketGraphQlTransportTests {
StepVerifier.create(this.transport.execute(request))
.consumeNextWith(result -> {
assertThat(result.isDataPresent()).isFalse();
assertThat(result.isValid()).isFalse();
assertThat(result.getErrors()).extracting(GraphQLError::getMessage).containsExactly("boo");
})
.expectComplete()
@@ -122,10 +122,10 @@ public class MockWebSocketGraphQlTransportTests {
@Test
void requestStreamError() {
GraphQlRequest request = this.mockServer.expectOperation("{Sub1}")
.andStreamWithError(Flux.just(this.result1), GraphqlErrorBuilder.newError().message("boo").build());
.andStreamWithError(Flux.just(this.response1), GraphqlErrorBuilder.newError().message("boo").build());
StepVerifier.create(this.transport.executeSubscription(request))
.expectNext(this.result1)
.expectNext(this.response1)
.expectErrorSatisfies(actualEx -> {
List<GraphQLError> errorList = ((SubscriptionErrorException) actualEx).getErrors();
assertThat(errorList).extracting(GraphQLError::getMessage).containsExactly("boo");
@@ -154,10 +154,10 @@ public class MockWebSocketGraphQlTransportTests {
@Test
void requestStreamCancelled() {
GraphQlRequest request = this.mockServer.expectOperation("{Sub1}")
.andStream(Flux.just(this.result1).concatWith(Flux.never()));
.andStream(Flux.just(this.response1).concatWith(Flux.never()));
StepVerifier.create(this.transport.executeSubscription(request))
.expectNext(this.result1)
.expectNext(this.response1)
.thenAwait(Duration.ofMillis(200))
.thenCancel()
.verify(TIMEOUT);
@@ -171,11 +171,11 @@ public class MockWebSocketGraphQlTransportTests {
@Test
void pingHandling() {
TestWebSocketClient client = new TestWebSocketClient(new PingResponseHandler(this.result1));
TestWebSocketClient client = new TestWebSocketClient(new PingResponseHandler(this.response1));
WebSocketGraphQlTransport transport = createTransport(client);
StepVerifier.create(transport.execute(new GraphQlRequest("{Query1}")))
.expectNext(this.result1)
.expectNext(this.response1)
.expectComplete()
.verify(TIMEOUT);
@@ -219,7 +219,7 @@ public class MockWebSocketGraphQlTransportTests {
assertThat(this.webSocketClient.getConnection(0).closeStatus().block(TIMEOUT)).isEqualTo(CloseStatus.NORMAL);
// New requests are rejected
GraphQlRequest request = this.mockServer.expectOperation("{Query1}").andRespond(this.result1);
GraphQlRequest request = this.mockServer.expectOperation("{Query1}").andRespond(this.response1);
StepVerifier.create(this.transport.execute(request))
.expectErrorMessage("WebSocketGraphQlTransport has been stopped")
.verify(TIMEOUT);
@@ -230,23 +230,23 @@ public class MockWebSocketGraphQlTransportTests {
assertThat(this.webSocketClient.getConnection(1).isOpen()).isTrue();
// Requests allowed again
request = this.mockServer.expectOperation("{Query1}").andRespond(this.result1);
request = this.mockServer.expectOperation("{Query1}").andRespond(this.response1);
StepVerifier.create(this.transport.execute(request))
.expectNext(this.result1).expectComplete()
.expectNext(this.response1).expectComplete()
.verify(TIMEOUT);
}
@Test
void sessionIsCachedUntilClosed() {
GraphQlRequest request1 = this.mockServer.expectOperation("{Query1}").andRespond(this.result1);
StepVerifier.create(this.transport.execute(request1)).expectNext(this.result1).expectComplete().verify(TIMEOUT);
GraphQlRequest request1 = this.mockServer.expectOperation("{Query1}").andRespond(this.response1);
StepVerifier.create(this.transport.execute(request1)).expectNext(this.response1).expectComplete().verify(TIMEOUT);
assertThat(this.webSocketClient.getConnectionCount()).isEqualTo(1);
TestWebSocketConnection originalConnection = this.webSocketClient.getConnection(0);
GraphQlRequest request2 = this.mockServer.expectOperation("{Query2}").andRespond(this.result2);
StepVerifier.create(this.transport.execute(request2)).expectNext(this.result2).expectComplete().verify(TIMEOUT);
GraphQlRequest request2 = this.mockServer.expectOperation("{Query2}").andRespond(this.response2);
StepVerifier.create(this.transport.execute(request2)).expectNext(this.response2).expectComplete().verify(TIMEOUT);
assertThat(this.webSocketClient.getConnectionCount()).isEqualTo(1);
assertThat(this.webSocketClient.getConnection(0)).isSameAs(originalConnection);
@@ -254,8 +254,8 @@ public class MockWebSocketGraphQlTransportTests {
// Close the connection
originalConnection.closeServerSession(CloseStatus.NORMAL).block(TIMEOUT);
request1 = this.mockServer.expectOperation("{Query1}").andRespond(this.result1);
StepVerifier.create(this.transport.execute(request1)).expectNext(this.result1).expectComplete().verify(TIMEOUT);
request1 = this.mockServer.expectOperation("{Query1}").andRespond(this.response1);
StepVerifier.create(this.transport.execute(request1)).expectNext(this.response1).expectComplete().verify(TIMEOUT);
assertThat(this.webSocketClient.getConnectionCount()).isEqualTo(2);
assertThat(this.webSocketClient.getConnection(1)).isNotSameAs(originalConnection);
@@ -332,12 +332,12 @@ public class MockWebSocketGraphQlTransportTests {
*/
private static class PingResponseHandler implements WebSocketHandler {
private final ExecutionResult result;
private final GraphQlResponse response;
private final CodecDelegate codecDelegate = new CodecDelegate();
private PingResponseHandler(ExecutionResult result) {
this.result = result;
private PingResponseHandler(GraphQlResponse response) {
this.response = response;
}
@Override
@@ -349,7 +349,7 @@ public class MockWebSocketGraphQlTransportTests {
case CONNECTION_INIT:
return Flux.just(GraphQlMessage.connectionAck(null), GraphQlMessage.ping(null));
case SUBSCRIBE:
return Flux.just(GraphQlMessage.next("1", this.result));
return Flux.just(GraphQlMessage.next("1", this.response.toMap()));
case PONG:
return Flux.empty();
default:

View File

@@ -28,7 +28,7 @@ import org.junit.jupiter.params.provider.MethodSource;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.TestRequestInput;
import org.springframework.graphql.data.method.annotation.BatchMapping;
@@ -70,10 +70,10 @@ public class BatchMappingInvocationTests extends BatchMappingTestSupport {
" }" +
"}";
Mono<RequestOutput> resultMono = createGraphQlService(controller)
Mono<RequestOutput> outputMono = createGraphQlService(controller)
.execute(TestRequestInput.forDocument(query));
List<Course> actualCourses = GraphQlResponse.from(resultMono).toList("courses", Course.class);
List<Course> actualCourses = ResponseHelper.forResponse(outputMono).toList("courses", Course.class);
List<Course> courses = Course.allCourses();
assertThat(actualCourses).hasSize(courses.size());
@@ -103,10 +103,10 @@ public class BatchMappingInvocationTests extends BatchMappingTestSupport {
" }" +
"}";
Mono<RequestOutput> resultMono = createGraphQlService(controller)
Mono<RequestOutput> outputMono = createGraphQlService(controller)
.execute(TestRequestInput.forDocument(document));
List<Course> actualCourses = GraphQlResponse.from(resultMono).toList("courses", Course.class);
List<Course> actualCourses = ResponseHelper.forResponse(outputMono).toList("courses", Course.class);
List<Course> courses = Course.allCourses();
assertThat(actualCourses).hasSize(courses.size());

View File

@@ -30,7 +30,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.TestRequestInput;
import org.springframework.graphql.data.method.annotation.BatchMapping;
@@ -92,14 +92,14 @@ public class BatchMappingPrincipalMethodArgumentResolverTests extends BatchMappi
}
private void testBatchLoading(PrincipalCourseController controller, Function<Context, Context> contextWriter) {
Mono<RequestOutput> resultMono = Mono.delay(Duration.ofMillis(10))
Mono<RequestOutput> outputMono = Mono.delay(Duration.ofMillis(10))
.flatMap(aLong -> {
String document = "{ courses { id instructor { id } } }";
return createGraphQlService(controller).execute(TestRequestInput.forDocument(document));
})
.contextWrite(contextWriter);
List<Course> actualCourses = GraphQlResponse.from(resultMono).toList("courses", Course.class);
List<Course> actualCourses = ResponseHelper.forResponse(outputMono).toList("courses", Course.class);
List<Course> courses = Course.allCourses();
assertThat(actualCourses).hasSize(courses.size());
for (int i = 0; i < courses.size(); i++) {

View File

@@ -33,7 +33,7 @@ import org.springframework.graphql.Author;
import org.springframework.graphql.Book;
import org.springframework.graphql.BookCriteria;
import org.springframework.graphql.BookSource;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.RequestInput;
@@ -71,9 +71,9 @@ public class SchemaMappingInvocationTests {
" }" +
"}";
Mono<RequestOutput> resultMono = graphQlService().execute(TestRequestInput.forDocument(document));
Mono<RequestOutput> outputMono = graphQlService().execute(TestRequestInput.forDocument(document));
Book book = GraphQlResponse.from(resultMono).toEntity("bookById", Book.class);
Book book = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(book.getId()).isEqualTo(1);
assertThat(book.getName()).isEqualTo("Nineteen Eighty-Four");
@@ -91,9 +91,9 @@ public class SchemaMappingInvocationTests {
" }" +
"}";
Mono<RequestOutput> resultMono = graphQlService().execute(TestRequestInput.forDocument(document));
Mono<RequestOutput> outputMono = graphQlService().execute(TestRequestInput.forDocument(document));
List<Book> bookList = GraphQlResponse.from(resultMono).toList("booksByCriteria", Book.class);
List<Book> bookList = ResponseHelper.forResponse(outputMono).toList("booksByCriteria", Book.class);
assertThat(bookList).hasSize(2);
assertThat(bookList.get(0).getName()).isEqualTo("Nineteen Eighty-Four");
assertThat(bookList.get(1).getName()).isEqualTo("Animal Farm");
@@ -108,9 +108,9 @@ public class SchemaMappingInvocationTests {
" }" +
"}";
Mono<RequestOutput> resultMono = graphQlService().execute(TestRequestInput.forDocument(document));
Mono<RequestOutput> outputMono = graphQlService().execute(TestRequestInput.forDocument(document));
List<Book> bookList = GraphQlResponse.from(resultMono).toList("booksByProjectedArguments", Book.class);
List<Book> bookList = ResponseHelper.forResponse(outputMono).toList("booksByProjectedArguments", Book.class);
assertThat(bookList).hasSize(2);
assertThat(bookList.get(0).getName()).isEqualTo("Nineteen Eighty-Four");
assertThat(bookList.get(1).getName()).isEqualTo("Animal Farm");
@@ -125,9 +125,9 @@ public class SchemaMappingInvocationTests {
" }" +
"}";
Mono<RequestOutput> resultMono = graphQlService().execute(TestRequestInput.forDocument(document));
Mono<RequestOutput> outputMono = graphQlService().execute(TestRequestInput.forDocument(document));
List<Book> bookList = GraphQlResponse.from(resultMono).toList("booksByProjectedCriteria", Book.class);
List<Book> bookList = ResponseHelper.forResponse(outputMono).toList("booksByProjectedCriteria", Book.class);
assertThat(bookList).hasSize(2);
assertThat(bookList.get(0).getName()).isEqualTo("Nineteen Eighty-Four");
assertThat(bookList.get(1).getName()).isEqualTo("Animal Farm");
@@ -150,9 +150,9 @@ public class SchemaMappingInvocationTests {
return executionInput;
});
Mono<RequestOutput> resultMono = graphQlService().execute(requestInput);
Mono<RequestOutput> outputMono = graphQlService().execute(requestInput);
Author author = GraphQlResponse.from(resultMono).toEntity("authorById", Author.class);
Author author = ResponseHelper.forResponse(outputMono).toEntity("authorById", Author.class);
assertThat(author.getId()).isEqualTo(101);
assertThat(author.getFirstName()).isEqualTo("George");
assertThat(author.getLastName()).isEqualTo("Orwell");
@@ -170,9 +170,9 @@ public class SchemaMappingInvocationTests {
" }" +
"}";
Mono<RequestOutput> resultMono = graphQlService().execute(TestRequestInput.forDocument(document));
Mono<RequestOutput> outputMono = graphQlService().execute(TestRequestInput.forDocument(document));
Author author = GraphQlResponse.from(resultMono).toEntity("addAuthor", Author.class);
Author author = ResponseHelper.forResponse(outputMono).toEntity("addAuthor", Author.class);
assertThat(author.getId()).isEqualTo(99);
assertThat(author.getFirstName()).isEqualTo("James");
assertThat(author.getLastName()).isEqualTo("Joyce");
@@ -187,9 +187,9 @@ public class SchemaMappingInvocationTests {
" }" +
"}";
Mono<RequestOutput> resultMono = graphQlService().execute(TestRequestInput.forDocument(document));
Mono<RequestOutput> outputMono = graphQlService().execute(TestRequestInput.forDocument(document));
Flux<Book> bookFlux = GraphQlResponse.forSubscription(resultMono)
Flux<Book> bookFlux = ResponseHelper.forSubscription(outputMono)
.map(response -> response.toEntity("bookSearch", Book.class));
StepVerifier.create(bookFlux)

View File

@@ -31,7 +31,7 @@ import reactor.util.context.Context;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.TestRequestInput;
@@ -106,7 +106,7 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests {
Mono<RequestOutput> resultMono = executeAsync(
"type Query { " + field + ": String }", "{ " + field + " }", contextWriter);
String greeting = GraphQlResponse.from(resultMono).toEntity(field, String.class);
String greeting = ResponseHelper.forResponse(resultMono).toEntity(field, String.class);
assertThat(greeting).isEqualTo("Hello");
assertThat(greetingController.principal()).isSameAs(authentication);
}
@@ -141,7 +141,7 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests {
"subscription Greeting { " + field + " }",
contextModifier);
Flux<String> greetingFlux = GraphQlResponse.forSubscription(resultMono)
Flux<String> greetingFlux = ResponseHelper.forSubscription(resultMono)
.map(response -> response.toEntity(field, String.class));
StepVerifier.create(greetingFlux).expectNext("Hello", "Hi").verifyComplete();

View File

@@ -43,7 +43,7 @@ import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.Repository;
import org.springframework.graphql.Author;
import org.springframework.graphql.BookSource;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.data.GraphQlRepository;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
@@ -77,7 +77,7 @@ class QuerydslDataFetcherTests {
Consumer<GraphQlSetup> tester = setup -> {
Mono<WebOutput> output = setup.toWebGraphQlHandler().handleRequest(input("{ bookById(id: 42) {name}}"));
Book actualBook = GraphQlResponse.from(output).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(output).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo(book.getName());
};
@@ -98,7 +98,7 @@ class QuerydslDataFetcherTests {
Consumer<GraphQlSetup> tester = graphQlSetup -> {
Mono<WebOutput> output = graphQlSetup.toWebGraphQlHandler().handleRequest(input("{ books {name}}"));
List<String> names = GraphQlResponse.from(output).toList("books", Book.class)
List<String> names = ResponseHelper.forResponse(output).toList("books", Book.class)
.stream().map(Book::getName).collect(Collectors.toList());
assertThat(names).containsExactlyInAnyOrder(book1.getName(), book2.getName());
@@ -120,7 +120,7 @@ class QuerydslDataFetcherTests {
Mono<WebOutput> output = graphQlSetup(mockRepository).toWebGraphQlHandler()
.handleRequest(input("{ booksById(id: [42,53]) {name}}"));
List<String> names = GraphQlResponse.from(output).toList("booksById", Book.class)
List<String> names = ResponseHelper.forResponse(output).toList("booksById", Book.class)
.stream().map(Book::getName).collect(Collectors.toList());
assertThat(names).containsExactlyInAnyOrder(book1.getName(), book2.getName());
@@ -136,7 +136,7 @@ class QuerydslDataFetcherTests {
Consumer<GraphQlSetup> tester = graphQlSetup -> {
Mono<WebOutput> output = graphQlSetup.toWebGraphQlHandler().handleRequest(input("{ books {name}}"));
List<String> names = GraphQlResponse.from(output).toList("books", Book.class)
List<String> names = ResponseHelper.forResponse(output).toList("books", Book.class)
.stream().map(Book::getName).collect(Collectors.toList());
assertThat(names).containsExactlyInAnyOrder(book1.getName(), book2.getName());
@@ -179,7 +179,7 @@ class QuerydslDataFetcherTests {
WebGraphQlHandler handler = graphQlSetup(mockRepository).toWebGraphQlHandler();
Mono<WebOutput> outputMono = handler.handleRequest(input("{ bookById(id: 1) {name}}"));
Book actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Hitchhiker's Guide to the Galaxy");
// 2) Automatic registration and explicit wiring
@@ -189,7 +189,7 @@ class QuerydslDataFetcherTests {
outputMono = handler.handleRequest(input("{ bookById(id: 1) {name}}"));
actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Breaking Bad");
}
@@ -203,7 +203,7 @@ class QuerydslDataFetcherTests {
Mono<WebOutput> outputMono = handler.handleRequest(input("{ bookById(id: 42) {name}}"));
Book actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Hitchhiker's Guide to the Galaxy by Douglas Adams");
}
@@ -217,7 +217,7 @@ class QuerydslDataFetcherTests {
Mono<WebOutput> outputMono = handler.handleRequest(input("{ bookById(id: 42) {name}}"));
Book actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("The book is: Hitchhiker's Guide to the Galaxy");
}
@@ -229,7 +229,7 @@ class QuerydslDataFetcherTests {
Consumer<GraphQlSetup> tester = setup -> {
Mono<WebOutput> outputMono = setup.toWebGraphQlHandler().handleRequest(input("{ bookById(id: 1) {name}}"));
Book actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo(book.getName());
};
@@ -251,7 +251,7 @@ class QuerydslDataFetcherTests {
Consumer<GraphQlSetup> tester = setup -> {
Mono<WebOutput> outputMono = setup.toWebGraphQlHandler().handleRequest(input("{ books {name}}"));
List<String> names = GraphQlResponse.from(outputMono).toList("books", Book.class)
List<String> names = ResponseHelper.forResponse(outputMono).toList("books", Book.class)
.stream().map(Book::getName).collect(Collectors.toList());
assertThat(names).containsExactlyInAnyOrder("Breaking Bad", "Hitchhiker's Guide to the Galaxy");

View File

@@ -40,7 +40,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.graphql.BookSource;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.data.query.QueryByExampleDataFetcher;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
@@ -80,7 +80,7 @@ class QueryByExampleDataFetcherJpaTests {
Consumer<GraphQlSetup> tester = setup -> {
Mono<WebOutput> output = setup.toWebGraphQlHandler().handleRequest(input("{ bookById(id: 42) {name}}"));
Book actualBook = GraphQlResponse.from(output).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(output).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo(book.getName());
};
@@ -101,7 +101,7 @@ class QueryByExampleDataFetcherJpaTests {
Consumer<GraphQlSetup> tester = graphQlSetup -> {
Mono<WebOutput> output = graphQlSetup.toWebGraphQlHandler().handleRequest(input("{ books {name}}"));
List<String> names = GraphQlResponse.from(output).toList("books", Book.class)
List<String> names = ResponseHelper.forResponse(output).toList("books", Book.class)
.stream()
.map(Book::getName)
.collect(Collectors.toList());
@@ -126,7 +126,7 @@ class QueryByExampleDataFetcherJpaTests {
WebGraphQlHandler handler = graphQlSetup(mockRepository).toWebGraphQlHandler();
Mono<WebOutput> outputMono = handler.handleRequest(input("{ bookById(id: 1) {name}}"));
Book actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Hitchhiker's Guide to the Galaxy");
// 2) Automatic registration and explicit wiring
@@ -136,7 +136,7 @@ class QueryByExampleDataFetcherJpaTests {
outputMono = handler.handleRequest(input("{ bookById(id: 1) {name}}"));
actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Breaking Bad");
}
@@ -150,7 +150,7 @@ class QueryByExampleDataFetcherJpaTests {
Mono<WebOutput> outputMono = handler.handleRequest(input("{ bookById(id: 42) {name}}"));
Book actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Hitchhiker's Guide to the Galaxy by Douglas Adams");
}
@@ -165,7 +165,7 @@ class QueryByExampleDataFetcherJpaTests {
Mono<WebOutput> outputMono = handler.handleRequest(input("{ bookById(id: 42) {name}}"));
Book actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("The book is: Hitchhiker's Guide to the Galaxy");
}

View File

@@ -42,7 +42,7 @@ import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.graphql.BookSource;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.data.query.QueryByExampleDataFetcher;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
@@ -81,7 +81,7 @@ class QueryByExampleDataFetcherMongoDbTests {
Consumer<GraphQlSetup> tester = setup -> {
Mono<WebOutput> output = setup.toWebGraphQlHandler().handleRequest(input("{ bookById(id: 42) {name}}"));
Book actualBook = GraphQlResponse.from(output).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(output).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo(book.getName());
};
@@ -102,7 +102,7 @@ class QueryByExampleDataFetcherMongoDbTests {
Consumer<GraphQlSetup> tester = graphQlSetup -> {
Mono<WebOutput> output = graphQlSetup.toWebGraphQlHandler().handleRequest(input("{ books {name}}"));
List<String> names = GraphQlResponse.from(output).toList("books", Book.class)
List<String> names = ResponseHelper.forResponse(output).toList("books", Book.class)
.stream().map(Book::getName).collect(Collectors.toList());
assertThat(names).containsExactlyInAnyOrder(book1.getName(), book2.getName());
@@ -125,7 +125,7 @@ class QueryByExampleDataFetcherMongoDbTests {
WebGraphQlHandler handler = graphQlSetup(mockRepository).toWebGraphQlHandler();
Mono<WebOutput> outputMono = handler.handleRequest(input("{ bookById(id: 1) {name}}"));
Book actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Hitchhiker's Guide to the Galaxy");
// 2) Automatic registration and explicit wiring
@@ -135,7 +135,7 @@ class QueryByExampleDataFetcherMongoDbTests {
outputMono = handler.handleRequest(input("{ bookById(id: 1) {name}}"));
actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Breaking Bad");
}
@@ -149,7 +149,7 @@ class QueryByExampleDataFetcherMongoDbTests {
Mono<WebOutput> outputMono = handler.handleRequest(input("{ bookById(id: 42) {name}}"));
Book actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Hitchhiker's Guide to the Galaxy by Douglas Adams");
}
@@ -163,7 +163,7 @@ class QueryByExampleDataFetcherMongoDbTests {
Mono<WebOutput> outputMono = handler.handleRequest(input("{ bookById(id: 42) {name}}"));
Book actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("The book is: Hitchhiker's Guide to the Galaxy");
}

View File

@@ -41,7 +41,7 @@ import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
import org.springframework.graphql.BookSource;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.data.query.QueryByExampleDataFetcher;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
@@ -77,7 +77,7 @@ class QueryByExampleDataFetcherReactiveMongoDbTests {
Consumer<GraphQlSetup> tester = setup -> {
Mono<WebOutput> outputMono = setup.toWebGraphQlHandler().handleRequest(input("{ bookById(id: 42) {name}}"));
Book actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo(book.getName());
};
@@ -99,7 +99,7 @@ class QueryByExampleDataFetcherReactiveMongoDbTests {
Mono<WebOutput> outputMono = handler.handleRequest(input("{ bookById(id: 42) {name}}"));
Book actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Hitchhiker's Guide to the Galaxy by Douglas Adams");
}
@@ -113,7 +113,7 @@ class QueryByExampleDataFetcherReactiveMongoDbTests {
Mono<WebOutput> outputMono = handler.handleRequest(input("{ bookById(id: 42) {name}}"));
Book actualBook = GraphQlResponse.from(outputMono).toEntity("bookById", Book.class);
Book actualBook = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("The book is: Hitchhiker's Guide to the Galaxy");
}
@@ -126,7 +126,7 @@ class QueryByExampleDataFetcherReactiveMongoDbTests {
Consumer<GraphQlSetup> tester = setup -> {
Mono<WebOutput> outputMono = setup.toWebGraphQlHandler().handleRequest(input("{ books {name}}"));
List<String> names = GraphQlResponse.from(outputMono).toList("books", Book.class)
List<String> names = ResponseHelper.forResponse(outputMono).toList("books", Book.class)
.stream().map(Book::getName).collect(Collectors.toList());
assertThat(names).containsExactlyInAnyOrder("Breaking Bad", "Hitchhiker's Guide to the Galaxy");

View File

@@ -27,7 +27,7 @@ import reactor.core.publisher.Mono;
import org.springframework.graphql.Author;
import org.springframework.graphql.Book;
import org.springframework.graphql.BookSource;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.RequestOutput;
@@ -76,9 +76,9 @@ public class BatchLoadingTests {
.dataLoaders(this.registry)
.toGraphQlService();
Mono<RequestOutput> resultMono = service.execute(TestRequestInput.forDocument(document));
Mono<RequestOutput> outputMono = service.execute(TestRequestInput.forDocument(document));
List<Book> books = GraphQlResponse.from(resultMono).toList("booksByCriteria", Book.class);
List<Book> books = ResponseHelper.forResponse(outputMono).toList("booksByCriteria", Book.class);
assertThat(books).hasSize(2);
Author author = books.get(0).getAuthor();

View File

@@ -22,7 +22,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.TestRequestInput;
@@ -83,11 +83,11 @@ public class ClassNameTypeResolverTests {
" }" +
"}";
Mono<RequestOutput> resultMono = graphQlSetup.queryFetcher("animals", env -> animalList)
Mono<RequestOutput> outputMono = graphQlSetup.queryFetcher("animals", env -> animalList)
.toGraphQlService()
.execute(TestRequestInput.forDocument(document));
GraphQlResponse response = GraphQlResponse.from(resultMono);
ResponseHelper response = ResponseHelper.forResponse(outputMono);
for (int i = 0; i < animalList.size(); i++) {
Animal animal = animalList.get(i);
if (animal instanceof Bird) {
@@ -125,12 +125,12 @@ public class ClassNameTypeResolverTests {
ClassNameTypeResolver typeResolver = new ClassNameTypeResolver();
typeResolver.addMapping(Tree.class, "Plant");
Mono<RequestOutput> result = graphQlSetup.queryFetcher("sightings", env -> animalAndPlantList)
Mono<RequestOutput> output = graphQlSetup.queryFetcher("sightings", env -> animalAndPlantList)
.typeResolver(typeResolver)
.toGraphQlService()
.execute(TestRequestInput.forDocument(document));
GraphQlResponse response = GraphQlResponse.from(result);
ResponseHelper response = ResponseHelper.forResponse(output);
for (int i = 0; i < animalAndPlantList.size(); i++) {
Object sighting = animalAndPlantList.get(i);
if (sighting instanceof Animal) {

View File

@@ -29,7 +29,7 @@ import reactor.test.StepVerifier;
import reactor.util.context.Context;
import reactor.util.context.ContextView;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.TestThreadLocalAccessor;
@@ -56,7 +56,7 @@ public class ContextDataFetcherDecoratorTests {
ExecutionResult executionResult = graphQl.executeAsync(input).get();
String greeting = GraphQlResponse.from(executionResult).toEntity("greeting", String.class);
String greeting = ResponseHelper.forResult(executionResult).toEntity("greeting", String.class);
assertThat(greeting).isEqualTo("Hello 007");
}
@@ -76,7 +76,7 @@ public class ContextDataFetcherDecoratorTests {
ExecutionResult result = graphQl.executeAsync(input).get();
List<String> data = GraphQlResponse.from(result).toList("greetings", String.class);
List<String> data = ResponseHelper.forResult(result).toList("greetings", String.class);
assertThat(data).containsExactly("Hi 007", "Bonjour 007", "Hola 007");
}
@@ -96,7 +96,7 @@ public class ContextDataFetcherDecoratorTests {
ExecutionResult executionResult = graphQl.executeAsync(input).get();
Flux<String> greetingsFlux = GraphQlResponse.forSubscription(executionResult)
Flux<String> greetingsFlux = ResponseHelper.forSubscription(executionResult)
.map(response -> response.toEntity("greetings", String.class));
StepVerifier.create(greetingsFlux)
@@ -121,7 +121,7 @@ public class ContextDataFetcherDecoratorTests {
Mono<ExecutionResult> resultMono = Mono.delay(Duration.ofMillis(10))
.flatMap((aLong) -> Mono.fromFuture(graphQl.executeAsync(input)));
String greeting = GraphQlResponse.from(resultMono).toEntity("greeting", String.class);
String greeting = ResponseHelper.forResult(resultMono).toEntity("greeting", String.class);
assertThat(greeting).isEqualTo("Hello 007");
}
finally {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,19 +18,16 @@ package org.springframework.graphql.execution;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import graphql.schema.DataFetchingEnvironment;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import reactor.util.context.ContextView;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.TestThreadLocalAccessor;
@@ -62,7 +59,7 @@ public class ExceptionResolversExceptionHandlerTests {
ExecutionResult result = this.graphQlSetup.exceptionResolver(resolver).toGraphQl()
.executeAsync(this.input).get();
GraphQlResponse response = GraphQlResponse.from(result);
ResponseHelper response = ResponseHelper.forResult(result);
assertThat(response.errorCount()).isEqualTo(1);
assertThat(response.error(0).message()).isEqualTo("Resolved error: Invalid greeting");
assertThat(response.error(0).errorType()).isEqualTo("BAD_REQUEST");
@@ -84,7 +81,7 @@ public class ExceptionResolversExceptionHandlerTests {
ExecutionResult result = this.graphQlSetup.exceptionResolver(resolver).toGraphQl()
.executeAsync(this.input).get();
GraphQlResponse response = GraphQlResponse.from(result);
ResponseHelper response = ResponseHelper.forResult(result);
assertThat(response.errorCount()).isEqualTo(1);
assertThat(response.error(0).message()).isEqualTo("Resolved error: Invalid greeting, name=007");
}
@@ -110,7 +107,7 @@ public class ExceptionResolversExceptionHandlerTests {
Mono<ExecutionResult> result = Mono.delay(Duration.ofMillis(10)).flatMap((aLong) ->
Mono.fromFuture(this.graphQlSetup.exceptionResolver(resolver).toGraphQl().executeAsync(this.input)));
GraphQlResponse response = GraphQlResponse.from(result);
ResponseHelper response = ResponseHelper.forResult(result);
assertThat(response.errorCount()).isEqualTo(1);
assertThat(response.error(0).message()).isEqualTo("Resolved error: Invalid greeting, name=007");
}
@@ -127,7 +124,7 @@ public class ExceptionResolversExceptionHandlerTests {
ExecutionResult result = this.graphQlSetup.exceptionResolver(resolver).toGraphQl()
.executeAsync(this.input).get();
GraphQlResponse response = GraphQlResponse.from(result);
ResponseHelper response = ResponseHelper.forResult(result);
assertThat(response.errorCount()).isEqualTo(1);
assertThat(response.error(0).message()).isEqualTo("Invalid greeting");
assertThat(response.error(0).errorType()).isEqualTo("INTERNAL_ERROR");
@@ -143,7 +140,7 @@ public class ExceptionResolversExceptionHandlerTests {
.exceptionResolver((ex, env) -> Mono.just(Collections.emptyList())).toGraphQl()
.executeAsync(input).get();
String greeting = GraphQlResponse.from(result).rawValue("greeting");
String greeting = ResponseHelper.forResult(result).rawValue("greeting");
assertThat(greeting).isNull();
}

View File

@@ -25,7 +25,7 @@ import graphql.schema.DataFetcher;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.TestThreadLocalAccessor;
import org.springframework.graphql.execution.DataFetcherExceptionResolver;
@@ -63,7 +63,7 @@ public class WebGraphQlHandlerTests {
.handleRequest(webInput)
.contextWrite((context) -> context.put("name", "007"));
String greeting = GraphQlResponse.from(outputMono).toEntity("greeting", String.class);
String greeting = ResponseHelper.forResponse(outputMono).toEntity("greeting", String.class);
assertThat(greeting).isEqualTo("Hello 007");
}
@@ -82,7 +82,7 @@ public class WebGraphQlHandlerTests {
.handleRequest(webInput)
.contextWrite((cxt) -> cxt.put("name", "007"));
GraphQlResponse response = GraphQlResponse.from(outputMono);
ResponseHelper response = ResponseHelper.forResponse(outputMono);
assertThat(response.errorCount()).isEqualTo(1);
assertThat(response.error(0).message()).isEqualTo("Resolved error: Invalid greeting, name=007");
@@ -104,7 +104,7 @@ public class WebGraphQlHandlerTests {
.toWebGraphQlHandler()
.handleRequest(webInput);
String greeting = GraphQlResponse.from(outputMono).toEntity("greeting", String.class);
String greeting = ResponseHelper.forResponse(outputMono).toEntity("greeting", String.class);
assertThat(greeting).isEqualTo("Hello 007");
}
finally {
@@ -131,7 +131,7 @@ public class WebGraphQlHandlerTests {
.toWebGraphQlHandler()
.handleRequest(webInput);
GraphQlResponse response = GraphQlResponse.from(outputMono);
ResponseHelper response = ResponseHelper.forResponse(outputMono);
assertThat(response.errorCount()).isEqualTo(1);
assertThat(response.error(0).message()).isEqualTo("Resolved error: Invalid greeting, name=007");
}