Skip to content

Commit 2f2b4a1

Browse files
author
Rafał Maciąg
committed
Bugfix with FailedCommand and Validation aspect integration
1 parent c8a2511 commit 2f2b4a1

File tree

13 files changed

+88
-21
lines changed

13 files changed

+88
-21
lines changed

src/MicroPlumberd.CommandBus.Abstractions/CommandFaultException.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
public class FaultException<TData> : FaultException
44
{
5-
public FaultException(string? message, TData data) : base(message)
5+
public FaultException(string? message, TData data, int code) : base(message)
66
{
77
Data = data;
8+
Code = code;
89
}
910

1011
public TData Data { get; init; }
@@ -15,17 +16,22 @@ public FaultException(string? message, TData data) : base(message)
1516
}
1617
public class FaultException : Exception
1718
{
19+
public int Code { get; init; }
1820
public FaultException()
1921
{
2022
}
2123

22-
public static FaultException Create(string message, object data)
24+
public static FaultException Create(string message, object data, int code)
2325
{
24-
return (FaultException)Activator.CreateInstance(typeof(FaultException<>).MakeGenericType(data.GetType()), message, data)!;
26+
return (FaultException)Activator.CreateInstance(typeof(FaultException<>).MakeGenericType(data.GetType()), message, data, code)!;
2527
}
2628
public FaultException(string? message) : base(message)
2729
{
2830
}
31+
public FaultException(string? message, int code) : base(message)
32+
{
33+
Code = code;
34+
}
2935

3036
public virtual object GetFaultData() => null;
3137
}

src/MicroPlumberd.DirectConnect/RequestInvokerExtensions.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ private static async Task<TResponse> OnExecute<TRequest, TResponse>(IRequestInvo
1919
return (TResponse)result;
2020
var fault = (IFaultEnvelope)result;
2121
if (fault.Data != null)
22-
throw FaultException.Create(fault.Error, fault.Data);
23-
throw new FaultException(fault.Error);
22+
throw FaultException.Create(fault.Error, fault.Data, (int)fault.Code);
23+
throw new FaultException(fault.Error, (int)fault.Code);
2424
}
2525
private static readonly ConcurrentDictionary<Type, object> _invokers = new();
2626
public static Task<TResponse> Execute<TResponse>(this IRequestInvoker ri, Guid id, object c)

src/MicroPlumberd.Services/CommandBus.cs

+8-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics;
44
using System.IO;
55
using System.Linq;
6+
using System.Net;
67
using System.Reflection;
78
using System.Runtime.Serialization;
89
using System.Security.Cryptography.X509Certificates;
@@ -23,7 +24,7 @@ class CommandBus : ICommandBus, IEventHandler
2324
private readonly ConcurrentDictionary<string, Type> _commandMapping = new();
2425
private readonly ConcurrentHashSet<Type> _supportedCommands = new();
2526
private bool _initialized;
26-
private object _sync = new object();
27+
private readonly object _sync = new object();
2728

2829
public Guid SessionId { get; } = Guid.NewGuid();
2930
public CommandBus(IPlumber plumber, ILogger<CommandBus> log)
@@ -97,7 +98,7 @@ public async Task SendAsync(Guid recipientId, object command)
9798
throw new TimeoutException("Command execution timeout.");
9899
}
99100
else if (executionResults.ErrorData != null)
100-
throw FaultException.Create(executionResults.ErrorMessage, executionResults.ErrorData);
101+
throw FaultException.Create(executionResults.ErrorMessage, executionResults.ErrorData, (int)executionResults.ErrorCode);
101102
throw new FaultException(executionResults.ErrorMessage);
102103
}
103104
}
@@ -161,7 +162,8 @@ public async ValueTask<bool> Handle(Metadata m, object ev)
161162
IsSuccess = false;
162163
ErrorMessage = ef.Message;
163164
ErrorData = ef.Fault;
164-
IsReady.SetResult(true);
165+
ErrorCode = ef.Code;
166+
IsReady.SetResult(true);
165167
return true;
166168
}
167169

@@ -173,6 +175,7 @@ public async ValueTask<bool> Handle(Metadata m, object ev)
173175
{
174176
IsSuccess = false;
175177
ErrorMessage = cf.Message;
178+
ErrorCode = cf.Code;
176179
IsReady.SetResult(true);
177180
return true;
178181
}
@@ -184,9 +187,9 @@ public async ValueTask<bool> Handle(Metadata m, object ev)
184187
return false;
185188
}
186189

187-
190+
public HttpStatusCode ErrorCode { get; private set; }
191+
188192

189-
190193
public string ErrorMessage { get; private set; }
191194
public object? ErrorData { get; private set; }
192195
public bool IsSuccess { get; private set; }

