Skip to content

Commit 320bce7

Browse files
committed
Merge branch '1.2.x'
2 parents abcc384 + 33bdea9 commit 320bce7

File tree

2 files changed

+67
-33
lines changed

2 files changed

+67
-33
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/method/InvocableHandlerMethodSupport.java

+41-20
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.concurrent.Executor;
2727

2828
import graphql.GraphQLContext;
29+
import io.micrometer.context.ContextSnapshot;
2930
import reactor.core.publisher.Mono;
3031

3132
import org.springframework.core.CoroutinesUtils;
@@ -110,32 +111,22 @@ protected Object doInvoke(GraphQLContext graphQLContext, Object... argValues) {
110111
Object result;
111112
if (this.invokeAsync) {
112113
Callable<Object> callable = () -> method.invoke(getBean(), argValues);
113-
result = adaptCallable(graphQLContext, callable);
114+
result = adaptCallable(graphQLContext, callable, method, argValues);
114115
}
115116
else {
116117
result = method.invoke(getBean(), argValues);
117118
if (this.hasCallableReturnValue && result != null) {
118-
result = adaptCallable(graphQLContext, (Callable<?>) result);
119+
result = adaptCallable(graphQLContext, (Callable<?>) result, method, argValues);
119120
}
120121
}
121122

122123
return result;
123124
}
124125
catch (IllegalArgumentException ex) {
125-
assertTargetBean(method, getBean(), argValues);
126-
String text = (ex.getMessage() != null) ? ex.getMessage() : "Illegal argument";
127-
return Mono.error(new IllegalStateException(formatInvokeError(text, argValues), ex));
126+
return Mono.error(processIllegalArgumentException(argValues, ex, method));
128127
}
129128
catch (InvocationTargetException ex) {
130-
// Unwrap for DataFetcherExceptionResolvers ...
131-
Throwable targetException = ex.getTargetException();
132-
if (targetException instanceof Error || targetException instanceof Exception) {
133-
return Mono.error(targetException);
134-
}
135-
else {
136-
return Mono.error(new IllegalStateException(
137-
formatInvokeError("Invocation failure", argValues), targetException));
138-
}
129+
return Mono.error(processInvocationTargetException(argValues, ex));
139130
}
140131
catch (Throwable ex) {
141132
return Mono.error(ex);
@@ -155,16 +146,46 @@ private static Object invokeSuspendingFunction(Object bean, Method method, Objec
155146
return result;
156147
}
157148

158-
private CompletableFuture<?> adaptCallable(GraphQLContext graphQLContext, Callable<?> result) {
159-
return CompletableFuture.supplyAsync(() -> {
149+
@SuppressWarnings("DataFlowIssue")
150+
private CompletableFuture<?> adaptCallable(
151+
GraphQLContext graphQLContext, Callable<?> result, Method method, Object[] argValues) {
152+
153+
CompletableFuture<Object> future = new CompletableFuture<>();
154+
this.executor.execute(() -> {
160155
try {
161-
return ContextSnapshotFactoryHelper.captureFrom(graphQLContext).wrap(result).call();
156+
ContextSnapshot snapshot = ContextSnapshotFactoryHelper.captureFrom(graphQLContext);
157+
Object value = snapshot.wrap((Callable<?>) result).call();
158+
future.complete(value);
159+
}
160+
catch (IllegalArgumentException ex) {
161+
future.completeExceptionally(processIllegalArgumentException(argValues, ex, method));
162+
}
163+
catch (InvocationTargetException ex) {
164+
future.completeExceptionally(processInvocationTargetException(argValues, ex));
162165
}
163166
catch (Exception ex) {
164-
String msg = "Failure in Callable returned from " + getBridgedMethod().toGenericString();
165-
throw new IllegalStateException(msg, ex);
167+
future.completeExceptionally(ex);
166168
}
167-
}, this.executor);
169+
});
170+
return future;
171+
}
172+
173+
private IllegalStateException processIllegalArgumentException(
174+
Object[] argValues, IllegalArgumentException ex, Method method) {
175+
176+
assertTargetBean(method, getBean(), argValues);
177+
String text = (ex.getMessage() != null) ? ex.getMessage() : "Illegal argument";
178+
return new IllegalStateException(formatInvokeError(text, argValues), ex);
179+
}
180+
181+
private Throwable processInvocationTargetException(Object[] argValues, InvocationTargetException ex) {
182+
// Unwrap for DataFetcherExceptionResolvers ...
183+
Throwable targetException = ex.getTargetException();
184+
if (targetException instanceof Error || targetException instanceof Exception) {
185+
return targetException;
186+
}
187+
String message = formatInvokeError("Invocation failure", argValues);
188+
return new IllegalStateException(message, targetException);
168189
}
169190

170191
/**

spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/DataFetcherHandlerMethodTests.java

+26-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -19,24 +19,23 @@
1919

2020
import java.lang.reflect.Method;
2121
import java.util.Collections;
22+
import java.util.Map;
2223
import java.util.concurrent.Callable;
2324
import java.util.concurrent.CompletableFuture;
2425

2526
import graphql.GraphQLContext;
2627
import graphql.schema.DataFetchingEnvironment;
2728
import graphql.schema.DataFetchingEnvironmentImpl;
2829
import org.junit.jupiter.api.Test;
29-
import org.mockito.Mockito;
3030
import reactor.core.publisher.Mono;
31+
import reactor.test.StepVerifier;
3132

3233
import org.springframework.core.task.SimpleAsyncTaskExecutor;
3334
import org.springframework.graphql.data.GraphQlArgumentBinder;
3435
import org.springframework.graphql.data.method.HandlerMethod;
35-
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
3636
import org.springframework.graphql.data.method.HandlerMethodArgumentResolverComposite;
3737
import org.springframework.graphql.data.method.annotation.Argument;
3838
import org.springframework.graphql.data.method.annotation.QueryMapping;
39-
import org.springframework.lang.Nullable;
4039
import org.springframework.security.authentication.TestingAuthenticationToken;
4140
import org.springframework.security.core.annotation.AuthenticationPrincipal;
4241
import org.springframework.security.core.context.SecurityContextHolder;
@@ -72,32 +71,43 @@ void annotatedMethodsOnInterface() {
7271

7372
@Test
7473
void asyncInvocation() throws Exception {
75-
testAsyncInvocation("handleSync", true);
74+
testAsyncInvocation("handleSync", false, true, "A");
7675
}
7776

7877
@Test
7978
void asyncInvocationWithCallableReturnValue() throws Exception {
80-
testAsyncInvocation("handleAndReturnCallable", false);
79+
testAsyncInvocation("handleAndReturnCallable", false, false, "A");
8180
}
8281

83-
private static void testAsyncInvocation(String methodName, boolean invokeAsync) throws Exception {
82+
@Test
83+
void asyncInvocationWithCallableReturnValueError() throws Exception {
84+
testAsyncInvocation("handleAndReturnCallable", true, false, "simulated exception");
85+
}
86+
87+
private static void testAsyncInvocation(
88+
String methodName, boolean raiseError, boolean invokeAsync, String expected) throws Exception {
89+
8490
HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
85-
resolvers.addResolver(Mockito.mock(HandlerMethodArgumentResolver.class));
91+
resolvers.addResolver(new ArgumentMethodArgumentResolver(new GraphQlArgumentBinder()));
8692

8793
DataFetcherHandlerMethod handlerMethod = new DataFetcherHandlerMethod(
8894
handlerMethodFor(new TestController(), methodName), resolvers, null,
8995
new SimpleAsyncTaskExecutor(), invokeAsync, false);
9096

9197
DataFetchingEnvironment environment = DataFetchingEnvironmentImpl
9298
.newDataFetchingEnvironment()
99+
.arguments(Map.of("raiseError", raiseError)) // gh-973
93100
.graphQLContext(GraphQLContext.newContext().build())
94101
.build();
95102

96103
Object result = handlerMethod.invoke(environment);
97104

98105
assertThat(result).isInstanceOf(CompletableFuture.class);
99106
CompletableFuture<String> future = (CompletableFuture<String>) result;
100-
assertThat(future.get()).isEqualTo("A");
107+
if (raiseError) {
108+
future = future.handle((s, ex) -> ex.getMessage());
109+
}
110+
assertThat(future.get()).isEqualTo(expected);
101111
}
102112

103113
@Test
@@ -144,14 +154,17 @@ public String hello(String name) {
144154
return "Hello, " + name;
145155
}
146156

147-
@Nullable
148157
public String handleSync() {
149158
return "A";
150159
}
151160

152-
@Nullable
153-
public Callable<String> handleAndReturnCallable() {
154-
return () -> "A";
161+
public Callable<String> handleAndReturnCallable(@Argument boolean raiseError) {
162+
return () -> {
163+
if (raiseError) {
164+
throw new IllegalStateException("simulated exception");
165+
}
166+
return "A";
167+
};
155168
}
156169

157170
public CompletableFuture<String> handleAndReturnFuture(@AuthenticationPrincipal User user) {

0 commit comments

Comments
 (0)