Skip to content

Commit 09bc118

Browse files
committed
review feedback
1 parent 07b266c commit 09bc118

File tree

10 files changed

+65
-36
lines changed

10 files changed

+65
-36
lines changed

docs/en/server/exception-handling.md

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This section describes how you can handle exceptions inside GrpcService layer wi
2222
- [Security](security.md)
2323

2424
## Proper exception handling
25+
2526
If you are already familiar with springs [error handling](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-error-handling),
2627
you should see some similarity with intended exception handling for gRPC.
2728

@@ -33,13 +34,13 @@ public class GrpcExceptionAdvice {
3334

3435

3536
@GrpcExceptionHandler
36-
public Status handleIllegalArgumentException(IllegalArgumentException e) {
37-
return Status.INVALID_ARGUMENT.withDescription(e.getMessage());
37+
public Status handleInvalidArgument(IllegalArgumentException e) {
38+
return Status.INVALID_ARGUMENT.withDescription("Your description");
3839
}
3940

4041
@GrpcExceptionHandler(ResourceNotFoundException.class)
4142
public StatusException handleResourceNotFoundException(ResourceNotFoundException e) {
42-
Status status = Status.NOT_FOUND.withDescription(e.getMessage());
43+
Status status = Status.NOT_FOUND.withDescription("Your description");
4344
Metadata metadata = ...
4445
return status.asException(metadata);
4546
}
@@ -49,14 +50,15 @@ public class GrpcExceptionAdvice {
4950

5051
- `@GrpcAdvice` marks a class to be picked up for exception handling
5152
- `@GrpcExceptionHandler` maps given method to be executed, in case of _specified_ thrown exception
52-
- f.e. if your application throws `IllegalArgumentException`, method `handleIllegalArgumentException(..)` is executed
53+
- f.e. if your application throws `IllegalArgumentException`, then the `handleInvalidArgument(IllegalArgumentException e)` method will be is executed
5354
- `io.grpc.Status` is specified and returned response status
5455

5556
> **Note:** Cause is not transmitted from server to client - as stated in [official docs](https://grpc.github.io/grpc-java/javadoc/io/grpc/Status.html#withCause-java.lang.Throwable-)
5657
5758
## Detailed explanation
5859

5960
### Priority of mapped exceptions
61+
6062
Given method with specified Exception in Annotation *and* as method argument
6163

6264
```java
@@ -73,26 +75,31 @@ _(Matching means: Exception type in annotation is superclass of listed method pa
7375
If no annotation type is provided in the annotation, listed method parameter are being picked up.
7476

7577
### Sending Metadata in response
78+
7679
In case you want to send metadata in your exception response, let's have a look at the following example.
7780

7881
```java
7982
@GrpcExceptionHandler
8083
public StatusRuntimeException handleResourceNotFoundException(IllegalArgumentException e) {
81-
Status status = Status.INVALID_ARGUMENT.withDescription(e.getMessage()).withCause(e);
82-
Metadata metadata = ...
83-
return status.asRuntimeException(metadata);
84+
Status status = Status.INVALID_ARGUMENT.withDescription("Your description");
85+
Metadata metadata = ...
86+
return status.asRuntimeException(metadata);
8487
}
8588
```
8689

8790
As you do not need `Metadata` in your response, just return your specified `Status`.
8891

8992
### Overview of returnable types
93+
9094
Here is a small overview of possible mapped return types with `@GrpcExceptionHandler` and if
9195
custom Metadata can be returned.
9296

93-
| return type | Status | StatusException | StatusRuntimeException | Throwable |
94-
|-----------------|---------|----------|----------|---------|
95-
| custom Metadata | ✗ | ✔ | ✔ | ✗ |
97+
| Return Type | Custom Metadata |
98+
| ----------- | --------------- |
99+
| Status | ✗ |
100+
| StatusException | ✔ |
101+
| StatusRuntimeException | ✔ |
102+
| Throwable | ✗ |
96103

97104
## Additional Topics <!-- omit in toc -->
98105

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
* @author Andjelko Perisic ([email protected])
3636
* @see GrpcExceptionHandler
3737
*/
38-
@Target(ElementType.TYPE)
38+
@Target({ElementType.TYPE, ElementType.METHOD})
3939
@Retention(RetentionPolicy.RUNTIME)
4040
@Documented
4141
@Component

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.lang.reflect.Type;
2424
import java.util.Map.Entry;
2525

26-
import org.aspectj.lang.annotation.Aspect;
2726
import org.springframework.lang.Nullable;
2827

2928
import lombok.extern.slf4j.Slf4j;
@@ -42,7 +41,6 @@
4241
* @see GrpcAdviceExceptionInterceptor
4342
*/
4443
@Slf4j
45-
@Aspect
4644
public class GrpcAdviceExceptionHandler {
4745

4846
private final GrpcExceptionHandlerMethodResolver grpcExceptionHandlerMethodResolver;
@@ -64,6 +62,7 @@ public GrpcAdviceExceptionHandler(
6462
*/
6563
@Nullable
6664
public <E extends Throwable> Object handleThrownException(E exception) throws Throwable {
65+
log.debug("Exception caught during gRPC execution: ", exception);
6766

6867
final Class<? extends Throwable> exceptionClass = exception.getClass();
6968
boolean exceptionIsMapped =

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,23 @@ public <ReqT, RespT> Listener<ReqT> interceptCall(
4545
ServerCall<ReqT, RespT> call,
4646
Metadata headers,
4747
ServerCallHandler<ReqT, RespT> next) {
48-
Listener<ReqT> delegate = next.startCall(call, headers);
49-
return new GrpcAdviceExceptionListener<>(delegate, call, grpcAdviceExceptionHandler);
48+
try {
49+
Listener<ReqT> delegate = next.startCall(call, headers);
50+
return new GrpcAdviceExceptionListener<>(delegate, call, grpcAdviceExceptionHandler);
51+
} catch (Throwable throwable) {
52+
return noOpCallListener();
53+
}
54+
}
55+
56+
/**
57+
* Creates a new no-op call listener because you can neither return null nor throw an exception in
58+
* {@link #interceptCall(ServerCall, Metadata, ServerCallHandler)}.
59+
*
60+
* @param <ReqT> The type of the request.
61+
* @return The newly created dummy listener.
62+
*/
63+
protected <ReqT> Listener<ReqT> noOpCallListener() {
64+
return new Listener<ReqT>() {};
5065
}
5166

5267
}

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,21 @@ protected GrpcAdviceExceptionListener(
5757
public void onHalfClose() {
5858
try {
5959
super.onHalfClose();
60-
} catch (Exception exception) {
61-
log.error("Exception caught during gRPC execution: ", exception);
62-
handleCaughtException(exception);
60+
61+
} catch (Throwable throwable) {
62+
handleCaughtException(throwable);
6363
}
6464
}
6565

66-
private void handleCaughtException(Exception exception) {
66+
private void handleCaughtException(Throwable throwable) {
6767
try {
68-
Object mappedReturnType = exceptionHandler.handleThrownException(exception);
69-
Status status = resolveStatus(mappedReturnType).withCause(exception);
68+
Object mappedReturnType = exceptionHandler.handleThrownException(throwable);
69+
Status status = resolveStatus(mappedReturnType).withCause(throwable);
7070
Metadata metadata = resolveMetadata(mappedReturnType);
7171

7272
serverCall.close(status, metadata);
73-
} catch (Throwable throwable) {
74-
handleThrownExceptionByImplementation(throwable);
73+
} catch (Throwable throwableWhileResolving) {
74+
handleThrownExceptionByImplementation(throwableWhileResolving);
7575
}
7676
}
7777

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@
2626

2727
/**
2828
* Condition to check if {@link GrpcAdvice @GrpcAdvice} is present. Mainly checking if {@link GrpcAdviceDiscoverer}
29-
* should be a instantiated.<br>
30-
* <br>
29+
* should be a instantiated.
3130
*
3231
* @author Andjelko Perisic ([email protected])
3332
* @see GrpcAdviceDiscoverer

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import io.grpc.Metadata;
3939
import io.grpc.Status;
4040
import lombok.extern.slf4j.Slf4j;
41+
import net.devh.boot.grpc.server.advice.GrpcAdviceExceptionHandler;
4142
import net.devh.boot.grpc.server.advice.GrpcAdviceExceptionListener;
4243
import net.devh.boot.grpc.server.autoconfigure.GrpcAdviceAutoConfiguration;
4344
import net.devh.boot.grpc.test.config.BaseAutoConfiguration;
@@ -73,7 +74,9 @@ class AdviceExceptionHandlingTest extends AbstractSimpleServerClientTest {
7374

7475
@BeforeEach
7576
void setup() {
76-
loggingEventListAppender = LoggerTestUtil.getListAppenderForClasses(GrpcAdviceExceptionListener.class);
77+
loggingEventListAppender = LoggerTestUtil.getListAppenderForClasses(
78+
GrpcAdviceExceptionListener.class,
79+
GrpcAdviceExceptionHandler.class);
7780
}
7881

7982
@Test
@@ -107,7 +110,7 @@ void testThrownClassCastException_IsMappedAsStatusRuntimeExceptionAndWithMetadat
107110
ClassCastException exceptionToMap = new ClassCastException("Casting with classes failed.");
108111
testGrpcAdviceService.setExceptionToSimulate(exceptionToMap);
109112
Status expectedStatus = Status.FAILED_PRECONDITION.withDescription(exceptionToMap.getMessage());
110-
Metadata metadata = GrpcMetdaDataUtils.createExpectedAsciiHeader();
113+
Metadata metadata = GrpcMetaDataUtils.createExpectedAsciiHeader();
111114

112115
testGrpcCallAndVerifyMappedException(expectedStatus, metadata);
113116
}
@@ -125,7 +128,7 @@ void testThrownMyRootRuntimeException_IsNotMappedAndResultsInInvocationException
125128
testGrpcCallAndVerifyMappedException(expectedStatus, metadata);
126129
assertThat(loggingEventListAppender.list)
127130
.extracting(ILoggingEvent::getMessage, ILoggingEvent::getLevel)
128-
.contains(Tuple.tuple("Exception caught during gRPC execution: ", Level.ERROR))
131+
.contains(Tuple.tuple("Exception caught during gRPC execution: ", Level.DEBUG))
129132
.contains(Tuple.tuple(
130133
"Exception thrown during invocation of annotated @GrpcExceptionHandler method: ",
131134
Level.ERROR));
@@ -138,7 +141,7 @@ void testThrownFirstLevelException_IsMappedAsStatusExceptionWithMetadata() {
138141
FirstLevelException exceptionToMap = new FirstLevelException("Trigger Advice");
139142
testGrpcAdviceService.setExceptionToSimulate(exceptionToMap);
140143
Status expectedStatus = Status.NOT_FOUND.withDescription(exceptionToMap.getMessage());
141-
Metadata metadata = GrpcMetdaDataUtils.createExpectedAsciiHeader();
144+
Metadata metadata = GrpcMetaDataUtils.createExpectedAsciiHeader();
142145

143146
testGrpcCallAndVerifyMappedException(expectedStatus, metadata);
144147
}
@@ -157,7 +160,7 @@ void testThrownStatusMappingException_IsResolvedAsInternalServerError() {
157160

158161
assertThat(loggingEventListAppender.list)
159162
.extracting(ILoggingEvent::getMessage, ILoggingEvent::getLevel)
160-
.contains(Tuple.tuple("Exception caught during gRPC execution: ", Level.ERROR))
163+
.contains(Tuple.tuple("Exception caught during gRPC execution: ", Level.DEBUG))
161164
.contains(Tuple.tuple(
162165
"Exception thrown during invocation of annotated @GrpcExceptionHandler method: ",
163166
Level.ERROR));

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ void testAdviceIsPresentWithExceptionMapping() {
8181
log.info("--- Starting tests with advice auto discovery ---");
8282

8383
Map<String, Object> expectedAdviceBeans = new HashMap<>();
84-
expectedAdviceBeans.put(TestAdviceWithOutMetadata.class.getName(), testAdviceWithOutMetadata);
84+
expectedAdviceBeans.put("grpcAdviceWithBean", testAdviceWithOutMetadata);
8585
expectedAdviceBeans.put(TestAdviceWithMetadata.class.getName(), testAdviceWithMetadata);
8686
Set<Method> expectedAdviceMethods = expectedMethods();
8787

tests/src/test/java/net/devh/boot/grpc/test/advice/GrpcMetdaDataUtils.java renamed to tests/src/test/java/net/devh/boot/grpc/test/advice/GrpcMetaDataUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919

2020
import io.grpc.Metadata;
2121

22-
public class GrpcMetdaDataUtils {
22+
public class GrpcMetaDataUtils {
2323

24-
private GrpcMetdaDataUtils() {
24+
private GrpcMetaDataUtils() {
2525
throw new UnsupportedOperationException("Util class not to be instantiated.");
2626
}
2727

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.security.AccessControlException;
2121

2222
import org.assertj.core.api.Assertions;
23+
import org.springframework.context.annotation.Bean;
2324
import org.springframework.context.annotation.Configuration;
2425
import org.springframework.core.convert.ConversionFailedException;
2526

@@ -33,7 +34,7 @@
3334
import net.devh.boot.grpc.server.advice.GrpcAdvice;
3435
import net.devh.boot.grpc.server.advice.GrpcExceptionHandler;
3536
import net.devh.boot.grpc.server.service.GrpcService;
36-
import net.devh.boot.grpc.test.advice.GrpcMetdaDataUtils;
37+
import net.devh.boot.grpc.test.advice.GrpcMetaDataUtils;
3738
import net.devh.boot.grpc.test.proto.SomeType;
3839
import net.devh.boot.grpc.test.proto.TestServiceGrpc;
3940

@@ -58,6 +59,11 @@ public <E extends RuntimeException> void setExceptionToSimulate(E exception) {
5859
}
5960

6061
@GrpcAdvice
62+
@Bean
63+
public TestAdviceWithOutMetadata grpcAdviceWithBean() {
64+
return new TestAdviceWithOutMetadata();
65+
}
66+
6167
public static class TestAdviceWithOutMetadata {
6268

6369
@GrpcExceptionHandler
@@ -85,15 +91,15 @@ public static class TestAdviceWithMetadata {
8591
public StatusException handleFirstLevelException(MyRootRuntimeException e) {
8692

8793
Status status = Status.NOT_FOUND.withCause(e).withDescription(e.getMessage());
88-
Metadata metadata = GrpcMetdaDataUtils.createExpectedAsciiHeader();
94+
Metadata metadata = GrpcMetaDataUtils.createExpectedAsciiHeader();
8995
return status.asException(metadata);
9096
}
9197

9298
@GrpcExceptionHandler(ClassCastException.class)
9399
public StatusRuntimeException handleClassCastException() {
94100

95101
Status status = Status.FAILED_PRECONDITION.withDescription("Casting with classes failed.");
96-
Metadata metadata = GrpcMetdaDataUtils.createExpectedAsciiHeader();
102+
Metadata metadata = GrpcMetaDataUtils.createExpectedAsciiHeader();
97103
return status.asRuntimeException(metadata);
98104
}
99105

0 commit comments

Comments
 (0)