src/MicroPlumberd.Services/CommandHandlerExecutor.cs

+17-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System.Collections.Concurrent;
2+
using System.ComponentModel.DataAnnotations;
23
using System.Diagnostics;
4+
using System.Net;
35
using EventStore.Client;
46
using Microsoft.Extensions.DependencyInjection;
57
using Microsoft.Extensions.Logging;
@@ -66,13 +68,24 @@ await plumber.AppendEvent(cmdStream, StreamState.Any, $"{cmdName}Executed",
6668
CommandId = cmdId,
6769
Duration = sw.Elapsed
6870
});
69-
log.LogDebug("Command {CommandType} appended to session steam {CommandStream}.", command.GetType().Name, cmdStream);
71+
log.LogDebug("Command {CommandType} appended to session steam {CommandStream}.", command.GetType().Name,
72+
cmdStream);
73+
}
74+
catch (ValidationException ex)
75+
{
76+
await plumber.AppendEvent(cmdStream, StreamState.StreamExists, $"{cmdName}Failed", new CommandFailed()
77+
{
78+
CommandId = cmdId,
79+
Duration = sw.Elapsed,
80+
Message = ex.Message,
81+
Code = HttpStatusCode.BadRequest
82+
});
7083
}
7184
catch (FaultException ex)
7285
{
7386
var faultData = ex.GetFaultData();
7487
await plumber.AppendEvent(cmdStream, StreamState.StreamExists,
75-
$"{cmdName}Failed<{faultData.GetType().Name}>", CommandFailed.Create(cmdId, ex.Message, sw.Elapsed,faultData));
88+
$"{cmdName}Failed<{faultData.GetType().Name}>", CommandFailed.Create(cmdId, ex.Message, sw.Elapsed, (HttpStatusCode)ex.Code, faultData));
7689
log.LogDebug(ex,"Command {CommandType}Failed<{FaultType}> appended to session steam {CommandStream}.",
7790
command.GetType().Name,
7891
faultData.GetType().Name,
@@ -85,7 +98,8 @@ await plumber.AppendEvent(cmdStream, StreamState.StreamExists,
8598
{
8699
CommandId = cmdId,
87100
Duration = sw.Elapsed,
88-
Message = ex.Message
101+
Message = ex.Message,
102+
Code = HttpStatusCode.InternalServerError
89103
});
90104
log.LogDebug(ex,"Command {CommandType}Failed appended to session steam {CommandStream}.", command.GetType().Name,
91105
cmdStream);

src/MicroPlumberd.Services/ContainerExtensions.cs

+5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using EventStore.Client;
2+
23
using Microsoft.Extensions.DependencyInjection;
34
using Microsoft.Extensions.DependencyInjection.Extensions;
45
using Microsoft.Extensions.Hosting;
@@ -32,6 +33,10 @@ public static IServiceCollection AddPlumberd(this IServiceCollection collection,
3233
collection.TryAddSingleton(typeof(ISnapshotPolicy<>), typeof(AttributeSnaphotPolicy<>));
3334
collection.TryAddSingleton<ICommandBus, CommandBus>();
3435
collection.TryAddSingleton( typeof(IEventHandler<>),typeof(EventHandlerExecutor<>));
36+
37+
38+
//collection.TryDecorate<ICommandBus, CommandBusAttributeValidator>();
39+
3540
return collection;
3641
}
3742

src/MicroPlumberd.Services/Events.cs

+12-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace MicroPlumberd.Services;
1+
using System.Net;
2+
3+
namespace MicroPlumberd.Services;
24

35

46
record CommandExecuted
@@ -7,16 +9,17 @@ record CommandExecuted
79
public TimeSpan Duration { get; init; }
810
}
911

10-
record CommandFailed
12+
record CommandFailed : ICommandFailed
1113
{
1214
public Guid CommandId { get; init; }
1315
public TimeSpan Duration { get; init; }
1416
public string Message { get; init; }
17+
public HttpStatusCode Code { get; init; }
1518

16-
public static ICommandFailedEx Create(Guid commandId, string message, TimeSpan duration, object fault)
19+
public static ICommandFailedEx Create(Guid commandId, string message, TimeSpan duration, HttpStatusCode code, object fault)
1720
{
1821
var type = typeof(CommandFailed<>).MakeGenericType(fault.GetType());
19-
return (ICommandFailedEx)Activator.CreateInstance(type, commandId, message,duration, fault)!;
22+
return (ICommandFailedEx)Activator.CreateInstance(type, commandId, message,duration, code, fault)!;
2023
}
2124
}
2225

