Extract ExecutionGraphQl[Request|Response] interfaces

Rename the implementation classes Request[Input|Output] accordingly to
match the interfaces, and move them into the support package.

Create AbstractGraphQlResponse in the support package that
pre-implements response field access.

This leaves mainly contracts in the top-level package.

See gh-332
This commit is contained in:
rstoyanchev
2022-03-18 21:27:09 +00:00
parent be05b031d6
commit 182e9e66f0
41 changed files with 623 additions and 452 deletions

View File

@@ -23,10 +23,13 @@ import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.graphql.ExecutionGraphQlRequest;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.GraphQlResponseError;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.support.DefaultExecutionGraphQlRequest;
import org.springframework.graphql.support.DefaultExecutionGraphQlResponse;
import org.springframework.graphql.client.GraphQlTransport;
import org.springframework.test.util.AssertionErrors;
import org.springframework.util.AlternativeJdkIdGenerator;
@@ -47,22 +50,22 @@ abstract class AbstractDirectTransport implements GraphQlTransport {
@Override
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
return executeInternal(request).cast(GraphQlResponse.class);
return executeInternal(toExecutionRequest(request)).cast(GraphQlResponse.class);
}
@SuppressWarnings({"ConstantConditions", "unchecked"})
@Override
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
return executeInternal(request).flatMapMany(output -> {
return executeInternal(toExecutionRequest(request)).flatMapMany(response -> {
try {
Object data = output.getData();
Object data = response.getData();
AssertionErrors.assertTrue("Not a Publisher: " + data, data instanceof Publisher);
List<GraphQlResponseError> errors = output.getErrors();
List<GraphQlResponseError> errors = response.getErrors();
AssertionErrors.assertTrue("Subscription errors: " + errors, CollectionUtils.isEmpty(errors));
return Flux.from((Publisher<ExecutionResult>) data)
.map(result -> new RequestOutput(output.getExecutionInput(), result));
return Flux.from((Publisher<ExecutionResult>) data).map(executionResult ->
new DefaultExecutionGraphQlResponse(response.getExecutionInput(), executionResult));
}
catch (AssertionError ex) {
throw new AssertionError(ex.getMessage() + "\nRequest: " + request, ex);
@@ -70,9 +73,15 @@ abstract class AbstractDirectTransport implements GraphQlTransport {
});
}
private ExecutionGraphQlRequest toExecutionRequest(GraphQlRequest request) {
return new DefaultExecutionGraphQlRequest(
request.getDocument(), request.getOperationName(), request.getVariables(),
idGenerator.generateId().toString(), null);
}
/**
* Subclasses must implement this to execute requests.
*/
protected abstract Mono<? extends RequestOutput> executeInternal(GraphQlRequest request);
protected abstract Mono<ExecutionGraphQlResponse> executeInternal(ExecutionGraphQlRequest request);
}

View File

@@ -34,7 +34,7 @@ import com.jayway.jsonpath.TypeRef;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.graphql.DefaultGraphQlRequest;
import org.springframework.graphql.support.DefaultGraphQlRequest;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.GraphQlResponseError;

View File

@@ -19,10 +19,9 @@ package org.springframework.graphql.test.tester;
import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.ExecutionGraphQlRequest;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
import org.springframework.util.Assert;
@@ -48,13 +47,8 @@ final class GraphQlServiceTransport extends AbstractDirectTransport {
}
@Override
protected Mono<RequestOutput> executeInternal(GraphQlRequest request) {
RequestInput requestInput = new RequestInput(
request.getDocument(), request.getOperationName(), request.getVariables(),
idGenerator.generateId().toString(), null);
return this.graphQlService.execute(requestInput);
protected Mono<ExecutionGraphQlResponse> executeInternal(ExecutionGraphQlRequest request) {
return this.graphQlService.execute(request);
}
}

View File

@@ -21,6 +21,8 @@ import java.net.URI;
import reactor.core.publisher.Mono;
import org.springframework.graphql.ExecutionGraphQlRequest;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.web.WebGraphQlHandler;
import org.springframework.graphql.web.WebInput;
@@ -75,9 +77,9 @@ final class WebGraphQlHandlerTransport extends AbstractDirectTransport {
@Override
protected Mono<WebOutput> executeInternal(GraphQlRequest request) {
return this.graphQlHandler.handleRequest(
new WebInput(this.url, this.headers, request.toMap(), idGenerator.generateId().toString(), null));
protected Mono<ExecutionGraphQlResponse> executeInternal(ExecutionGraphQlRequest request) {
WebInput input = new WebInput(this.url, this.headers, request.toMap(), idGenerator.generateId().toString(), null);
return this.graphQlHandler.handleRequest(input).cast(ExecutionGraphQlResponse.class);
}
}

View File

@@ -48,13 +48,13 @@ public class GraphQlTesterBuilderTests extends GraphQlTesterTestSupport {
GraphQlTester tester = builder.build();
tester.documentName("name").execute();
assertThat(requestInput().getDocument()).isEqualTo(DOCUMENT);
assertThat(request().getDocument()).isEqualTo(DOCUMENT);
// Mutate
tester = tester.mutate().build();
tester.documentName("name").execute();
assertThat(requestInput().getDocument()).isEqualTo(DOCUMENT);
assertThat(request().getDocument()).isEqualTo(DOCUMENT);
}
@Test
@@ -74,7 +74,7 @@ public class GraphQlTesterBuilderTests extends GraphQlTesterTestSupport {
.errors().verify()
.path("me").pathDoesNotExist();
assertThat(requestInput().getDocument()).contains(document);
assertThat(request().getDocument()).contains(document);
}
}

View File

@@ -29,9 +29,9 @@ import graphql.GraphQLError;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;
import org.springframework.graphql.ExecutionGraphQlRequest;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.support.DefaultExecutionGraphQlResponse;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
@@ -46,7 +46,7 @@ public class GraphQlTesterTestSupport {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final ArgumentCaptor<RequestInput> inputCaptor = ArgumentCaptor.forClass(RequestInput.class);
private final ArgumentCaptor<ExecutionGraphQlRequest> requestCaptor = ArgumentCaptor.forClass(ExecutionGraphQlRequest.class);
private final GraphQlService graphQlService = mock(GraphQlService.class);
@@ -63,8 +63,8 @@ public class GraphQlTesterTestSupport {
return this.graphQlTesterBuilder;
}
protected RequestInput requestInput() {
return this.inputCaptor.getValue();
protected ExecutionGraphQlRequest request() {
return this.requestCaptor.getValue();
}
@@ -83,8 +83,8 @@ public class GraphQlTesterTestSupport {
ExecutionInput executionInput = ExecutionInput.newExecutionInput("{}").build();
ExecutionResult result = builder.build();
given(this.graphQlService.execute(this.inputCaptor.capture()))
.willReturn(Mono.just(new RequestOutput(executionInput, result)));
given(this.graphQlService.execute(this.requestCaptor.capture()))
.willReturn(Mono.just(new DefaultExecutionGraphQlResponse(executionInput, result)));
}
private void serialize(String data, ExecutionResultImpl.Builder builder) {

View File

@@ -26,8 +26,8 @@ import graphql.language.SourceLocation;
import org.junit.jupiter.api.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.graphql.ExecutionGraphQlRequest;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.RequestInput;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -51,7 +51,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
response.path("me.friends").pathExists().valueExists();
response.path("hero").pathDoesNotExist().valueDoesNotExist();
assertThat(requestInput().getDocument()).contains(document);
assertThat(request().getDocument()).contains(document);
}
@Test
@@ -69,7 +69,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
.as("Path does not even exist")
.hasMessageContaining("No value at JSON path \"$['data']['hero']");
assertThat(requestInput().getDocument()).contains(document);
assertThat(request().getDocument()).contains(document);
}
@Test
@@ -88,7 +88,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
.as("Extended fields should fail in strict mode")
.hasMessageContaining("Unexpected: name");
assertThat(requestInput().getDocument()).contains(document);
assertThat(request().getDocument()).contains(document);
}
@Test
@@ -118,7 +118,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
.entity(new ParameterizedTypeReference<Map<String, MovieCharacter>>() {})
.isEqualTo(Collections.singletonMap("me", luke));
assertThat(requestInput().getDocument()).contains(document);
assertThat(request().getDocument()).contains(document);
}
@Test
@@ -153,7 +153,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
.entityList(new ParameterizedTypeReference<MovieCharacter>() {})
.containsExactly(han, leia);
assertThat(requestInput().getDocument()).contains(document);
assertThat(request().getDocument()).contains(document);
}
@Test
@@ -176,13 +176,13 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
response.path("hero").entity(MovieCharacter.class).isEqualTo(MovieCharacter.create("R2-D2"));
RequestInput input = requestInput();
assertThat(input.getDocument()).contains(document);
assertThat(input.getOperationName()).isEqualTo("HeroNameAndFriends");
assertThat(input.getVariables()).hasSize(3);
assertThat(input.getVariables()).containsEntry("episode", "JEDI");
assertThat(input.getVariables()).containsEntry("foo", "bar");
assertThat(input.getVariables()).containsEntry("keyOnly", null);
ExecutionGraphQlRequest request = request();
assertThat(request.getDocument()).contains(document);
assertThat(request.getOperationName()).isEqualTo("HeroNameAndFriends");
assertThat(request.getVariables()).hasSize(3);
assertThat(request.getVariables()).containsEntry("episode", "JEDI");
assertThat(request.getVariables()).containsEntry("foo", "bar");
assertThat(request.getVariables()).containsEntry("keyOnly", null);
}
@Test
@@ -193,7 +193,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
graphQlTester().document(document).executeAndVerify();
assertThat(requestInput().getDocument()).contains(document);
assertThat(request().getDocument()).contains(document);
}
@Test
@@ -205,7 +205,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
assertThatThrownBy(() -> graphQlTester().document(document).executeAndVerify())
.hasMessageContaining("Response has 1 unexpected error(s)");
assertThat(requestInput().getDocument()).contains(document);
assertThat(request().getDocument()).contains(document);
}
@Test
@@ -217,7 +217,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
assertThatThrownBy(() -> graphQlTester().document(document).execute().path("me"))
.hasMessageContaining("Response has 1 unexpected error(s)");
assertThat(requestInput().getDocument()).contains(document);
assertThat(request().getDocument()).contains(document);
}
@Test
@@ -236,7 +236,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
.verify())
.hasMessageContaining("Response has 1 unexpected error(s) of 2 total.");
assertThat(requestInput().getDocument()).contains(document);
assertThat(request().getDocument()).contains(document);
}
@Test
@@ -255,7 +255,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
.path("me")
.pathDoesNotExist();
assertThat(requestInput().getDocument()).contains(document);
assertThat(request().getDocument()).contains(document);
}
@Test
@@ -273,7 +273,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
.verify()
.path("me").pathDoesNotExist();
assertThat(requestInput().getDocument()).contains(document);
assertThat(request().getDocument()).contains(document);
}
@Test
@@ -312,7 +312,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
})
.path("me").pathDoesNotExist();
assertThat(requestInput().getDocument()).contains(document);
assertThat(request().getDocument()).contains(document);
}
}

