Skip to content

Commit 5b0496c

Browse files
committed
Allow setting custom ExecutionId on RequestInput
Prior to this commit, we would use the request id as an execution id, regardless of the overall GraphQL configuration. This commit: * adds a new executionId field on `RequestInput` and uses it in the `ExecutionInput`, if provided * only uses the request id as a fallback if it's been asked to * auto-detect in the `GraphQlService` whether a custom `ExecutionIdProvider` implementation was configured on `GraphQL`. Closes gh-243
1 parent c9f35ce commit 5b0496c

File tree

4 files changed

+82
-16
lines changed

4 files changed

+82
-16
lines changed

spring-graphql/src/main/java/org/springframework/graphql/RequestInput.java

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@
3333

3434
/**
3535
* Common representation for GraphQL request input. This can be converted to
36-
* {@link ExecutionInput} via {@link #toExecutionInput()} and the
36+
* {@link ExecutionInput} via {@link #toExecutionInput(boolean)} and the
3737
* {@code ExecutionInput} further customized via
3838
* {@link #configureExecutionInput(BiFunction)}.
3939
*
@@ -57,6 +57,8 @@ public class RequestInput {
5757

5858
private final List<BiFunction<ExecutionInput, ExecutionInput.Builder, ExecutionInput>> executionInputConfigurers = new ArrayList<>();
5959

60+
@Nullable
61+
private ExecutionId executionId;
6062

6163
/**
6264
* Create an instance.
@@ -80,14 +82,9 @@ public RequestInput(
8082
}
8183

8284

83-
@SuppressWarnings("unchecked")
84-
private static <T> T getKey(String key, Map<String, Object> body) {
85-
return (T) body.get(key);
86-
}
87-
8885
/**
89-
* Return an identifier for the request. This id is later propagated
90-
* as the {@link ExecutionId} of the execution input.
86+
* Return an identifier for the request. This id can be later propagated
87+
* as the {@link ExecutionId} if {@link #executionId(ExecutionId) none has been set}.
9188
* <p>For web transports, this identifier can be used to correlate
9289
* request and response messages on a multiplexed connection.
9390
* @return the request id.
@@ -131,6 +128,15 @@ public Locale getLocale() {
131128
return this.locale;
132129
}
133130

131+
/**
132+
* Set an {@link ExecutionId} to be used for the {@link ExecutionInput}
133+
* @param executionId the execution id to use with the {@link ExecutionInput}.
134+
*/
135+
public void executionId(ExecutionId executionId) {
136+
Assert.notNull(executionId, "executionId should not be null");
137+
this.executionId = executionId;
138+
}
139+
134140
/**
135141
* Provide a consumer to configure the {@link ExecutionInput} used for input to
136142
* {@link graphql.GraphQL#executeAsync(ExecutionInput)}. The builder is initially
@@ -148,16 +154,22 @@ public void configureExecutionInput(BiFunction<ExecutionInput, ExecutionInput.Bu
148154
* populated from {@link #getQuery()}, {@link #getOperationName()}, and
149155
* {@link #getVariables()}, and is then further customized through
150156
* {@link #configureExecutionInput(BiFunction)}.
157+
* @param useRequestId whether the {@link #getId()} should be used as a fallback for {@link ExecutionId}.
151158
* @return the execution input
152159
*/
153-
public ExecutionInput toExecutionInput() {
154-
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
160+
public ExecutionInput toExecutionInput(boolean useRequestId) {
161+
ExecutionInput.Builder inputBuilder = ExecutionInput.newExecutionInput()
155162
.query(this.query)
156163
.operationName(this.operationName)
157164
.variables(this.variables)
158-
.locale(this.locale)
159-
.executionId(ExecutionId.from(this.id))
160-
.build();
165+
.locale(this.locale);
166+
if (this.executionId != null) {
167+
inputBuilder.executionId(this.executionId);
168+
}
169+
else if (useRequestId) {
170+
inputBuilder.executionId(ExecutionId.from(this.id));
171+
}
172+
ExecutionInput executionInput = inputBuilder.build();
161173

162174
for (BiFunction<ExecutionInput, ExecutionInput.Builder, ExecutionInput> configurer : this.executionInputConfigurers) {
163175
ExecutionInput current = executionInput;

spring-graphql/src/main/java/org/springframework/graphql/execution/ExecutionGraphQlService.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import graphql.ExecutionInput;
2323
import graphql.GraphQL;
2424
import graphql.GraphQLContext;
25+
import graphql.execution.ExecutionIdProvider;
2526
import org.dataloader.DataLoaderRegistry;
2627
import reactor.core.publisher.Mono;
2728

@@ -42,9 +43,12 @@ public class ExecutionGraphQlService implements GraphQlService {
4243

4344
private final List<DataLoaderRegistrar> dataLoaderRegistrars = new ArrayList<>();
4445

46+
private final boolean hasDefaultExecutionIdProvider;
47+
4548

4649
public ExecutionGraphQlService(GraphQlSource graphQlSource) {
4750
this.graphQlSource = graphQlSource;
51+
this.hasDefaultExecutionIdProvider = ExecutionIdProvider.DEFAULT_EXECUTION_ID_PROVIDER == graphQlSource.graphQl().getIdProvider();
4852
}
4953

5054

@@ -61,7 +65,7 @@ public void addDataLoaderRegistrar(DataLoaderRegistrar registrar) {
6165
@Override
6266
public final Mono<RequestOutput> execute(RequestInput requestInput) {
6367
return Mono.deferContextual((contextView) -> {
64-
ExecutionInput executionInput = requestInput.toExecutionInput();
68+
ExecutionInput executionInput = requestInput.toExecutionInput(this.hasDefaultExecutionIdProvider);
6569
ReactorContextManager.setReactorContext(contextView, executionInput);
6670
ExecutionInput updatedExecutionInput = registerDataLoaders(executionInput);
6771
return Mono.fromFuture(this.graphQlSource.graphQl().executeAsync(updatedExecutionInput))
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2020-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.graphql;
18+
19+
import graphql.execution.ExecutionId;
20+
import org.junit.jupiter.api.Test;
21+
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
24+
/**
25+
* Tests for {@link RequestInput}.
26+
*
27+
* @author Brian Clozel
28+
*/
29+
class RequestInputTests {
30+
31+
private RequestInput requestInput = new RequestInput("greeting", "Greeting", null, null, "id");
32+
33+
@Test
34+
void shouldUseCustomExecutionIdIfPresent() {
35+
ExecutionId customId = ExecutionId.from("customId");
36+
this.requestInput.executionId(customId);
37+
assertThat(this.requestInput.toExecutionInput(true).getExecutionId()).isEqualTo(customId);
38+
assertThat(this.requestInput.toExecutionInput(false).getExecutionId()).isEqualTo(customId);
39+
}
40+
41+
@Test
42+
void executionIdShouldFallBackToRequestId() {
43+
assertThat(this.requestInput.toExecutionInput(true).getExecutionId()).isEqualTo(ExecutionId.from("id"));
44+
}
45+
46+
@Test
47+
void executionIdShouldFallBackToProvider() {
48+
assertThat(this.requestInput.toExecutionInput(false).getExecutionId()).isNull();
49+
}
50+
}

spring-graphql/src/test/java/org/springframework/graphql/web/WebInterceptorTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ void executionInputCustomization() {
7979

8080
WebGraphQlHandler handler = WebGraphQlHandler
8181
.builder((input) -> {
82-
actualName.set(input.toExecutionInput().getOperationName());
82+
actualName.set(input.toExecutionInput(true).getOperationName());
8383
return emptyExecutionResult(input);
8484
})
8585
.interceptor((webInput, next) -> {

0 commit comments

Comments
 (0)