@@ -25,23 +28,26 @@ interface ICommandFailed
2528
Guid CommandId { get; }
2629
TimeSpan Duration { get; }
2730
string Message { get; }
28-
31+
public HttpStatusCode Code { get; }
32+
2933
}
3034
interface ICommandFailedEx : ICommandFailed
3135
{
3236
object Fault { get; }
37+
3338
}
3439
record CommandFailed<TFault> : CommandFailed, ICommandFailedEx
3540
{
3641
public CommandFailed() { }
3742

3843
object ICommandFailedEx.Fault => this.Fault;
39-
public CommandFailed(Guid commandId, string message, TimeSpan duration, TFault Fault)
44+
public CommandFailed(Guid commandId, string message, TimeSpan duration, HttpStatusCode code, TFault Fault)
4045
{
4146
this.Fault = Fault;
4247
this.CommandId = commandId;
4348
this.Duration = duration;
4449
this.Message = message;
50+
this.Code = code;
4551
}
4652
public TFault Fault { get; init; }
4753

src/MicroPlumberd.Services/ICommandHandler.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ public interface ICommandHandler
66
}
77
public interface ICommandHandler<in TCommand> : ICommandHandler
88
{
9-
Task<object> Execute(Guid id, TCommand command);
9+
Task<object?> Execute(Guid id, TCommand command);
1010
}

src/MicroPlumberd.Services/MicroPlumberd.Services.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
<ItemGroup>
3232
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
33+
<PackageReference Include="Scrutor" Version="4.2.2" />
3334
</ItemGroup>
3435

3536
<ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=validation/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace MicroPlumberd.Services;
4+
5+
public class CommandHandlerAttributeValidator<T>(ICommandHandler<T> nx, IServiceProvider sp) : ICommandHandler<T>
6+
{
7+
public Task<object?> Execute(Guid id, T command)
8+
{
9+
var validationContext = new ValidationContext(command, sp, null);
10+
Validator.ValidateObject(command, validationContext);
11+
return nx.Execute(id, command);
12+
}
13+
14+
public Task<object?> Execute(Guid id, object command) => Execute(id, (T)command);
15+
}
16+
17+
class CommandBusAttributeValidator(ICommandBus cb, IServiceProvider sp) : ICommandBus
18+
{
19+
public async Task SendAsync(Guid recipientId, object command)
20+
{
21+
var validationContext = new ValidationContext(command, sp, null);
22+
Validator.ValidateObject(command, validationContext);
23+
await cb.SendAsync(recipientId, command);
24+
}
25+
}

src/MicroPlumberd.SourceGenerators/CommandHandlerSourceGenerator.cs

+2
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,9 @@ public void Execute(GeneratorExecutionContext context)
125125
sb.AppendLine(" {");
126126
foreach (var command in commands)
127127
{
128+
//.Decorate<ICommandHandler<CreateFoo>, CommandHandlerAttributeValidator<CreateFoo>>()
128129
sb.AppendLine($" services.AddScoped<ICommandHandler<{command}>, {className}>();");
130+
sb.AppendLine($" services.Decorate<ICommandHandler<{command}>, CommandHandlerAttributeValidator<{command}>>();");
129131
}
130132
sb.AppendLine(" return services;");
131133
sb.AppendLine(" }");

src/MicroPlumberd.Tests.App/Srv/CreateFoo.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.ComponentModel.DataAnnotations;
12
using MicroPlumberd.DirectConnect;
23
using MicroPlumberd.Services;
34
using MicroPlumberd.Tests.App.Domain;
@@ -13,6 +14,7 @@ namespace MicroPlumberd.Tests.App.Srv;
1314
public class CreateFoo : IId
1415
{
1516
[ProtoMember(2)]
17+
[Required]
1618
public string? Name { get; set; }
1719
[ProtoMember(1)]
1820
public Guid Id { get; set; } = Guid.NewGuid();

src/MicroPlumberd.Tests/Integration/Services/CommandHandlerTests.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using MicroPlumberd.Tests.App.Domain;
1717
using MicroPlumberd.Tests.App.Srv;
1818

19+
1920
namespace MicroPlumberd.Tests.Integration.Services
2021
{
2122
[TestCategory("Integration")]
@@ -117,7 +118,7 @@ public async Task HandleCommand()
117118
sw.Start();
118119

119120
var client = await _clientTestApp.Configure( x=>x
120-
.AddPlumberd(_eventStore.GetEventStoreSettings(), x=> x.ServicesConfig().DefaultTimeout = TimeSpan.FromSeconds(5)))
121+
.AddPlumberd(_eventStore.GetEventStoreSettings(), x=> x.ServicesConfig().DefaultTimeout = TimeSpan.FromSeconds(120)))
121122
.StartAsync();
122123

123124
await client.GetRequiredService<ICommandBus>().SendAsync(recipientId, cmd);

0 commit comments

Comments
 (0)