From b49c63f3763752cd610a8f5369d9bb656f35369f Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 1 Mar 2022 11:21:02 +0000 Subject: [PATCH] Extract GraphQlRequest and rename "query" to "document" Extract GraphQlRequest, as a parent of RequestInput, that holds the actual request inputs sent from the client side. RequestInput then adds server-side transport details and ExecutionInput support. Also replace "query" with "document" in GraphQlRequest and in GraphQlClient. Closes gh-310 --- .../test/tester/GraphQlTesterTests.java | 82 ++++----- .../test/tester/WebGraphQlTesterTests.java | 8 +- .../graphql/GraphQlRequest.java | 125 +++++++++++++ .../graphql/GraphQlService.java | 4 +- .../springframework/graphql/RequestInput.java | 168 ++++++------------ .../graphql/client/DefaultGraphQlClient.java | 59 +++--- .../client/DefaultGraphQlClientBuilder.java | 12 +- ...ContentLoader.java => DocumentSource.java} | 12 +- .../graphql/client/GraphQlClient.java | 49 ++--- .../graphql/client/GraphQlTransport.java | 17 +- .../graphql/client/HttpGraphQlTransport.java | 14 +- ...oader.java => ResourceDocumentSource.java} | 16 +- .../client/WebSocketGraphQlTransport.java | 18 +- .../support/HandlerMethodInputValidator.java | 2 +- .../data/query/QueryByExampleDataFetcher.java | 4 +- .../data/query/QuerydslDataFetcher.java | 6 +- .../execution/ReactorContextManager.java | 2 +- .../springframework/graphql/package-info.java | 4 +- .../springframework/graphql/web/WebInput.java | 11 +- .../graphql/web/WebInterceptor.java | 41 ++--- .../graphql/web/WebInterceptorChain.java | 15 +- .../web/webflux/GraphQlHttpHandler.java | 4 +- .../web/webflux/GraphQlWebSocketHandler.java | 2 +- .../web/webflux/GraphQlWebSocketMessage.java | 6 +- .../web/webmvc/GraphQlHttpHandler.java | 4 +- .../web/webmvc/GraphQlWebSocketHandler.java | 2 +- .../client/DefaultGraphQlClientTests.java | 18 +- .../MockWebSocketGraphQlTransportTests.java | 61 +++---- .../graphql/client/MockWebSocketServer.java | 36 ++-- 29 files changed, 437 insertions(+), 365 deletions(-) create mode 100644 spring-graphql/src/main/java/org/springframework/graphql/GraphQlRequest.java rename spring-graphql/src/main/java/org/springframework/graphql/client/{OperationContentLoader.java => DocumentSource.java} (72%) rename spring-graphql/src/main/java/org/springframework/graphql/client/{ResourceOperationContentLoader.java => ResourceDocumentSource.java} (84%) diff --git a/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/GraphQlTesterTests.java b/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/GraphQlTesterTests.java index 12bd377b..d7eecc5b 100644 --- a/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/GraphQlTesterTests.java +++ b/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/GraphQlTesterTests.java @@ -68,25 +68,25 @@ public class GraphQlTesterTests { @Test void pathAndValueExist() throws Exception { - String query = "{me {name, friends}}"; + String document = "{me {name, friends}}"; setResponse("{\"me\": {\"name\":\"Luke Skywalker\", \"friends\":[]}}"); - GraphQlTester.ResponseSpec spec = this.graphQlTester.query(query).execute(); + GraphQlTester.ResponseSpec spec = this.graphQlTester.query(document).execute(); spec.path("me.name").pathExists().valueExists(); spec.path("me.friends").pathExists().valueExists(); spec.path("hero").pathDoesNotExist().valueDoesNotExist(); - assertThat(this.inputCaptor.getValue().getQuery()).contains(query); + assertThat(this.inputCaptor.getValue().getDocument()).contains(document); } @Test void valueIsEmpty() throws Exception { - String query = "{me {name, friends}}"; + String document = "{me {name, friends}}"; setResponse("{\"me\": {\"name\":null, \"friends\":[]}}"); - GraphQlTester.ResponseSpec spec = this.graphQlTester.query(query).execute(); + GraphQlTester.ResponseSpec spec = this.graphQlTester.query(document).execute(); spec.path("me.name").valueIsEmpty(); spec.path("me.friends").valueIsEmpty(); @@ -95,16 +95,16 @@ public class GraphQlTesterTests { .as("Path does not even exist") .hasMessageContaining("No value at JSON path \"$['data']['hero']"); - assertThat(this.inputCaptor.getValue().getQuery()).contains(query); + assertThat(this.inputCaptor.getValue().getDocument()).contains(document); } @Test void matchesJson() throws Exception { - String query = "{me {name}}"; + String document = "{me {name}}"; setResponse("{\"me\": {\"name\":\"Luke Skywalker\", \"friends\":[]}}"); - GraphQlTester.ResponseSpec spec = this.graphQlTester.query(query).execute(); + GraphQlTester.ResponseSpec spec = this.graphQlTester.query(document).execute(); spec.path("").matchesJson("{\"me\": {\"name\":\"Luke Skywalker\",\"friends\":[]}}"); spec.path("me").matchesJson("{\"name\":\"Luke Skywalker\"}"); @@ -115,16 +115,16 @@ public class GraphQlTesterTests { .as("Extended fields should fail in strict mode") .hasMessageContaining("Unexpected: name"); - assertThat(this.inputCaptor.getValue().getQuery()).contains(query); + assertThat(this.inputCaptor.getValue().getDocument()).contains(document); } @Test void entity() throws Exception { - String query = "{me {name}}"; + String document = "{me {name}}"; setResponse("{\"me\": {\"name\":\"Luke Skywalker\"}}"); - GraphQlTester.ResponseSpec spec = this.graphQlTester.query(query).execute(); + GraphQlTester.ResponseSpec spec = this.graphQlTester.query(document).execute(); MovieCharacter luke = MovieCharacter.create("Luke Skywalker"); MovieCharacter han = MovieCharacter.create("Han Solo"); @@ -145,13 +145,13 @@ public class GraphQlTesterTests { .entity(new ParameterizedTypeReference>() {}) .isEqualTo(Collections.singletonMap("me", luke)); - assertThat(this.inputCaptor.getValue().getQuery()).contains(query); + assertThat(this.inputCaptor.getValue().getDocument()).contains(document); } @Test void entityList() throws Exception { - String query = "{me {name, friends}}"; + String document = "{me {name, friends}}"; setResponse("{" + " \"me\":{" + " \"name\":\"Luke Skywalker\"," @@ -159,7 +159,7 @@ public class GraphQlTesterTests { " }" + "}"); - GraphQlTester.ResponseSpec spec = this.graphQlTester.query(query).execute(); + GraphQlTester.ResponseSpec spec = this.graphQlTester.query(document).execute(); MovieCharacter han = MovieCharacter.create("Han Solo"); MovieCharacter leia = MovieCharacter.create("Leia Organa"); @@ -181,13 +181,13 @@ public class GraphQlTesterTests { .entityList(new ParameterizedTypeReference() {}) .containsExactly(han, leia); - assertThat(this.inputCaptor.getValue().getQuery()).contains(query); + assertThat(this.inputCaptor.getValue().getDocument()).contains(document); } @Test void operationNameAndVariables() throws Exception { - String query = "query HeroNameAndFriends($episode: Episode) {" + + String document = "query HeroNameAndFriends($episode: Episode) {" + " hero(episode: $episode) {" + " name" + " }" + @@ -195,7 +195,7 @@ public class GraphQlTesterTests { setResponse("{\"hero\": {\"name\":\"R2-D2\"}}"); - GraphQlTester.ResponseSpec spec = this.graphQlTester.query(query) + GraphQlTester.ResponseSpec spec = this.graphQlTester.query(document) .operationName("HeroNameAndFriends") .variable("episode", "JEDI") .variable("foo", "bar") @@ -205,7 +205,7 @@ public class GraphQlTesterTests { spec.path("hero").entity(MovieCharacter.class).isEqualTo(MovieCharacter.create("R2-D2")); RequestInput input = this.inputCaptor.getValue(); - assertThat(input.getQuery()).contains(query); + assertThat(input.getDocument()).contains(document); assertThat(input.getOperationName()).isEqualTo("HeroNameAndFriends"); assertThat(input.getVariables()).hasSize(3); assertThat(input.getVariables()).containsEntry("episode", "JEDI"); @@ -216,55 +216,55 @@ public class GraphQlTesterTests { @Test void errorsCheckedOnExecuteAndVerify() throws Exception { - String query = "{me {name, friends}}"; + String document = "{me {name, friends}}"; setResponse(GraphqlErrorBuilder.newError().message("Invalid query").build()); - assertThatThrownBy(() -> this.graphQlTester.query(query).executeAndVerify()) + assertThatThrownBy(() -> this.graphQlTester.query(document).executeAndVerify()) .hasMessageContaining("Response has 1 unexpected error(s)."); - assertThat(this.inputCaptor.getValue().getQuery()).contains(query); + assertThat(this.inputCaptor.getValue().getDocument()).contains(document); } @Test void errorsCheckedOnTraverse() throws Exception { - String query = "{me {name, friends}}"; + String document = "{me {name, friends}}"; setResponse(GraphqlErrorBuilder.newError().message("Invalid query").build()); - assertThatThrownBy(() -> this.graphQlTester.query(query).execute().path("me")) + assertThatThrownBy(() -> this.graphQlTester.query(document).execute().path("me")) .hasMessageContaining("Response has 1 unexpected error(s)."); - assertThat(this.inputCaptor.getValue().getQuery()).contains(query); + assertThat(this.inputCaptor.getValue().getDocument()).contains(document); } @Test void errorsPartiallyFiltered() throws Exception { - String query = "{me {name, friends}}"; + String document = "{me {name, friends}}"; setResponse( GraphqlErrorBuilder.newError().message("some error").build(), GraphqlErrorBuilder.newError().message("some other error").build()); assertThatThrownBy(() -> - this.graphQlTester.query(query) + this.graphQlTester.query(document) .execute() .errors() .filter((error) -> error.getMessage().equals("some error")) .verify()) .hasMessageContaining("Response has 1 unexpected error(s) of 2 total."); - assertThat(this.inputCaptor.getValue().getQuery()).contains(query); + assertThat(this.inputCaptor.getValue().getDocument()).contains(document); } @Test void errorsFiltered() throws Exception { - String query = "{me {name, friends}}"; + String document = "{me {name, friends}}"; setResponse( GraphqlErrorBuilder.newError().message("some error").build(), GraphqlErrorBuilder.newError().message("some other error").build()); - this.graphQlTester.query(query) + this.graphQlTester.query(document) .execute() .errors() .filter((error) -> error.getMessage().startsWith("some ")) @@ -272,13 +272,13 @@ public class GraphQlTesterTests { .path("me") .pathDoesNotExist(); - assertThat(this.inputCaptor.getValue().getQuery()).contains(query); + assertThat(this.inputCaptor.getValue().getDocument()).contains(document); } @Test void errorsFilteredGlobally() throws Exception { - String query = "{me {name, friends}}"; + String document = "{me {name, friends}}"; setResponse( GraphqlErrorBuilder.newError().message("some error").build(), GraphqlErrorBuilder.newError().message("some other error").build()); @@ -286,25 +286,25 @@ public class GraphQlTesterTests { GraphQlTester.builder(this.service) .errorFilter((error) -> error.getMessage().startsWith("some ")) .build() - .query(query) + .query(document) .execute() .errors() .verify() .path("me") .pathDoesNotExist(); - assertThat(this.inputCaptor.getValue().getQuery()).contains(query); + assertThat(this.inputCaptor.getValue().getDocument()).contains(document); } @Test void errorsExpected() throws Exception { - String query = "{me {name, friends}}"; + String document = "{me {name, friends}}"; setResponse( GraphqlErrorBuilder.newError().message("some error").build(), GraphqlErrorBuilder.newError().message("some other error").build()); - this.graphQlTester.query(query) + this.graphQlTester.query(document) .execute() .errors() .expect((error) -> error.getMessage().startsWith("some ")) @@ -312,19 +312,19 @@ public class GraphQlTesterTests { .path("me") .pathDoesNotExist(); - assertThat(this.inputCaptor.getValue().getQuery()).contains(query); + assertThat(this.inputCaptor.getValue().getDocument()).contains(document); } @Test void errorsExpectedButNotFound() throws Exception { - String query = "{me {name, friends}}"; + String document = "{me {name, friends}}"; setResponse( GraphqlErrorBuilder.newError().message("some error").build(), GraphqlErrorBuilder.newError().message("some other error").build()); assertThatThrownBy(() -> - this.graphQlTester.query(query) + this.graphQlTester.query(document) .execute() .errors().expect((error) -> error.getMessage().startsWith("another "))) .hasMessageStartingWith("No matching errors."); @@ -333,13 +333,13 @@ public class GraphQlTesterTests { @Test void errorsConsumed() throws Exception { - String query = "{me {name, friends}}"; + String document = "{me {name, friends}}"; setResponse(GraphqlErrorBuilder.newError() .message("Invalid query") .location(new SourceLocation(1, 2)) .build()); - this.graphQlTester.query(query) + this.graphQlTester.query(document) .execute() .errors() .satisfy((errors) -> { @@ -352,7 +352,7 @@ public class GraphQlTesterTests { .path("me") .pathDoesNotExist(); - assertThat(this.inputCaptor.getValue().getQuery()).contains(query); + assertThat(this.inputCaptor.getValue().getDocument()).contains(document); } private void setResponse(String data) throws Exception { diff --git a/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/WebGraphQlTesterTests.java b/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/WebGraphQlTesterTests.java index fde0b1de..8ad45321 100644 --- a/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/WebGraphQlTesterTests.java +++ b/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/WebGraphQlTesterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -91,7 +91,7 @@ public class WebGraphQlTesterTests { spec.path("me.name").entity(String.class).isEqualTo("Luke Skywalker"); setup.verifyRequest((input) -> { - assertThat(input.getQuery()).contains(query); + assertThat(input.getDocument()).contains(query); assertThat(input.getHeaders().get("myHeader1")).containsExactly("myValue1a", "myValue1b"); assertThat(input.getHeaders().getFirst("myHeader2")).isEqualTo("myValue2"); }); @@ -116,7 +116,7 @@ public class WebGraphQlTesterTests { spec.path("me.name").entity(String.class).isEqualTo("Luke Skywalker"); setup.verifyRequest((input) -> { - assertThat(input.getQuery()).contains(query); + assertThat(input.getDocument()).contains(query); assertThat(input.getHeaders().get("myHeader1")).containsExactly("myValue1a", "myValue1b"); assertThat(input.getHeaders().getFirst("myHeader2")).isEqualTo("myValue2"); }); @@ -143,7 +143,7 @@ public class WebGraphQlTesterTests { .path("me") .pathDoesNotExist(); - setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query)); + setup.verifyRequest((input) -> assertThat(input.getDocument()).contains(query)); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/GraphQlRequest.java b/spring-graphql/src/main/java/org/springframework/graphql/GraphQlRequest.java new file mode 100644 index 00000000..56b41a38 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/GraphQlRequest.java @@ -0,0 +1,125 @@ +/* + * 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.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +/** + * Represents a GraphQL request with the inputs to pass to a GraphQL service + * including a {@link #getDocument() document}, {@link #getOperationName() + * operationName}, and {@link #getVariables() variables}. + * + *

The request can be turned to a Map via {@link #toMap()} and to be + * submitted as JSON over HTTP or WebSocket. + * + * @author Rossen Stoyanchev + * @since 1.0.0 + */ +public class GraphQlRequest { + + private final String document; + + @Nullable + private final String operationName; + + private final Map variables; + + + /** + * Create a request. + * @param document textual representation of the operation(s) + */ + public GraphQlRequest(String document) { + this(document, null, null); + } + + /** + * Create a request with a complete set of inputs. + * @param document textual representation of the operation(s) + * @param operationName optionally, the name of the operation to execute + * @param variables variables by which the operation is parameterized + */ + public GraphQlRequest(String document, @Nullable String operationName, @Nullable Map variables) { + Assert.notNull(document, "'document' is required"); + this.document = document; + this.operationName = operationName; + this.variables = ((variables != null) ? variables : Collections.emptyMap()); + } + + + /** + * Return the GraphQL document which is the textual representation of an + * operation (or operations) to perform, including any selection sets and + * fragments. + */ + public String getDocument() { + return this.document; + } + + /** + * Return the name of the operation in the {@link #getDocument() document} + * to execute, if the document contains multiple operations. + */ + @Nullable + public String getOperationName() { + return this.operationName; + } + + /** + * Return values for variable defined by the operation. + */ + public Map getVariables() { + return this.variables; + } + + /** + * Convert the request to a {@link Map} as defined in + * GraphQL over HTTP and + * GraphQL over WebSocket: + * + * + * + * + * + *
KeyValue
query{@link #getDocument() document}
operationName{@link #getOperationName() operationName}
variables{@link #getVariables() variables}
+ */ + public Map toMap() { + Map map = new LinkedHashMap<>(3); + map.put("query", getDocument()); + if (getOperationName() != null) { + map.put("operationName", getOperationName()); + } + if (!CollectionUtils.isEmpty(getVariables())) { + map.put("variables", new LinkedHashMap<>(getVariables())); + } + return map; + } + + @Override + public String toString() { + return "document='" + getDocument() + "'" + + ((getOperationName() != null) ? ", operationName='" + getOperationName() + "'" : "") + + (!CollectionUtils.isEmpty(getVariables()) ? ", variables=" + getVariables() : ""); + } + +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/GraphQlService.java b/spring-graphql/src/main/java/org/springframework/graphql/GraphQlService.java index c3d601b5..b63215da 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/GraphQlService.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/GraphQlService.java @@ -19,7 +19,7 @@ package org.springframework.graphql; import reactor.core.publisher.Mono; /** - * Strategy to perform a GraphQL request. + * Strategy to execute a GraphQL request. * * @author Rossen Stoyanchev * @since 1.0.0 @@ -27,7 +27,7 @@ import reactor.core.publisher.Mono; public interface GraphQlService { /** - * Perform the operation and return the result. + * Execute the GraphQL request and return the result. * @param input container for GraphQL request input * @return the result from execution */ diff --git a/spring-graphql/src/main/java/org/springframework/graphql/RequestInput.java b/spring-graphql/src/main/java/org/springframework/graphql/RequestInput.java index dbc47b50..096c8eb8 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/RequestInput.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/RequestInput.java @@ -17,8 +17,6 @@ package org.springframework.graphql; import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -29,26 +27,22 @@ import graphql.execution.ExecutionId; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; /** - * Common, server-side representation of GraphQL request input independent of - * the underlying transport. This can be converted to {@link ExecutionInput} - * via {@link #toExecutionInput()} while the resulting {@code ExecutionInput} - * can be customized via {@link #configureExecutionInput(BiFunction)} callbacks. + * Extension of {@link GraphQlRequest} for server side handling, adding the + * transport (e.g. HTTP or WebSocket handler) assigned {@link #getId() id} and + * {@link #getLocale() locale} in the addition to the {@link GraphQlRequest} + * inputs. + * + *

{@code RequestInput} supports the initialization of {@link ExecutionInput} + * that is passed to {@link graphql.GraphQL}. You can customize that via + * {@link #configureExecutionInput(BiFunction)}. * * @author Rossen Stoyanchev * @author Brian Clozel * @since 1.0.0 */ -public class RequestInput { - - private final String query; - - @Nullable - private final String operationName; - - private final Map variables; +public class RequestInput extends GraphQlRequest { @Nullable private final Locale locale; @@ -63,52 +57,60 @@ public class RequestInput { /** * Create an instance. - * @param query the query, mutation, or subscription for the request - * @param operationName an optional, explicit name assigned to the query - * @param variables variables by which the query is parameterized - * @param locale the locale associated with the request, if any + * @param document textual representation of the operation(s) + * @param operationName optionally, the name of the operation to execute + * @param variables variables by which the query is parameterized + * @param locale the locale associated with the request * @param id the request id, to be used as the {@link ExecutionId} */ public RequestInput( - String query, @Nullable String operationName, @Nullable Map variables, + String document, @Nullable String operationName, @Nullable Map variables, @Nullable Locale locale, String id) { - Assert.notNull(query, "'query' is required"); + super(document, operationName, variables); Assert.notNull(id, "'id' is required"); - this.query = query; - this.operationName = operationName; - this.variables = ((variables != null) ? variables : Collections.emptyMap()); this.locale = locale; this.id = id; } /** - * Return the id for the request selected by the transport handler. + * 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: *

    + *
  • For WebFlux, this is the {@code ServerHttpRequest} id which correlates + * to WebFlux log messages. For Reactor Netty, it also correlates to server + * log messages. *
  • For Spring MVC, the id is generated via - * {@link org.springframework.util.AlternativeJdkIdGenerator}, which is more - * efficient than {@code UUID.randomUUID()}. - *
  • For WebFlux the id is from the {@code ServerHttpRequest}, which is - * useful to correlate to WebFlux log messages. - *
  • For WebSocket, the id is from the {@code Subscribe} message of the - * GraphQL over WebSocket protocol, which is useful to correlate to - * WebSocket messages. + * {@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()}. + *
  • For WebSocket, this is the GraphQL over WebSocket {@code "subscribe"} + * message id, which correlates to WebSocket messages. *
- *

By default, the transport id becomes the - * {@link ExecutionInput.Builder#executionId(ExecutionId) executionId} for - * the GraphQL request. You can override this via - * {@link #executionId(ExecutionId)} or by configuring an - * {@link graphql.execution.ExecutionIdProvider} on {@link graphql.GraphQL}. + *

To override this id, use {@link #executionId(ExecutionId)} or configure + * {@link graphql.GraphQL} with an {@link graphql.execution.ExecutionIdProvider}. * @return the request id - * @see GraphQL over WebSocket Protocol */ public String getId() { return this.id; } /** - * Return the {@code executionId} configured via {@link #executionId(ExecutionId)}. + * Configure the {@link ExecutionId} to set on + * {@link ExecutionInput#getExecutionId()}, overriding the transport assigned + * {@link #getId() id}. + * @param executionId the id to use + */ + public void executionId(ExecutionId executionId) { + Assert.notNull(executionId, "executionId is required"); + this.executionId = executionId; + } + + /** + * Return the configured {@link #executionId(ExecutionId) executionId}. */ @Nullable public ExecutionId getExecutionId() { @@ -116,33 +118,7 @@ public class RequestInput { } /** - * Return the query, mutation, or subscription for the request. - * @return the query, a non-empty string. - */ - public String getQuery() { - return this.query; - } - - /** - * Return the explicitly assigned name for the query. - * @return the operation name or {@code null}. - */ - @Nullable - public String getOperationName() { - return this.operationName; - } - - /** - * Return values for variable referenced within the query via $syntax. - * @return a map of variables, or an empty map. - */ - public Map getVariables() { - return this.variables; - } - - /** - * Return the locale associated with the request. - * @return the locale of {@code null}. + * Return the transport assigned locale value, if any. */ @Nullable public Locale getLocale() { @@ -150,40 +126,29 @@ public class RequestInput { } /** - * Configure the {@link ExecutionId} to use for the GraphQL request, which - * is set on the {@link ExecutionInput}. This option overrides the - * {@link #getId() id} selected by the transport handler. - * @param executionId the execution id to set on the {@link ExecutionInput}. - */ - public void executionId(ExecutionId executionId) { - Assert.notNull(executionId, "executionId should not be null"); - this.executionId = executionId; - } - - /** - * Provide a consumer to configure the {@link ExecutionInput} used for input to - * {@link graphql.GraphQL#executeAsync(ExecutionInput)}. The builder is initially - * populated with the values from {@link #getQuery()}, {@link #getOperationName()}, - * and {@link #getVariables()}. - * @param configurer a {@code BiFunction} with the current {@code ExecutionInput} and - * a builder to modify it. + * 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. */ public void configureExecutionInput(BiFunction configurer) { this.executionInputConfigurers.add(configurer); } /** - * Create the {@link ExecutionInput} for request execution. This is initially - * populated from {@link #getQuery()}, {@link #getOperationName()}, and - * {@link #getVariables()}, and is then further customized through - * {@link #configureExecutionInput(BiFunction)}. - * @return the execution input + * 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} */ public ExecutionInput toExecutionInput() { ExecutionInput.Builder inputBuilder = ExecutionInput.newExecutionInput() - .query(this.query) - .operationName(this.operationName) - .variables(this.variables) + .query(getDocument()) + .operationName(getOperationName()) + .variables(getVariables()) .locale(this.locale) .executionId(this.executionId != null ? this.executionId : ExecutionId.from(this.id)); @@ -197,28 +162,9 @@ public class RequestInput { return executionInput; } - /** - * Return a Map representation of the request input. - * @return map representation of the input - */ - public Map toMap() { - Map map = new LinkedHashMap<>(3); - map.put("query", getQuery()); - if (getOperationName() != null) { - map.put("operationName", getOperationName()); - } - if (!CollectionUtils.isEmpty(getVariables())) { - map.put("variables", new LinkedHashMap<>(getVariables())); - } - return map; - } - @Override public String toString() { - return "Query='" + getQuery() + "'" + - ((getOperationName() != null) ? ", Operation='" + getOperationName() + "'" : "") + - (!CollectionUtils.isEmpty(getVariables()) ? ", Variables=" + getVariables() : "") + - (getLocale() != null ? ", Locale=" + getLocale() : ""); + return super.toString() + (getLocale() != null ? ", Locale=" + getLocale() : ""); } } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java index f1295669..ff74f2dc 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java @@ -28,7 +28,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.graphql.RequestInput; +import org.springframework.graphql.GraphQlRequest; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -45,18 +45,19 @@ final class DefaultGraphQlClient implements GraphQlClient { private final Configuration jsonPathConfig; - private final OperationContentLoader operationContentLoader; + private final DocumentSource documentSource; - public DefaultGraphQlClient(GraphQlTransport transport, Configuration jsonPathConfig, - OperationContentLoader operationContentLoader) { + DefaultGraphQlClient( + GraphQlTransport transport, Configuration jsonPathConfig, DocumentSource documentSource) { Assert.notNull(transport, "GraphQlTransport is required"); - Assert.notNull(jsonPathConfig, "'jsonPathConfig' is required"); - Assert.notNull(operationContentLoader, "RequestNameResolver is required"); + Assert.notNull(jsonPathConfig, "Configuration is required"); + Assert.notNull(documentSource, "DocumentSource is required"); + this.transport = transport; this.jsonPathConfig = jsonPathConfig; - this.operationContentLoader = operationContentLoader; + this.documentSource = documentSource; } @@ -67,21 +68,21 @@ final class DefaultGraphQlClient implements GraphQlClient { } @Override - public RequestSpec operation(String operation) { - return new DefaultRequestSpec(operation, this.transport, this.jsonPathConfig); + public RequestSpec document(String document) { + return new DefaultRequestSpec(document, this.transport, this.jsonPathConfig); } @Override - public RequestSpec loadOperationContent(String key) { - String requestContent = this.operationContentLoader.loadOperation(key); - Assert.notNull(requestContent, "Failed to load operation content for key: " + key); - return new DefaultRequestSpec(requestContent, this.transport, this.jsonPathConfig); + public RequestSpec documentName(String name) { + String document = this.documentSource.getDocument(name); + Assert.notNull(document, "Failed to find document for name: '" + name + "'"); + return new DefaultRequestSpec(document, this.transport, this.jsonPathConfig); } private static final class DefaultRequestSpec implements RequestSpec { - private final String operation; + private final String document; @Nullable private String operationName; @@ -92,16 +93,16 @@ final class DefaultGraphQlClient implements GraphQlClient { private final Configuration jsonPathConfig; - DefaultRequestSpec(String operation, GraphQlTransport transport, Configuration jsonPathConfig) { - Assert.hasText(operation, "'operation' is required"); - this.operation = operation; + DefaultRequestSpec(String document, GraphQlTransport transport, Configuration jsonPathConfig) { + Assert.hasText(document, "'document' is required"); + this.document = document; this.transport = transport; this.jsonPathConfig = jsonPathConfig; } @Override - public DefaultRequestSpec operationName(@Nullable String name) { - this.operationName = name; + public DefaultRequestSpec operationName(@Nullable String operationName) { + this.operationName = operationName; return this; } @@ -113,18 +114,18 @@ final class DefaultGraphQlClient implements GraphQlClient { @Override public Mono execute() { - return this.transport.execute(initRequestInput()) + return this.transport.execute(createRequest()) .map(payload -> new DefaultResponseSpec(payload, this.jsonPathConfig)); } @Override public Flux executeSubscription() { - return this.transport.executeSubscription(initRequestInput()) + return this.transport.executeSubscription(createRequest()) .map(payload -> new DefaultResponseSpec(payload, this.jsonPathConfig)); } - private RequestInput initRequestInput() { - return new RequestInput(this.operation, this.operationName, this.variables, null, "1"); + private GraphQlRequest createRequest() { + return new GraphQlRequest(this.document, this.operationName, this.variables); } } @@ -132,33 +133,33 @@ final class DefaultGraphQlClient implements GraphQlClient { private static class DefaultResponseSpec implements ResponseSpec { - private final DocumentContext documentContext; + private final DocumentContext jsonPathDocument; private final List errors; private DefaultResponseSpec(ExecutionResult result, Configuration jsonPathConfig) { - this.documentContext = JsonPath.parse(result.toSpecification(), jsonPathConfig); + this.jsonPathDocument = JsonPath.parse(result.toSpecification(), jsonPathConfig); this.errors = result.getErrors(); } @Override public D toEntity(String path, Class entityType) { - return this.documentContext.read(initJsonPath(path), new TypeRefAdapter<>(entityType)); + return this.jsonPathDocument.read(initJsonPath(path), new TypeRefAdapter<>(entityType)); } @Override public D toEntity(String path, ParameterizedTypeReference entityType) { - return this.documentContext.read(initJsonPath(path), new TypeRefAdapter<>(entityType)); + return this.jsonPathDocument.read(initJsonPath(path), new TypeRefAdapter<>(entityType)); } @Override public List toEntityList(String path, Class elementType) { - return this.documentContext.read(initJsonPath(path), new TypeRefAdapter<>(List.class, elementType)); + return this.jsonPathDocument.read(initJsonPath(path), new TypeRefAdapter<>(List.class, elementType)); } @Override public List toEntityList(String path, ParameterizedTypeReference elementType) { - return this.documentContext.read(initJsonPath(path), new TypeRefAdapter<>(List.class, elementType)); + return this.jsonPathDocument.read(initJsonPath(path), new TypeRefAdapter<>(List.class, elementType)); } private static JsonPath initJsonPath(String path) { diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClientBuilder.java b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClientBuilder.java index adc683a7..c413d563 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClientBuilder.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClientBuilder.java @@ -46,7 +46,7 @@ class DefaultGraphQlClientBuilder implements GraphQlClient.Builder { private Configuration jsonPathConfig; @Nullable - private OperationContentLoader operationContentLoader; + private DocumentSource documentSource; DefaultGraphQlClientBuilder(GraphQlTransport transport) { @@ -62,8 +62,8 @@ class DefaultGraphQlClientBuilder implements GraphQlClient.Builder { } @Override - public GraphQlClient.Builder operationContentLoader(@Nullable OperationContentLoader contentLoader) { - this.operationContentLoader = contentLoader; + public GraphQlClient.Builder documentSource(@Nullable DocumentSource contentLoader) { + this.documentSource = contentLoader; return this; } @@ -84,9 +84,9 @@ class DefaultGraphQlClientBuilder implements GraphQlClient.Builder { } } - private OperationContentLoader initRequestNameResolver() { - return (this.operationContentLoader == null ? - new ResourceOperationContentLoader() : this.operationContentLoader); + private DocumentSource initRequestNameResolver() { + return (this.documentSource == null ? + new ResourceDocumentSource() : this.documentSource); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/OperationContentLoader.java b/spring-graphql/src/main/java/org/springframework/graphql/client/DocumentSource.java similarity index 72% rename from spring-graphql/src/main/java/org/springframework/graphql/client/OperationContentLoader.java rename to spring-graphql/src/main/java/org/springframework/graphql/client/DocumentSource.java index 5abb6c1f..1446c4ee 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/OperationContentLoader.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/DocumentSource.java @@ -18,19 +18,19 @@ package org.springframework.graphql.client; import org.springframework.lang.Nullable; /** - * Strategy to load the content of a GraphQL operation from a key. + * Strategy to locate a GraphQL document identified by name. * * @author Rossen Stoyanchev * @since 1.0.0 */ -public interface OperationContentLoader { +public interface DocumentSource { /** - * Return the operation for the given key. - * @param key the key to look up the operation content - * @return the content of the operation, if found + * Return the document that matches the given name. + * @param name the name to use for the lookup + * @return the document, or {@code null} */ @Nullable - String loadOperation(String key); + String getDocument(String name); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClient.java b/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClient.java index 2bae1353..1b4c5564 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClient.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClient.java @@ -40,6 +40,7 @@ import org.springframework.lang.Nullable; */ public interface GraphQlClient { + /** * Return the underlying transport or {@code null} if the required type * does not match the transport type. See {@link GraphQlTransport} @@ -47,21 +48,23 @@ public interface GraphQlClient { @Nullable T getTransport(Class requiredType); - /** - * Start a new request with the given GraphQL operation, which can be a - * query, a mutation, or subscription. - * @param operation the operation to perform - * @return spec to further define and execute the request - */ - RequestSpec operation(String operation); /** - * Variant of {@link #operation(String)} that loads the content of the - * operation from the given key through the configured - * {@link OperationContentLoader}. + * Start defining a GraphQL request with the given document, which is the + * textual representation of an operation (or operations) to perform, + * including selection sets and fragments. + * @param document the document for the request + * @return spec to further define or execute the request + */ + RequestSpec document(String document); + + /** + * Variant of {@link #document(String)} that uses the given key to resolve + * the GraphQL document from a file, or in another way with the help of the + * {@link DocumentSource} that the client is configured with. * @throws IllegalArgumentException if the content could not be loaded */ - RequestSpec loadOperationContent(String key); + RequestSpec documentName(String name); /** @@ -87,11 +90,11 @@ public interface GraphQlClient { Builder jsonPathConfig(@Nullable Configuration config); /** - * Configure an {@link OperationContentLoader} to use with - * {@link #loadOperationContent(String) GraphQlClient#loadOperationContent}. - *

By default, {@link ResourceOperationContentLoader} is used. + * Configure a {@link DocumentSource} for use with + * {@link #documentName(String)} for resolving a document by name. + *

By default, {@link ResourceDocumentSource} is used. */ - Builder operationContentLoader(@Nullable OperationContentLoader contentLoader); + Builder documentSource(@Nullable DocumentSource contentLoader); /** * Build the {@code GraphQlClient} instance. @@ -102,12 +105,13 @@ public interface GraphQlClient { /** - * Declare options to execute a request. + * Declare options for GraphQL request execution. */ interface ExecuteSpec { /** - * Execute as a single-response operation such as "query" or "mutation". + * Execute as a request with a single response such as a "query" or + * "mutation" operation. * @return a {@code Mono} with a {@code ResponseSpec} for further * decoding of the response. The {@code Mono} may end wth an error due * to transport level issues. @@ -115,7 +119,7 @@ public interface GraphQlClient { Mono execute(); /** - * Execute a "subscription" request and stream a stream of responses. + * Execute a "subscription" request with a stream of responses. * @return a {@code Flux} with a {@code ResponseSpec} for further * decoding of the response. The {@code Flux} may terminate as follows: *

    @@ -139,14 +143,15 @@ public interface GraphQlClient { interface RequestSpec extends ExecuteSpec { /** - * Set the operation name. - * @param name the operation name + * Set the name of the operation in the {@link #document(String) document} + * to execute, if the document contains multiple operations. + * @param operationName the operation name * @return this request spec */ - RequestSpec operationName(@Nullable String name); + RequestSpec operationName(@Nullable String operationName); /** - * Add a variable. + * Add a value for a variable defined by the operation. * @param name the variable name * @param value the variable value * @return this request spec diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlTransport.java b/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlTransport.java index 0713383e..81d59394 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlTransport.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlTransport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import graphql.ExecutionResult; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import org.springframework.graphql.RequestInput; +import org.springframework.graphql.GraphQlRequest; /** * Contract for a transport, over which to execute GraphQL requests. @@ -30,18 +30,19 @@ import org.springframework.graphql.RequestInput; public interface GraphQlTransport { /** - * Execute a single-response operation such as "query" or "mutation". - * @param input the request to execute + * Execute a request that returns a single response such as a "query" or a + * "mutation" operation. + * @param request the request to execute * @return a {@code Mono} with the {@code ExecutionResult} for the response. * The {@code Mono} may end wth an error due to transport or other issues * such as failures to encode the request or decode the response. *
*/ - Mono execute(RequestInput input); + Mono execute(GraphQlRequest request); /** - * Execute a "subscription" request and stream the responses. - * @param input the request to execute + * Execute a "subscription" request that returns a stream of responses. + * @param request the request to execute * @return a {@code Flux} with an {@code ExecutionResult} for each response. * The {@code Flux} may terminate as follows: *
    @@ -54,6 +55,6 @@ public interface GraphQlTransport { *

    The {@code Flux} may be cancelled to notify the server to end the * subscription stream. */ - Flux executeSubscription(RequestInput input); + Flux executeSubscription(GraphQlRequest request); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/HttpGraphQlTransport.java b/spring-graphql/src/main/java/org/springframework/graphql/client/HttpGraphQlTransport.java index ed8723c8..ee5c5408 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/HttpGraphQlTransport.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/HttpGraphQlTransport.java @@ -22,17 +22,17 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.graphql.RequestInput; +import org.springframework.graphql.GraphQlRequest; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.web.reactive.function.client.WebClient; /** * Transport to execute GraphQL requests over HTTP via {@link WebClient}. - * Single-response requests are performed over HTTP POST while subscriptions - * over HTTP are not supported. + * Supports only single-response requests over HTTP POST. For subscription + * requests, see {@link WebSocketGraphQlTransport}. * - *

    Use the builder to initialize the transport and the {@link GraphQlClient} + *

    Use the builder to initialize the transport and the {@code GraphQlClient} * in a single chain: * *

    @@ -73,18 +73,18 @@ public class HttpGraphQlTransport implements GraphQlTransport {
     
     
     	@Override
    -	public Mono execute(RequestInput requestInput) {
    +	public Mono execute(GraphQlRequest request) {
     		return this.webClient.post()
     				.contentType(MediaType.APPLICATION_JSON)
     				.accept(MediaType.APPLICATION_JSON)
    -				.bodyValue(requestInput.toMap())
    +				.bodyValue(request.toMap())
     				.retrieve()
     				.bodyToMono(MAP_TYPE)
     				.map(MapExecutionResult::new);
     	}
     
     	@Override
    -	public Flux executeSubscription(RequestInput requestInput) {
    +	public Flux executeSubscription(GraphQlRequest request) {
     		throw new UnsupportedOperationException("Subscriptions not supported over HTTP");
     	}
     
    diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/ResourceOperationContentLoader.java b/spring-graphql/src/main/java/org/springframework/graphql/client/ResourceDocumentSource.java
    similarity index 84%
    rename from spring-graphql/src/main/java/org/springframework/graphql/client/ResourceOperationContentLoader.java
    rename to spring-graphql/src/main/java/org/springframework/graphql/client/ResourceDocumentSource.java
    index 63d40c4a..b331f370 100644
    --- a/spring-graphql/src/main/java/org/springframework/graphql/client/ResourceOperationContentLoader.java
    +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/ResourceDocumentSource.java
    @@ -28,13 +28,13 @@ import org.springframework.core.io.Resource;
     import org.springframework.util.FileCopyUtils;
     
     /**
    - * {@link OperationContentLoader} that looks for resources relative to a list of
    - * {@link Resource} locations with a list of extensions.
    + * {@link DocumentSource} that looks under a set of locations for a
    + * {@link Resource} with the document name and a list of configured extensions.
      *
      * @author Rossen Stoyanchev
      * @since 1.0.0
      */
    -public class ResourceOperationContentLoader implements OperationContentLoader {
    +public class ResourceDocumentSource implements DocumentSource {
     
     	private static final List FILE_EXTENSIONS = Arrays.asList(".graphql", ".gql");
     
    @@ -48,21 +48,21 @@ public class ResourceOperationContentLoader implements OperationContentLoader {
     	 * Default constructor to look under {@code graphql/} on the classpath for
     	 * resources with extensions ".graphql" and ".gql".
     	 */
    -	public ResourceOperationContentLoader() {
    +	public ResourceDocumentSource() {
     		this(Collections.singletonList(new ClassPathResource("graphql/")));
     	}
     
     	/**
     	 * Constructor with custom locations with extensions ".graphql" and ".gql".
     	 */
    -	public ResourceOperationContentLoader(List locations) {
    +	public ResourceDocumentSource(List locations) {
     		this(locations, FILE_EXTENSIONS);
     	}
     
     	/**
     	 * Constructor with given locations and extensions.
     	 */
    -	public ResourceOperationContentLoader(List locations, List extensions) {
    +	public ResourceDocumentSource(List locations, List extensions) {
     		this.locations = new ArrayList<>(locations);
     		this.extensions = new ArrayList<>(extensions);
     	}
    @@ -84,9 +84,9 @@ public class ResourceOperationContentLoader implements OperationContentLoader {
     
     
     	@Override
    -	public String loadOperation(String key) {
    +	public String getDocument(String name) {
     		return this.locations.stream()
    -				.flatMap(location -> this.extensions.stream().map(ext -> getRelativeResource(location, key, ext)))
    +				.flatMap(location -> this.extensions.stream().map(ext -> getRelativeResource(location, name, ext)))
     				.filter(Resource::exists)
     				.findFirst()
     				.map(resource -> {
    diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/WebSocketGraphQlTransport.java b/spring-graphql/src/main/java/org/springframework/graphql/client/WebSocketGraphQlTransport.java
    index bbcc82ec..8b13d103 100644
    --- a/spring-graphql/src/main/java/org/springframework/graphql/client/WebSocketGraphQlTransport.java
    +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/WebSocketGraphQlTransport.java
    @@ -35,7 +35,7 @@ import reactor.core.publisher.Flux;
     import reactor.core.publisher.Mono;
     import reactor.core.publisher.Sinks;
     
    -import org.springframework.graphql.RequestInput;
    +import org.springframework.graphql.GraphQlRequest;
     import org.springframework.graphql.web.webflux.GraphQlWebSocketMessage;
     import org.springframework.http.HttpHeaders;
     import org.springframework.http.codec.ClientCodecConfigurer;
    @@ -156,13 +156,13 @@ public final class WebSocketGraphQlTransport implements GraphQlTransport {
     	}
     
     	@Override
    -	public Mono execute(RequestInput input) {
    -		return this.graphQlSessionMono.flatMap(session -> session.execute(input));
    +	public Mono execute(GraphQlRequest request) {
    +		return this.graphQlSessionMono.flatMap(session -> session.execute(request));
     	}
     
     	@Override
    -	public Flux executeSubscription(RequestInput input) {
    -		return this.graphQlSessionMono.flatMapMany(session -> session.executeSubscription(input));
    +	public Flux executeSubscription(GraphQlRequest request) {
    +		return this.graphQlSessionMono.flatMapMany(session -> session.executeSubscription(request));
     	}
     
     	/**
    @@ -540,10 +540,10 @@ public final class WebSocketGraphQlTransport implements GraphQlTransport {
     			return this.requestSink.asFlux();
     		}
     
    -		public Mono execute(RequestInput requestInput) {
    +		public Mono execute(GraphQlRequest request) {
     			String id = String.valueOf(this.requestIndex.incrementAndGet());
     			try {
    -				GraphQlWebSocketMessage message = GraphQlWebSocketMessage.subscribe(id, requestInput);
    +				GraphQlWebSocketMessage message = GraphQlWebSocketMessage.subscribe(id, request);
     				Sinks.One sink = Sinks.one();
     				this.resultSinks.put(id, sink);
     				trySend(message);
    @@ -555,10 +555,10 @@ public final class WebSocketGraphQlTransport implements GraphQlTransport {
     			}
     		}
     
    -		public Flux executeSubscription(RequestInput requestInput) {
    +		public Flux executeSubscription(GraphQlRequest request) {
     			String id = String.valueOf(this.requestIndex.incrementAndGet());
     			try {
    -				GraphQlWebSocketMessage message = GraphQlWebSocketMessage.subscribe(id, requestInput);
    +				GraphQlWebSocketMessage message = GraphQlWebSocketMessage.subscribe(id, request);
     				Sinks.Many sink = Sinks.many().unicast().onBackpressureBuffer();
     				this.streamingSinks.put(id, sink);
     				trySend(message);
    diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/HandlerMethodInputValidator.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/HandlerMethodInputValidator.java
    index 226df823..d775ca56 100644
    --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/HandlerMethodInputValidator.java
    +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/HandlerMethodInputValidator.java
    @@ -69,7 +69,7 @@ class HandlerMethodInputValidator {
     	 * Validate the {@link HandlerMethod} input before invocation, throwing
     	 * an {@link ConstraintViolationException} if validation fails.
     	 *
    -	 * @param handlerMethod the handler method for the current query
    +	 * @param handlerMethod the handler method for the current request
     	 * @param arguments the resolved arguments for the method invocation
     	 */
     	public void validate(HandlerMethod handlerMethod, Object[] arguments) {
    diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/query/QueryByExampleDataFetcher.java b/spring-graphql/src/main/java/org/springframework/graphql/data/query/QueryByExampleDataFetcher.java
    index d0d2d3b5..04e492f1 100644
    --- a/spring-graphql/src/main/java/org/springframework/graphql/data/query/QueryByExampleDataFetcher.java
    +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/query/QueryByExampleDataFetcher.java
    @@ -101,8 +101,8 @@ public abstract class QueryByExampleDataFetcher {
     
     
     	/**
    -	 * Prepare an {@link Example} from GraphQL query arguments.
    -	 * @param env contextual info for the GraphQL query
    +	 * Prepare an {@link Example} from GraphQL request arguments.
    +	 * @param env contextual info for the GraphQL request
     	 * @return the resulting example
     	 */
     	@SuppressWarnings({"ConstantConditions", "unchecked"})
    diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/query/QuerydslDataFetcher.java b/spring-graphql/src/main/java/org/springframework/graphql/data/query/QuerydslDataFetcher.java
    index b771f1f8..52ee2aae 100644
    --- a/spring-graphql/src/main/java/org/springframework/graphql/data/query/QuerydslDataFetcher.java
    +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/query/QuerydslDataFetcher.java
    @@ -116,9 +116,9 @@ public abstract class QuerydslDataFetcher {
     
     
     	/**
    -	 * Prepare a {@link Predicate} from GraphQL query arguments, also applying
    +	 * Prepare a {@link Predicate} from GraphQL request arguments, also applying
     	 * any {@link QuerydslBinderCustomizer} that may have been configured.
    -	 * @param environment contextual info for the GraphQL query
    +	 * @param environment contextual info for the GraphQL request
     	 * @return the resulting predicate
     	 */
     	@SuppressWarnings({"unchecked", "rawtypes"})
    @@ -341,7 +341,7 @@ public abstract class QuerydslDataFetcher {
     		 * {@link #autoRegistrationConfigurer(List, List) auto-registration}.
     		 * For manual registration, you will need to use this method to apply it.
     		 *
    -		 * @param customizer to customize the GraphQL query to Querydsl
    +		 * @param customizer to customize GraphQL request input to Querydsl
     		 * Predicate binding with
     		 * @return a new {@link Builder} instance with all previously configured
     		 * options and {@code QuerydslBinderCustomizer} applied
    diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/ReactorContextManager.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/ReactorContextManager.java
    index 2815c985..c76aa8ed 100644
    --- a/spring-graphql/src/main/java/org/springframework/graphql/execution/ReactorContextManager.java
    +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/ReactorContextManager.java
    @@ -54,7 +54,7 @@ public abstract class ReactorContextManager {
     	 * Save the given Reactor {@link ContextView} in the an {@link ExecutionInput} for
     	 * later access through the {@link DataFetchingEnvironment}.
     	 * @param contextView the reactor context view
    -	 * @param input the GraphQL query input
    +	 * @param input the input prepared from the GraphQL request
     	 */
     	static void setReactorContext(ContextView contextView, ExecutionInput input) {
     		input.getGraphQLContext().put(CONTEXT_VIEW_KEY, contextView);
    diff --git a/spring-graphql/src/main/java/org/springframework/graphql/package-info.java b/spring-graphql/src/main/java/org/springframework/graphql/package-info.java
    index 6ea9c77e..933810f6 100644
    --- a/spring-graphql/src/main/java/org/springframework/graphql/package-info.java
    +++ b/spring-graphql/src/main/java/org/springframework/graphql/package-info.java
    @@ -15,9 +15,7 @@
      */
     
     /**
    - * Top level abstractions for processing GraphQL requests including
    - * {@link org.springframework.graphql.GraphQlService} for executing a request and
    - * {@link org.springframework.graphql.RequestInput} to represent the input for a request.
    + * Top level abstractions for processing GraphQL requests.
      */
     @NonNullApi
     @NonNullFields
    diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/WebInput.java b/spring-graphql/src/main/java/org/springframework/graphql/web/WebInput.java
    index 4585f624..9658fb14 100644
    --- a/spring-graphql/src/main/java/org/springframework/graphql/web/WebInput.java
    +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/WebInput.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2020-2021 the original author or authors.
    + * Copyright 2020-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.
    @@ -48,16 +48,13 @@ public class WebInput extends RequestInput {
     	 * Create an instance.
     	 * @param uri the URL for the HTTP request or WebSocket handshake
     	 * @param headers the HTTP request headers
    -	 * @param body the content of the request deserialized from JSON
    +	 * @param body the deserialized content of the GraphQL request
     	 * @param locale the locale from the HTTP request, if any
     	 * @param id an identifier for the GraphQL request, e.g. a subscription id for
     	 * correlating request and response messages, or it could be an id associated with the
     	 * underlying request/connection id, if available
     	 */
    -	public WebInput(
    -			URI uri, HttpHeaders headers, Map body,
    -			@Nullable Locale locale, String id) {
    -
    +	public WebInput(URI uri, HttpHeaders headers, Map body, @Nullable Locale locale, String id) {
     		super(getKey("query", body), getKey("operationName", body), getKey("variables", body), locale, id);
     		Assert.notNull(uri, "URI is required'");
     		Assert.notNull(headers, "HttpHeaders is required'");
    @@ -68,7 +65,7 @@ public class WebInput extends RequestInput {
     	@SuppressWarnings("unchecked")
     	private static  T getKey(String key, Map body) {
     		if (key.equals("query") && !StringUtils.hasText((String) body.get(key))) {
    -			throw new ServerWebInputException("Query is required");
    +			throw new ServerWebInputException("No \"query\" in the request input");
     		}
     		return (T) body.get(key);
     	}
    diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/WebInterceptor.java b/spring-graphql/src/main/java/org/springframework/graphql/web/WebInterceptor.java
    index e50497df..a8ba9843 100644
    --- a/spring-graphql/src/main/java/org/springframework/graphql/web/WebInterceptor.java
    +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/WebInterceptor.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2020-2021 the original author or authors.
    + * Copyright 2020-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.
    @@ -24,15 +24,15 @@ import org.springframework.beans.factory.ObjectProvider;
     import org.springframework.util.Assert;
     
     /**
    - * Interceptor for intercepting GraphQL over HTTP or GraphQL over WebSocket
    - * requests. Provides information about the HTTP request or WebSocket handshake,
    - * and allows customization of the {@link ExecutionInput} as well as of the
    - * {@link ExecutionResult}.
    + * Interceptor for the handling of GraphQL over HTTP or GraphQL over WebSocket
    + * requests. Exposes the details of the underlying HTTP request or WebSocket
    + * handshake, the decoded GraphQL request, and allows customization of the
    + * {@link ExecutionInput} and the resulting {@link ExecutionResult}.
      *
    - * 

    Interceptors are typically declared as beans in Spring configuration and + *

    Interceptors are typically declared as beans in Spring configuration and * ordered as defined in {@link ObjectProvider#orderedStream()}. * - *

    Supported for Spring MVC and WebFlux. + *

    Supported for Spring MVC and WebFlux. * * @author Rossen Stoyanchev * @since 1.0.0 @@ -41,26 +41,27 @@ import org.springframework.util.Assert; public interface WebInterceptor { /** - * Intercept a request and possibly delegate to the rest of the chain - * consisting of more interceptors as well as a - * {@link org.springframework.graphql.GraphQlService} at the end to actually - * handle the request through the GraphQL engine. - * @param webInput container for HTTP request information and options to - * customize the {@link ExecutionInput}. - * @param chain the rest of the chain to delegate to for request execution + * Intercept a request and delegate to the rest of the chain that consists + * of other interceptors followed by a + * {@link org.springframework.graphql.GraphQlService} that executes the + * request through the GraphQL Java. + * @param webInput provides access to GraphQL request input and allows + * customizing the {@link ExecutionInput} that will be used. + * @param chain the rest of the chain to handle the request * @return a {@link Mono} with the result */ Mono intercept(WebInput webInput, WebInterceptorChain chain); /** - * Return a composed {@link WebInterceptor} that invokes the current - * interceptor first and then the one that is passed in. - * @param interceptor the interceptor to delegate to after "this" interceptor - * @return the composed WebInterceptor + * Return a new {@link WebInterceptor} that invokes the current interceptor + * first and then the one that is passed in. + * @param interceptor the interceptor to delegate to after "this" + * @return the new {@code WebInterceptor} */ default WebInterceptor andThen(WebInterceptor interceptor) { - Assert.notNull(interceptor, "WebInterceptor must not be null"); - return (currentInput, next) -> intercept(currentInput, (nextInput) -> interceptor.intercept(nextInput, next)); + Assert.notNull(interceptor, "WebInterceptor is required"); + return (currentInput, next) -> intercept(currentInput, + (nextInput) -> interceptor.intercept(nextInput, next)); } } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/WebInterceptorChain.java b/spring-graphql/src/main/java/org/springframework/graphql/web/WebInterceptorChain.java index d11c8638..4d8b318c 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/WebInterceptorChain.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/WebInterceptorChain.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,11 @@ */ package org.springframework.graphql.web; +import graphql.ExecutionInput; import reactor.core.publisher.Mono; /** - * Contract that allows a {@link WebInterceptor} to delegate to the remainder - * of the chain. + * Allows a {@link WebInterceptor} to invoke the rest of the chain. * * @author Rossen Stoyanchev * @since 1.0.0 @@ -27,10 +27,11 @@ import reactor.core.publisher.Mono; public interface WebInterceptorChain { /** - * Delegate to the next rest of the chain consisting of more interceptors - * as well as a {@link org.springframework.graphql.GraphQlService} at the - * end to actually handle the request through the GraphQL engine. - * @param webInput the input for the request + * Delegate to the rest of the chain that consists of other interceptors + * followed by a {@link org.springframework.graphql.GraphQlService} that + * executes the request through the GraphQL Java. + * @param webInput provides access to GraphQL request input and allows + * customizing the {@link ExecutionInput} that will be used. * @return the output with the result from request execution */ Mono next(WebInput webInput); diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlHttpHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlHttpHandler.java index 9aa4a6a1..a18f49ed 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlHttpHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlHttpHandler.java @@ -78,9 +78,7 @@ public class GraphQlHttpHandler { logger.debug("Execution complete"); } ServerResponse.BodyBuilder builder = ServerResponse.ok(); - if (output.getResponseHeaders() != null) { - builder.headers((headers) -> headers.putAll(output.getResponseHeaders())); - } + builder.headers(headers -> headers.putAll(output.getResponseHeaders())); return builder.bodyValue(spec); }); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlWebSocketHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlWebSocketHandler.java index bcade02a..690e3312 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlWebSocketHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlWebSocketHandler.java @@ -178,7 +178,7 @@ public class GraphQlWebSocketHandler implements WebSocketHandler { }); } else { - // Single response operation (query or mutation) + // Single response (query or mutation) outputFlux = (CollectionUtils.isEmpty(output.getErrors()) ? Flux.just(output) : Flux.error(new IllegalStateException("Execution failed: " + output.getErrors()))); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlWebSocketMessage.java b/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlWebSocketMessage.java index d604c41f..261b0681 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlWebSocketMessage.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlWebSocketMessage.java @@ -21,7 +21,7 @@ import java.util.Collections; import graphql.ExecutionResult; import graphql.GraphQLError; -import org.springframework.graphql.RequestInput; +import org.springframework.graphql.GraphQlRequest; import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; @@ -137,8 +137,8 @@ public class GraphQlWebSocketMessage { /** * Create a "subscribe" message. */ - public static GraphQlWebSocketMessage subscribe(String id, RequestInput input) { - return new GraphQlWebSocketMessage(id, "subscribe", input.toMap()); + public static GraphQlWebSocketMessage subscribe(String id, GraphQlRequest request) { + return new GraphQlWebSocketMessage(id, "subscribe", request.toMap()); } /** diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandler.java index be130b52..a9521ae0 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandler.java @@ -87,9 +87,7 @@ public class GraphQlHttpHandler { logger.debug("Execution complete"); } ServerResponse.BodyBuilder builder = ServerResponse.ok(); - if (output.getResponseHeaders() != null) { - builder.headers((headers) -> headers.putAll(output.getResponseHeaders())); - } + builder.headers(headers -> headers.putAll(output.getResponseHeaders())); return builder.body(output.toSpecification()); }); diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlWebSocketHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlWebSocketHandler.java index b904bee7..b20a8354 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlWebSocketHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlWebSocketHandler.java @@ -227,7 +227,7 @@ public class GraphQlWebSocketHandler extends TextWebSocketHandler implements Sub }); } else { - // Single response operation (query or mutation) + // Single response (query or mutation) outputFlux = (CollectionUtils.isEmpty(output.getErrors()) ? Flux.just(output) : Flux.error(new IllegalStateException("Execution failed: " + output.getErrors()))); } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/client/DefaultGraphQlClientTests.java b/spring-graphql/src/test/java/org/springframework/graphql/client/DefaultGraphQlClientTests.java index 04acd9d9..8cfea8f0 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/client/DefaultGraphQlClientTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/client/DefaultGraphQlClientTests.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import org.springframework.graphql.RequestInput; +import org.springframework.graphql.GraphQlRequest; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -58,7 +58,7 @@ public class DefaultGraphQlClientTests { TestTransport transport = new TestTransport(result); Project project = GraphQlClient.builder(transport).build() - .operation(query) + .document(query) .execute() .map(spec -> spec.toEntity("project", Project.class)) .block(); @@ -75,7 +75,7 @@ public class DefaultGraphQlClientTests { private final Mono response; @Nullable - private RequestInput savedRequestInput; + private GraphQlRequest savedRequest; public TestTransport(ExecutionResult response) { this(Mono.just(response)); @@ -85,19 +85,19 @@ public class DefaultGraphQlClientTests { this.response = response; } - public RequestInput getSavedRequestInput() { - Assert.notNull(this.savedRequestInput, "No saved RequestInput"); - return this.savedRequestInput; + public GraphQlRequest getSavedRequest() { + Assert.notNull(this.savedRequest, "No saved request"); + return this.savedRequest; } @Override - public Mono execute(RequestInput input) { - this.savedRequestInput = input; + public Mono execute(GraphQlRequest request) { + this.savedRequest = request; return this.response; } @Override - public Flux executeSubscription(RequestInput input) { + public Flux executeSubscription(GraphQlRequest request) { throw new UnsupportedOperationException(); } } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/client/MockWebSocketGraphQlTransportTests.java b/spring-graphql/src/test/java/org/springframework/graphql/client/MockWebSocketGraphQlTransportTests.java index a293d513..31fbc2e7 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/client/MockWebSocketGraphQlTransportTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/client/MockWebSocketGraphQlTransportTests.java @@ -33,6 +33,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import org.springframework.graphql.GraphQlRequest; import org.springframework.graphql.RequestInput; import org.springframework.graphql.web.webflux.GraphQlWebSocketMessage; import org.springframework.http.HttpHeaders; @@ -71,36 +72,36 @@ public class MockWebSocketGraphQlTransportTests { @Test void request() { - RequestInput input = this.mockServer.expectOperation("Query1").andRespond(this.result1); + GraphQlRequest request = this.mockServer.expectOperation("Query1").andRespond(this.result1); - StepVerifier.create(this.transport.execute(input)) + StepVerifier.create(this.transport.execute(request)) .expectNext(this.result1).expectComplete() .verify(TIMEOUT); assertActualClientMessages( GraphQlWebSocketMessage.connectionInit(null), - GraphQlWebSocketMessage.subscribe("1", input)); + GraphQlWebSocketMessage.subscribe("1", request)); } @Test void requestStream() { - RequestInput input = this.mockServer.expectOperation("Sub1").andStream(Flux.just(this.result1, result2)); + GraphQlRequest request = this.mockServer.expectOperation("Sub1").andStream(Flux.just(this.result1, result2)); - StepVerifier.create(this.transport.executeSubscription(input)) + StepVerifier.create(this.transport.executeSubscription(request)) .expectNext(this.result1, result2).expectComplete() .verify(TIMEOUT); assertActualClientMessages( GraphQlWebSocketMessage.connectionInit(null), - GraphQlWebSocketMessage.subscribe("1", input)); + GraphQlWebSocketMessage.subscribe("1", request)); } @Test void requestError() { - RequestInput input = this.mockServer.expectOperation("Query1") + GraphQlRequest request = this.mockServer.expectOperation("Query1") .andRespondWithError(GraphqlErrorBuilder.newError().message("boo").build()); - StepVerifier.create(this.transport.execute(input)) + StepVerifier.create(this.transport.execute(request)) .consumeNextWith(result -> { assertThat(result.isDataPresent()).isFalse(); assertThat(result.getErrors()).extracting(GraphQLError::getMessage).containsExactly("boo"); @@ -110,15 +111,15 @@ public class MockWebSocketGraphQlTransportTests { assertActualClientMessages( GraphQlWebSocketMessage.connectionInit(null), - GraphQlWebSocketMessage.subscribe("1", input)); + GraphQlWebSocketMessage.subscribe("1", request)); } @Test void requestStreamError() { - RequestInput input = this.mockServer.expectOperation("Sub1") + GraphQlRequest request = this.mockServer.expectOperation("Sub1") .andStreamWithError(Flux.just(this.result1), GraphqlErrorBuilder.newError().message("boo").build()); - StepVerifier.create(this.transport.executeSubscription(input)) + StepVerifier.create(this.transport.executeSubscription(request)) .expectNext(this.result1) .expectErrorSatisfies(actualEx -> { List errorList = ((SubscriptionErrorException) actualEx).getErrors(); @@ -128,29 +129,29 @@ public class MockWebSocketGraphQlTransportTests { assertActualClientMessages( GraphQlWebSocketMessage.connectionInit(null), - GraphQlWebSocketMessage.subscribe("1", input)); + GraphQlWebSocketMessage.subscribe("1", request)); } @Test void requestCancelled() { - RequestInput input = this.mockServer.expectOperation("Query1").andRespond(Mono.never()); + GraphQlRequest request = this.mockServer.expectOperation("Query1").andRespond(Mono.never()); - StepVerifier.create(this.transport.execute(input)) + StepVerifier.create(this.transport.execute(request)) .thenAwait(Duration.ofMillis(200)) .thenCancel() .verify(TIMEOUT); assertActualClientMessages( GraphQlWebSocketMessage.connectionInit(null), - GraphQlWebSocketMessage.subscribe("1", input)); + GraphQlWebSocketMessage.subscribe("1", request)); } @Test void requestStreamCancelled() { - RequestInput input = this.mockServer.expectOperation("s1") + GraphQlRequest request = this.mockServer.expectOperation("s1") .andStream(Flux.just(this.result1).concatWith(Flux.never())); - StepVerifier.create(this.transport.executeSubscription(input)) + StepVerifier.create(this.transport.executeSubscription(request)) .expectNext(this.result1) .thenAwait(Duration.ofMillis(200)) .thenCancel() @@ -158,7 +159,7 @@ public class MockWebSocketGraphQlTransportTests { assertActualClientMessages( GraphQlWebSocketMessage.connectionInit(null), - GraphQlWebSocketMessage.subscribe("1", input), + GraphQlWebSocketMessage.subscribe("1", request), GraphQlWebSocketMessage.complete("1")); } @@ -197,8 +198,8 @@ public class MockWebSocketGraphQlTransportTests { assertThat(this.testClient.getConnection(0).closeStatus().block(TIMEOUT)).isEqualTo(CloseStatus.NORMAL); // New requests are rejected - RequestInput input = this.mockServer.expectOperation("Query1").andRespond(this.result1); - StepVerifier.create(this.transport.execute(input)) + GraphQlRequest request = this.mockServer.expectOperation("Query1").andRespond(this.result1); + StepVerifier.create(this.transport.execute(request)) .expectErrorMessage("WebSocketGraphQlTransport has been stopped") .verify(TIMEOUT); @@ -208,8 +209,8 @@ public class MockWebSocketGraphQlTransportTests { assertThat(this.testClient.getConnection(1).isOpen()).isTrue(); // Requests allowed again - input = this.mockServer.expectOperation("Query1").andRespond(this.result1); - StepVerifier.create(this.transport.execute(input)) + request = this.mockServer.expectOperation("Query1").andRespond(this.result1); + StepVerifier.create(this.transport.execute(request)) .expectNext(this.result1).expectComplete() .verify(TIMEOUT); } @@ -217,14 +218,14 @@ public class MockWebSocketGraphQlTransportTests { @Test void sessionIsCachedUntilClosed() { - RequestInput input1 = this.mockServer.expectOperation("Query1").andRespond(this.result1); - StepVerifier.create(this.transport.execute(input1)).expectNext(this.result1).expectComplete().verify(TIMEOUT); + GraphQlRequest request1 = this.mockServer.expectOperation("Query1").andRespond(this.result1); + StepVerifier.create(this.transport.execute(request1)).expectNext(this.result1).expectComplete().verify(TIMEOUT); assertThat(this.testClient.getConnectionCount()).isEqualTo(1); TestWebSocketConnection originalConnection = this.testClient.getConnection(0); - RequestInput input2 = this.mockServer.expectOperation("Query2").andRespond(this.result2); - StepVerifier.create(this.transport.execute(input2)).expectNext(this.result2).expectComplete().verify(TIMEOUT); + GraphQlRequest request2 = this.mockServer.expectOperation("Query2").andRespond(this.result2); + StepVerifier.create(this.transport.execute(request2)).expectNext(this.result2).expectComplete().verify(TIMEOUT); assertThat(this.testClient.getConnectionCount()).isEqualTo(1); assertThat(this.testClient.getConnection(0)).isSameAs(originalConnection); @@ -232,8 +233,8 @@ public class MockWebSocketGraphQlTransportTests { // Close the connection originalConnection.closeServerSession(CloseStatus.NORMAL).block(TIMEOUT); - input1 = this.mockServer.expectOperation("Query1").andRespond(this.result1); - StepVerifier.create(this.transport.execute(input1)).expectNext(this.result1).expectComplete().verify(TIMEOUT); + request1 = this.mockServer.expectOperation("Query1").andRespond(this.result1); + StepVerifier.create(this.transport.execute(request1)).expectNext(this.result1).expectComplete().verify(TIMEOUT); assertThat(this.testClient.getConnectionCount()).isEqualTo(2); assertThat(this.testClient.getConnection(1)).isNotSameAs(originalConnection); @@ -280,7 +281,7 @@ public class MockWebSocketGraphQlTransportTests { String expectedMessage = "GraphQlSession over client-session-1 disconnected " + "with CloseStatus[code=1002, reason=null]"; - StepVerifier.create(transport.execute(new RequestInput("Query1", null, null, null, ""))) + StepVerifier.create(transport.execute(new GraphQlRequest("Query1"))) .expectErrorMessage(expectedMessage) .verify(TIMEOUT); } @@ -321,7 +322,7 @@ public class MockWebSocketGraphQlTransportTests { GraphQlWebSocketMessage responseMessage = (requestMessage.getType().equals("connection_init") ? GraphQlWebSocketMessage.connectionAck(null) : - GraphQlWebSocketMessage.subscribe(id, new RequestInput("..", null, null, null, ""))); + GraphQlWebSocketMessage.subscribe(id, new GraphQlRequest(""))); return Flux.just(this.codecDelegate.encode(session, responseMessage)); })); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/client/MockWebSocketServer.java b/spring-graphql/src/test/java/org/springframework/graphql/client/MockWebSocketServer.java index 276428b1..323ca230 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/client/MockWebSocketServer.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/client/MockWebSocketServer.java @@ -26,7 +26,7 @@ import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import org.springframework.graphql.RequestInput; +import org.springframework.graphql.GraphQlRequest; import org.springframework.graphql.web.webflux.GraphQlWebSocketMessage; import org.springframework.lang.Nullable; import org.springframework.web.reactive.socket.WebSocketHandler; @@ -63,7 +63,7 @@ public class MockWebSocketServer implements WebSocketHandler { */ public ResponseSpec expectOperation(String operation) { Exchange exchange = new Exchange(operation); - this.expectedExchanges.put(exchange.getInput().toMap(), exchange); + this.expectedExchanges.put(exchange.getRequest().toMap(), exchange); return exchange; } @@ -112,34 +112,34 @@ public class MockWebSocketServer implements WebSocketHandler { /** * Respond with the given a single result. */ - RequestInput andRespond(ExecutionResult result); + GraphQlRequest andRespond(ExecutionResult result); /** * Respond with the given a single result {@code Mono}. */ - RequestInput andRespond(Mono resultMono); + GraphQlRequest andRespond(Mono resultMono); /** * Respond with a GraphQL over WebSocket "error" message. */ - RequestInput andRespondWithError(GraphQLError error); + GraphQlRequest andRespondWithError(GraphQLError error); /** * Respond with the given stream of responses. */ - RequestInput andStream(Flux resultFlux); + GraphQlRequest andStream(Flux resultFlux); /** * Respond with the given stream of responses and terminate with an error. */ - RequestInput andStreamWithError(Flux resultFlux, GraphQLError error); + GraphQlRequest andStreamWithError(Flux resultFlux, GraphQLError error); } private static class Exchange implements ResponseSpec { - private final RequestInput requestInput; + private final GraphQlRequest request; private Flux responseFlux = Flux.empty(); @@ -148,42 +148,42 @@ public class MockWebSocketServer implements WebSocketHandler { private Exchange(String operation) { - this.requestInput = new RequestInput(operation, null, null, null, ""); + this.request = new GraphQlRequest(operation); } @Override - public RequestInput andRespond(ExecutionResult result) { + public GraphQlRequest andRespond(ExecutionResult result) { return addResponse(Flux.just(result), null); } @Override - public RequestInput andRespond(Mono resultMono) { + public GraphQlRequest andRespond(Mono resultMono) { return addResponse(Flux.from(resultMono), null); } @Override - public RequestInput andRespondWithError(GraphQLError error) { + public GraphQlRequest andRespondWithError(GraphQLError error) { return addResponse(Flux.empty(), error); } @Override - public RequestInput andStream(Flux resultFlux) { + public GraphQlRequest andStream(Flux resultFlux) { return addResponse(resultFlux, null); } @Override - public RequestInput andStreamWithError(Flux resultFlux, GraphQLError error) { + public GraphQlRequest andStreamWithError(Flux resultFlux, GraphQLError error) { return addResponse(resultFlux, error); } - private RequestInput addResponse(Flux resultFlux, @Nullable GraphQLError error) { + private GraphQlRequest addResponse(Flux resultFlux, @Nullable GraphQLError error) { this.responseFlux = resultFlux; this.error = error; - return this.requestInput; + return this.request; } - public RequestInput getInput() { - return this.requestInput; + public GraphQlRequest getRequest() { + return this.request; } public Flux getResponseFlux() {