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:
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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())
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user