View File

@@ -34,7 +34,8 @@ import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.DecodingException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.support.DefaultExecutionGraphQlResponse;
import org.springframework.graphql.support.DocumentSource;
import org.springframework.graphql.web.TestWebSocketClient;
import org.springframework.graphql.web.TestWebSocketConnection;
@@ -210,11 +211,11 @@ public class WebGraphQlTesterBuilderTests {
private WebInput webInput;
private final Map<String, RequestOutput> responses = new HashMap<>();
private final Map<String, ExecutionGraphQlResponse> responses = new HashMap<>();
public WebBuilderSetup() {
RequestOutput defaultResponse = new RequestOutput(
ExecutionGraphQlResponse defaultResponse = new DefaultExecutionGraphQlResponse(
ExecutionInput.newExecutionInput().query(DOCUMENT).build(),
ExecutionResultImpl.newExecutionResult().build());
@@ -227,11 +228,11 @@ public class WebGraphQlTesterBuilderTests {
}
protected WebGraphQlHandler webGraphQlHandler() {
return WebGraphQlHandler.builder(requestInput -> {
String document = requestInput.getDocument();
RequestOutput output = this.responses.get(document);
Assert.notNull(output, "Unexpected request: " + document);
return Mono.just(output);
return WebGraphQlHandler.builder(request -> {
String document = request.getDocument();
ExecutionGraphQlResponse response = this.responses.get(document);
Assert.notNull(response, "Unexpected request: " + document);
return Mono.just(response);
})
.interceptor((input, chain) -> {
this.webInput = input;
@@ -243,7 +244,7 @@ public class WebGraphQlTesterBuilderTests {
@Override
public void setMockResponse(String document, ExecutionResult result) {
ExecutionInput executionInput = ExecutionInput.newExecutionInput().query(document).build();
this.responses.put(document, new RequestOutput(executionInput, result));
this.responses.put(document, new DefaultExecutionGraphQlResponse(executionInput, result));
}
@Override

View File

@@ -1,182 +0,0 @@
/*
* 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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Default implementation of {@link GraphQlResponseField}.
*
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public class DefaultGraphQlResponseField implements GraphQlResponseField {
private final GraphQlResponse response;
private final String path;
private final List<Object> parsedPath;
@Nullable
private final Object value;
private final List<GraphQlResponseError> fieldErrors;
protected DefaultGraphQlResponseField(GraphQlResponse response, String path) {
this.response = response;
this.path = path;
this.parsedPath = parsePath(path);
this.value = initFieldValue(this.parsedPath, response);
this.fieldErrors = initFieldErrors(path, response);
}
private static List<Object> parsePath(String path) {
if (!StringUtils.hasText(path)) {
return Collections.emptyList();
}
String invalidPathMessage = "Invalid path: '" + path + "'";
List<Object> dataPath = new ArrayList<>();
StringBuilder sb = new StringBuilder();
boolean readingIndex = false;
for (int i = 0; i < path.length(); i++) {
char c = path.charAt(i);
switch (c) {
case '.':
case '[':
Assert.isTrue(!readingIndex, invalidPathMessage);
break;
case ']':
i++;
Assert.isTrue(readingIndex, invalidPathMessage);
Assert.isTrue(i == path.length() || path.charAt(i) == '.', invalidPathMessage);
break;
default:
sb.append(c);
if (i < path.length() - 1) {
continue;
}
}
String token = sb.toString();
Assert.hasText(token, invalidPathMessage);
dataPath.add(readingIndex ? Integer.parseInt(token) : token);
sb.delete(0, sb.length());
readingIndex = (c == '[');
}
return dataPath;
}
@Nullable
private static Object initFieldValue(List<Object> path, GraphQlResponse response) {
Object value = (response.isValid() ? response.getData() : null);
for (Object segment : path) {
if (value == null) {
return null;
}
if (segment instanceof String) {
Assert.isTrue(value instanceof Map, () -> "Invalid path " + path + ", data: " + response.getData());
value = ((Map<?, ?>) value).getOrDefault(segment, null);
}
else {
Assert.isTrue(value instanceof List, () -> "Invalid path " + path + ", data: " + response.getData());
int index = (int) segment;
value = (index < ((List<?>) value).size() ? ((List<?>) value).get(index) : null);
}
}
return value;
}
/**
* Return field errors whose path starts with the given field path.
* @param path the field path to match
* @return errors whose path starts with the dataPath
*/
private static List<GraphQlResponseError> initFieldErrors(String path, GraphQlResponse response) {
if (path.isEmpty() || response.getErrors().isEmpty()) {
return Collections.emptyList();
}
return response.getErrors().stream()
.filter(error -> {
String errorPath = error.getPath();
return !errorPath.isEmpty() && (errorPath.startsWith(path) || path.startsWith(errorPath));
})
.collect(Collectors.toList());
}
@SuppressWarnings("unchecked")
protected <R extends GraphQlResponse> R getResponse() {
return (R) this.response;
}
@Override
public String getPath() {
return this.path;
}
@Override
public List<Object> getParsedPath() {
return this.parsedPath;
}
@Override
public boolean hasValue() {
return (this.value != null);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getValue() {
return (T) this.value;
}
@Override
public GraphQlResponseError getError() {
if (!hasValue()) {
if (!this.fieldErrors.isEmpty()) {
return this.fieldErrors.get(0);
}
if (!this.response.getErrors().isEmpty()) {
return this.response.getErrors().get(0);
}
// No errors, set to null by DataFetcher
}
return null;
}
@Override
public List<GraphQlResponseError> getErrors() {
return this.fieldErrors;
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.Locale;
import java.util.function.BiFunction;
import graphql.ExecutionInput;
import graphql.execution.ExecutionId;
import org.springframework.lang.Nullable;
/**
* Implementation of {@link GraphQlRequest} for request handling through GraphQL
* Java with support for customizing the {@link ExecutionInput} passed into
* {@link graphql.GraphQL}.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @since 1.0.0
*/
public interface ExecutionGraphQlRequest extends GraphQlRequest {
/**
* Return the transport assigned id for the request that in turn sets
* {@link ExecutionInput.Builder#executionId(ExecutionId) executionId}.
* <p>By default, the id is initialized as follows:
* <ul>
* <li>On WebFlux, this is the {@code ServerHttpRequest} id which correlates
* to WebFlux log messages. For Reactor Netty, it also correlates to server
* log messages.
* <li>On Spring MVC, the id is generated via
* {@link org.springframework.util.AlternativeJdkIdGenerator}, which does
* not correlate to anything, but is more efficient than the default
* {@link graphql.execution.ExecutionIdProvider} which relies on
* {@code UUID.randomUUID()}.
* <li>On WebSocket, the id is set to the message id of the {@code "subscribe"}
* message from the GraphQL over WebSocket protocol that is used to correlate
* request and response messages on the the WebSocket.
* </ul>
* <p>To override this id, use {@link #executionId(ExecutionId)} or configure
* {@link graphql.GraphQL} with an {@link graphql.execution.ExecutionIdProvider}.
* @return the request id
*/
String getId();
/**
* Configure the {@link ExecutionId} to set on
* {@link ExecutionInput#getExecutionId()}, overriding the transport assigned
* {@link #getId() id}.
* @param executionId the id to use
*/
void executionId(ExecutionId executionId);
/**
* Return the configured {@link #executionId(ExecutionId) executionId}.
*/
@Nullable
ExecutionId getExecutionId();
/**
* Return the transport assigned locale value, if any.
*/
@Nullable
Locale getLocale();
/**
* Provide a {@code BiFunction} to help initialize the {@link ExecutionInput}
* passed to {@link graphql.GraphQL}. The {@code ExecutionInput} is first
* pre-populated with values from "this" {@code RequestInput}, and is then
* customized with the functions provided here.
* @param configurer a {@code BiFunction} that accepts the
* {@code ExecutionInput} initialized so far, and a builder to customize it.
*/
void configureExecutionInput(BiFunction<ExecutionInput, ExecutionInput.Builder, ExecutionInput> configurer);
/**
* Create the {@link ExecutionInput} to pass to {@link graphql.GraphQL}.
* passed to {@link graphql.GraphQL}. The {@code ExecutionInput} is populated
* with values from "this" {@code RequestInput}, and then customized with
* functions provided via {@link #configureExecutionInput(BiFunction)}.
* @return the resulting {@code ExecutionInput}
*/
ExecutionInput toExecutionInput();
}

View File

@@ -0,0 +1,45 @@
/*
* 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 graphql.ExecutionInput;
import graphql.ExecutionResult;
/**
* Implementation of {@link GraphQlResponse} that wraps the {@link ExecutionResult}
* returned from {@link graphql.GraphQL} to expose it as {@link GraphQlResponse},
* also providing access to the {@link ExecutionInput} used for the request.
*
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public interface ExecutionGraphQlResponse extends GraphQlResponse {
/**
* Return the {@link ExecutionInput} that was prepared through the
* {@link ExecutionGraphQlRequest} and passed to {@link graphql.GraphQL}.
*/
ExecutionInput getExecutionInput();
/**
* Return the {@link ExecutionResult} that was returned from the invocation
* to {@link graphql.GraphQL}.
*/
ExecutionResult getExecutionResult();
}

View File

@@ -81,9 +81,7 @@ public interface GraphQlResponse {
* decode its value; use {@link GraphQlResponseField#hasValue()} to check if
* the field actually exists and has a value.
*/
default GraphQlResponseField field(String path) {
return new DefaultGraphQlResponseField(this, path);
}
GraphQlResponseField field(String path);
/**
* Return implementor specific, protocol extensions, if any.

View File

@@ -19,7 +19,7 @@ package org.springframework.graphql;
import reactor.core.publisher.Mono;
/**
* Strategy to execute a GraphQL request.
* Strategy to execute a GraphQL request by inoking GraphQL Java.
*
* @author Rossen Stoyanchev
* @since 1.0.0
@@ -27,10 +27,10 @@ import reactor.core.publisher.Mono;
public interface GraphQlService {
/**
* Execute the GraphQL request and return the result.
* @param input container for GraphQL request input
* @return the result from execution
* Execute the request and return the response.
* @param request the request to execute
* @return the resulting response
*/
Mono<RequestOutput> execute(RequestInput input);
Mono<ExecutionGraphQlResponse> execute(ExecutionGraphQlRequest request);
}

View File

@@ -65,7 +65,7 @@ final class DefaultClientGraphQlResponse extends MapGraphQlResponse implements C
@Override
public ClientGraphQlResponseField field(String path) {
return new DefaultClientGraphQlResponseField(this, path);
return new DefaultClientGraphQlResponseField(this, super.field(path));
}
@Override

View File

@@ -27,25 +27,63 @@ import org.springframework.core.codec.Encoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.graphql.DefaultGraphQlResponseField;
import org.springframework.graphql.GraphQlResponseError;
import org.springframework.graphql.GraphQlResponseField;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
/**
* Default implementation of {@link ClientGraphQlResponseField}.
* Default implementation of {@link ClientGraphQlResponseField} that wraps the
* field from {@link org.springframework.graphql.GraphQlResponse} and adds
* support for decoding.
*
* @author Rossen Stoyanchev
* @since 1.0.0
*/
final class DefaultClientGraphQlResponseField extends DefaultGraphQlResponseField implements ClientGraphQlResponseField {
final class DefaultClientGraphQlResponseField implements ClientGraphQlResponseField {
private final DefaultClientGraphQlResponse response;
private final GraphQlResponseField field;
DefaultClientGraphQlResponseField(DefaultClientGraphQlResponse response, String path) {
super(response, path);
DefaultClientGraphQlResponseField(DefaultClientGraphQlResponse response, GraphQlResponseField field) {
this.response = response;
this.field = field;
}
@Override
public boolean hasValue() {
return this.field.hasValue();
}
@Override
public String getPath() {
return this.field.getPath();
}
@Override
public List<Object> getParsedPath() {
return this.field.getParsedPath();
}
@Override
public <T> T getValue() {
return this.field.getValue();
}
@Override
public GraphQlResponseError getError() {
return this.field.getError();
}
@Override
public List<GraphQlResponseError> getErrors() {
return this.field.getErrors();
}
@Override
public <D> D toEntity(Class<D> entityType) {
return toEntity(ResolvableType.forType(entityType));
@@ -68,19 +106,18 @@ final class DefaultClientGraphQlResponseField extends DefaultGraphQlResponseFiel
@SuppressWarnings({"unchecked", "ConstantConditions"})
private <T> T toEntity(ResolvableType targetType) {
DefaultClientGraphQlResponse response = getResponse();
if (!hasValue()) {
throw new FieldAccessException(response, this);
throw new FieldAccessException(this.response, this);
}
DataBufferFactory bufferFactory = DefaultDataBufferFactory.sharedInstance;
MimeType mimeType = MimeTypeUtils.APPLICATION_JSON;
Map<String, Object> hints = Collections.emptyMap();
DataBuffer buffer = ((Encoder<T>) response.getEncoder()).encodeValue(
DataBuffer buffer = ((Encoder<T>) this.response.getEncoder()).encodeValue(
(T) getValue(), bufferFactory, ResolvableType.forInstance(getValue()), mimeType, hints);
return ((Decoder<T>) response.getDecoder()).decode(buffer, targetType, mimeType, hints);
return ((Decoder<T>) this.response.getDecoder()).decode(buffer, targetType, mimeType, hints);
}
}

View File

@@ -27,7 +27,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.Encoder;
import org.springframework.graphql.DefaultGraphQlRequest;
import org.springframework.graphql.support.DefaultGraphQlRequest;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.support.DocumentSource;

View File

@@ -27,6 +27,7 @@ import graphql.language.SourceLocation;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.GraphQlResponseError;
import org.springframework.graphql.support.AbstractGraphQlResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -37,7 +38,7 @@ import org.springframework.util.ObjectUtils;
* @author Rossen Stoyanchev
* @since 1.0.0
*/
class MapGraphQlResponse implements GraphQlResponse {
class MapGraphQlResponse extends AbstractGraphQlResponse implements GraphQlResponse {
private final Map<String, Object> responseMap;

View File

@@ -27,9 +27,10 @@ import graphql.execution.ExecutionIdProvider;
import org.dataloader.DataLoaderRegistry;
import reactor.core.publisher.Mono;
import org.springframework.graphql.ExecutionGraphQlRequest;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.support.DefaultExecutionGraphQlResponse;
/**
* {@link GraphQlService} that uses a {@link GraphQlSource} to obtain a
@@ -69,16 +70,16 @@ public class ExecutionGraphQlService implements GraphQlService {
@Override
public final Mono<RequestOutput> execute(RequestInput requestInput) {
public final Mono<ExecutionGraphQlResponse> execute(ExecutionGraphQlRequest request) {
return Mono.deferContextual((contextView) -> {
if (!this.isDefaultExecutionIdProvider && requestInput.getExecutionId() == null) {
requestInput.configureExecutionInput(RESET_EXECUTION_ID_CONFIGURER);
if (!this.isDefaultExecutionIdProvider && request.getExecutionId() == null) {
request.configureExecutionInput(RESET_EXECUTION_ID_CONFIGURER);
}
ExecutionInput executionInput = requestInput.toExecutionInput();
ExecutionInput executionInput = request.toExecutionInput();
ReactorContextManager.setReactorContext(contextView, executionInput);
ExecutionInput updatedExecutionInput = registerDataLoaders(executionInput);
return Mono.fromFuture(this.graphQlSource.graphQl().executeAsync(updatedExecutionInput))
.map(result -> new RequestOutput(updatedExecutionInput, result));
.map(result -> new DefaultExecutionGraphQlResponse(updatedExecutionInput, result));
});
}

View File

@@ -0,0 +1,195 @@
/*
* 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.support;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.GraphQlResponseError;
import org.springframework.graphql.GraphQlResponseField;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Base class for {@link GraphQlResponse} that pre-implements the ability to
* access a {@link GraphQlResponseField}.
*
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public abstract class AbstractGraphQlResponse implements GraphQlResponse {
@Override
public GraphQlResponseField field(String path) {
return new DefaultGraphQlResponseField(this, path);
}
/**
* Default implementation of {@link GraphQlResponseField}.
*/
private static class DefaultGraphQlResponseField implements GraphQlResponseField {
private final GraphQlResponse response;
private final String path;
private final List<Object> parsedPath;
@Nullable
private final Object value;
private final List<GraphQlResponseError> fieldErrors;
DefaultGraphQlResponseField(GraphQlResponse response, String path) {
this.response = response;
this.path = path;
this.parsedPath = parsePath(path);
this.value = initFieldValue(this.parsedPath, response);
this.fieldErrors = initFieldErrors(path, response);
}
private static List<Object> parsePath(String path) {
if (!StringUtils.hasText(path)) {
return Collections.emptyList();
}
String invalidPathMessage = "Invalid path: '" + path + "'";
List<Object> dataPath = new ArrayList<>();
StringBuilder sb = new StringBuilder();
boolean readingIndex = false;
for (int i = 0; i < path.length(); i++) {
char c = path.charAt(i);
switch (c) {
case '.':
case '[':
Assert.isTrue(!readingIndex, invalidPathMessage);
break;
case ']':
i++;
Assert.isTrue(readingIndex, invalidPathMessage);
Assert.isTrue(i == path.length() || path.charAt(i) == '.', invalidPathMessage);
break;
default:
sb.append(c);
if (i < path.length() - 1) {
continue;
}
}
String token = sb.toString();
Assert.hasText(token, invalidPathMessage);
dataPath.add(readingIndex ? Integer.parseInt(token) : token);
sb.delete(0, sb.length());
readingIndex = (c == '[');
}
return dataPath;
}
@Nullable
private static Object initFieldValue(List<Object> path, GraphQlResponse response) {
Object value = (response.isValid() ? response.getData() : null);
for (Object segment : path) {
if (value == null) {
return null;
}
if (segment instanceof String) {
Assert.isTrue(value instanceof Map, () -> "Invalid path " + path + ", data: " + response.getData());
value = ((Map<?, ?>) value).getOrDefault(segment, null);
}
else {
Assert.isTrue(value instanceof List, () -> "Invalid path " + path + ", data: " + response.getData());
int index = (int) segment;
value = (index < ((List<?>) value).size() ? ((List<?>) value).get(index) : null);
}
}
return value;
}
/**
* Return field errors whose path starts with the given field path.
* @param path the field path to match
* @return errors whose path starts with the dataPath
*/
private static List<GraphQlResponseError> initFieldErrors(String path, GraphQlResponse response) {
if (path.isEmpty() || response.getErrors().isEmpty()) {
return Collections.emptyList();
}
return response.getErrors().stream()
.filter(error -> {
String errorPath = error.getPath();
return !errorPath.isEmpty() && (errorPath.startsWith(path) || path.startsWith(errorPath));
})
.collect(Collectors.toList());
}
@Override
public String getPath() {
return this.path;
}
@Override
public List<Object> getParsedPath() {
return this.parsedPath;
}
@Override
public boolean hasValue() {
return (this.value != null);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getValue() {
return (T) this.value;
}
@Override
public GraphQlResponseError getError() {
if (!hasValue()) {
if (!this.fieldErrors.isEmpty()) {
return this.fieldErrors.get(0);
}
if (!this.response.getErrors().isEmpty()) {
return this.response.getErrors().get(0);
}
// No errors, set to null by DataFetcher
}
return null;
}
@Override
public List<GraphQlResponseError> getErrors() {
return this.fieldErrors;
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.graphql;
package org.springframework.graphql.support;
import java.util.ArrayList;
import java.util.List;
@@ -25,6 +25,8 @@ import java.util.function.BiFunction;
import graphql.ExecutionInput;
import graphql.execution.ExecutionId;
import org.springframework.graphql.ExecutionGraphQlRequest;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -41,7 +43,7 @@ import org.springframework.util.Assert;
* @author Brian Clozel
* @since 1.0.0
*/
public class RequestInput extends DefaultGraphQlRequest {
public class DefaultExecutionGraphQlRequest extends DefaultGraphQlRequest implements ExecutionGraphQlRequest {
private final String id;
@@ -62,7 +64,7 @@ public class RequestInput extends DefaultGraphQlRequest {
* @param id the request id, to be used as the {@link ExecutionId}
* @param locale the locale associated with the request
*/
public RequestInput(
public DefaultExecutionGraphQlRequest(
String document, @Nullable String operationName, @Nullable Map<String, Object> variables,
String id, @Nullable Locale locale) {
@@ -73,76 +75,35 @@ public class RequestInput extends DefaultGraphQlRequest {
}
/**
* Return the transport assigned id for the request which is then used to set
* {@link ExecutionInput.Builder#executionId(ExecutionId) executionId}.
* The is initialized as follows:
* <ul>
* <li>For WebFlux, this is the {@code ServerHttpRequest} id which correlates
* to WebFlux log messages. For Reactor Netty, it also correlates to server
* log messages.
* <li>For Spring MVC, the id is generated via
* {@link org.springframework.util.AlternativeJdkIdGenerator}, which does
* not correlate to anything, but is more efficient than the default
* {@link graphql.execution.ExecutionIdProvider} which relies on
* {@code UUID.randomUUID()}.
* <li>For WebSocket, this is the GraphQL over WebSocket {@code "subscribe"}
* message id, which correlates to WebSocket messages.
* </ul>
* <p>To override this id, use {@link #executionId(ExecutionId)} or configure
* {@link graphql.GraphQL} with an {@link graphql.execution.ExecutionIdProvider}.
* @return the request id
*/
@Override
public String getId() {
return this.id;
}
/**
* Configure the {@link ExecutionId} to set on
* {@link ExecutionInput#getExecutionId()}, overriding the transport assigned
* {@link #getId() id}.
* @param executionId the id to use
*/
@Override
public void executionId(ExecutionId executionId) {
Assert.notNull(executionId, "executionId is required");
this.executionId = executionId;
}
/**
* Return the configured {@link #executionId(ExecutionId) executionId}.
*/
@Override
@Nullable
public ExecutionId getExecutionId() {
return this.executionId;
}
/**
* Return the transport assigned locale value, if any.
*/
@Override
@Nullable
public Locale getLocale() {
return this.locale;
}
/**
* Provide a {@code BiFunction} to help initialize the {@link ExecutionInput}
* passed to {@link graphql.GraphQL}. The {@code ExecutionInput} is first
* pre-populated with values from "this" {@code RequestInput}, and is then
* customized with the functions provided here.
* @param configurer a {@code BiFunction} that accepts the
* {@code ExecutionInput} initialized so far, and a builder to customize it.
*/
@Override
public void configureExecutionInput(BiFunction<ExecutionInput, ExecutionInput.Builder, ExecutionInput> configurer) {
this.executionInputConfigurers.add(configurer);
}
/**
* Create the {@link ExecutionInput} to pass to {@link graphql.GraphQL}.
* passed to {@link graphql.GraphQL}. The {@code ExecutionInput} is populated
* with values from "this" {@code RequestInput}, and then customized with
* functions provided via {@link #configureExecutionInput(BiFunction)}.
* @return the resulting {@code ExecutionInput}
*/
@Override
public ExecutionInput toExecutionInput() {
ExecutionInput.Builder inputBuilder = ExecutionInput.newExecutionInput()
.query(getDocument())

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql;
package org.springframework.graphql.support;
import java.util.Collections;
import java.util.List;
@@ -26,6 +26,9 @@ import graphql.ExecutionResult;
import graphql.GraphQLError;
import graphql.language.SourceLocation;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.GraphQlResponseError;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -37,7 +40,7 @@ import org.springframework.util.Assert;
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public class RequestOutput implements GraphQlResponse {
public class DefaultExecutionGraphQlResponse extends AbstractGraphQlResponse implements ExecutionGraphQlResponse {
private final ExecutionInput input;
@@ -47,9 +50,9 @@ public class RequestOutput implements GraphQlResponse {
/**
* Constructor to create initial instance.
*/
public RequestOutput(ExecutionInput input, ExecutionResult result) {
Assert.notNull(input, "ExecutionInput is required.");
Assert.notNull(result, "ExecutionResult is required.");
public DefaultExecutionGraphQlResponse(ExecutionInput input, ExecutionResult result) {
Assert.notNull(input, "ExecutionInput is required");
Assert.notNull(result, "ExecutionResult is required");
this.input = input;
this.result = result;
}
@@ -57,20 +60,18 @@ public class RequestOutput implements GraphQlResponse {
/**
* Constructor to re-wrap from transport specific subclass.
*/
protected RequestOutput(RequestOutput requestOutput) {
this(requestOutput.getExecutionInput(), requestOutput.result);
protected DefaultExecutionGraphQlResponse(ExecutionGraphQlResponse response) {
this(response.getExecutionInput(), response.getExecutionResult());
}
/**
* Return the {@link ExecutionInput} that was prepared from the
* {@link RequestInput} and passed to {@link graphql.GraphQL}.
*/
@Override
public ExecutionInput getExecutionInput() {
return this.input;
}
protected ExecutionResult getExecutionResult() {
@Override
public ExecutionResult getExecutionResult() {
return this.result;
}
@@ -85,10 +86,12 @@ public class RequestOutput implements GraphQlResponse {
return this.result.getData();
}
@Override
public List<GraphQlResponseError> getErrors() {
return this.result.getErrors().stream().map(Error::new).collect(Collectors.toList());
}
@Override
public Map<Object, Object> getExtensions() {
return (this.result.getExtensions() != null ? this.result.getExtensions() : Collections.emptyMap());
}
@@ -150,5 +153,4 @@ public class RequestOutput implements GraphQlResponse {
}
}

View File

@@ -14,12 +14,13 @@
* limitations under the License.
*/
package org.springframework.graphql;
package org.springframework.graphql.support;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

View File

@@ -20,7 +20,7 @@ import java.net.URI;
import java.util.Locale;
import java.util.Map;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.support.DefaultExecutionGraphQlRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -38,7 +38,7 @@ import org.springframework.web.util.UriComponentsBuilder;
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public class WebInput extends RequestInput {
public class WebInput extends DefaultExecutionGraphQlRequest {
private final UriComponents uri;

View File

@@ -20,15 +20,14 @@ import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.GraphQLError;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.support.DefaultExecutionGraphQlResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Decorate an {@link ExecutionResult}, provide a way to {@link #transform(Consumer)
@@ -38,24 +37,23 @@ import org.springframework.util.Assert;
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public class WebOutput extends RequestOutput {
public class WebOutput extends DefaultExecutionGraphQlResponse {
private final HttpHeaders responseHeaders;
/**
* Create an instance from the given {@link RequestOutput}.
* @param requestOutput the output from an executed request
* Create an instance that wraps the given {@link ExecutionGraphQlResponse}.
* @param response the response to wrap
*/
public WebOutput(RequestOutput requestOutput) {
super(requestOutput);
public WebOutput(ExecutionGraphQlResponse response) {
super(response);
this.responseHeaders = new HttpHeaders();
}
private WebOutput(ExecutionInput executionInput, ExecutionResult executionResult, HttpHeaders headers) {
super(executionInput, executionResult);
Assert.notNull(headers, "HttpHeaders is required");
this.responseHeaders = headers;
private WebOutput(WebOutput original, ExecutionResult executionResult) {
super(original.getExecutionInput(), executionResult);
this.responseHeaders = original.getResponseHeaders();
}
@@ -90,11 +88,11 @@ public class WebOutput extends RequestOutput {
private final WebOutput original;
private final ExecutionResultImpl.Builder builder;
private final ExecutionResultImpl.Builder executionResultBuilder;
private Builder(WebOutput original) {
this.original = original;
this.builder = ExecutionResultImpl.newExecutionResult().from(original.getExecutionResult());
this.executionResultBuilder = ExecutionResultImpl.newExecutionResult().from(original.getExecutionResult());
}
/**
@@ -103,7 +101,7 @@ public class WebOutput extends RequestOutput {
* @return the current builder
*/
public Builder data(Object data) {
this.builder.data(data);
this.executionResultBuilder.data(data);
return this;
}
@@ -114,7 +112,7 @@ public class WebOutput extends RequestOutput {
* @return the current builder
*/
public Builder errors(@Nullable List<GraphQLError> errors) {
this.builder.errors(errors);
this.executionResultBuilder.errors(errors);
return this;
}
@@ -125,13 +123,12 @@ public class WebOutput extends RequestOutput {
* @return the current builder
*/
public Builder extensions(@Nullable Map<Object, Object> extensions) {
this.builder.extensions(extensions);
this.executionResultBuilder.extensions(extensions);
return this;
}
public WebOutput build() {
return new WebOutput(this.original.getExecutionInput(), this.builder.build(),
this.original.getResponseHeaders());
return new WebOutput(this.original, this.executionResultBuilder.build());
}
}

View File

@@ -22,8 +22,8 @@ import java.util.Map;
import graphql.GraphQLError;
import org.springframework.graphql.ExecutionGraphQlResponse;
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;
@@ -179,8 +179,8 @@ public class GraphQlMessage {
* @param id unique request id
* @param output the output to obtain the result map from
*/
public static GraphQlMessage next(String id, RequestOutput output) {
Assert.notNull(output, "'RequestOutput' is required");
public static GraphQlMessage next(String id, ExecutionGraphQlResponse output) {
Assert.notNull(output, "ExecutionGraphQlResponse is required");
return next(id, output.toMap());
}

View File

@@ -19,28 +19,31 @@ package org.springframework.graphql;
import graphql.execution.ExecutionId;
import org.junit.jupiter.api.Test;
import org.springframework.graphql.support.DefaultExecutionGraphQlRequest;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link RequestInput}.
* Tests for {@link DefaultExecutionGraphQlRequest}.
*
* @author Brian Clozel
*/
class RequestInputTests {
class DefaultExecutionGraphQlRequestTests {
private final RequestInput requestInput = new RequestInput("greeting", "Greeting", null, "id", null);
private final DefaultExecutionGraphQlRequest request =
new DefaultExecutionGraphQlRequest("greeting", "Greeting", null, "id", null);
@Test
void shouldUseRequestId() {
assertThat(this.requestInput.toExecutionInput().getExecutionId()).isEqualTo(ExecutionId.from("id"));
assertThat(this.request.toExecutionInput().getExecutionId()).isEqualTo(ExecutionId.from("id"));
}
@Test
void shouldUseExecutionId() {
ExecutionId customId = ExecutionId.from("customId");
this.requestInput.executionId(customId);
assertThat(this.requestInput.toExecutionInput().getExecutionId()).isEqualTo(customId);
this.request.executionId(customId);
assertThat(this.request.toExecutionInput().getExecutionId()).isEqualTo(customId);
}
}

View File

@@ -142,10 +142,10 @@ public class ResponseHelper {
return forResult(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 ResponseHelper forResponse(Mono<? extends ExecutionGraphQlResponse> responseMono) {
ExecutionGraphQlResponse response = responseMono.block(Duration.ofSeconds(5));
assertThat(response).isNotNull();
return forResult(response.getExecutionResult());
}
public static Flux<ResponseHelper> forSubscription(ExecutionResult result) {
@@ -155,10 +155,10 @@ public class ResponseHelper {
}
@SuppressWarnings("BlockingMethodInNonBlockingContext")
public static Flux<ResponseHelper> forSubscription(Mono<? extends RequestOutput> resultMono) {
RequestOutput output = resultMono.block(Duration.ofSeconds(5));
assertThat(output).isNotNull();
return forSubscription(output.getExecutionResult());
public static Flux<ResponseHelper> forSubscription(Mono<? extends ExecutionGraphQlResponse> resultMono) {
ExecutionGraphQlResponse response = resultMono.block(Duration.ofSeconds(5));
assertThat(response).isNotNull();
return forSubscription(response.getExecutionResult());
}

View File

@@ -28,7 +28,7 @@ import graphql.execution.ResultPath;
import org.junit.jupiter.api.Test;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.graphql.DefaultGraphQlRequest;
import org.springframework.graphql.support.DefaultGraphQlRequest;
import org.springframework.graphql.GraphQlResponseError;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;

View File

@@ -28,7 +28,7 @@ import graphql.GraphQLError;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;
import org.springframework.graphql.DefaultGraphQlRequest;
import org.springframework.graphql.support.DefaultGraphQlRequest;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;

View File

@@ -30,7 +30,7 @@ import graphql.validation.ValidationErrorType;
import org.junit.jupiter.api.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.graphql.DefaultGraphQlRequest;
import org.springframework.graphql.support.DefaultGraphQlRequest;
import org.springframework.graphql.GraphQlRequest;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -29,6 +29,7 @@ import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.support.DefaultGraphQlRequest;
import org.springframework.graphql.web.support.GraphQlMessage;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.socket.WebSocketHandler;
@@ -154,7 +155,7 @@ public final class MockGraphQlWebSocketServer implements WebSocketHandler {
private Exchange(String operation) {
this.request = new GraphQlRequest(operation);
this.request = new DefaultGraphQlRequest(operation);
}
@Override

View File

@@ -31,7 +31,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.graphql.DefaultGraphQlRequest;
import org.springframework.graphql.support.DefaultGraphQlRequest;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.GraphQlResponseError;

View File

@@ -34,7 +34,8 @@ import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.DecodingException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.support.DefaultExecutionGraphQlResponse;
import org.springframework.graphql.support.DocumentSource;
import org.springframework.graphql.web.TestWebSocketClient;
import org.springframework.graphql.web.TestWebSocketConnection;
@@ -223,11 +224,11 @@ public class WebGraphQlClientBuilderTests {
private WebInput webInput;
private final Map<String, RequestOutput> responses = new HashMap<>();
private final Map<String, ExecutionGraphQlResponse> responses = new HashMap<>();
public AbstractBuilderSetup() {
RequestOutput defaultResponse = new RequestOutput(
ExecutionGraphQlResponse defaultResponse = new DefaultExecutionGraphQlResponse(
ExecutionInput.newExecutionInput().query(DOCUMENT).build(),
ExecutionResultImpl.newExecutionResult().build());
@@ -235,11 +236,11 @@ public class WebGraphQlClientBuilderTests {
}
protected WebGraphQlHandler webGraphQlHandler() {
return WebGraphQlHandler.builder(requestInput -> {
String document = requestInput.getDocument();
RequestOutput output = this.responses.get(document);
Assert.notNull(output, "Unexpected request: " + document);
return Mono.just(output);
return WebGraphQlHandler.builder(request -> {
String document = request.getDocument();
ExecutionGraphQlResponse response = this.responses.get(document);
Assert.notNull(response, "Unexpected request: " + document);
return Mono.just(response);
})
.interceptor((input, chain) -> {
this.webInput = input;
@@ -251,7 +252,7 @@ public class WebGraphQlClientBuilderTests {
@Override
public void setMockResponse(String document, ExecutionResult result) {
ExecutionInput executionInput = ExecutionInput.newExecutionInput().query(document).build();
this.responses.put(document, new RequestOutput(executionInput, result));
this.responses.put(document, new DefaultExecutionGraphQlResponse(executionInput, result));
}
@Override

View File

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

View File

@@ -30,9 +30,9 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.TestRequestInput;
import org.springframework.graphql.TestExecutionRequest;
import org.springframework.graphql.data.method.annotation.BatchMapping;
import org.springframework.graphql.execution.ReactorContextManager;
import org.springframework.graphql.execution.SecurityContextThreadLocalAccessor;
@@ -92,14 +92,14 @@ public class BatchMappingPrincipalMethodArgumentResolverTests extends BatchMappi
}
private void testBatchLoading(PrincipalCourseController controller, Function<Context, Context> contextWriter) {
Mono<RequestOutput> outputMono = Mono.delay(Duration.ofMillis(10))
Mono<ExecutionGraphQlResponse> responseMono = Mono.delay(Duration.ofMillis(10))
.flatMap(aLong -> {
String document = "{ courses { id instructor { id } } }";
return createGraphQlService(controller).execute(TestRequestInput.forDocument(document));
return createGraphQlService(controller).execute(TestExecutionRequest.forDocument(document));
})
.contextWrite(contextWriter);
List<Course> actualCourses = ResponseHelper.forResponse(outputMono).toList("courses", Course.class);
List<Course> actualCourses = ResponseHelper.forResponse(responseMono).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,12 +33,12 @@ import org.springframework.graphql.Author;
import org.springframework.graphql.Book;
import org.springframework.graphql.BookCriteria;
import org.springframework.graphql.BookSource;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.ExecutionGraphQlRequest;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.TestRequestInput;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.TestExecutionRequest;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
@@ -71,9 +71,9 @@ public class SchemaMappingInvocationTests {
" }" +
"}";
Mono<RequestOutput> outputMono = graphQlService().execute(TestRequestInput.forDocument(document));
Mono<ExecutionGraphQlResponse> responseMono = graphQlService().execute(TestExecutionRequest.forDocument(document));
Book book = ResponseHelper.forResponse(outputMono).toEntity("bookById", Book.class);
Book book = ResponseHelper.forResponse(responseMono).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> outputMono = graphQlService().execute(TestRequestInput.forDocument(document));
Mono<ExecutionGraphQlResponse> responseMono = graphQlService().execute(TestExecutionRequest.forDocument(document));
List<Book> bookList = ResponseHelper.forResponse(outputMono).toList("booksByCriteria", Book.class);
List<Book> bookList = ResponseHelper.forResponse(responseMono).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> outputMono = graphQlService().execute(TestRequestInput.forDocument(document));
Mono<ExecutionGraphQlResponse> responseMono = graphQlService().execute(TestExecutionRequest.forDocument(document));
List<Book> bookList = ResponseHelper.forResponse(outputMono).toList("booksByProjectedArguments", Book.class);
List<Book> bookList = ResponseHelper.forResponse(responseMono).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> outputMono = graphQlService().execute(TestRequestInput.forDocument(document));
Mono<ExecutionGraphQlResponse> responseMono = graphQlService().execute(TestExecutionRequest.forDocument(document));
List<Book> bookList = ResponseHelper.forResponse(outputMono).toList("booksByProjectedCriteria", Book.class);
List<Book> bookList = ResponseHelper.forResponse(responseMono).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");
@@ -144,15 +144,15 @@ public class SchemaMappingInvocationTests {
"}";
AtomicReference<GraphQLContext> contextRef = new AtomicReference<>();
RequestInput requestInput = TestRequestInput.forDocument(document);
requestInput.configureExecutionInput((executionInput, builder) -> {
ExecutionGraphQlRequest request = TestExecutionRequest.forDocument(document);
request.configureExecutionInput((executionInput, builder) -> {
contextRef.set(executionInput.getGraphQLContext());
return executionInput;
});
Mono<RequestOutput> outputMono = graphQlService().execute(requestInput);
Mono<ExecutionGraphQlResponse> responseMono = graphQlService().execute(request);
Author author = ResponseHelper.forResponse(outputMono).toEntity("authorById", Author.class);
Author author = ResponseHelper.forResponse(responseMono).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> outputMono = graphQlService().execute(TestRequestInput.forDocument(document));
Mono<ExecutionGraphQlResponse> responseMono = graphQlService().execute(TestExecutionRequest.forDocument(document));
Author author = ResponseHelper.forResponse(outputMono).toEntity("addAuthor", Author.class);
Author author = ResponseHelper.forResponse(responseMono).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> outputMono = graphQlService().execute(TestRequestInput.forDocument(document));
Mono<ExecutionGraphQlResponse> responseMono = graphQlService().execute(TestExecutionRequest.forDocument(document));
Flux<Book> bookFlux = ResponseHelper.forSubscription(outputMono)
Flux<Book> bookFlux = ResponseHelper.forSubscription(responseMono)
.map(response -> response.toEntity("bookSearch", Book.class));
StepVerifier.create(bookFlux)

View File

@@ -31,10 +31,10 @@ import reactor.util.context.Context;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.TestRequestInput;
import org.springframework.graphql.TestExecutionRequest;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SubscriptionMapping;
import org.springframework.graphql.execution.ExecutionGraphQlService;
@@ -103,10 +103,10 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests {
}
private void testQuery(String field, Function<Context, Context> contextWriter) {
Mono<RequestOutput> resultMono = executeAsync(
Mono<ExecutionGraphQlResponse> responseMono = executeAsync(
"type Query { " + field + ": String }", "{ " + field + " }", contextWriter);
String greeting = ResponseHelper.forResponse(resultMono).toEntity(field, String.class);
String greeting = ResponseHelper.forResponse(responseMono).toEntity(field, String.class);
assertThat(greeting).isEqualTo("Hello");
assertThat(greetingController.principal()).isSameAs(authentication);
}
@@ -136,12 +136,12 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests {
private void testSubscription(Function<Context, Context> contextModifier) {
String field = "greetingSubscription";
Mono<RequestOutput> resultMono = executeAsync(
Mono<ExecutionGraphQlResponse> responseMono = executeAsync(
"type Query { greeting: String } type Subscription { " + field + ": String }",
"subscription Greeting { " + field + " }",
contextModifier);
Flux<String> greetingFlux = ResponseHelper.forSubscription(resultMono)
Flux<String> greetingFlux = ResponseHelper.forSubscription(responseMono)
.map(response -> response.toEntity(field, String.class));
StepVerifier.create(greetingFlux).expectNext("Hello", "Hi").verifyComplete();
@@ -150,7 +150,7 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests {
}
private Mono<RequestOutput> executeAsync(
private Mono<ExecutionGraphQlResponse> executeAsync(
String schema, String document, Function<Context, Context> contextWriter) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@@ -162,7 +162,7 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests {
.toGraphQlService();
return Mono.delay(Duration.ofMillis(10))
.flatMap(aLong -> graphQlService.execute(TestRequestInput.forDocument(document)))
.flatMap(aLong -> graphQlService.execute(TestExecutionRequest.forDocument(document)))
.contextWrite(contextWriter);
}

View File

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

View File

@@ -22,10 +22,10 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.TestRequestInput;
import org.springframework.graphql.TestExecutionRequest;
import static org.assertj.core.api.Assertions.assertThat;
@@ -83,11 +83,11 @@ public class ClassNameTypeResolverTests {
" }" +
"}";
Mono<RequestOutput> outputMono = graphQlSetup.queryFetcher("animals", env -> animalList)
Mono<ExecutionGraphQlResponse> responseMono = graphQlSetup.queryFetcher("animals", env -> animalList)
.toGraphQlService()
.execute(TestRequestInput.forDocument(document));
.execute(TestExecutionRequest.forDocument(document));
ResponseHelper response = ResponseHelper.forResponse(outputMono);
ResponseHelper response = ResponseHelper.forResponse(responseMono);
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> output = graphQlSetup.queryFetcher("sightings", env -> animalAndPlantList)
Mono<ExecutionGraphQlResponse> responseMono = graphQlSetup.queryFetcher("sightings", env -> animalAndPlantList)
.typeResolver(typeResolver)
.toGraphQlService()
.execute(TestRequestInput.forDocument(document));
.execute(TestExecutionRequest.forDocument(document));
ResponseHelper response = ResponseHelper.forResponse(output);
ResponseHelper response = ResponseHelper.forResponse(responseMono);
for (int i = 0; i < animalAndPlantList.size(); i++) {
Object sighting = animalAndPlantList.get(i);
if (sighting instanceof Animal) {

View File

@@ -27,8 +27,9 @@ import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.ExecutionGraphQlRequest;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.support.DefaultExecutionGraphQlResponse;
import org.springframework.http.HttpHeaders;
import static org.assertj.core.api.Assertions.assertThat;
@@ -76,9 +77,9 @@ public class WebInterceptorTests {
AtomicReference<String> actualName = new AtomicReference<>();
WebGraphQlHandler handler = WebGraphQlHandler
.builder((input) -> {
actualName.set(input.toExecutionInput().getOperationName());
return emptyExecutionResult(input);
.builder((request) -> {
actualName.set(request.toExecutionInput().getOperationName());
return emptyExecutionResult(request);
})
.interceptor((webInput, next) -> {
webInput.configureExecutionInput((input, builder) -> builder.operationName("testOp").build());
@@ -91,8 +92,8 @@ public class WebInterceptorTests {
assertThat(actualName.get()).isEqualTo("testOp");
}
private Mono<RequestOutput> emptyExecutionResult(RequestInput input) {
return Mono.just(new RequestOutput(
private Mono<ExecutionGraphQlResponse> emptyExecutionResult(ExecutionGraphQlRequest request) {
return Mono.just(new DefaultExecutionGraphQlResponse(
ExecutionInput.newExecutionInput("{}").build(),
ExecutionResultImpl.newExecutionResult().build()));
}

View File

@@ -18,24 +18,26 @@ package org.springframework.graphql;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.graphql.support.DefaultExecutionGraphQlRequest;
/**
* {@link RequestInput} for use in tests with a convenient single-arg constructor
* and simple incrementing id generation.
* {@link ExecutionGraphQlRequest} for use in tests with a convenient single-arg
* constructor and simple incrementing id generation.
*
* @author Rossen Stoyanchev
*/
public class TestRequestInput extends RequestInput {
public class TestExecutionRequest extends DefaultExecutionGraphQlRequest {
private static final AtomicLong idIndex = new AtomicLong();
private TestRequestInput(String document) {
private TestExecutionRequest(String document) {
super(document, null, null, String.valueOf(idIndex.incrementAndGet()), null);
}
public static RequestInput forDocument(String document) {
return new TestRequestInput(document);
public static ExecutionGraphQlRequest forDocument(String document) {
return new TestExecutionRequest(document);
}
}