Skip to content

Commit 6a9dc94

Browse files
committed
make server exception interceptors more reusable via ServerExceptionsInterceptorBase
1 parent 3b64943 commit 6a9dc94

File tree

2 files changed

+58
-38
lines changed

2 files changed

+58
-38
lines changed

src/protobuf-net.Grpc/Configuration/SimpleRpcExceptionsInterceptor.cs

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,25 @@
22
using Grpc.Core.Interceptors;
33
using System;
44
using System.IO;
5-
using System.Runtime.CompilerServices;
65
using System.Security;
76
using System.Threading.Tasks;
87

98
namespace ProtoBuf.Grpc.Configuration
109
{
1110
/// <summary>
12-
/// Indicates that a service or method should use simplified exception handling - which means that all server exceptions are treated as <see cref="RpcException"/>; this
13-
/// will expose the <see cref="Exception.Message"/> to the caller (and the type may be interpreted as a <see cref="StatusCode"/> when possible), which should only be
14-
/// done with caution as this may present security implications. Additional exception metadata (<see cref="Exception.Data"/>, <see cref="Exception.InnerException"/>,
15-
/// <see cref="Exception.StackTrace"/>, etc) is not propagated. The exception is still exposed at the client as an <see cref="RpcException"/>.
11+
/// A base interceptor that handles all server-side exceptions.
1612
/// </summary>
17-
public sealed class SimpleRpcExceptionsInterceptor : Interceptor
13+
public abstract class ServerExceptionsInterceptorBase : Interceptor
1814
{
19-
private SimpleRpcExceptionsInterceptor() { }
20-
private static SimpleRpcExceptionsInterceptor? s_Instance;
2115
/// <summary>
22-
/// Provides a shared instance of this interceptor
16+
/// Allows implementors to intercept exceptions, optionally re-exposing them as <see cref="RpcException"/>.
2317
/// </summary>
24-
public static SimpleRpcExceptionsInterceptor Instance => s_Instance ??= new SimpleRpcExceptionsInterceptor();
18+
/// <returns><c>true</c> if the exception should be re-exposed as an <see cref="RpcException"/>, <c>false</c> otherwise</returns>
19+
protected virtual bool OnException(Exception exception, out Status status)
20+
{
21+
status = default;
22+
return false;
23+
}
2524

2625
/// <inheritdoc/>
2726
public override async Task<TResponse> ClientStreamingServerHandler<TRequest, TResponse>(IAsyncStreamReader<TRequest> requestStream, ServerCallContext context, ClientStreamingServerMethod<TRequest, TResponse> continuation)
@@ -30,10 +29,9 @@ public override async Task<TResponse> ClientStreamingServerHandler<TRequest, TRe
3029
{
3130
return await base.ClientStreamingServerHandler(requestStream, context, continuation).ConfigureAwait(false);
3231
}
33-
catch (Exception ex) when (IsNotRpcException(ex))
32+
catch (Exception ex) when (OnException(ex, out var status))
3433
{
35-
RethrowAsRpcException(ex);
36-
return default!; // make compiler happy
34+
throw new RpcException(status, ex.Message);
3735
}
3836
}
3937

@@ -44,9 +42,9 @@ public override async Task ServerStreamingServerHandler<TRequest, TResponse>(TRe
4442
{
4543
await base.ServerStreamingServerHandler(request, responseStream, context, continuation).ConfigureAwait(false);
4644
}
47-
catch (Exception ex) when (IsNotRpcException(ex))
45+
catch (Exception ex) when (OnException(ex, out var status))
4846
{
49-
RethrowAsRpcException(ex);
47+
throw new RpcException(status, ex.Message);
5048
}
5149
}
5250

@@ -57,9 +55,9 @@ public override async Task DuplexStreamingServerHandler<TRequest, TResponse>(IAs
5755
{
5856
await base.DuplexStreamingServerHandler(requestStream, responseStream, context, continuation).ConfigureAwait(false);
5957
}
60-
catch (Exception ex) when (IsNotRpcException(ex))
58+
catch (Exception ex) when (OnException(ex, out var status))
6159
{
62-
RethrowAsRpcException(ex);
60+
throw new RpcException(status, ex.Message);
6361
}
6462
}
6563

@@ -70,33 +68,56 @@ public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TR
7068
{
7169
return await base.UnaryServerHandler(request, context, continuation).ConfigureAwait(false);
7270
}
73-
catch (Exception ex) when (IsNotRpcException(ex))
71+
catch (Exception ex) when (OnException(ex, out var status))
7472
{
75-
RethrowAsRpcException(ex);
76-
return default!; // make compiler happy
73+
throw new RpcException(status, ex.Message);
7774
}
7875
}
76+
}
7977

