Skip to content

Commit 9d41013

Browse files
committed
exception handling - best match
1 parent acb90ae commit 9d41013

File tree

4 files changed

+78
-8
lines changed

4 files changed

+78
-8
lines changed

grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcExceptionHandlerMethodResolver.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
import java.util.function.Function;
3030

3131
import org.springframework.beans.factory.InitializingBean;
32+
import org.springframework.core.ExceptionDepthComparator;
3233
import org.springframework.lang.NonNull;
34+
import org.springframework.lang.Nullable;
3335
import org.springframework.util.Assert;
3436

3537
/**
@@ -175,11 +177,13 @@ public <E extends Throwable> boolean isMethodMappedForException(Class<E> excepti
175177
return extractExtendedThrowable(exception) != null;
176178
}
177179

178-
private <E extends Throwable> Method extractExtendedThrowable(Class<E> exception) {
180+
@Nullable
181+
private <E extends Throwable> Method extractExtendedThrowable(Class<E> exceptionType) {
182+
179183
return mappedMethods.keySet()
180184
.stream()
181-
.filter(clazz -> clazz.isAssignableFrom(exception))
182-
.findAny()
185+
.filter(ex -> ex.isAssignableFrom(exceptionType))
186+
.min(new ExceptionDepthComparator(exceptionType))
183187
.map(mappedMethods::get)
184188
.orElse(null);
185189
}

tests/src/test/java/net/devh/boot/grpc/test/advice/AdviceExceptionHandlingTest.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.beans.factory.annotation.Autowired;
3030
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
3131
import org.springframework.boot.test.context.SpringBootTest;
32+
import org.springframework.security.authentication.AccountExpiredException;
3233
import org.springframework.test.annotation.DirtiesContext;
3334
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
3435

@@ -45,6 +46,7 @@
4546
import net.devh.boot.grpc.test.config.GrpcAdviceConfig;
4647
import net.devh.boot.grpc.test.config.GrpcAdviceConfig.TestAdviceWithMetadata.FirstLevelException;
4748
import net.devh.boot.grpc.test.config.GrpcAdviceConfig.TestAdviceWithMetadata.MyRootRuntimeException;
49+
import net.devh.boot.grpc.test.config.GrpcAdviceConfig.TestAdviceWithMetadata.SecondLevelException;
4850
import net.devh.boot.grpc.test.config.GrpcAdviceConfig.TestAdviceWithMetadata.StatusMappingException;
4951
import net.devh.boot.grpc.test.config.GrpcAdviceConfig.TestGrpcAdviceService;
5052
import net.devh.boot.grpc.test.config.InProcessConfiguration;
@@ -119,7 +121,8 @@ void testThrownClassCastException_IsMappedAsStatusRuntimeExceptionAndWithMetadat
119121
@DirtiesContext
120122
void testThrownMyRootRuntimeException_IsNotMappedAndResultsInInvocationException() {
121123

122-
MyRootRuntimeException exceptionToMap = new MyRootRuntimeException("Trigger Advice");
124+
AccountExpiredException exceptionToMap =
125+
new AccountExpiredException("Trigger Advice"); // not mapped in GrpcAdviceConfig
123126
testGrpcAdviceService.setExceptionToSimulate(exceptionToMap);
124127
Status expectedStatus =
125128
Status.INTERNAL.withDescription("There was a server error trying to handle an exception");
@@ -166,6 +169,33 @@ void testThrownStatusMappingException_IsResolvedAsInternalServerError() {
166169
Level.ERROR));
167170
}
168171

172+
@Test
173+
@DirtiesContext
174+
void testThrownRootDepth_IsMappedCorrectlyWithRootException() {
175+
176+
MyRootRuntimeException rootRuntimeException = new MyRootRuntimeException("root exception triggered.");
177+
178+
testGrpcAdviceService.setExceptionToSimulate(rootRuntimeException);
179+
Status expectedStatus = Status.DEADLINE_EXCEEDED.withDescription(rootRuntimeException.getMessage());
180+
Metadata metadata = new Metadata();
181+
182+
testGrpcCallAndVerifyMappedException(expectedStatus, metadata);
183+
}
184+
185+
@Test
186+
@DirtiesContext
187+
void testThrownSecondLevenDepth_IsMappedCorrectlyWithSecondLevelException() {
188+
189+
SecondLevelException secondLevelException =
190+
new SecondLevelException("level under first level and second level under root triggered.");
191+
192+
testGrpcAdviceService.setExceptionToSimulate(secondLevelException);
193+
Status expectedStatus = Status.ABORTED.withDescription(secondLevelException.getMessage());
194+
Metadata metadata = new Metadata();
195+
196+
testGrpcCallAndVerifyMappedException(expectedStatus, metadata);
197+
}
198+
169199

170200

171201
@BeforeAll

tests/src/test/java/net/devh/boot/grpc/test/advice/AdviceIsPresentAutoConfigurationTest.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import net.devh.boot.grpc.server.autoconfigure.GrpcAdviceAutoConfiguration;
4545
import net.devh.boot.grpc.test.config.BaseAutoConfiguration;
4646
import net.devh.boot.grpc.test.config.GrpcAdviceConfig;
47+
import net.devh.boot.grpc.test.config.GrpcAdviceConfig.TestAdviceForInheritedExceptions;
4748
import net.devh.boot.grpc.test.config.GrpcAdviceConfig.TestAdviceWithMetadata;
4849
import net.devh.boot.grpc.test.config.GrpcAdviceConfig.TestAdviceWithOutMetadata;
4950

@@ -59,8 +60,8 @@
5960
@DirtiesContext
6061
class AdviceIsPresentAutoConfigurationTest {
6162

62-
private static final int ADVICE_CLASSES = 2;
63-
private static final int ADVICE_METHODS = 5;
63+
private static final int ADVICE_CLASSES = 3;
64+
private static final int ADVICE_METHODS = 7;
6465

6566

6667
@Autowired
@@ -70,6 +71,8 @@ class AdviceIsPresentAutoConfigurationTest {
7071
private TestAdviceWithOutMetadata testAdviceWithOutMetadata;
7172
@Autowired
7273
private TestAdviceWithMetadata testAdviceWithMetadata;
74+
@Autowired
75+
private TestAdviceForInheritedExceptions testAdviceForInheritedExceptions;
7376

7477

7578
@Test
@@ -80,6 +83,7 @@ void testAdviceIsPresentWithExceptionMapping() {
8083
Map<String, Object> expectedAdviceBeans = new HashMap<>();
8184
expectedAdviceBeans.put("grpcAdviceWithBean", testAdviceWithOutMetadata);
8285
expectedAdviceBeans.put(TestAdviceWithMetadata.class.getName(), testAdviceWithMetadata);
86+
expectedAdviceBeans.put(TestAdviceForInheritedExceptions.class.getName(), testAdviceForInheritedExceptions);
8387
Set<Method> expectedAdviceMethods = expectedMethods();
8488

8589
Map<String, Object> actualAdviceBeans = grpcAdviceDiscoverer.getAnnotatedBeans();
@@ -103,8 +107,11 @@ private Set<Method> expectedMethods() {
103107
Arrays.stream(testAdviceWithMetadata.getClass().getDeclaredMethods()).collect(Collectors.toSet());
104108
Set<Method> methodsWithOutMetadata =
105109
Arrays.stream(testAdviceWithOutMetadata.getClass().getDeclaredMethods()).collect(Collectors.toSet());
110+
Set<Method> methodsForInheritedExceptions =
111+
Arrays.stream(testAdviceForInheritedExceptions.getClass().getDeclaredMethods())
112+
.collect(Collectors.toSet());
106113

107-
return Stream.of(methodsWithMetadata, methodsWithOutMetadata)
114+
return Stream.of(methodsWithMetadata, methodsWithOutMetadata, methodsForInheritedExceptions)
108115
.flatMap(Collection::stream)
109116
.filter(method -> method.isAnnotationPresent(GrpcExceptionHandler.class))
110117
.collect(Collectors.toSet());

tests/src/test/java/net/devh/boot/grpc/test/config/GrpcAdviceConfig.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.context.annotation.Bean;
2424
import org.springframework.context.annotation.Configuration;
2525
import org.springframework.core.convert.ConversionFailedException;
26+
import org.springframework.security.authentication.AccountExpiredException;
2627

2728
import com.google.protobuf.Empty;
2829

@@ -35,6 +36,8 @@
3536
import net.devh.boot.grpc.server.advice.GrpcExceptionHandler;
3637
import net.devh.boot.grpc.server.service.GrpcService;
3738
import net.devh.boot.grpc.test.advice.GrpcMetaDataUtils;
39+
import net.devh.boot.grpc.test.config.GrpcAdviceConfig.TestAdviceWithMetadata.MyRootRuntimeException;
40+
import net.devh.boot.grpc.test.config.GrpcAdviceConfig.TestAdviceWithMetadata.SecondLevelException;
3841
import net.devh.boot.grpc.test.proto.SomeType;
3942
import net.devh.boot.grpc.test.proto.TestServiceGrpc;
4043

@@ -78,7 +81,7 @@ public Throwable handleConversionFailedExceptionAndAccessControlException(
7881
return (e1 != null) ? e1 : ((e2 != null) ? e2 : new RuntimeException("Should not happen."));
7982
}
8083

81-
public Status methodNotToBePickup(IllegalArgumentException e) {
84+
public Status methodNotToBePickup(AccountExpiredException e) {
8285
Assertions.fail("Not supposed to be picked up.");
8386
return Status.FAILED_PRECONDITION;
8487
}
@@ -124,6 +127,13 @@ public FirstLevelException(String msg) {
124127
}
125128
}
126129

130+
public static class SecondLevelException extends FirstLevelException {
131+
132+
public SecondLevelException(String msg) {
133+
super(msg);
134+
}
135+
}
136+
127137
public static class StatusMappingException extends RuntimeException {
128138

129139
public StatusMappingException(String msg) {
@@ -134,4 +144,23 @@ public StatusMappingException(String msg) {
134144
}
135145

136146

147+
@GrpcAdvice
148+
public static class TestAdviceForInheritedExceptions {
149+
150+
151+
@GrpcExceptionHandler(SecondLevelException.class)
152+
public Status handleSecondLevelException(SecondLevelException e) {
153+
154+
return Status.ABORTED.withCause(e).withDescription(e.getMessage());
155+
}
156+
157+
@GrpcExceptionHandler
158+
public Status handleMyRootRuntimeException(MyRootRuntimeException e) {
159+
160+
return Status.DEADLINE_EXCEEDED.withCause(e).withDescription(e.getMessage());
161+
}
162+
163+
}
164+
165+
137166
}

0 commit comments

Comments
 (0)