diff --git a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultWebGraphQlTester.java b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultWebGraphQlTester.java index 333a565c..9140d409 100644 --- a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultWebGraphQlTester.java +++ b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultWebGraphQlTester.java @@ -29,6 +29,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; /** * Default implementation of {@link WebGraphQlTester}. @@ -161,7 +162,8 @@ class DefaultWebGraphQlTester implements WebGraphQlTester { private WebInput createWebInput() { RequestInput input = createRequestInput(); - return new WebInput(DEFAULT_URL, this.headers, input.toMap(), input.getLocale(), null); + return new WebInput(DEFAULT_URL, this.headers, input.toMap(), input.getLocale(), + (input.getId() != null) ? input.getId() : ObjectUtils.getIdentityHexString(input)); } } diff --git a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/GraphQlTesterRequestSpecSupport.java b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/GraphQlTesterRequestSpecSupport.java index 2a90cee8..1fce864a 100644 --- a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/GraphQlTesterRequestSpecSupport.java +++ b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/GraphQlTesterRequestSpecSupport.java @@ -66,7 +66,7 @@ class GraphQlTesterRequestSpecSupport { } protected RequestInput createRequestInput() { - return new RequestInput(this.query, this.operationName, this.variables, this.locale); + return new RequestInput(this.query, this.operationName, this.variables, this.locale, null); } } 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 42d054aa..c4d9e7f0 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 @@ -45,6 +45,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -227,7 +228,7 @@ public class WebGraphQlTesterTests { String content = request.getBody().readUtf8(); Map map = new ObjectMapper().readValue(content, new TypeReference>() {}); - WebInput webInput = new WebInput(request.getRequestUrl().uri(), headers, map, null, null); + WebInput webInput = new WebInput(request.getRequestUrl().uri(), headers, map, null, ObjectUtils.getIdentityHexString(request)); consumer.accept(webInput); } 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 bff28512..437437c5 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/RequestInput.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/RequestInput.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.function.BiFunction; import graphql.ExecutionInput; +import graphql.execution.ExecutionId; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -46,6 +47,9 @@ public class RequestInput { @Nullable private final String operationName; + @Nullable + private final String id; + private final Map variables; @Nullable @@ -60,16 +64,18 @@ public class RequestInput { * @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 id an optional request id, to be used as the execution id */ public RequestInput( String query, @Nullable String operationName, @Nullable Map variables, - @Nullable Locale locale) { + @Nullable Locale locale, @Nullable String id) { Assert.notNull(query, "'query' is required"); this.query = query; this.operationName = operationName; this.variables = ((variables != null) ? variables : Collections.emptyMap()); this.locale = locale; + this.id = id; } @@ -78,6 +84,15 @@ public class RequestInput { return (T) body.get(key); } + /** + * Return the explicitly assigned request id. + * @return the request id or {@code null}. + */ + @Nullable + public String getId() { + return this.id; + } + /** * Return the query, mutation, or subscription for the request. * @return the query, a non-empty string. @@ -137,6 +152,7 @@ public class RequestInput { .operationName(this.operationName) .variables(this.variables) .locale(this.locale) + .executionId((this.id != null) ? ExecutionId.from(this.id) : null) .build(); for (BiFunction configurer : this.executionInputConfigurers) { 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 fcd05f6d..a5e2dd4c 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 @@ -45,8 +45,6 @@ public class WebInput extends RequestInput { private final HttpHeaders headers; - private final String id; - /** * Create an instance. * @param uri the URL for the HTTP request or WebSocket handshake @@ -61,12 +59,11 @@ public class WebInput extends RequestInput { URI uri, HttpHeaders headers, Map body, @Nullable Locale locale, @Nullable String id) { - super(getKey("query", body), getKey("operationName", body), getKey("variables", body), locale); + super(getKey("query", body), getKey("operationName", body), getKey("variables", body), locale, id); Assert.notNull(uri, "URI is required'"); Assert.notNull(headers, "HttpHeaders is required'"); this.uri = UriComponentsBuilder.fromUri(uri).build(true); this.headers = headers; - this.id = (id != null) ? id : ObjectUtils.identityToString(this); } @SuppressWarnings("unchecked") @@ -97,8 +94,9 @@ public class WebInput extends RequestInput { * request and response messages on a multiplexed connection. * @see GraphQL over WebSocket Protocol */ + @Override public String getId() { - return this.id; + return (super.getId() != null) ? super.getId() : ObjectUtils.getIdentityHexString(this); } } 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 a2496672..2066f2ee 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 @@ -30,6 +30,7 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.graphql.web.WebGraphQlHandler; import org.springframework.graphql.web.WebInput; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.server.ServerWebInputException; import org.springframework.web.servlet.function.ServerRequest; @@ -72,7 +73,7 @@ public class GraphQlHttpHandler { WebInput input = new WebInput( request.uri(), request.headers().asHttpHeaders(), readBody(request), - LocaleContextHolder.getLocale(), null); + LocaleContextHolder.getLocale(), ObjectUtils.getIdentityHexString(request)); if (logger.isDebugEnabled()) { logger.debug("Executing: " + input); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/BatchMappingInvocationTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/BatchMappingInvocationTests.java index d0f40f0e..6bc378f3 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/BatchMappingInvocationTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/BatchMappingInvocationTests.java @@ -71,7 +71,7 @@ public class BatchMappingInvocationTests extends BatchMappingTestSupport { "}"; Mono resultMono = createGraphQlService(controller) - .execute(new RequestInput(query, null, null, null)); + .execute(new RequestInput(query, null, null, null, null)); List actualCourses = GraphQlResponse.from(resultMono).toList("courses", Course.class); List courses = Course.allCourses(); @@ -104,7 +104,7 @@ public class BatchMappingInvocationTests extends BatchMappingTestSupport { "}"; Mono resultMono = createGraphQlService(controller) - .execute(new RequestInput(query, null, null, null)); + .execute(new RequestInput(query, null, null, null, null)); List actualCourses = GraphQlResponse.from(resultMono).toList("courses", Course.class); List courses = Course.allCourses(); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/BatchMappingPrincipalMethodArgumentResolverTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/BatchMappingPrincipalMethodArgumentResolverTests.java index 08fded42..49550742 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/BatchMappingPrincipalMethodArgumentResolverTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/BatchMappingPrincipalMethodArgumentResolverTests.java @@ -95,7 +95,7 @@ public class BatchMappingPrincipalMethodArgumentResolverTests extends BatchMappi Mono resultMono = Mono.delay(Duration.ofMillis(10)) .flatMap(aLong -> { String query = "{ courses { id instructor { id } } }"; - return createGraphQlService(controller).execute(new RequestInput(query, null, null, null)); + return createGraphQlService(controller).execute(new RequestInput(query, null, null, null, null)); }) .contextWrite(contextWriter); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingInvocationTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingInvocationTests.java index 4b85762f..dff141d2 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingInvocationTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingInvocationTests.java @@ -68,7 +68,7 @@ public class SchemaMappingInvocationTests { " }" + "}"; - Mono resultMono = graphQlService().execute(new RequestInput(query, null, null, null)); + Mono resultMono = graphQlService().execute(new RequestInput(query, null, null, null, null)); Book book = GraphQlResponse.from(resultMono).toEntity("bookById", Book.class); assertThat(book.getId()).isEqualTo(1); @@ -88,7 +88,7 @@ public class SchemaMappingInvocationTests { " }" + "}"; - Mono resultMono = graphQlService().execute(new RequestInput(query, null, null, null)); + Mono resultMono = graphQlService().execute(new RequestInput(query, null, null, null, null)); List bookList = GraphQlResponse.from(resultMono).toList("booksByCriteria", Book.class); assertThat(bookList).hasSize(2); @@ -107,7 +107,7 @@ public class SchemaMappingInvocationTests { "}"; AtomicReference contextRef = new AtomicReference<>(); - RequestInput requestInput = new RequestInput(query, null, null, null); + RequestInput requestInput = new RequestInput(query, null, null, null, null); requestInput.configureExecutionInput((executionInput, builder) -> { contextRef.set(executionInput.getGraphQLContext()); return executionInput; @@ -134,7 +134,7 @@ public class SchemaMappingInvocationTests { "}"; Mono resultMono = graphQlService() - .execute(new RequestInput(operation, null, null, null)); + .execute(new RequestInput(operation, null, null, null, null)); Author author = GraphQlResponse.from(resultMono).toEntity("addAuthor", Author.class); assertThat(author.getId()).isEqualTo(99); @@ -152,7 +152,7 @@ public class SchemaMappingInvocationTests { "}"; Mono resultMono = graphQlService() - .execute(new RequestInput(operation, null, null, null)); + .execute(new RequestInput(operation, null, null, null, null)); Flux bookFlux = GraphQlResponse.forSubscription(resultMono) .map(response -> response.toEntity("bookSearch", Book.class)); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingPrincipalMethodArgumentResolverTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingPrincipalMethodArgumentResolverTests.java index 9ab5d562..1aee915a 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingPrincipalMethodArgumentResolverTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingPrincipalMethodArgumentResolverTests.java @@ -163,7 +163,7 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests { .toGraphQlService(); return Mono.delay(Duration.ofMillis(10)) - .flatMap(aLong -> graphQlService.execute(new RequestInput(op, null, null, null))) + .flatMap(aLong -> graphQlService.execute(new RequestInput(op, null, null, null, null))) .contextWrite(contextWriter); } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/execution/BatchLoadingTests.java b/spring-graphql/src/test/java/org/springframework/graphql/execution/BatchLoadingTests.java index 7425bb44..d22d7fb7 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/execution/BatchLoadingTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/execution/BatchLoadingTests.java @@ -76,7 +76,7 @@ public class BatchLoadingTests { .dataLoaders(this.registry) .toGraphQlService(); - Mono resultMono = service.execute(new RequestInput(query, null, null, null)); + Mono resultMono = service.execute(new RequestInput(query, null, null, null, null)); List books = GraphQlResponse.from(resultMono).toList("booksByCriteria", Book.class); assertThat(books).hasSize(2); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/execution/ClassNameTypeResolverTests.java b/spring-graphql/src/test/java/org/springframework/graphql/execution/ClassNameTypeResolverTests.java index aae4d1d8..124345e2 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/execution/ClassNameTypeResolverTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/execution/ClassNameTypeResolverTests.java @@ -85,7 +85,7 @@ public class ClassNameTypeResolverTests { Mono resultMono = graphQlSetup.queryFetcher("animals", env -> animalList) .toGraphQlService() - .execute(new RequestInput(query, null, null, null)); + .execute(new RequestInput(query, null, null, null, null)); GraphQlResponse response = GraphQlResponse.from(resultMono); for (int i = 0; i < animalList.size(); i++) { @@ -128,7 +128,7 @@ public class ClassNameTypeResolverTests { Mono result = graphQlSetup.queryFetcher("sightings", env -> animalAndPlantList) .typeResolver(typeResolver) .toGraphQlService() - .execute(new RequestInput(query, null, null, null)); + .execute(new RequestInput(query, null, null, null, null)); GraphQlResponse response = GraphQlResponse.from(result); for (int i = 0; i < animalAndPlantList.size(); i++) { diff --git a/spring-graphql/src/test/java/org/springframework/graphql/web/webflux/GraphQlHttpHandlerTests.java b/spring-graphql/src/test/java/org/springframework/graphql/web/webflux/GraphQlHttpHandlerTests.java index 1ae1cedb..7387dac6 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/web/webflux/GraphQlHttpHandlerTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/web/webflux/GraphQlHttpHandlerTests.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; @@ -59,6 +61,22 @@ public class GraphQlHttpHandlerTests { .isEqualTo("{\"data\":{\"greeting\":\"Hello in fr\"}}"); } + @Test + void shouldSetExecutionId() { + GraphQlHttpHandler handler = GraphQlSetup.schemaContent("type Query { showId: String }") + .queryFetcher("showId", (env) -> env.getExecutionId().toString()) + .toHttpHandlerWebFlux(); + + MockServerHttpRequest httpRequest = MockServerHttpRequest.post("/").build(); + + MockServerHttpResponse httpResponse = handleRequest( + httpRequest, handler, Collections.singletonMap("query", "{showId}")); + + DocumentContext document = JsonPath.parse(httpResponse.getBodyAsString().block()); + String id = document.read("data.showId", String.class); + assertThat(id).isEqualTo(httpRequest.getId()); + } + private MockServerHttpResponse handleRequest( MockServerHttpRequest httpRequest, GraphQlHttpHandler handler, Map body) { diff --git a/spring-graphql/src/test/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandlerTests.java b/spring-graphql/src/test/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandlerTests.java index fe3118b0..c4f9211d 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandlerTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandlerTests.java @@ -23,6 +23,8 @@ import java.util.Locale; import javax.servlet.ServletException; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; import org.junit.jupiter.api.Test; import org.springframework.context.i18n.LocaleContextHolder; @@ -52,13 +54,9 @@ public class GraphQlHttpHandlerTests { GraphQlHttpHandler handler = GraphQlSetup.schemaContent("type Query { greeting: String }") .queryFetcher("greeting", (env) -> "Hello in " + env.getLocale()) .toHttpHandler(); - - MockHttpServletRequest servletRequest = new MockHttpServletRequest("POST", "/"); - servletRequest.setContentType("application/json"); - servletRequest.setContent("{\"query\":\"{ greeting }\"}".getBytes(StandardCharsets.UTF_8)); - servletRequest.setAsyncSupported(true); - + MockHttpServletRequest servletRequest = createServletRequest("{\"query\":\"{ greeting }\"}"); LocaleContextHolder.setLocale(Locale.FRENCH); + try { MockHttpServletResponse servletResponse = handleRequest(servletRequest, handler); @@ -70,6 +68,28 @@ public class GraphQlHttpHandlerTests { } } + @Test + void shouldSetExecutionId() throws Exception { + GraphQlHttpHandler handler = GraphQlSetup.schemaContent("type Query { showId: ID! }") + .queryFetcher("showId", (env) -> env.getExecutionId().toString()) + .toHttpHandler(); + + MockHttpServletRequest servletRequest = createServletRequest("{\"query\":\"{ showId }\"}"); + + MockHttpServletResponse servletResponse = handleRequest(servletRequest, handler); + DocumentContext document = JsonPath.parse(servletResponse.getContentAsString()); + String id = document.read("data.showId", String.class); + assertThat(id).hasSize(8); + } + + private MockHttpServletRequest createServletRequest(String query) { + MockHttpServletRequest servletRequest = new MockHttpServletRequest("POST", "/"); + servletRequest.setContentType("application/json"); + servletRequest.setContent(query.getBytes(StandardCharsets.UTF_8)); + servletRequest.setAsyncSupported(true); + return servletRequest; + } + private MockHttpServletResponse handleRequest( MockHttpServletRequest servletRequest, GraphQlHttpHandler handler) throws ServletException, IOException {