Skip to content

Commit f7c7fb2

Browse files
authored
Merge branch 'develop' into develop
2 parents dfb5463 + c060bcb commit f7c7fb2

File tree

13 files changed

+334
-68
lines changed

13 files changed

+334
-68
lines changed

src/JKang.IpcServiceFramework.Client/IpcServiceClient.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public async Task InvokeAsync(Expression<Action<TInterface>> exp,
3737
}
3838
else
3939
{
40-
throw new InvalidOperationException(response.Failure);
40+
throw response.GetException();
4141
}
4242
}
4343

@@ -60,7 +60,7 @@ public async Task<TResult> InvokeAsync<TResult>(Expression<Func<TInterface, TRes
6060
}
6161
else
6262
{
63-
throw new InvalidOperationException(response.Failure);
63+
throw response.GetException();
6464
}
6565
}
6666

@@ -76,7 +76,7 @@ public async Task InvokeAsync(Expression<Func<TInterface, Task>> exp,
7676
}
7777
else
7878
{
79-
throw new InvalidOperationException(response.Failure);
79+
throw response.GetException();
8080
}
8181
}
8282

@@ -99,7 +99,7 @@ public async Task<TResult> InvokeAsync<TResult>(Expression<Func<TInterface, Task
9999
}
100100
else
101101
{
102-
throw new InvalidOperationException(response.Failure);
102+
throw response.GetException();
103103
}
104104
}
105105

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,82 @@
11
using System;
2+
using System.Reflection;
23
using Newtonsoft.Json;
34