80-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
81-
internal static bool IsNotRpcException(Exception ex) => !(ex is RpcException);
78+
/// <summary>
79+
/// Indicates that a service or method should use simplified exception handling - which means that all server exceptions are treated as <see cref="RpcException"/>; this
80+
/// will expose the <see cref="Exception.Message"/> to the caller (and the type may be interpreted as a <see cref="StatusCode"/> when possible), which should only be
81+
/// done with caution as this may present security implications. Additional exception metadata (<see cref="Exception.Data"/>, <see cref="Exception.InnerException"/>,
82+
/// <see cref="Exception.StackTrace"/>, etc) is not propagated. The exception is still exposed at the client as an <see cref="RpcException"/>.
83+
/// </summary>
84+
public class SimpleRpcExceptionsInterceptor : ServerExceptionsInterceptorBase
85+
{
86+
private SimpleRpcExceptionsInterceptor() { }
87+
private static SimpleRpcExceptionsInterceptor? s_Instance;
88+
89+
/// <summary>
90+
/// Provides a shared instance of this interceptor
91+
/// </summary>
92+
public static SimpleRpcExceptionsInterceptor Instance => s_Instance ??= new SimpleRpcExceptionsInterceptor();
8293

83-
internal static void RethrowAsRpcException(Exception ex)
94+
internal static bool ShouldWrap(Exception exception, out Status status)
8495
{
85-
#pragma warning disable IDE0059 // needs more recent compiler than the CI server has
86-
var code = ex switch
96+
if (exception is RpcException)
97+
{
98+
status = default;
99+
return false;
100+
}
101+
status = new Status(exception switch
87102
{
103+
#pragma warning disable IDE0059 // needs more recent compiler than the CI server has
88104
OperationCanceledException a => StatusCode.Cancelled,
89105
ArgumentException b => StatusCode.InvalidArgument,
90106
NotImplementedException c => StatusCode.Unimplemented,
91-
SecurityException d => StatusCode.PermissionDenied,
92-
EndOfStreamException e => StatusCode.OutOfRange,
93-
FileNotFoundException f => StatusCode.NotFound,
94-
DirectoryNotFoundException g => StatusCode.NotFound,
95-
TimeoutException h => StatusCode.DeadlineExceeded,
96-
_ => StatusCode.Unknown,
97-
};
107+
NotSupportedException d => StatusCode.Unimplemented,
108+
SecurityException e => StatusCode.PermissionDenied,
109+
EndOfStreamException f => StatusCode.OutOfRange,
110+
FileNotFoundException g => StatusCode.NotFound,
111+
DirectoryNotFoundException h => StatusCode.NotFound,
112+
TimeoutException i => StatusCode.DeadlineExceeded,
98113
#pragma warning restore IDE0059 // needs more recent compiler than the CI server has
99-
throw new RpcException(new Status(code, ex.Message), ex.Message);
114+
_ => StatusCode.Unknown,
115+
}, exception.Message);
116+
return true;
100117
}
118+
119+
/// <inheritdoc/>
120+
protected override bool OnException(Exception exception, out Status status)
121+
=> ShouldWrap(exception, out status);
101122
}
102123
}

src/protobuf-net.Grpc/Internal/Reshape.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,9 @@ static async Task Awaited(Task task)
152152
{
153153
await task.ConfigureAwait(false);
154154
}
155-
catch (Exception ex) when (SimpleRpcExceptionsInterceptor.IsNotRpcException(ex))
155+
catch (Exception ex) when (SimpleRpcExceptionsInterceptor.ShouldWrap(ex, out var status))
156156
{
157-
SimpleRpcExceptionsInterceptor.RethrowAsRpcException(ex);
157+
throw new RpcException(status, ex.Message);
158158
}
159159
}
160160
}
@@ -176,10 +176,9 @@ static async Task<T> Awaited(Task<T> task)
176176
{
177177
return await task.ConfigureAwait(false);
178178
}
179-
catch (Exception ex) when (SimpleRpcExceptionsInterceptor.IsNotRpcException(ex))
179+
catch (Exception ex) when (SimpleRpcExceptionsInterceptor.ShouldWrap(ex, out var status))
180180
{
181-
SimpleRpcExceptionsInterceptor.RethrowAsRpcException(ex);
182-
return default!; // make compiler happy
181+
throw new RpcException(status, ex.Message);
183182
}
184183
}
185184
}

0 commit comments

Comments
 (0)