45
namespace JKang.IpcServiceFramework
56
{
67
public class IpcResponse
78
{
89
[JsonConstructor]
9-
private IpcResponse(bool succeed, object data, string failure)
10+
private IpcResponse(bool succeed, object data, string failure, string failureDetails, bool userCodeFailure)
1011
{
1112
Succeed = succeed;
1213
Data = data;
1314
Failure = failure;
15+
FailureDetails = failureDetails;
16+
UserCodeFailure = userCodeFailure;
1417
}
1518

1619
public static IpcResponse Fail(string failure)
1720
{
18-
return new IpcResponse(false, null, failure);
21+
return new IpcResponse(false, null, failure, null, false);
22+
}
23+
24+
public static IpcResponse Fail(Exception ex, bool includeDetails, bool userFailure = false)
25+
{
26+
string message = null;
27+
string details = null;
28+
29+
if (!userFailure)
30+
{
31+
message = "Internal server error: ";
32+
}
33+
34+
message += GetFirstUsableMessage(ex);
35+
36+
if (includeDetails)
37+
{
38+
details = ex.ToString();
39+
}
40+
41+
return new IpcResponse(false, null, message, details, userFailure);
1942
}
2043

2144
public static IpcResponse Success(object data)
2245
{
23-
return new IpcResponse(true, data, null);
46+
return new IpcResponse(true, data, null, null, false);
2447
}
2548

2649
public bool Succeed { get; }
2750
public object Data { get; }
2851
public string Failure { get; }
52+
public string FailureDetails { get; }
53+
public bool UserCodeFailure { get; set; }
54+
55+
public Exception GetException()
56+
{
57+
if (UserCodeFailure)
58+
{
59+
throw new IpcServerUserCodeException(Failure, FailureDetails);
60+
}
61+
62+
throw new IpcServerException(Failure, FailureDetails);
63+
}
64+
65+
private static string GetFirstUsableMessage(Exception ex)
66+
{
67+
var e = ex;
68+
69+
while (e != null)
70+
{
71+
if (!(e is TargetInvocationException))
72+
{
73+
return e.Message;
74+
}
75+
76+
e = e.InnerException;
77+
}
78+
79+
return ex.Message;
80+
}
2981
}
3082
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Runtime.Serialization;
4+
using System.Text;
5+
6+
namespace JKang.IpcServiceFramework
7+
{
8+
/// <summary>
9+
/// An exception that originated at the server.
10+
/// </summary>
11+
[Serializable]
12+
public class IpcServerException : InvalidOperationException
13+
{
14+
public const string ServerFailureDetails = "Server failure details:";
15+
public string FailureDetails { get; }
16+
17+
public IpcServerException()
18+
{
19+
}
20+
21+
public IpcServerException(string message, string failureDetails)
22+
: base(message)
23+
{
24+
FailureDetails = failureDetails;
25+
}
26+
27+
public IpcServerException(string message, Exception inner)
28+
: base(message, inner)
29+
{
30+
}
31+
32+
protected IpcServerException(SerializationInfo info, StreamingContext context)
33+
: base(info, context)
34+
{
35+
if (info == null)
36+
throw new ArgumentNullException(nameof(info));
37+
38+
FailureDetails = info.GetString("FailureDetails");
39+
}
40+
41+
public override void GetObjectData(SerializationInfo info, StreamingContext context)
42+
{
43+
base.GetObjectData(info, context);
44+
45+
info.AddValue("FailureDetails", FailureDetails, typeof(string));
46+
}
47+
48+
public override string ToString()
49+
{
50+
if (!string.IsNullOrWhiteSpace(FailureDetails))
51+
{
52+
var sb = new StringBuilder();
53+
sb.AppendLine(base.ToString());
54+
sb.AppendLine();
55+
sb.AppendLine(ServerFailureDetails);
56+
sb.Append(FailureDetails);
57+
return sb.ToString();
58+
}
59+
60+
return base.ToString();
61+
}
62+
}
63+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Runtime.Serialization;
3+
4+
namespace JKang.IpcServiceFramework
5+
{
6+
/// <summary>
7+
/// An exception that originated at the server, in user code.
8+
/// </summary>
9+
[Serializable]
10+
public class IpcServerUserCodeException : IpcServerException
11+
{
12+
public IpcServerUserCodeException()
13+
{
14+
}
15+
16+
public IpcServerUserCodeException(string message, string failureDetails)
17+
: base(message, failureDetails)
18+
{
19+
}
20+
21+
public IpcServerUserCodeException(string message, Exception inner)
22+
: base(message, inner)
23+
{
24+
}
25+
26+
protected IpcServerUserCodeException(SerializationInfo info, StreamingContext context)
27+
: base(info, context)
28+
{
29+
}
30+
}
31+
}

src/JKang.IpcServiceFramework.IntegrationTests/ContractTest.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,20 @@ public async Task ExplicitInterfaceOperation()
104104
int actual = await _client.InvokeAsync(x => x.ExplicitInterfaceMember());
105105
Assert.True(actual == 0);
106106
}
107+
108+
[Fact]
109+
public async Task ThrowException()
110+
{
111+
try
112+
{
113+
await _client.InvokeAsync(x => x.ThrowException("This was forced"));
114+
}
115+
catch (IpcServerException ex)
116+
{
117+
Assert.Contains("This was forced", ex.Message);
118+
Assert.DoesNotContain(IpcServerException.ServerFailureDetails, ex.ToString());
119+
}
120+
}
107121

108122
public void Dispose()
109123
{
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using AutoFixture.Xunit2;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Globalization;
6+
using System.Net;
7+
using System.Numerics;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Xunit;
11+
12+
namespace JKang.IpcServiceFramework.IntegrationTests
13+
{
14+
public class FailureDetailTest : IDisposable
15+
{
16+
private readonly CancellationTokenSource _cancellationToken;
17+
private readonly int _port;
18+
private readonly IpcServiceClient<ITestService> _client;
19+
20+
public FailureDetailTest()
21+
{
22+
// configure DI
23+
IServiceCollection services = new ServiceCollection()
24+
.AddIpc(builder => builder.AddNamedPipe().AddService<ITestService, TestService>());
25+
_port = new Random().Next(10000, 50000);
26+
IIpcServiceHost host = new IpcServiceHostBuilder(services.BuildServiceProvider())
27+
.AddTcpEndpoint<ITestService>(
28+
name: Guid.NewGuid().ToString(),
29+
ipEndpoint: IPAddress.Loopback,
30+
port: _port,
31+
includeFailureDetailsInResponse: true)
32+
.Build();
33+
_cancellationToken = new CancellationTokenSource();
34+
host.RunAsync(_cancellationToken.Token);
35+
36+
_client = new IpcServiceClientBuilder<ITestService>()
37+
.UseTcp(IPAddress.Loopback, _port)
38+
.Build();
39+
}
40+
41+
[Fact]
42+
public async Task ThrowException()
43+
{
44+
try
45+
{
46+
await _client.InvokeAsync(x => x.ThrowException("This was forced"));
47+
}
48+
catch (IpcServerException ex)
49+
{
50+
Assert.Contains("This was forced", ex.Message);
51+
Assert.Contains(IpcServerException.ServerFailureDetails, ex.ToString());
52+
}
53+
}
54+
55+
public void Dispose()
56+
{
57+
_cancellationToken.Cancel();
58+
}
59+
}
60+
}

src/JKang.IpcServiceFramework.IntegrationTests/ITestService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ public interface ITestService
1717
T GetDefaultValue<T>();
1818
Task<long> WaitAsync(int milliseconds);
1919
int ExplicitInterfaceMember();
20+
void ThrowException(string message);
2021
}
2122
}

src/JKang.IpcServiceFramework.IntegrationTests/TestService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,10 @@ int ITestService.ExplicitInterfaceMember()
5858
{
5959
return 0;
6060
}
61+
62+
public void ThrowException(string message)
63+
{
64+
throw new Exception(message);
65+
}
6166
}
6267
}

0 commit comments

Comments
 (0)