From da8b9ffdde5c7dd7e8e7ddc4107b84e239d08020 Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Fri, 1 Jan 2021 12:44:16 +0100 Subject: [PATCH 01/17] Methods matching includes also compatible types. --- .../Inspection/MethodAnalyzer.cs | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/NScatterGather/Inspection/MethodAnalyzer.cs b/src/NScatterGather/Inspection/MethodAnalyzer.cs index cf0953d..63c00bc 100644 --- a/src/NScatterGather/Inspection/MethodAnalyzer.cs +++ b/src/NScatterGather/Inspection/MethodAnalyzer.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Reflection; using System.Threading.Tasks; @@ -26,13 +25,15 @@ public bool IsMatch( if (parameters.Count != 1) return false; - var theParameter = parameters.First(); + var theParameter = parameters[0]; - if (theParameter.ParameterType != requestType) - return false; + if (IsSameOrCompatible(baseType: theParameter.ParameterType, requestType)) + { + match = method; + return true; + } - match = method; - return true; + return false; } public bool IsMatch( @@ -51,7 +52,7 @@ public bool IsMatch( var (_, method, _, returnType) = inspection; - if (returnType == responseType) + if (IsSameOrCompatible(baseType: responseType, returnType)) { match = method; return true; @@ -63,13 +64,28 @@ public bool IsMatch( if (!returnType.IsAwaitableWithResult(out var awaitResultType)) return false; - if (awaitResultType != responseType) - return false; + if (IsSameOrCompatible(baseType: responseType, awaitResultType)) + { + // It's a match: Task/ValueTask/Awaitable of TResponse. + match = method; + return true; + } + + return false; + } + + private bool IsSameOrCompatible(Type baseType, Type otherType) + { + if (baseType == otherType) + return true; - // It's a match: Task/ValueTask/Awaitable of TResponse. + var nonNullableBaseType = Nullable.GetUnderlyingType(baseType); + + if (nonNullableBaseType is not null && + nonNullableBaseType == otherType) + return true; - match = method; - return true; + return false; } } } From 7a9d7967f7c387f4bdc4faeb0b8e4e703b83b221 Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Fri, 1 Jan 2021 12:45:37 +0100 Subject: [PATCH 02/17] Enforces non-null when it's safe. --- .../Recipients/Invokers/PreparedInvocation.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/NScatterGather/Recipients/Invokers/PreparedInvocation.cs b/src/NScatterGather/Recipients/Invokers/PreparedInvocation.cs index 44031ab..49ffe46 100644 --- a/src/NScatterGather/Recipients/Invokers/PreparedInvocation.cs +++ b/src/NScatterGather/Recipients/Invokers/PreparedInvocation.cs @@ -63,10 +63,9 @@ public async Task Execute() // At this point the task is completed. - var description = Awaitable.Describe(response); - - if (description is null) - throw new Exception("Couldn't extract async response."); + // The AwaitableDescription is not null since the response was + // evaluated with `IsAwaitableWithResult` + var description = Awaitable.Describe(response)!; var awaiter = description.GetAwaiterMethod.Invoke(response, null); var awaitedResult = description.AwaiterDescriptor.GetResultMethod.Invoke(awaiter, null); From dce6e722c7b46e42976d163edb543919b0c3965a Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Fri, 1 Jan 2021 12:47:29 +0100 Subject: [PATCH 03/17] Introduces CollisionStrategy. Recipients can return more than one runner when the strategy allows it, one for each method matching the request. --- src/NScatterGather/Aggregator.cs | 4 +- .../Inspection/MethodMatchEvaluation.cs | 31 ++-- .../Inspection/MethodMatchEvaluationCache.cs | 45 +----- .../Inspection/TypeInspector.cs | 142 +++++++++++------- .../Collection/CollisionStrategy.cs | 8 + .../Collection/RecipientsCollection.cs | 67 ++++++--- .../Recipients/DelegateRecipient.cs | 3 +- .../DelegateRecipientDescriptor.cs | 4 +- .../Descriptors/IRecipientDescriptor.cs | 4 +- .../Descriptors/TypeRecipientDescriptor.cs | 8 +- .../Recipients/InstanceRecipient.cs | 18 ++- .../Invokers/DelegateRecipientInvoker.cs | 15 +- .../Recipients/Invokers/IRecipientInvoker.cs | 8 +- .../Invokers/InstanceRecipientInvoker.cs | 39 +++-- src/NScatterGather/Recipients/Recipient.cs | 34 +++-- .../Recipients/TypeRecipient.cs | 17 ++- 16 files changed, 275 insertions(+), 172 deletions(-) create mode 100644 src/NScatterGather/Recipients/Collection/CollisionStrategy.cs diff --git a/src/NScatterGather/Aggregator.cs b/src/NScatterGather/Aggregator.cs index b3f5064..01b61f7 100644 --- a/src/NScatterGather/Aggregator.cs +++ b/src/NScatterGather/Aggregator.cs @@ -47,7 +47,7 @@ public Aggregator(RecipientsCollection collection) object request, CancellationToken cancellationToken) { - var runners = recipients.Select(recipient => recipient.Accept(request)).ToArray(); + var runners = recipients.SelectMany(recipient => recipient.Accept(request)).ToArray(); var tasks = runners .Select(runner => runner.Start()) @@ -92,7 +92,7 @@ private async Task>> Invoke( object request, CancellationToken cancellationToken) { - var runners = recipients.Select(recipient => recipient.ReplyWith(request)).ToArray(); + var runners = recipients.SelectMany(recipient => recipient.ReplyWith(request)).ToArray(); var tasks = runners .Select(runner => runner.Start()) diff --git a/src/NScatterGather/Inspection/MethodMatchEvaluation.cs b/src/NScatterGather/Inspection/MethodMatchEvaluation.cs index 9914c38..9e0cf61 100644 --- a/src/NScatterGather/Inspection/MethodMatchEvaluation.cs +++ b/src/NScatterGather/Inspection/MethodMatchEvaluation.cs @@ -1,22 +1,35 @@ -using System.Reflection; +using System; +using System.Collections.Generic; +using System.Reflection; namespace NScatterGather.Inspection { internal class MethodMatchEvaluation { - public bool IsMatch { get; } + public Type RequestType { get; } - public MethodInfo? Method { get; } + public Type? ResponseType { get; } - public MethodMatchEvaluation(bool isMatch, MethodInfo? method) => - (IsMatch, Method) = (isMatch, method); + public IReadOnlyList Methods { get; } + + public MethodMatchEvaluation( + Type requestType, + Type? responseType, + IReadOnlyList methods) + { + RequestType = requestType; + ResponseType = responseType; + Methods = methods; + } public void Deconstruct( - out bool isMatch, - out MethodInfo? method) + out Type requestType, + out Type? responseType, + out IReadOnlyList methods) { - isMatch = IsMatch; - method = Method; + requestType = RequestType; + responseType = ResponseType; + methods = Methods; } } } diff --git a/src/NScatterGather/Inspection/MethodMatchEvaluationCache.cs b/src/NScatterGather/Inspection/MethodMatchEvaluationCache.cs index 034aa47..847d4b5 100644 --- a/src/NScatterGather/Inspection/MethodMatchEvaluationCache.cs +++ b/src/NScatterGather/Inspection/MethodMatchEvaluationCache.cs @@ -9,25 +9,19 @@ internal class MethodMatchEvaluationCache private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); - #region Request only - - public bool TryAdd(MethodMatchEvaluation evaluation) => - TryAdd(typeof(TRequest), evaluation); - - public bool TryAdd( - Type requestType, - MethodMatchEvaluation evaluation) + public bool TryAdd(MethodMatchEvaluation evaluation) { - if (requestType is null) throw new ArgumentNullException(nameof(requestType)); if (evaluation is null) throw new ArgumentNullException(nameof(evaluation)); + if (evaluation.RequestType is null) throw new ArgumentNullException(nameof(evaluation.RequestType)); + if (evaluation.Methods is null) throw new ArgumentNullException(nameof(evaluation.Methods)); + + var hash = evaluation.ResponseType is null + ? HashCode.Combine(evaluation.RequestType) + : HashCode.Combine(evaluation.RequestType, evaluation.ResponseType); - var hash = HashCode.Combine(requestType); return _cache.TryAdd(hash, evaluation); } - public bool TryFindEvaluation([NotNullWhen(true)] out MethodMatchEvaluation? evaluation) => - TryFindEvaluation(typeof(TRequest), out evaluation); - public bool TryFindEvaluation( Type requestType, [NotNullWhen(true)] out MethodMatchEvaluation? evaluation) @@ -38,29 +32,6 @@ public bool TryFindEvaluation( return _cache.TryGetValue(hash, out evaluation); } - #endregion - - #region Request and response - - public bool TryAdd(MethodMatchEvaluation evaluation) => - TryAdd(typeof(TRequest), typeof(TResponse), evaluation); - - public bool TryAdd( - Type requestType, - Type responseType, - MethodMatchEvaluation evaluation) - { - if (requestType is null) throw new ArgumentNullException(nameof(requestType)); - if (responseType is null) throw new ArgumentNullException(nameof(responseType)); - if (evaluation is null) throw new ArgumentNullException(nameof(evaluation)); - - var hash = HashCode.Combine(requestType, responseType); - return _cache.TryAdd(hash, evaluation); - } - - public bool TryFindEvaluation([NotNullWhen(true)] out MethodMatchEvaluation? evaluation) => - TryFindEvaluation(typeof(TRequest), typeof(TResponse), out evaluation); - public bool TryFindEvaluation( Type requestType, Type responseType, @@ -72,7 +43,5 @@ public bool TryFindEvaluation( var hash = HashCode.Combine(requestType, responseType); return _cache.TryGetValue(hash, out evaluation); } - - #endregion } } diff --git a/src/NScatterGather/Inspection/TypeInspector.cs b/src/NScatterGather/Inspection/TypeInspector.cs index d42804d..5e2fc00 100644 --- a/src/NScatterGather/Inspection/TypeInspector.cs +++ b/src/NScatterGather/Inspection/TypeInspector.cs @@ -29,6 +29,7 @@ static IReadOnlyList InspectMethods(Type type) { var methods = type.GetMethods(DefaultFlags); return methods + .Where(method => method.DeclaringType == type) .Select(method => new MethodInspection(type, method)) .Where(inspection => inspection.ReturnsAResponse) .ToArray(); @@ -36,43 +37,42 @@ static IReadOnlyList InspectMethods(Type type) } #region Request only - public bool HasMethodAccepting(Type requestType) => - TryGetMethodAccepting(requestType, out _); + public bool HasMethodsAccepting(Type requestType, CollisionStrategy collisionStrategy) => + TryGetMethodsAccepting(requestType, collisionStrategy, out _); - public bool TryGetMethodAccepting( + public bool TryGetMethodsAccepting( Type requestType, - [NotNullWhen(true)] out MethodInfo? method) + CollisionStrategy collisionStrategy, + out IReadOnlyList methods) { - if (requestType is null) throw new ArgumentNullException(nameof(requestType)); + if (requestType is null) + throw new ArgumentNullException(nameof(requestType)); - // Check in the cache if the analysis was already done. - if (_evaluationsCache.TryFindEvaluation(requestType, out var cached)) - { - // Compliance with the request type is already known. - method = cached.Method!; - return cached.IsMatch; - } - - // Analyze the methods and find a match. + if (!collisionStrategy.IsValid()) + throw new ArgumentException($"Invalid {nameof(collisionStrategy)} value: {collisionStrategy}"); - var matches = ListMatchingMethods(requestType); + var evaluation = FindOrEvaluate(requestType); - if (matches.Count == 1) - { - method = matches.Single(); - _evaluationsCache.TryAdd(requestType, new MethodMatchEvaluation(true, method)); - return true; - } + return IsCompliantWithCollisionStrategyOrThrow( + requestType, + responseType: null, + evaluation, + collisionStrategy, + out methods); + } - // Single match not found. + private MethodMatchEvaluation FindOrEvaluate(Type requestType) + { + // Check in the cache if the analysis was already done. + if (_evaluationsCache.TryFindEvaluation(requestType, out var cached)) + return cached; - method = null; - _evaluationsCache.TryAdd(requestType, new MethodMatchEvaluation(false, method)); + var matches = ListMatchingMethods(requestType); - if (matches.Count > 1) - throw new CollisionException(_type, requestType); + var evaluation = new MethodMatchEvaluation(requestType, responseType: null, matches); + _evaluationsCache.TryAdd(evaluation); - return false; + return evaluation; } private IReadOnlyList ListMatchingMethods(Type requestType) @@ -92,45 +92,46 @@ private IReadOnlyList ListMatchingMethods(Type requestType) #region Request and response - public bool HasMethodReturning(Type requestType, Type responseType) => - TryGetMethodReturning(requestType, responseType, out _); + public bool HasMethodsReturning(Type requestType, Type responseType, CollisionStrategy collisionStrategy) => + TryGetMethodsReturning(requestType, responseType, collisionStrategy, out _); - public bool TryGetMethodReturning( + public bool TryGetMethodsReturning( Type requestType, Type responseType, - [NotNullWhen(true)] out MethodInfo? method) + CollisionStrategy collisionStrategy, + out IReadOnlyList methods) { - if (requestType is null) throw new ArgumentNullException(nameof(requestType)); - if (responseType is null) throw new ArgumentNullException(nameof(responseType)); + if (requestType is null) + throw new ArgumentNullException(nameof(requestType)); - // Check in the cache if the analysis was already done. - if (_evaluationsCache.TryFindEvaluation(requestType, responseType, out var cached)) - { - // Compliance with the request type is already known. - method = cached.Method!; - return cached.IsMatch; - } + if (responseType is null) + throw new ArgumentNullException(nameof(responseType)); - // Analyze the methods and find a match. + if (!collisionStrategy.IsValid()) + throw new ArgumentException($"Invalid {nameof(collisionStrategy)} value: {collisionStrategy}"); - var matches = ListMatchingMethods(requestType, responseType); + var evaluation = FindOrEvaluate(requestType, responseType); - if (matches.Count == 1) - { - method = matches.Single(); - _evaluationsCache.TryAdd(requestType, responseType, new MethodMatchEvaluation(true, method)); - return true; - } + return IsCompliantWithCollisionStrategyOrThrow( + requestType, + responseType, + evaluation, + collisionStrategy, + out methods); + } - // Single match not found. + private MethodMatchEvaluation FindOrEvaluate(Type requestType, Type responseType) + { + // Check in the cache if the analysis was already done. + if (_evaluationsCache.TryFindEvaluation(requestType, responseType, out var cached)) + return cached; - method = null; - _evaluationsCache.TryAdd(requestType, responseType, new MethodMatchEvaluation(false, method)); + var matches = ListMatchingMethods(requestType, responseType); - if (matches.Count > 1) - throw new CollisionException(_type, requestType, responseType); + var evaluation = new MethodMatchEvaluation(requestType, responseType, matches); + _evaluationsCache.TryAdd(evaluation); - return false; + return evaluation; } private IReadOnlyList ListMatchingMethods(Type requestType, Type responseType) @@ -147,5 +148,38 @@ private IReadOnlyList ListMatchingMethods(Type requestType, Type res } #endregion + + private bool IsCompliantWithCollisionStrategyOrThrow( + Type requestType, + Type? responseType, + MethodMatchEvaluation evaluation, + CollisionStrategy collisionStrategy, + out IReadOnlyList methods) + { + var (_, _, matches) = evaluation; + + // Check if evaluation has methods and is compliant with the CollisionStrategy + + if (matches.Count == 0) + { + methods = Array.Empty(); + return false; + } + else if (matches.Count == 1) + { + methods = matches; + return true; + } + + // More than one match found + + if (collisionStrategy == CollisionStrategy.UseAllMethodsMatching) + { + methods = matches; + return true; + } + + throw new CollisionException(_type, requestType, responseType); + } } } diff --git a/src/NScatterGather/Recipients/Collection/CollisionStrategy.cs b/src/NScatterGather/Recipients/Collection/CollisionStrategy.cs new file mode 100644 index 0000000..9ff8ce8 --- /dev/null +++ b/src/NScatterGather/Recipients/Collection/CollisionStrategy.cs @@ -0,0 +1,8 @@ +namespace NScatterGather +{ + public enum CollisionStrategy + { + IgnoreRecipient, + UseAllMethodsMatching + } +} diff --git a/src/NScatterGather/Recipients/Collection/RecipientsCollection.cs b/src/NScatterGather/Recipients/Collection/RecipientsCollection.cs index 6c8e55a..1eabe86 100644 --- a/src/NScatterGather/Recipients/Collection/RecipientsCollection.cs +++ b/src/NScatterGather/Recipients/Collection/RecipientsCollection.cs @@ -4,6 +4,8 @@ using NScatterGather.Inspection; using NScatterGather.Recipients; using NScatterGather.Recipients.Collection.Scope; +using static NScatterGather.CollisionStrategy; +using static NScatterGather.Lifetime; namespace NScatterGather { @@ -15,24 +17,53 @@ public class RecipientsCollection private readonly List _recipients = new(); private readonly TypeInspectorRegistry _registry = new TypeInspectorRegistry(); + private readonly CollisionStrategy _defaultCollisionStrategy; - public void Add(string? name = null) => - Add(name: name, lifetime: Lifetime.Transient); + public RecipientsCollection(CollisionStrategy defaultCollisionStrategy = IgnoreRecipient) + { + _defaultCollisionStrategy = defaultCollisionStrategy; + } + + public void Add(string name) => + AddWithDefaultFactoryMethod(name: name); public void Add(Lifetime lifetime) => - Add(name: null, lifetime: lifetime); + AddWithDefaultFactoryMethod(lifetime: lifetime); public void Add(Func factoryMethod) => - Add(factoryMethod, name: null, lifetime: Lifetime.Transient); + Internal_Add(factoryMethod: factoryMethod); + + public void Add(CollisionStrategy collisionStrategy) => + AddWithDefaultFactoryMethod(collisionStrategy: collisionStrategy); + + public void Add( + string? name = null, + Lifetime lifetime = Transient, + CollisionStrategy? collisionStrategy = null) + { + AddWithDefaultFactoryMethod(name, lifetime, collisionStrategy); + } public void Add( - string? name, - Lifetime lifetime) + Func factoryMethod, + string? name = null, + Lifetime lifetime = Transient, + CollisionStrategy? collisionStrategy = null) + { + Internal_Add(factoryMethod, name, lifetime, collisionStrategy); + } + + internal void AddWithDefaultFactoryMethod( + string? name = null, + Lifetime lifetime = Transient, + CollisionStrategy? collisionStrategy = null) { if (!HasADefaultConstructor()) throw new ArgumentException($"Type '{typeof(TRecipient).Name}' is missing a public, parameterless constructor."); - Add(() => ((TRecipient)Activator.CreateInstance(typeof(TRecipient)))!, name, lifetime); + static TRecipient factoryMethod() => ((TRecipient)Activator.CreateInstance(typeof(TRecipient)))!; + + Internal_Add(factoryMethod, name, lifetime, collisionStrategy); // Local functions. @@ -43,27 +74,31 @@ static bool HasADefaultConstructor() } } - public void Add( + internal void Internal_Add( Func factoryMethod, - string? name, - Lifetime lifetime) + string? name = null, + Lifetime lifetime = Transient, + CollisionStrategy? collisionStrategy = null) { if (factoryMethod is null) throw new ArgumentNullException(nameof(factoryMethod)); - var typeRecipient = TypeRecipient.Create(_registry, factoryMethod, name, lifetime); + var typeRecipient = TypeRecipient.Create( + _registry, factoryMethod, name, lifetime, collisionStrategy ?? _defaultCollisionStrategy); Add(typeRecipient); } public void Add( object instance, - string? name = null) + string? name = null, + CollisionStrategy? collisionStrategy = null) { if (instance is null) throw new ArgumentNullException(nameof(instance)); - var instanceRecipient = InstanceRecipient.Create(_registry, instance, name); + var instanceRecipient = InstanceRecipient.Create( + _registry, instance, name, collisionStrategy ?? _defaultCollisionStrategy); Add(instanceRecipient); } @@ -90,11 +125,7 @@ internal IRecipientsScope CreateScope() var scope = new RecipientsScope(); var scopedRecipients = _recipients.Select(recipient => - { - return recipient.Lifetime == Lifetime.Scoped - ? recipient.Clone() - : recipient; - }); + recipient.Lifetime == Scoped ? recipient.Clone() : recipient); scope.AddRange(scopedRecipients); diff --git a/src/NScatterGather/Recipients/DelegateRecipient.cs b/src/NScatterGather/Recipients/DelegateRecipient.cs index c5c3cb2..aff4653 100644 --- a/src/NScatterGather/Recipients/DelegateRecipient.cs +++ b/src/NScatterGather/Recipients/DelegateRecipient.cs @@ -35,7 +35,8 @@ protected DelegateRecipient( Type responseType, IRecipientDescriptor descriptor, IRecipientInvoker invoker, - string? name) : base(descriptor, invoker, name, Lifetime.Singleton) + string? name) + : base(descriptor, invoker, name, Lifetime.Singleton, CollisionStrategy.IgnoreRecipient) { RequestType = requestType; ResponseType = responseType; diff --git a/src/NScatterGather/Recipients/Descriptors/DelegateRecipientDescriptor.cs b/src/NScatterGather/Recipients/Descriptors/DelegateRecipientDescriptor.cs index 651e85e..610e541 100644 --- a/src/NScatterGather/Recipients/Descriptors/DelegateRecipientDescriptor.cs +++ b/src/NScatterGather/Recipients/Descriptors/DelegateRecipientDescriptor.cs @@ -14,13 +14,13 @@ public DelegateRecipientDescriptor(Type requestType, Type responseType) ResponseType = responseType; } - public bool CanAccept(Type requestType) + public bool CanAccept(Type requestType, CollisionStrategy collisionStrategy) { var requestTypeMatches = TypesMatch(RequestType, requestType); return requestTypeMatches; } - public bool CanReplyWith(Type requestType, Type responseType) + public bool CanReplyWith(Type requestType, Type responseType, CollisionStrategy collisionStrategy) { var requestAndResponseMatch = TypesMatch(RequestType, requestType) && diff --git a/src/NScatterGather/Recipients/Descriptors/IRecipientDescriptor.cs b/src/NScatterGather/Recipients/Descriptors/IRecipientDescriptor.cs index 580e372..c0c6410 100644 --- a/src/NScatterGather/Recipients/Descriptors/IRecipientDescriptor.cs +++ b/src/NScatterGather/Recipients/Descriptors/IRecipientDescriptor.cs @@ -4,8 +4,8 @@ namespace NScatterGather.Recipients.Descriptors { internal interface IRecipientDescriptor { - bool CanAccept(Type requestType); + bool CanAccept(Type requestType, CollisionStrategy collisionStrategy); - bool CanReplyWith(Type requestType, Type responseType); + bool CanReplyWith(Type requestType, Type responseType, CollisionStrategy collisionStrategy); } } diff --git a/src/NScatterGather/Recipients/Descriptors/TypeRecipientDescriptor.cs b/src/NScatterGather/Recipients/Descriptors/TypeRecipientDescriptor.cs index a36149f..d8e5fe9 100644 --- a/src/NScatterGather/Recipients/Descriptors/TypeRecipientDescriptor.cs +++ b/src/NScatterGather/Recipients/Descriptors/TypeRecipientDescriptor.cs @@ -12,15 +12,15 @@ public TypeRecipientDescriptor(TypeInspector inspector) _inspector = inspector; } - public bool CanAccept(Type requestType) + public bool CanAccept(Type requestType, CollisionStrategy collisionStrategy) { - var accepts = _inspector.HasMethodAccepting(requestType); + var accepts = _inspector.HasMethodsAccepting(requestType, collisionStrategy); return accepts; } - public bool CanReplyWith(Type requestType, Type responseType) + public bool CanReplyWith(Type requestType, Type responseType, CollisionStrategy collisionStrategy) { - var repliesWith = _inspector.HasMethodReturning(requestType, responseType); + var repliesWith = _inspector.HasMethodsReturning(requestType, responseType, collisionStrategy); return repliesWith; } } diff --git a/src/NScatterGather/Recipients/InstanceRecipient.cs b/src/NScatterGather/Recipients/InstanceRecipient.cs index f57b202..cf610bb 100644 --- a/src/NScatterGather/Recipients/InstanceRecipient.cs +++ b/src/NScatterGather/Recipients/InstanceRecipient.cs @@ -13,7 +13,8 @@ internal class InstanceRecipient : TypeRecipient public static InstanceRecipient Create( TypeInspectorRegistry registry, object instance, - string? name) + string? name, + CollisionStrategy collisionStrategy) { if (registry is null) throw new ArgumentNullException(nameof(registry)); @@ -21,25 +22,32 @@ public static InstanceRecipient Create( if (instance is null) throw new ArgumentNullException(nameof(instance)); + if (!collisionStrategy.IsValid()) + throw new ArgumentException($"Invalid {nameof(collisionStrategy)} value: {collisionStrategy}"); + var inspector = registry.For(instance.GetType()); var descriptor = new TypeRecipientDescriptor(inspector); var invoker = new InstanceRecipientInvoker( inspector, - new SingletonRecipientFactory(instance)); + new SingletonRecipientFactory(instance), + collisionStrategy); return new InstanceRecipient( instance, descriptor, invoker, - name); + name, + collisionStrategy); } protected InstanceRecipient( object instance, IRecipientDescriptor descriptor, IRecipientInvoker invoker, - string? name) : base(instance.GetType(), descriptor, invoker, name, Lifetime.Singleton) + string? name, + CollisionStrategy collisionStrategy) + : base(instance.GetType(), descriptor, invoker, name, Lifetime.Singleton, collisionStrategy) { _instance = instance; } @@ -51,7 +59,7 @@ public override InstanceRecipient Clone() #endif { var invoker = _invoker.Clone(); - return new InstanceRecipient(_instance, _descriptor, invoker, Name); + return new InstanceRecipient(_instance, _descriptor, invoker, Name, CollisionStrategy); } } } diff --git a/src/NScatterGather/Recipients/Invokers/DelegateRecipientInvoker.cs b/src/NScatterGather/Recipients/Invokers/DelegateRecipientInvoker.cs index 4c64313..8c3eeba 100644 --- a/src/NScatterGather/Recipients/Invokers/DelegateRecipientInvoker.cs +++ b/src/NScatterGather/Recipients/Invokers/DelegateRecipientInvoker.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using NScatterGather.Recipients.Descriptors; namespace NScatterGather.Recipients.Invokers @@ -16,25 +17,27 @@ public DelegateRecipientInvoker( _delegate = @delegate; } - public PreparedInvocation PrepareInvocation(object request) + public IReadOnlyList> PrepareInvocations(object request) { - if (!_descriptor.CanAccept(request.GetType())) + if (!_descriptor.CanAccept(request.GetType(), CollisionStrategy.IgnoreRecipient)) throw new InvalidOperationException( $"Delegate '{_delegate}' doesn't support accepting requests " + $"of type '{request.GetType().Name}'."); - return new PreparedInvocation(() => _delegate(request!)); + var preparedInvocation = new PreparedInvocation(() => _delegate(request!)); + return new[] { preparedInvocation }; } - public PreparedInvocation PrepareInvocation(object request) + public IReadOnlyList> PrepareInvocations(object request) { - if (!_descriptor.CanReplyWith(request.GetType(), typeof(TResult))) + if (!_descriptor.CanReplyWith(request.GetType(), typeof(TResult), CollisionStrategy.IgnoreRecipient)) throw new InvalidOperationException( $"Type '{_delegate}' doesn't support accepting " + $"requests of type '{request.GetType().Name}' and " + $"returning '{typeof(TResult).Name}'."); - return new PreparedInvocation(() => (TResult)_delegate(request)!); + var preparedInvocation = new PreparedInvocation(() => (TResult)_delegate(request)!); + return new[] { preparedInvocation }; } public IRecipientInvoker Clone() => diff --git a/src/NScatterGather/Recipients/Invokers/IRecipientInvoker.cs b/src/NScatterGather/Recipients/Invokers/IRecipientInvoker.cs index cc4ea45..514db3b 100644 --- a/src/NScatterGather/Recipients/Invokers/IRecipientInvoker.cs +++ b/src/NScatterGather/Recipients/Invokers/IRecipientInvoker.cs @@ -1,10 +1,12 @@ -namespace NScatterGather.Recipients.Invokers +using System.Collections.Generic; + +namespace NScatterGather.Recipients.Invokers { internal interface IRecipientInvoker { - PreparedInvocation PrepareInvocation(object request); + IReadOnlyList> PrepareInvocations(object request); - PreparedInvocation PrepareInvocation(object request); + IReadOnlyList> PrepareInvocations(object request); IRecipientInvoker Clone(); } diff --git a/src/NScatterGather/Recipients/Invokers/InstanceRecipientInvoker.cs b/src/NScatterGather/Recipients/Invokers/InstanceRecipientInvoker.cs index d428a3b..11b1f6c 100644 --- a/src/NScatterGather/Recipients/Invokers/InstanceRecipientInvoker.cs +++ b/src/NScatterGather/Recipients/Invokers/InstanceRecipientInvoker.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using NScatterGather.Inspection; using NScatterGather.Recipients.Factories; @@ -8,46 +10,59 @@ internal class InstanceRecipientInvoker : IRecipientInvoker { private readonly TypeInspector _inspector; private readonly IRecipientFactory _factory; + private readonly CollisionStrategy _collisionStrategy; public InstanceRecipientInvoker( TypeInspector inspector, - IRecipientFactory factory) + IRecipientFactory factory, + CollisionStrategy collisionStrategy) { _inspector = inspector; _factory = factory; + _collisionStrategy = collisionStrategy; } - public PreparedInvocation PrepareInvocation(object request) + public IReadOnlyList> PrepareInvocations(object request) { - if (!_inspector.TryGetMethodAccepting(request.GetType(), out var method)) + if (!_inspector.TryGetMethodsAccepting(request.GetType(), _collisionStrategy, out var methods)) throw new InvalidOperationException( $"Type '{_inspector.Type.Name}' doesn't support accepting requests " + $"of type '{request.GetType().Name}'."); - var recipientInstance = _factory.Get(); + var preparedInvocations = methods.Select(method => + { + var recipientInstance = _factory.Get(); - return new PreparedInvocation(invocation: () => - method.Invoke(recipientInstance, new object?[] { request })); + return new PreparedInvocation(invocation: () => + method.Invoke(recipientInstance, new object?[] { request })); + }); + + return preparedInvocations.ToArray(); } - public PreparedInvocation PrepareInvocation(object request) + public IReadOnlyList> PrepareInvocations(object request) { - if (!_inspector.TryGetMethodReturning(request.GetType(), typeof(TResult), out var method)) + if (!_inspector.TryGetMethodsReturning(request.GetType(), typeof(TResult), _collisionStrategy, out var methods)) throw new InvalidOperationException( $"Type '{_inspector.Type.Name}' doesn't support accepting " + $"requests of type '{request.GetType().Name}' and " + $"returning '{typeof(TResult).Name}'."); - var recipientInstance = _factory.Get(); + var preparedInvocations = methods.Select(method => + { + var recipientInstance = _factory.Get(); + + return new PreparedInvocation(invocation: () => + method.Invoke(recipientInstance, new object?[] { request })!); + }); - return new PreparedInvocation(invocation: () => - method.Invoke(recipientInstance, new object?[] { request })!); + return preparedInvocations.ToArray(); } public IRecipientInvoker Clone() { var factory = _factory.Clone(); - return new InstanceRecipientInvoker(_inspector, factory); + return new InstanceRecipientInvoker(_inspector, factory, _collisionStrategy); } } } diff --git a/src/NScatterGather/Recipients/Recipient.cs b/src/NScatterGather/Recipients/Recipient.cs index 9729dc6..e12aca7 100644 --- a/src/NScatterGather/Recipients/Recipient.cs +++ b/src/NScatterGather/Recipients/Recipient.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using NScatterGather.Recipients.Descriptors; using NScatterGather.Recipients.Invokers; using NScatterGather.Recipients.Run; @@ -11,6 +13,8 @@ internal abstract class Recipient public Lifetime Lifetime { get; } + public CollisionStrategy CollisionStrategy { get; } + protected readonly IRecipientDescriptor _descriptor; protected readonly IRecipientInvoker _invoker; @@ -18,33 +22,41 @@ public Recipient( IRecipientDescriptor descriptor, IRecipientInvoker invoker, string? name, - Lifetime lifetime) + Lifetime lifetime, + CollisionStrategy collisionStrategy) { _descriptor = descriptor; _invoker = invoker; Name = name; Lifetime = lifetime; + CollisionStrategy = collisionStrategy; } public bool CanAccept(Type requestType) => - _descriptor.CanAccept(requestType); + _descriptor.CanAccept(requestType, CollisionStrategy); public bool CanReplyWith(Type requestType, Type responseType) => - _descriptor.CanReplyWith(requestType, responseType); + _descriptor.CanReplyWith(requestType, responseType, CollisionStrategy); - public RecipientRun Accept(object request) + public IReadOnlyList> Accept(object request) { - var preparedInvocation = _invoker.PrepareInvocation(request); - var run = new RecipientRun(this, preparedInvocation); - return run; + var preparedInvocations = _invoker.PrepareInvocations(request); + + var runners = preparedInvocations.Select(preparedInvocation => + new RecipientRun(this, preparedInvocation)); + + return runners.ToArray(); } - public RecipientRun ReplyWith(object request) + public IReadOnlyList> ReplyWith(object request) { - var preparedInvocation = _invoker.PrepareInvocation(request); - var run = new RecipientRun(this, preparedInvocation); - return run; + var preparedInvocations = _invoker.PrepareInvocations(request); + + var runners = preparedInvocations.Select(preparedInvocation => + new RecipientRun(this, preparedInvocation)); + + return runners.ToArray(); } public abstract Recipient Clone(); diff --git a/src/NScatterGather/Recipients/TypeRecipient.cs b/src/NScatterGather/Recipients/TypeRecipient.cs index 1729462..b26b88b 100644 --- a/src/NScatterGather/Recipients/TypeRecipient.cs +++ b/src/NScatterGather/Recipients/TypeRecipient.cs @@ -14,7 +14,8 @@ public static TypeRecipient Create( TypeInspectorRegistry registry, Func factoryMethod, string? name, - Lifetime lifetime) + Lifetime lifetime, + CollisionStrategy collisionStrategy) { if (registry is null) throw new ArgumentNullException(nameof(registry)); @@ -25,6 +26,9 @@ public static TypeRecipient Create( if (!lifetime.IsValid()) throw new ArgumentException($"Invalid {nameof(lifetime)} value: {lifetime}"); + if (!collisionStrategy.IsValid()) + throw new ArgumentException($"Invalid {nameof(collisionStrategy)} value: {collisionStrategy}"); + var inspector = registry.For(); IRecipientFactory factory = new RecipientFactory(() => factoryMethod()!); @@ -35,9 +39,10 @@ public static TypeRecipient Create( return new TypeRecipient( typeof(TRecipient), new TypeRecipientDescriptor(inspector), - new InstanceRecipientInvoker(inspector, factory), + new InstanceRecipientInvoker(inspector, factory, collisionStrategy), name, - lifetime); + lifetime, + collisionStrategy); } protected TypeRecipient( @@ -45,7 +50,9 @@ protected TypeRecipient( IRecipientDescriptor descriptor, IRecipientInvoker invoker, string? name, - Lifetime lifetime) : base(descriptor, invoker, name, lifetime) + Lifetime lifetime, + CollisionStrategy collisionStrategy) + : base(descriptor, invoker, name, lifetime, collisionStrategy) { Type = type; } @@ -57,7 +64,7 @@ public override TypeRecipient Clone() #endif { var invoker = _invoker.Clone(); - return new TypeRecipient(Type, _descriptor, invoker, Name, Lifetime); + return new TypeRecipient(Type, _descriptor, invoker, Name, Lifetime, CollisionStrategy); } } } From 334cb4f8d89cbc11148a58dcdf80a7c9e6ec794a Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Fri, 1 Jan 2021 12:53:32 +0100 Subject: [PATCH 04/17] Tweaks severity for arguments usage. --- .editorconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 12db45d..c96843d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -45,6 +45,8 @@ dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_auto_properties = false:silent dotnet_style_prefer_conditional_expression_over_assignment = true:silent dotnet_style_prefer_conditional_expression_over_return = true:silent +# CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = none ############################### # Naming Conventions # ############################### @@ -57,7 +59,6 @@ dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_ dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.applicable_accessibilities = * dotnet_naming_symbols.constant_fields.required_modifiers = const -# Performance rules # Mark members as static dotnet_diagnostic.CA1822.severity = none ############################### From 3cf9a1faa4560ead0c19bc70415453c1a0f74580 Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Fri, 1 Jan 2021 12:57:33 +0100 Subject: [PATCH 05/17] ConfigureAwait everything. --- src/NScatterGather/Aggregator.cs | 10 ++++------ src/NScatterGather/Recipients/Run/RecipientRun.cs | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/NScatterGather/Aggregator.cs b/src/NScatterGather/Aggregator.cs index 01b61f7..174418e 100644 --- a/src/NScatterGather/Aggregator.cs +++ b/src/NScatterGather/Aggregator.cs @@ -24,7 +24,7 @@ public Aggregator(RecipientsCollection collection) TimeSpan timeout) { using var cts = new CancellationTokenSource(timeout); - return await Send(request, cts.Token); + return await Send(request, cts.Token).ConfigureAwait(false); } public async Task> Send( @@ -36,8 +36,7 @@ public Aggregator(RecipientsCollection collection) var recipients = _scope.ListRecipientsAccepting(request.GetType()); - var invocations = await Invoke(recipients, request, cancellationToken) - .ConfigureAwait(false); + var invocations = await Invoke(recipients, request, cancellationToken).ConfigureAwait(false); return AggregatedResponseFactory.CreateFrom(invocations); } @@ -69,7 +68,7 @@ public async Task> Send( TimeSpan timeout) { using var cts = new CancellationTokenSource(timeout); - return await Send(request, cts.Token); + return await Send(request, cts.Token).ConfigureAwait(false); } public async Task> Send( @@ -81,8 +80,7 @@ public async Task> Send( var recipients = _scope.ListRecipientsReplyingWith(request.GetType(), typeof(TResponse)); - var runners = await Invoke(recipients, request, cancellationToken) - .ConfigureAwait(false); + var runners = await Invoke(recipients, request, cancellationToken).ConfigureAwait(false); return AggregatedResponseFactory.CreateFrom(runners); } diff --git a/src/NScatterGather/Recipients/Run/RecipientRun.cs b/src/NScatterGather/Recipients/Run/RecipientRun.cs index 1e69762..4d54e61 100644 --- a/src/NScatterGather/Recipients/Run/RecipientRun.cs +++ b/src/NScatterGather/Recipients/Run/RecipientRun.cs @@ -41,10 +41,10 @@ public Task Start() StartedAt = DateTime.UtcNow; - var runnerTask = Task.Run(async () => await _preparedInvocation.Execute()); - var tcs = new TaskCompletionSource(); + var runnerTask = Task.Run(async () => await _preparedInvocation.Execute().ConfigureAwait(false)); + runnerTask.ContinueWith(completedTask => { InspectAndExtract(completedTask); From 79c636bd95f1a227da49b3cfdd7a8e5ff176f3ff Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Sat, 2 Jan 2021 10:00:16 +0100 Subject: [PATCH 06/17] Introduces Descriptors tests. --- .../DelegateRecipientDescriptorTests.cs | 37 +++++++++++++++++ .../TypeRecipientDescriptorTests.cs | 40 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 tests/NScatterGather.Tests/Recipients/Descriptors/DelegateRecipientDescriptorTests.cs create mode 100644 tests/NScatterGather.Tests/Recipients/Descriptors/TypeRecipientDescriptorTests.cs diff --git a/tests/NScatterGather.Tests/Recipients/Descriptors/DelegateRecipientDescriptorTests.cs b/tests/NScatterGather.Tests/Recipients/Descriptors/DelegateRecipientDescriptorTests.cs new file mode 100644 index 0000000..e293767 --- /dev/null +++ b/tests/NScatterGather.Tests/Recipients/Descriptors/DelegateRecipientDescriptorTests.cs @@ -0,0 +1,37 @@ +using System; +using Xunit; +using static NScatterGather.CollisionStrategy; + +namespace NScatterGather.Recipients.Descriptors +{ + public class DelegateRecipientDescriptorTests + { + [Fact] + public void Can_accept_request_type() + { + var descriptor = new DelegateRecipientDescriptor(typeof(int), typeof(DateTime)); + + Assert.False(descriptor.CanAccept(typeof(int?), IgnoreRecipient)); + Assert.True(descriptor.CanAccept(typeof(int), IgnoreRecipient)); + + var nullableDescriptor = new DelegateRecipientDescriptor(typeof(int?), typeof(DateTime?)); + + Assert.True(nullableDescriptor.CanAccept(typeof(int?), IgnoreRecipient)); + Assert.True(nullableDescriptor.CanAccept(typeof(int), IgnoreRecipient)); + } + + [Fact] + public void Can_reply_with_response_type() + { + var descriptor = new DelegateRecipientDescriptor(typeof(int), typeof(DateTime)); + + Assert.False(descriptor.CanReplyWith(typeof(int?), typeof(DateTime?), IgnoreRecipient)); + Assert.True(descriptor.CanReplyWith(typeof(int), typeof(DateTime), IgnoreRecipient)); + + var nullableDescriptor = new DelegateRecipientDescriptor(typeof(int?), typeof(DateTime?)); + + Assert.True(nullableDescriptor.CanReplyWith(typeof(int?), typeof(DateTime?), IgnoreRecipient)); + Assert.True(nullableDescriptor.CanReplyWith(typeof(int), typeof(DateTime), IgnoreRecipient)); + } + } +} diff --git a/tests/NScatterGather.Tests/Recipients/Descriptors/TypeRecipientDescriptorTests.cs b/tests/NScatterGather.Tests/Recipients/Descriptors/TypeRecipientDescriptorTests.cs new file mode 100644 index 0000000..e555d73 --- /dev/null +++ b/tests/NScatterGather.Tests/Recipients/Descriptors/TypeRecipientDescriptorTests.cs @@ -0,0 +1,40 @@ +using System; +using NScatterGather.Inspection; +using Xunit; +using static NScatterGather.CollisionStrategy; + +namespace NScatterGather.Recipients.Descriptors +{ + public class TypeRecipientDescriptorTests + { + [Fact] + public void Can_accept_request_type() + { + var descriptor = new TypeRecipientDescriptor(new TypeInspector(typeof(SomeNonNullableType))); + + Assert.False(descriptor.CanAccept(typeof(int?), IgnoreRecipient)); + Assert.True(descriptor.CanAccept(typeof(int), IgnoreRecipient)); + + var nullableDescriptor = new TypeRecipientDescriptor(new TypeInspector(typeof(SomeNullableType))); + + Assert.True(nullableDescriptor.CanAccept(typeof(int?), IgnoreRecipient)); + Assert.True(nullableDescriptor.CanAccept(typeof(int), IgnoreRecipient)); + } + + [Fact] + public void Can_reply_with_response_type() + { + var descriptor = new TypeRecipientDescriptor(new TypeInspector(typeof(SomeNonNullableType))); + + Assert.False(descriptor.CanReplyWith(typeof(int?), typeof(DateTime?), IgnoreRecipient)); + Assert.True(descriptor.CanReplyWith(typeof(int), typeof(DateTime), IgnoreRecipient)); + + var nullableDescriptor = new TypeRecipientDescriptor(new TypeInspector(typeof(SomeNullableType))); + + Assert.True(nullableDescriptor.CanReplyWith(typeof(int?), typeof(DateTime?), IgnoreRecipient)); + Assert.True(nullableDescriptor.CanReplyWith(typeof(int), typeof(DateTime?), IgnoreRecipient)); + Assert.False(nullableDescriptor.CanReplyWith(typeof(int?), typeof(DateTime), IgnoreRecipient)); + Assert.False(nullableDescriptor.CanReplyWith(typeof(int), typeof(DateTime), IgnoreRecipient)); + } + } +} From 542e3a588666996a7fe8eb4c2ac5fc480d404f23 Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Sat, 2 Jan 2021 10:00:34 +0100 Subject: [PATCH 07/17] Introduces Invokers tests. --- .../Invokers/DelegateRecipientInvokerTests.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/NScatterGather.Tests/Recipients/Invokers/DelegateRecipientInvokerTests.cs diff --git a/tests/NScatterGather.Tests/Recipients/Invokers/DelegateRecipientInvokerTests.cs b/tests/NScatterGather.Tests/Recipients/Invokers/DelegateRecipientInvokerTests.cs new file mode 100644 index 0000000..db661d5 --- /dev/null +++ b/tests/NScatterGather.Tests/Recipients/Invokers/DelegateRecipientInvokerTests.cs @@ -0,0 +1,21 @@ +using NScatterGather.Recipients.Descriptors; +using Xunit; + +namespace NScatterGather.Recipients.Invokers +{ + public class DelegateRecipientInvokerTests + { + [Fact] + public void Can_be_cloned() + { + var descriptor = new DelegateRecipientDescriptor(typeof(int), typeof(string)); + static object? @delegate(object o) { return o.ToString(); } + + var invoker = new DelegateRecipientInvoker(descriptor, @delegate); + var clone = invoker.Clone(); + + Assert.NotNull(clone); + Assert.IsType(invoker); + } + } +} From 553c31f99fcf8bb6273166722376dae9840750f1 Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Sat, 2 Jan 2021 10:03:17 +0100 Subject: [PATCH 08/17] Updates tests with CollisionStrategy. --- tests/NScatterGather.Tests/AggregatorTests.cs | 52 ++++++- .../Inspection/InspectionResultTests.cs | 21 +-- .../MethodMatchEvaluationCacheTests.cs | 131 +++++------------- .../Inspection/TypeInspectorTests.cs | 50 +++++-- .../Collection/RecipientsCollectionTests.cs | 9 +- .../Recipients/DelegateRecipientTests.cs | 6 +- .../Recipients/InstanceRecipientTests.cs | 13 +- .../Recipients/Run/RecipientRunTests.cs | 31 +++-- .../Recipients/TypeRecipientTests.cs | 122 ++++++++++++---- .../AggregatedResponseExtensionsTests.cs | 24 ++-- .../Responses/AggregatedResponseTests.cs | 24 ++-- .../_TestTypes/SomeNonNullableType.cs | 9 ++ .../_TestTypes/SomeNullableType.cs | 11 ++ .../_Utils/TestExtensions.cs | 4 +- 14 files changed, 323 insertions(+), 184 deletions(-) create mode 100644 tests/NScatterGather.Tests/_TestTypes/SomeNonNullableType.cs create mode 100644 tests/NScatterGather.Tests/_TestTypes/SomeNullableType.cs diff --git a/tests/NScatterGather.Tests/AggregatorTests.cs b/tests/NScatterGather.Tests/AggregatorTests.cs index 2627e52..efabb6f 100644 --- a/tests/NScatterGather.Tests/AggregatorTests.cs +++ b/tests/NScatterGather.Tests/AggregatorTests.cs @@ -140,7 +140,7 @@ public async Task Recipients_can_return_null() Assert.Single(response.Completed); Assert.Empty(response.Faulted); - var completed = response.Completed.First(); + var completed = response.Completed[0]; Assert.Equal(typeof(SomeTypeReturningNull), completed.RecipientType); Assert.Null(completed.Result); } @@ -159,9 +159,57 @@ public async Task Recipients_can_return_nullable() Assert.Single(response.Completed); Assert.Empty(response.Faulted); - var completed = response.Completed.First(); + var completed = response.Completed[0]; Assert.Equal(typeof(SomeTypeReturningNullable), completed.RecipientType); Assert.Null(completed.Result); } + + [Fact] + public async Task Colliding_recipients_are_ignored_by_design() + { + var collection = new RecipientsCollection(); + collection.Add(CollisionStrategy.IgnoreRecipient); + + var collisionDetected = false; + collection.OnCollision += _ => collisionDetected = true; + + var aggregator = new Aggregator(collection); + + var (completed, faulted, incomplete) = await aggregator.Send(42); + + Assert.Empty(completed); + Assert.Empty(faulted); + Assert.Empty(incomplete); + + Assert.True(collisionDetected); + } + + [Fact] + public async Task Colliding_recipients_use_all_methods_by_design() + { + var collection = new RecipientsCollection(); + collection.Add(CollisionStrategy.UseAllMethodsMatching); + + var collisionDetected = false; + collection.OnCollision += _ => collisionDetected = true; + + var aggregator = new Aggregator(collection); + + var (completed, faulted, incomplete) = await aggregator.Send(42); + + Assert.Equal(2, completed.Count); + Assert.Empty(faulted); + Assert.Empty(incomplete); + + Assert.False(collisionDetected); + + var stringsOnly = await aggregator.Send(42); + + Assert.Equal(2, stringsOnly.Completed.Count); + Assert.Empty(stringsOnly.Faulted); + Assert.Empty(stringsOnly.Incomplete); + + Assert.False(collisionDetected); + } } } diff --git a/tests/NScatterGather.Tests/Inspection/InspectionResultTests.cs b/tests/NScatterGather.Tests/Inspection/InspectionResultTests.cs index 026db34..2c0d79f 100644 --- a/tests/NScatterGather.Tests/Inspection/InspectionResultTests.cs +++ b/tests/NScatterGather.Tests/Inspection/InspectionResultTests.cs @@ -1,4 +1,6 @@ -using Xunit; +using System; +using System.Reflection; +using Xunit; namespace NScatterGather.Inspection { @@ -7,19 +9,22 @@ public class InspectionResultTests [Fact] public void Can_be_constructed() { - new MethodMatchEvaluation(false, typeof(object).GetMethod(nameof(object.ToString))); + _ = new MethodMatchEvaluation(typeof(object), typeof(object), Array.Empty()); } [Fact] public void Can_be_deconstructed() { - var evaluation = new MethodMatchEvaluation( - false, - typeof(object).GetMethod(nameof(object.ToString))); + var expectedRequestType = typeof(int); + var expectedResponseType = typeof(int); + var expectedMethods = Array.Empty(); - var (isMatch, method) = evaluation; - Assert.Equal(isMatch, evaluation.IsMatch); - Assert.Equal(method, evaluation.Method); + var evaluation = new MethodMatchEvaluation(expectedRequestType, expectedResponseType, expectedMethods); + var (requestType, responseType, methods) = evaluation; + + Assert.Same(expectedRequestType, requestType); + Assert.Same(expectedResponseType, responseType); + Assert.Same(expectedMethods, methods); } } } diff --git a/tests/NScatterGather.Tests/Inspection/MethodMatchEvaluationCacheTests.cs b/tests/NScatterGather.Tests/Inspection/MethodMatchEvaluationCacheTests.cs index 0036799..729d100 100644 --- a/tests/NScatterGather.Tests/Inspection/MethodMatchEvaluationCacheTests.cs +++ b/tests/NScatterGather.Tests/Inspection/MethodMatchEvaluationCacheTests.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using Xunit; namespace NScatterGather.Inspection @@ -10,34 +11,37 @@ class SomeRequest { } class SomeResponse { } [Fact] - public void Error_if_request_type_is_null() + public void Accepts_empty_methods() { var cache = new MethodMatchEvaluationCache(); - Assert.Throws(() => cache.TryAdd( - null!, new MethodMatchEvaluation(false, null))); + cache.TryAdd(new MethodMatchEvaluation(typeof(SomeRequest), typeof(SomeResponse), methods: Array.Empty())); + } - Assert.Throws(() => cache.TryAdd( - null!, typeof(SomeResponse), new MethodMatchEvaluation(false, null))); + [Fact] + public void Error_if_evaluation_is_null() + { + var cache = new MethodMatchEvaluationCache(); + + Assert.Throws(() => cache.TryAdd(null!)); } [Fact] - public void Error_if_response_type_is_null() + public void Error_if_request_type_is_null() { var cache = new MethodMatchEvaluationCache(); Assert.Throws(() => cache.TryAdd( - typeof(SomeRequest), null!, new MethodMatchEvaluation(false, null))); + new MethodMatchEvaluation(requestType: null!, typeof(SomeResponse), Array.Empty()))); } [Fact] - public void Error_if_evaluation_is_null() + public void Error_if_methods_are_null() { var cache = new MethodMatchEvaluationCache(); - Assert.Throws(() => cache.TryAdd(null!)); Assert.Throws(() => cache.TryAdd( - typeof(SomeRequest), typeof(SomeResponse), (null as MethodMatchEvaluation)!)); + new MethodMatchEvaluation(requestType: typeof(SomeRequest), responseType: null, methods: null!))); } [Fact] @@ -56,38 +60,22 @@ public void Error_if_response_type_is_null_when_searching() } [Fact] - public void Accepts_generic_request_type() + public void Accepts_request_type() { var cache = new MethodMatchEvaluationCache(); - bool added = cache.TryAdd(new MethodMatchEvaluation(false, null)); + bool added = cache.TryAdd(new MethodMatchEvaluation(typeof(SomeRequest), null, Array.Empty())); Assert.True(added); } [Fact] - public void Accepts_explicit_request_type() + public void Accepts_request_and_response_types() { var cache = new MethodMatchEvaluationCache(); - bool added = cache.TryAdd(typeof(SomeRequest), new MethodMatchEvaluation(false, null)); - Assert.True(added); - } - [Fact] - public void Accepts_generic_request_and_response_types() - { - var cache = new MethodMatchEvaluationCache(); - bool added = cache.TryAdd(new MethodMatchEvaluation(false, null)); - Assert.True(added); - } - - [Fact] - public void Accepts_explicit_request_and_response_types() - { - var cache = new MethodMatchEvaluationCache(); - - bool added = cache.TryAdd( - typeof(SomeRequest), + bool added = cache.TryAdd(new MethodMatchEvaluation( + typeof(SomeResponse), typeof(SomeResponse), - new MethodMatchEvaluation(false, null)); + Array.Empty())); Assert.True(added); } @@ -97,18 +85,11 @@ public void Ignores_duplicate_request_type() { var cache = new MethodMatchEvaluationCache(); - var evaluation = new MethodMatchEvaluation(false, null); - _ = cache.TryAdd(evaluation); + var evaluation = new MethodMatchEvaluation(typeof(SomeRequest), null, Array.Empty()); + _ = cache.TryAdd(evaluation); - { - bool added = cache.TryAdd(evaluation); - Assert.False(added); - } - - { - bool added = cache.TryAdd(typeof(SomeRequest), evaluation); - Assert.False(added); - } + bool added = cache.TryAdd(evaluation); + Assert.False(added); } [Fact] @@ -116,76 +97,34 @@ public void Ignores_duplicate_request_and_response_types() { var cache = new MethodMatchEvaluationCache(); - var evaluation = new MethodMatchEvaluation(false, null); - _ = cache.TryAdd(evaluation); - - { - bool added = cache.TryAdd(evaluation); - Assert.False(added); - } + var evaluation = new MethodMatchEvaluation(typeof(SomeRequest), typeof(SomeResponse), Array.Empty()); + _ = cache.TryAdd(evaluation); - { - bool added = cache.TryAdd( - typeof(SomeRequest), - typeof(SomeResponse), - evaluation); - - Assert.False(added); - } - } - - [Fact] - public void Finds_generic_request_type() - { - var cache = new MethodMatchEvaluationCache(); - - var evaluation = new MethodMatchEvaluation(false, null); - _ = cache.TryAdd(evaluation); - - bool found = cache.TryFindEvaluation(out var cached); - - Assert.True(found); - Assert.Same(evaluation, cached); + bool added = cache.TryAdd(evaluation); + Assert.False(added); } [Fact] - public void Finds_explicit_request_type() + public void Finds_request_type() { var cache = new MethodMatchEvaluationCache(); - var evaluation = new MethodMatchEvaluation(false, null); - _ = cache.TryAdd(typeof(SomeRequest), evaluation); + var evaluation = new MethodMatchEvaluation(typeof(SomeRequest), null, Array.Empty()); + _ = cache.TryAdd(evaluation); - bool found = cache.TryFindEvaluation(out var cached); + bool found = cache.TryFindEvaluation(typeof(SomeRequest), out var cached); Assert.True(found); Assert.Same(evaluation, cached); } [Fact] - public void Finds_generic_request_and_response_types() + public void Finds_request_and_response_types() { var cache = new MethodMatchEvaluationCache(); - var evaluation = new MethodMatchEvaluation(false, null); - _ = cache.TryAdd(evaluation); - - bool found = cache.TryFindEvaluation(out var cached); - - Assert.True(found); - Assert.Same(evaluation, cached); - } - - [Fact] - public void Finds_explicit_request_and_response_types() - { - var cache = new MethodMatchEvaluationCache(); - - var evaluation = new MethodMatchEvaluation(false, null); - _ = cache.TryAdd( - typeof(SomeRequest), - typeof(SomeResponse), - evaluation); + var evaluation = new MethodMatchEvaluation(typeof(SomeRequest), typeof(SomeResponse), Array.Empty()); + _ = cache.TryAdd(evaluation); bool found = cache.TryFindEvaluation( typeof(SomeRequest), diff --git a/tests/NScatterGather.Tests/Inspection/TypeInspectorTests.cs b/tests/NScatterGather.Tests/Inspection/TypeInspectorTests.cs index 8604d4d..a83f12c 100644 --- a/tests/NScatterGather.Tests/Inspection/TypeInspectorTests.cs +++ b/tests/NScatterGather.Tests/Inspection/TypeInspectorTests.cs @@ -1,5 +1,6 @@ using System; using Xunit; +using static NScatterGather.CollisionStrategy; namespace NScatterGather.Inspection { @@ -16,12 +17,14 @@ public void Method_accepting_request_is_found() { var inspector = new TypeInspector(typeof(SomeOtherType)); - Assert.True(inspector.HasMethodAccepting(typeof(int))); - Assert.True(inspector.HasMethodAccepting(typeof(long))); + Assert.True(inspector.HasMethodsAccepting(typeof(int), IgnoreRecipient)); + Assert.True(inspector.HasMethodsAccepting(typeof(long), IgnoreRecipient)); + + bool found = inspector.TryGetMethodsAccepting(typeof(int), IgnoreRecipient, out var methods); - bool found = inspector.TryGetMethodAccepting(typeof(int), out var method); Assert.True(found); - Assert.Equal(typeof(SomeOtherType).GetMethod(nameof(SomeOtherType.Do)), method); + Assert.Single(methods); + Assert.Equal(typeof(SomeOtherType).GetMethod(nameof(SomeOtherType.Do)), methods[0]); } [Fact] @@ -29,19 +32,48 @@ public void Method_returning_response_is_found() { var inspector = new TypeInspector(typeof(SomeOtherType)); - Assert.True(inspector.HasMethodReturning(typeof(int), typeof(int))); - Assert.True(inspector.HasMethodReturning(typeof(long), typeof(string))); + Assert.True(inspector.HasMethodsReturning(typeof(int), typeof(int), IgnoreRecipient)); + Assert.True(inspector.HasMethodsReturning(typeof(long), typeof(string), IgnoreRecipient)); + + bool found = inspector.TryGetMethodsReturning(typeof(int), typeof(int), IgnoreRecipient, out var methods); - bool found = inspector.TryGetMethodReturning(typeof(int), typeof(int), out var method); Assert.True(found); - Assert.Equal(typeof(SomeOtherType).GetMethod(nameof(SomeOtherType.Do)), method); + Assert.Single(methods); + Assert.Equal(typeof(SomeOtherType).GetMethod(nameof(SomeOtherType.Do)), methods[0]); } [Fact] public void Error_if_request_type_is_null() { var inspector = new TypeInspector(typeof(SomeType)); - Assert.Throws(() => inspector.HasMethodAccepting((null as Type)!)); + Assert.Throws(() => inspector.HasMethodsAccepting((null as Type)!, IgnoreRecipient)); + } + + [Fact] + public void Error_if_collision() + { + var inspector = new TypeInspector(typeof(SomeCollidingType)); + Assert.Throws(() => inspector.HasMethodsAccepting(typeof(int), IgnoreRecipient)); + } + + [Fact] + public void Collisions_can_be_allowed() + { + var inspector = new TypeInspector(typeof(SomeCollidingType)); + + var hasMethodsAccepting = inspector.HasMethodsAccepting(typeof(int), UseAllMethodsMatching); + Assert.True(hasMethodsAccepting); + + var hasMethodsReturning = inspector.HasMethodsReturning(typeof(int), typeof(string), UseAllMethodsMatching); + Assert.True(hasMethodsReturning); + } + + [Fact] + public void Error_if_invalid_collision_strategy() + { + var inspector = new TypeInspector(typeof(SomeType)); + Assert.Throws(() => inspector.HasMethodsAccepting(typeof(int), (CollisionStrategy)42)); + Assert.Throws(() => inspector.HasMethodsReturning(typeof(int), typeof(string), (CollisionStrategy)42)); } } } diff --git a/tests/NScatterGather.Tests/Recipients/Collection/RecipientsCollectionTests.cs b/tests/NScatterGather.Tests/Recipients/Collection/RecipientsCollectionTests.cs index e1a6f09..ddaba0c 100644 --- a/tests/NScatterGather.Tests/Recipients/Collection/RecipientsCollectionTests.cs +++ b/tests/NScatterGather.Tests/Recipients/Collection/RecipientsCollectionTests.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Xunit; namespace NScatterGather.Recipients.Collection @@ -21,7 +22,7 @@ public void Can_add_generic_type() [Fact] public void Can_add_generic_type_with_name() { - _collection.Add(name: "My name is"); + _collection.Add("My name is"); } [Fact] @@ -38,6 +39,12 @@ public void Can_add_generic_type_with_factory_method() _collection.Add(() => new SomeType()); } + [Fact] + public void Can_add_generic_type_with_collision_strategy() + { + _collection.Add(CollisionStrategy.UseAllMethodsMatching); + } + [Fact] public void Error_if_factory_method_is_null() { diff --git a/tests/NScatterGather.Tests/Recipients/DelegateRecipientTests.cs b/tests/NScatterGather.Tests/Recipients/DelegateRecipientTests.cs index 7d7871c..5e5051d 100644 --- a/tests/NScatterGather.Tests/Recipients/DelegateRecipientTests.cs +++ b/tests/NScatterGather.Tests/Recipients/DelegateRecipientTests.cs @@ -89,7 +89,8 @@ public async Task Invokes_delegate_with_matching_request_type() var recipient = DelegateRecipient.Create(func, name: null); var input = 42; - var runner = recipient.Accept(input); + var runners = recipient.Accept(input); + var runner = runners[0]; await runner.Start(); var result = runner.Result; @@ -112,7 +113,8 @@ public async Task Invokes_delegate_with_matching_response_type() var recipient = DelegateRecipient.Create(func, name: null); var input = 42; - var runner = recipient.ReplyWith(input); + var runners = recipient.ReplyWith(input); + var runner = runners[0]; await runner.Start(); var result = runner.Result; diff --git a/tests/NScatterGather.Tests/Recipients/InstanceRecipientTests.cs b/tests/NScatterGather.Tests/Recipients/InstanceRecipientTests.cs index 7832ff7..b8eed27 100644 --- a/tests/NScatterGather.Tests/Recipients/InstanceRecipientTests.cs +++ b/tests/NScatterGather.Tests/Recipients/InstanceRecipientTests.cs @@ -1,6 +1,7 @@ using System; using NScatterGather.Inspection; using Xunit; +using static NScatterGather.CollisionStrategy; namespace NScatterGather.Recipients { @@ -10,14 +11,14 @@ public class InstanceRecipientTests public void Recipient_can_be_created_from_instance() { var registry = new TypeInspectorRegistry(); - _ = InstanceRecipient.Create(registry, new SomeType(), name: null); + _ = InstanceRecipient.Create(registry, new SomeType(), name: null, IgnoreRecipient); } [Fact] public void Error_if_registry_is_null() { Assert.Throws(() => - InstanceRecipient.Create((null as TypeInspectorRegistry)!, new SomeType(), name: null)); + InstanceRecipient.Create((null as TypeInspectorRegistry)!, new SomeType(), name: null, IgnoreRecipient)); } [Fact] @@ -26,14 +27,14 @@ public void Error_if_instance_is_null() var registry = new TypeInspectorRegistry(); Assert.Throws(() => - InstanceRecipient.Create(registry, (null as object)!, name: null)); + InstanceRecipient.Create(registry, (null as object)!, name: null, IgnoreRecipient)); } [Fact] public void Recipient_has_a_name() { var registry = new TypeInspectorRegistry(); - var recipient = InstanceRecipient.Create(registry, new SomeType(), name: "My name is"); + var recipient = InstanceRecipient.Create(registry, new SomeType(), name: "My name is", IgnoreRecipient); Assert.NotNull(recipient.Name); Assert.NotEmpty(recipient.Name); } @@ -42,7 +43,7 @@ public void Recipient_has_a_name() public void Can_be_cloned() { var registry = new TypeInspectorRegistry(); - var recipient = InstanceRecipient.Create(registry, new SomeType(), name: "My name is"); + var recipient = InstanceRecipient.Create(registry, new SomeType(), name: "My name is", IgnoreRecipient); var clone = recipient.Clone(); Assert.NotNull(clone); @@ -56,7 +57,7 @@ public void Can_be_cloned() public void Has_expected_lifetime() { var registry = new TypeInspectorRegistry(); - var recipient = InstanceRecipient.Create(registry, new SomeType(), name: null); + var recipient = InstanceRecipient.Create(registry, new SomeType(), name: null, IgnoreRecipient); Assert.Equal(Lifetime.Singleton, recipient.Lifetime); } } diff --git a/tests/NScatterGather.Tests/Recipients/Run/RecipientRunTests.cs b/tests/NScatterGather.Tests/Recipients/Run/RecipientRunTests.cs index b69899c..d17b046 100644 --- a/tests/NScatterGather.Tests/Recipients/Run/RecipientRunTests.cs +++ b/tests/NScatterGather.Tests/Recipients/Run/RecipientRunTests.cs @@ -3,6 +3,7 @@ using NScatterGather.Inspection; using NScatterGather.Recipients; using Xunit; +using static NScatterGather.CollisionStrategy; namespace NScatterGather.Run { @@ -15,22 +16,24 @@ public class RecipientRunTests public RecipientRunTests() { var registry = new TypeInspectorRegistry(); - _recipient = InstanceRecipient.Create(registry, new SomeType(), name: null); - _faultingRecipient = InstanceRecipient.Create(registry, new SomeFaultingType(), name: null); - _anotherFaultingRecipient = InstanceRecipient.Create(registry, new SomeComplexFaultingType(), name: null); + _recipient = InstanceRecipient.Create(registry, new SomeType(), name: null, IgnoreRecipient); + _faultingRecipient = InstanceRecipient.Create(registry, new SomeFaultingType(), name: null, IgnoreRecipient); + _anotherFaultingRecipient = InstanceRecipient.Create(registry, new SomeComplexFaultingType(), name: null, IgnoreRecipient); } [Fact] public void Can_be_created() { - var runner = _recipient.Accept(42); + var runners = _recipient.Accept(42); + var runner = runners[0]; Assert.Same(_recipient, runner.Recipient); } [Fact] public void Initially_has_default_parameters() { - var runner = _recipient.Accept(42); + var runners = _recipient.Accept(42); + var runner = runners[0]; Assert.False(runner.CompletedSuccessfully); Assert.Equal(default, runner.Result); Assert.False(runner.Faulted); @@ -42,7 +45,8 @@ public void Initially_has_default_parameters() [Fact] public async Task Error_if_started_multiple_times() { - var runner = _recipient.Accept(42); + var runners = _recipient.Accept(42); + var runner = runners[0]; await runner.Start(); await Assert.ThrowsAsync(() => runner.Start()); } @@ -50,7 +54,8 @@ public async Task Error_if_started_multiple_times() [Fact] public async Task Runner_completes() { - var runner = _recipient.Accept(42); + var runners = _recipient.Accept(42); + var runner = runners[0]; await runner.Start(); Assert.True(runner.CompletedSuccessfully); @@ -67,7 +72,8 @@ public async Task Runner_completes() [Fact] public async Task Runner_fails() { - var runner = _faultingRecipient.Accept(42); + var runners = _faultingRecipient.Accept(42); + var runner = runners[0]; await runner.Start(); Assert.False(runner.CompletedSuccessfully); @@ -84,7 +90,8 @@ public async Task Runner_fails() [Fact] public async Task Exception_is_extracted() { - var runner = _faultingRecipient.Accept(42); + var runners = _faultingRecipient.Accept(42); + var runner = runners[0]; await runner.Start(); Assert.False(runner.CompletedSuccessfully); @@ -103,7 +110,8 @@ public async Task Exception_is_extracted() [Fact] public async Task Aggregated_exceptions_are_decomposed() { - var runner = _anotherFaultingRecipient.Accept(42); + var runners = _anotherFaultingRecipient.Accept(42); + var runner = runners[0]; await runner.Start(); Assert.False(runner.CompletedSuccessfully); @@ -132,7 +140,8 @@ public async Task Aggregated_exceptions_are_decomposed() [Fact] public async Task Reflection_exception_are_decomposed() { - var runner = _anotherFaultingRecipient.Accept(42L); + var runners = _anotherFaultingRecipient.Accept(42L); + var runner = runners[0]; await runner.Start(); Assert.False(runner.CompletedSuccessfully); diff --git a/tests/NScatterGather.Tests/Recipients/TypeRecipientTests.cs b/tests/NScatterGather.Tests/Recipients/TypeRecipientTests.cs index af14e8a..ca3b488 100644 --- a/tests/NScatterGather.Tests/Recipients/TypeRecipientTests.cs +++ b/tests/NScatterGather.Tests/Recipients/TypeRecipientTests.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using NScatterGather.Inspection; using Xunit; +using static NScatterGather.CollisionStrategy; namespace NScatterGather.Recipients { @@ -14,7 +15,8 @@ public void Recipient_can_be_created_from_type() registry: new TypeInspectorRegistry(), () => new SomeType(), name: null, - lifetime: Lifetime.Transient); + lifetime: Lifetime.Transient, + IgnoreRecipient); } [Fact] @@ -26,7 +28,8 @@ public void Error_if_registry_is_null() registry: (null as TypeInspectorRegistry)!, () => new SomeType(), name: null, - lifetime: Lifetime.Transient); + lifetime: Lifetime.Transient, + IgnoreRecipient); }); } @@ -39,7 +42,8 @@ public void Error_if_no_factory_method() registry: new TypeInspectorRegistry(), (null as Func)!, name: null, - lifetime: Lifetime.Transient); + lifetime: Lifetime.Transient, + IgnoreRecipient); }); } @@ -52,7 +56,8 @@ public void Error_if_invalid_lifetime() registry: new TypeInspectorRegistry(), () => new SomeType(), name: null, - lifetime: (Lifetime)42); + lifetime: (Lifetime)42, + IgnoreRecipient); }); } @@ -60,7 +65,10 @@ public void Error_if_invalid_lifetime() public void Recipient_has_a_name() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeType(), name: "My name is", Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeType(), name: "My name is", Lifetime.Transient, IgnoreRecipient); + Assert.NotNull(recipient.Name); Assert.NotEmpty(recipient.Name); } @@ -69,10 +77,15 @@ public void Recipient_has_a_name() public async Task Recipient_accepts_request() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeType(), name: null, Lifetime.Transient, IgnoreRecipient); + var input = 42; - var runner = recipient.Accept(input); + var runners = recipient.Accept(input); + var runner = runners[0]; await runner.Start(); + Assert.Equal(input.ToString(), runner.Result); } @@ -80,10 +93,15 @@ public async Task Recipient_accepts_request() public async Task Recipient_accepts_request_and_replies_with_task() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeAsyncType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeAsyncType(), name: null, Lifetime.Transient, IgnoreRecipient); + var input = 42; - var runner = recipient.Accept(input); + var runners = recipient.Accept(input); + var runner = runners[0]; await runner.Start(); + Assert.Equal(input.ToString(), runner.Result); } @@ -91,8 +109,12 @@ public async Task Recipient_accepts_request_and_replies_with_task() public void Recipient_accepts_input_parameter_type() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeType(), name: null, Lifetime.Transient, IgnoreRecipient); + bool canAccept = recipient.CanAccept(typeof(int)); + Assert.True(canAccept); } @@ -100,7 +122,10 @@ public void Recipient_accepts_input_parameter_type() public void Recipient_type_is_visible() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeType(), name: null, Lifetime.Transient, IgnoreRecipient); + Assert.Same(typeof(SomeType), recipient.Type); } @@ -108,8 +133,12 @@ public void Recipient_type_is_visible() public void Recipient_replies_with_response_type() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeType(), name: null, Lifetime.Transient, IgnoreRecipient); + bool canAccept = recipient.CanReplyWith(typeof(int), typeof(string)); + Assert.True(canAccept); } @@ -117,8 +146,12 @@ public void Recipient_replies_with_response_type() public void Recipient_replies_with_async_response_type() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeAsyncType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeAsyncType(), name: null, Lifetime.Transient, IgnoreRecipient); + bool canAccept = recipient.CanReplyWith(typeof(int), typeof(string)); + Assert.True(canAccept); } @@ -126,7 +159,10 @@ public void Recipient_replies_with_async_response_type() public void Error_if_request_or_response_types_are_null() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeType(), name: null, Lifetime.Transient, IgnoreRecipient); + Assert.Throws(() => recipient.CanReplyWith(typeof(int), null!)); Assert.Throws(() => recipient.CanReplyWith(null!, typeof(string))); } @@ -135,7 +171,9 @@ public void Error_if_request_or_response_types_are_null() public void Error_if_request_type_not_supported() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeType(), name: null, Lifetime.Transient, IgnoreRecipient); Assert.Throws(() => recipient.Accept(Guid.NewGuid())); } @@ -144,7 +182,9 @@ public void Error_if_request_type_not_supported() public void Error_if_response_type_not_supported() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeType(), name: null, Lifetime.Transient, IgnoreRecipient); Assert.Throws(() => recipient.ReplyWith(42)); } @@ -153,11 +193,15 @@ public void Error_if_response_type_not_supported() public async Task Recipient_replies_with_response() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeType(), name: null, Lifetime.Transient, IgnoreRecipient); var input = 42; - var runner = recipient.ReplyWith(input); + var runners = recipient.ReplyWith(input); + var runner = runners[0]; await runner.Start(); + Assert.Equal(input.ToString(), runner.Result); } @@ -165,11 +209,15 @@ public async Task Recipient_replies_with_response() public async Task Recipient_can_reply_with_task() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeAsyncType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeAsyncType(), name: null, Lifetime.Transient, IgnoreRecipient); var input = 42; - var runner = recipient.ReplyWith(input); + var runners = recipient.ReplyWith(input); + var runner = runners[0]; await runner.Start(); + Assert.Equal(input.ToString(), runner.Result); } @@ -177,9 +225,12 @@ public async Task Recipient_can_reply_with_task() public void Recipient_must_return_something() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeComputingType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeComputingType(), name: null, Lifetime.Transient, IgnoreRecipient); bool accepts = recipient.CanAccept(typeof(int)); + Assert.False(accepts); } @@ -187,7 +238,9 @@ public void Recipient_must_return_something() public void Error_if_void_returning() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeComputingType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeComputingType(), name: null, Lifetime.Transient, IgnoreRecipient); Assert.Throws(() => recipient.Accept(42)); } @@ -196,7 +249,9 @@ public void Error_if_void_returning() public void Recipient_must_return_something_async() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeAsyncComputingType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeAsyncComputingType(), name: null, Lifetime.Transient, IgnoreRecipient); bool accepts = recipient.CanReplyWith(typeof(int), typeof(Task)); Assert.False(accepts); @@ -206,7 +261,9 @@ public void Recipient_must_return_something_async() public void Error_if_returning_task_without_result() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeAsyncComputingType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeAsyncComputingType(), name: null, Lifetime.Transient, IgnoreRecipient); Assert.Throws(() => recipient.ReplyWith(42)); } @@ -215,7 +272,10 @@ public void Error_if_returning_task_without_result() public void Can_be_cloned() { var registry = new TypeInspectorRegistry(); - var recipient = TypeRecipient.Create(registry, () => new SomeType(), name: null, Lifetime.Transient); + + var recipient = TypeRecipient.Create( + registry, () => new SomeType(), name: null, Lifetime.Transient, IgnoreRecipient); + var clone = recipient.Clone(); Assert.NotNull(clone); @@ -229,9 +289,15 @@ public void Can_be_cloned() public void Has_expected_lifetime() { var registry = new TypeInspectorRegistry(); - var transientRecipient = TypeRecipient.Create(registry, () => new SomeType(), name: null, Lifetime.Transient); - var scopedRecipient = TypeRecipient.Create(registry, () => new SomeType(), name: null, Lifetime.Scoped); - var singletonRecipient = TypeRecipient.Create(registry, () => new SomeType(), name: null, Lifetime.Singleton); + + var transientRecipient = TypeRecipient.Create( + registry, () => new SomeType(), name: null, Lifetime.Transient, IgnoreRecipient); + + var scopedRecipient = TypeRecipient.Create( + registry, () => new SomeType(), name: null, Lifetime.Scoped, IgnoreRecipient); + + var singletonRecipient = TypeRecipient.Create( + registry, () => new SomeType(), name: null, Lifetime.Singleton, IgnoreRecipient); Assert.Equal(Lifetime.Transient, transientRecipient.Lifetime); Assert.Equal(Lifetime.Scoped, scopedRecipient.Lifetime); diff --git a/tests/NScatterGather.Tests/Responses/AggregatedResponseExtensionsTests.cs b/tests/NScatterGather.Tests/Responses/AggregatedResponseExtensionsTests.cs index 0bd6a18..28e6800 100644 --- a/tests/NScatterGather.Tests/Responses/AggregatedResponseExtensionsTests.cs +++ b/tests/NScatterGather.Tests/Responses/AggregatedResponseExtensionsTests.cs @@ -4,6 +4,7 @@ using NScatterGather.Recipients; using NScatterGather.Recipients.Run; using Xunit; +using static NScatterGather.CollisionStrategy; namespace NScatterGather.Responses { @@ -15,19 +16,22 @@ public AggregatedResponseExtensionsTests() { var registry = new TypeInspectorRegistry(); - var someRecipient = InstanceRecipient.Create(registry, new SomeType(), name: null); - var someRun = someRecipient.Accept(42); - someRun.Start().Wait(); + var someRecipient = InstanceRecipient.Create(registry, new SomeType(), name: null, IgnoreRecipient); + var someRunners = someRecipient.Accept(42); + var aRunner = someRunners[0]; + aRunner.Start().Wait(); - var someFaultingRecipient = InstanceRecipient.Create(registry, new SomeFaultingType(), name: null); - var someFaultingRun = someFaultingRecipient.Accept(42); - someFaultingRun.Start().Wait(); + var someFaultingRecipient = InstanceRecipient.Create(registry, new SomeFaultingType(), name: null, IgnoreRecipient); + var someFaultingRunners = someFaultingRecipient.Accept(42); + var aFaultingRunner = someFaultingRunners[0]; + aFaultingRunner.Start().Wait(); - var someNeverEndingRecipient = InstanceRecipient.Create(registry, new SomeNeverEndingType(), name: null); - var someNeverEndingRun = someNeverEndingRecipient.Accept(42); - someNeverEndingRun.Start(); + var someNeverEndingRecipient = InstanceRecipient.Create(registry, new SomeNeverEndingType(), name: null, IgnoreRecipient); + var someNeverEndingRunners = someNeverEndingRecipient.Accept(42); + var aNeverEndingRunner = someNeverEndingRunners[0]; + aNeverEndingRunner.Start(); - _runners = new[] { someRun, someFaultingRun, someNeverEndingRun }; + _runners = new[] { aRunner, aFaultingRunner, aNeverEndingRunner }; } [Fact] diff --git a/tests/NScatterGather.Tests/Responses/AggregatedResponseTests.cs b/tests/NScatterGather.Tests/Responses/AggregatedResponseTests.cs index d75f643..79e006b 100644 --- a/tests/NScatterGather.Tests/Responses/AggregatedResponseTests.cs +++ b/tests/NScatterGather.Tests/Responses/AggregatedResponseTests.cs @@ -2,6 +2,7 @@ using NScatterGather.Recipients; using NScatterGather.Recipients.Run; using Xunit; +using static NScatterGather.CollisionStrategy; namespace NScatterGather.Responses { @@ -13,19 +14,22 @@ public AggregatedResponseTests() { var registry = new TypeInspectorRegistry(); - var someRecipient = InstanceRecipient.Create(registry, new SomeType(), name: null); - var someRun = someRecipient.Accept(42); - someRun.Start().Wait(); + var someRecipient = InstanceRecipient.Create(registry, new SomeType(), name: null, IgnoreRecipient); + var someRunners = someRecipient.Accept(42); + var aRunner = someRunners[0]; + aRunner.Start().Wait(); - var someFaultingRecipient = InstanceRecipient.Create(registry, new SomeFaultingType(), name: null); - var someFaultingRun = someFaultingRecipient.Accept(42); - someFaultingRun.Start().Wait(); + var someFaultingRecipient = InstanceRecipient.Create(registry, new SomeFaultingType(), name: null, IgnoreRecipient); + var someFaultingRunners = someFaultingRecipient.Accept(42); + var aFaultingRunner = someFaultingRunners[0]; + aFaultingRunner.Start().Wait(); - var someNeverEndingRecipient = InstanceRecipient.Create(registry, new SomeNeverEndingType(), name: null); - var someNeverEndingRun = someNeverEndingRecipient.Accept(42); - someNeverEndingRun.Start(); + var someNeverEndingRecipient = InstanceRecipient.Create(registry, new SomeNeverEndingType(), name: null, IgnoreRecipient); + var someNeverEndingRunners = someNeverEndingRecipient.Accept(42); + var aNeverEndingRunner = someNeverEndingRunners[0]; + aNeverEndingRunner.Start(); - _runners = new[] { someRun, someFaultingRun, someNeverEndingRun }; + _runners = new[] { aRunner, aFaultingRunner, aNeverEndingRunner }; } [Fact] diff --git a/tests/NScatterGather.Tests/_TestTypes/SomeNonNullableType.cs b/tests/NScatterGather.Tests/_TestTypes/SomeNonNullableType.cs new file mode 100644 index 0000000..dad4266 --- /dev/null +++ b/tests/NScatterGather.Tests/_TestTypes/SomeNonNullableType.cs @@ -0,0 +1,9 @@ +using System; + +namespace NScatterGather +{ + public class SomeNonNullableType + { + public DateTime Seconds(int seconds) => default(DateTime).AddSeconds(seconds); + } +} diff --git a/tests/NScatterGather.Tests/_TestTypes/SomeNullableType.cs b/tests/NScatterGather.Tests/_TestTypes/SomeNullableType.cs new file mode 100644 index 0000000..29a9d13 --- /dev/null +++ b/tests/NScatterGather.Tests/_TestTypes/SomeNullableType.cs @@ -0,0 +1,11 @@ +using System; + +namespace NScatterGather +{ + public class SomeNullableType + { + public DateTime? Seconds(int? seconds) => seconds is null + ? null + : default(DateTime).AddSeconds(seconds.Value); + } +} diff --git a/tests/NScatterGather.Tests/_Utils/TestExtensions.cs b/tests/NScatterGather.Tests/_Utils/TestExtensions.cs index 3ad0154..77b88d2 100644 --- a/tests/NScatterGather.Tests/_Utils/TestExtensions.cs +++ b/tests/NScatterGather.Tests/_Utils/TestExtensions.cs @@ -1,6 +1,7 @@ using NScatterGather.Inspection; using NScatterGather.Recipients; using NScatterGather.Recipients.Collection.Scope; +using static NScatterGather.CollisionStrategy; namespace NScatterGather { @@ -16,7 +17,8 @@ public static void AddTypeRecipient(this RecipientsScope scope) new TypeInspectorRegistry(), () => new TRecipients(), name: null, - Lifetime.Transient) + Lifetime.Transient, + IgnoreRecipient) }); } } From 53ec1bdbf035988a18cfa391f4b98198f16b9004 Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Sat, 2 Jan 2021 10:08:24 +0100 Subject: [PATCH 09/17] Renames RecipientRun to RecipientRunner. --- src/NScatterGather/Aggregator.cs | 4 ++-- src/NScatterGather/Recipients/Recipient.cs | 8 ++++---- .../Run/{RecipientRun.cs => RecipientRunner.cs} | 4 ++-- src/NScatterGather/Responses/AggregatedResponseFactory.cs | 2 +- .../Run/{RecipientRunTests.cs => RecipientRunnerTests.cs} | 4 ++-- .../Responses/AggregatedResponseExtensionsTests.cs | 2 +- .../Responses/AggregatedResponseTests.cs | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) rename src/NScatterGather/Recipients/Run/{RecipientRun.cs => RecipientRunner.cs} (95%) rename tests/NScatterGather.Tests/Recipients/Run/{RecipientRunTests.cs => RecipientRunnerTests.cs} (98%) diff --git a/src/NScatterGather/Aggregator.cs b/src/NScatterGather/Aggregator.cs index 174418e..dfdbd38 100644 --- a/src/NScatterGather/Aggregator.cs +++ b/src/NScatterGather/Aggregator.cs @@ -41,7 +41,7 @@ public Aggregator(RecipientsCollection collection) return AggregatedResponseFactory.CreateFrom(invocations); } - private async Task>> Invoke( + private async Task>> Invoke( IReadOnlyList recipients, object request, CancellationToken cancellationToken) @@ -85,7 +85,7 @@ public async Task> Send( return AggregatedResponseFactory.CreateFrom(runners); } - private async Task>> Invoke( + private async Task>> Invoke( IReadOnlyList recipients, object request, CancellationToken cancellationToken) diff --git a/src/NScatterGather/Recipients/Recipient.cs b/src/NScatterGather/Recipients/Recipient.cs index e12aca7..393f242 100644 --- a/src/NScatterGather/Recipients/Recipient.cs +++ b/src/NScatterGather/Recipients/Recipient.cs @@ -39,22 +39,22 @@ public bool CanAccept(Type requestType) => public bool CanReplyWith(Type requestType, Type responseType) => _descriptor.CanReplyWith(requestType, responseType, CollisionStrategy); - public IReadOnlyList> Accept(object request) + public IReadOnlyList> Accept(object request) { var preparedInvocations = _invoker.PrepareInvocations(request); var runners = preparedInvocations.Select(preparedInvocation => - new RecipientRun(this, preparedInvocation)); + new RecipientRunner(this, preparedInvocation)); return runners.ToArray(); } - public IReadOnlyList> ReplyWith(object request) + public IReadOnlyList> ReplyWith(object request) { var preparedInvocations = _invoker.PrepareInvocations(request); var runners = preparedInvocations.Select(preparedInvocation => - new RecipientRun(this, preparedInvocation)); + new RecipientRunner(this, preparedInvocation)); return runners.ToArray(); } diff --git a/src/NScatterGather/Recipients/Run/RecipientRun.cs b/src/NScatterGather/Recipients/Run/RecipientRunner.cs similarity index 95% rename from src/NScatterGather/Recipients/Run/RecipientRun.cs rename to src/NScatterGather/Recipients/Run/RecipientRunner.cs index 4d54e61..0c938a6 100644 --- a/src/NScatterGather/Recipients/Run/RecipientRun.cs +++ b/src/NScatterGather/Recipients/Run/RecipientRunner.cs @@ -7,7 +7,7 @@ namespace NScatterGather.Recipients.Run { - internal class RecipientRun + internal class RecipientRunner { public Recipient Recipient { get; } @@ -28,7 +28,7 @@ internal class RecipientRun private readonly PreparedInvocation _preparedInvocation; - public RecipientRun(Recipient recipient, PreparedInvocation preparedInvocation) + public RecipientRunner(Recipient recipient, PreparedInvocation preparedInvocation) { Recipient = recipient; _preparedInvocation = preparedInvocation; diff --git a/src/NScatterGather/Responses/AggregatedResponseFactory.cs b/src/NScatterGather/Responses/AggregatedResponseFactory.cs index 076bdaf..6a7a09a 100644 --- a/src/NScatterGather/Responses/AggregatedResponseFactory.cs +++ b/src/NScatterGather/Responses/AggregatedResponseFactory.cs @@ -8,7 +8,7 @@ namespace NScatterGather.Responses internal class AggregatedResponseFactory { public static AggregatedResponse CreateFrom( - IEnumerable> invocations) + IEnumerable> invocations) { var completed = new List>(); var faulted = new List(); diff --git a/tests/NScatterGather.Tests/Recipients/Run/RecipientRunTests.cs b/tests/NScatterGather.Tests/Recipients/Run/RecipientRunnerTests.cs similarity index 98% rename from tests/NScatterGather.Tests/Recipients/Run/RecipientRunTests.cs rename to tests/NScatterGather.Tests/Recipients/Run/RecipientRunnerTests.cs index d17b046..50afa01 100644 --- a/tests/NScatterGather.Tests/Recipients/Run/RecipientRunTests.cs +++ b/tests/NScatterGather.Tests/Recipients/Run/RecipientRunnerTests.cs @@ -7,13 +7,13 @@ namespace NScatterGather.Run { - public class RecipientRunTests + public class RecipientRunnerTests { private readonly Recipient _recipient; private readonly Recipient _faultingRecipient; private readonly Recipient _anotherFaultingRecipient; - public RecipientRunTests() + public RecipientRunnerTests() { var registry = new TypeInspectorRegistry(); _recipient = InstanceRecipient.Create(registry, new SomeType(), name: null, IgnoreRecipient); diff --git a/tests/NScatterGather.Tests/Responses/AggregatedResponseExtensionsTests.cs b/tests/NScatterGather.Tests/Responses/AggregatedResponseExtensionsTests.cs index 28e6800..f90d872 100644 --- a/tests/NScatterGather.Tests/Responses/AggregatedResponseExtensionsTests.cs +++ b/tests/NScatterGather.Tests/Responses/AggregatedResponseExtensionsTests.cs @@ -10,7 +10,7 @@ namespace NScatterGather.Responses { public class AggregatedResponseExtensionsTests { - private readonly RecipientRun[] _runners; + private readonly RecipientRunner[] _runners; public AggregatedResponseExtensionsTests() { diff --git a/tests/NScatterGather.Tests/Responses/AggregatedResponseTests.cs b/tests/NScatterGather.Tests/Responses/AggregatedResponseTests.cs index 79e006b..97ade03 100644 --- a/tests/NScatterGather.Tests/Responses/AggregatedResponseTests.cs +++ b/tests/NScatterGather.Tests/Responses/AggregatedResponseTests.cs @@ -8,7 +8,7 @@ namespace NScatterGather.Responses { public class AggregatedResponseTests { - private readonly RecipientRun[] _runners; + private readonly RecipientRunner[] _runners; public AggregatedResponseTests() { From 5c2fe1b6caf901b808c8c8a70a0a3346cb9fb6b6 Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Mon, 4 Jan 2021 12:35:43 +0100 Subject: [PATCH 10/17] Introduces recipient Id. --- .../Invocations/CompletedInvocation.cs | 6 +++ .../Invocations/FaultedInvocation.cs | 6 +++ .../Invocations/IncompleteInvocation.cs | 6 +++ .../Collection/RecipientsCollection.cs | 49 ++++++++++--------- src/NScatterGather/Recipients/Recipient.cs | 2 + .../Responses/AggregatedResponseFactory.cs | 17 ++++--- 6 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/NScatterGather/Invocations/CompletedInvocation.cs b/src/NScatterGather/Invocations/CompletedInvocation.cs index 19f1dd7..412e61a 100644 --- a/src/NScatterGather/Invocations/CompletedInvocation.cs +++ b/src/NScatterGather/Invocations/CompletedInvocation.cs @@ -5,6 +5,8 @@ namespace NScatterGather { public class CompletedInvocation { + public Guid RecipientId { get; } + public string? RecipientName { get; } public Type? RecipientType { get; } @@ -15,11 +17,13 @@ public class CompletedInvocation public TimeSpan Duration { get; } internal CompletedInvocation( + Guid recipientId, string? recipientName, Type? recipientType, [AllowNull] TResponse result, TimeSpan duration) { + RecipientId = recipientId; RecipientName = recipientName; RecipientType = recipientType; Result = result; @@ -27,11 +31,13 @@ internal CompletedInvocation( } public void Deconstruct( + out Guid recipientId, out string? recipientName, out Type? recipientType, [MaybeNull] out TResponse result, out TimeSpan duration) { + recipientId = RecipientId; recipientName = RecipientName; recipientType = RecipientType; result = Result; diff --git a/src/NScatterGather/Invocations/FaultedInvocation.cs b/src/NScatterGather/Invocations/FaultedInvocation.cs index 7045ba3..9a38b86 100644 --- a/src/NScatterGather/Invocations/FaultedInvocation.cs +++ b/src/NScatterGather/Invocations/FaultedInvocation.cs @@ -4,6 +4,8 @@ namespace NScatterGather { public class FaultedInvocation { + public Guid RecipientId { get; } + public string? RecipientName { get; } public Type? RecipientType { get; } @@ -13,11 +15,13 @@ public class FaultedInvocation public TimeSpan Duration { get; } internal FaultedInvocation( + Guid recipientId, string? recipientName, Type? recipientType, Exception? exception, TimeSpan duration) { + RecipientId = recipientId; RecipientName = recipientName; RecipientType = recipientType; Exception = exception; @@ -25,11 +29,13 @@ internal FaultedInvocation( } public void Deconstruct( + out Guid recipientId, out string? recipientName, out Type? recipientType, out Exception? exception, out TimeSpan duration) { + recipientId = RecipientId; recipientName = RecipientName; recipientType = RecipientType; exception = Exception; diff --git a/src/NScatterGather/Invocations/IncompleteInvocation.cs b/src/NScatterGather/Invocations/IncompleteInvocation.cs index b86417b..b0b347a 100644 --- a/src/NScatterGather/Invocations/IncompleteInvocation.cs +++ b/src/NScatterGather/Invocations/IncompleteInvocation.cs @@ -4,22 +4,28 @@ namespace NScatterGather { public class IncompleteInvocation { + public Guid RecipientId { get; } + public string? RecipientName { get; } public Type? RecipientType { get; } internal IncompleteInvocation( + Guid recipientId, string? recipientName, Type? recipientType) { + RecipientId = recipientId; RecipientName = recipientName; RecipientType = recipientType; } public void Deconstruct( + out Guid recipientId, out string? recipientName, out Type? recipientType) { + recipientId = RecipientId; recipientName = RecipientName; recipientType = RecipientType; } diff --git a/src/NScatterGather/Recipients/Collection/RecipientsCollection.cs b/src/NScatterGather/Recipients/Collection/RecipientsCollection.cs index 1eabe86..ea875a6 100644 --- a/src/NScatterGather/Recipients/Collection/RecipientsCollection.cs +++ b/src/NScatterGather/Recipients/Collection/RecipientsCollection.cs @@ -24,36 +24,36 @@ public RecipientsCollection(CollisionStrategy defaultCollisionStrategy = IgnoreR _defaultCollisionStrategy = defaultCollisionStrategy; } - public void Add(string name) => - AddWithDefaultFactoryMethod(name: name); + public Guid Add(string name) => + AddTypeRecipientWithDefaultFactoryMethod(name: name); - public void Add(Lifetime lifetime) => - AddWithDefaultFactoryMethod(lifetime: lifetime); + public Guid Add(Lifetime lifetime) => + AddTypeRecipientWithDefaultFactoryMethod(lifetime: lifetime); - public void Add(Func factoryMethod) => - Internal_Add(factoryMethod: factoryMethod); + public Guid Add(Func factoryMethod) => + AddTypeRecipient(factoryMethod: factoryMethod); - public void Add(CollisionStrategy collisionStrategy) => - AddWithDefaultFactoryMethod(collisionStrategy: collisionStrategy); + public Guid Add(CollisionStrategy collisionStrategy) => + AddTypeRecipientWithDefaultFactoryMethod(collisionStrategy: collisionStrategy); - public void Add( + public Guid Add( string? name = null, Lifetime lifetime = Transient, CollisionStrategy? collisionStrategy = null) { - AddWithDefaultFactoryMethod(name, lifetime, collisionStrategy); + return AddTypeRecipientWithDefaultFactoryMethod(name, lifetime, collisionStrategy); } - public void Add( + public Guid Add( Func factoryMethod, string? name = null, Lifetime lifetime = Transient, CollisionStrategy? collisionStrategy = null) { - Internal_Add(factoryMethod, name, lifetime, collisionStrategy); + return AddTypeRecipient(factoryMethod, name, lifetime, collisionStrategy); } - internal void AddWithDefaultFactoryMethod( + internal Guid AddTypeRecipientWithDefaultFactoryMethod( string? name = null, Lifetime lifetime = Transient, CollisionStrategy? collisionStrategy = null) @@ -63,7 +63,7 @@ internal void AddWithDefaultFactoryMethod( static TRecipient factoryMethod() => ((TRecipient)Activator.CreateInstance(typeof(TRecipient)))!; - Internal_Add(factoryMethod, name, lifetime, collisionStrategy); + return AddTypeRecipient(factoryMethod, name, lifetime, collisionStrategy); // Local functions. @@ -74,7 +74,7 @@ static bool HasADefaultConstructor() } } - internal void Internal_Add( + internal Guid AddTypeRecipient( Func factoryMethod, string? name = null, Lifetime lifetime = Transient, @@ -86,10 +86,12 @@ internal void Internal_Add( var typeRecipient = TypeRecipient.Create( _registry, factoryMethod, name, lifetime, collisionStrategy ?? _defaultCollisionStrategy); - Add(typeRecipient); + _recipients.Add(typeRecipient); + + return typeRecipient.Id; } - public void Add( + public Guid Add( object instance, string? name = null, CollisionStrategy? collisionStrategy = null) @@ -100,10 +102,12 @@ public void Add( var instanceRecipient = InstanceRecipient.Create( _registry, instance, name, collisionStrategy ?? _defaultCollisionStrategy); - Add(instanceRecipient); + _recipients.Add(instanceRecipient); + + return instanceRecipient.Id; } - public void Add( + public Guid Add( Func @delegate, string? name = null) { @@ -112,12 +116,9 @@ public void Add( var delegateRecipient = DelegateRecipient.Create(@delegate, name); - Add(delegateRecipient); - } + _recipients.Add(delegateRecipient); - private void Add(Recipient recipient) - { - _recipients.Add(recipient); + return delegateRecipient.Id; } internal IRecipientsScope CreateScope() diff --git a/src/NScatterGather/Recipients/Recipient.cs b/src/NScatterGather/Recipients/Recipient.cs index 393f242..2bb113a 100644 --- a/src/NScatterGather/Recipients/Recipient.cs +++ b/src/NScatterGather/Recipients/Recipient.cs @@ -9,6 +9,8 @@ namespace NScatterGather.Recipients { internal abstract class Recipient { + public Guid Id { get; set; } = Guid.NewGuid(); + public string? Name { get; } public Lifetime Lifetime { get; } diff --git a/src/NScatterGather/Responses/AggregatedResponseFactory.cs b/src/NScatterGather/Responses/AggregatedResponseFactory.cs index 6a7a09a..6d887fb 100644 --- a/src/NScatterGather/Responses/AggregatedResponseFactory.cs +++ b/src/NScatterGather/Responses/AggregatedResponseFactory.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using NScatterGather.Recipients; using NScatterGather.Recipients.Run; @@ -16,12 +15,14 @@ public static AggregatedResponse CreateFrom( foreach (var invocation in invocations) { - var recipientType = invocation.Recipient is TypeRecipient ir ? ir.Type : null; + var recipient = invocation.Recipient; + var recipientType = recipient is TypeRecipient ir ? ir.Type : null; if (invocation.CompletedSuccessfully) { var completedInvocation = new CompletedInvocation( - invocation.Recipient.Name, + recipient.Id, + recipient.Name, recipientType, invocation.Result, invocation.Duration); @@ -31,7 +32,8 @@ public static AggregatedResponse CreateFrom( else if (invocation.Faulted) { var faultedInvocation = new FaultedInvocation( - invocation.Recipient.Name, + recipient.Id, + recipient.Name, recipientType, invocation.Exception, invocation.Duration); @@ -39,7 +41,10 @@ public static AggregatedResponse CreateFrom( faulted.Add(faultedInvocation); } else - incomplete.Add(new IncompleteInvocation(invocation.Recipient.Name, recipientType)); + incomplete.Add(new IncompleteInvocation( + recipient.Id, + recipient.Name, + recipientType)); } return new AggregatedResponse(completed, faulted, incomplete); From 6caa00c76428ed081f9588f2065369d8ed624948 Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Mon, 4 Jan 2021 12:35:53 +0100 Subject: [PATCH 11/17] Updates tests for recipient Id. --- .../Invocations/CompletedInvocationTests.cs | 10 +++++-- .../Invocations/FaultedInvocation.cs | 10 +++++-- .../Invocations/IncompleteInvocationTests.cs | 9 ++++-- .../Collection/RecipientsCollectionTests.cs | 30 ++++++++++++------- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/tests/NScatterGather.Tests/Invocations/CompletedInvocationTests.cs b/tests/NScatterGather.Tests/Invocations/CompletedInvocationTests.cs index e7411de..318f158 100644 --- a/tests/NScatterGather.Tests/Invocations/CompletedInvocationTests.cs +++ b/tests/NScatterGather.Tests/Invocations/CompletedInvocationTests.cs @@ -8,16 +8,22 @@ public class CompletedInvocationTests [Fact] public void Can_be_deconstructed() { + var expectedId = Guid.NewGuid(); var expectedName = "foo"; var expectedType = typeof(int); var expectedResult = 42; var expectedDuration = TimeSpan.FromSeconds(1); var invocation = new CompletedInvocation( - expectedName, expectedType, expectedResult, expectedDuration); + expectedId, + expectedName, + expectedType, + expectedResult, + expectedDuration); - var (name, type, result, duration) = invocation; + var (id, name, type, result, duration) = invocation; + Assert.Equal(expectedId, id); Assert.Equal(expectedName, name); Assert.Equal(expectedType, type); Assert.Equal(expectedResult, result); diff --git a/tests/NScatterGather.Tests/Invocations/FaultedInvocation.cs b/tests/NScatterGather.Tests/Invocations/FaultedInvocation.cs index 2cad899..995bf49 100644 --- a/tests/NScatterGather.Tests/Invocations/FaultedInvocation.cs +++ b/tests/NScatterGather.Tests/Invocations/FaultedInvocation.cs @@ -8,16 +8,22 @@ public class FaultedInvocationTests [Fact] public void Can_be_deconstructed() { + var expectedId = Guid.NewGuid(); var expectedName = "foo"; var expectedType = typeof(int); var expectedException = new Exception(); var expectedDuration = TimeSpan.FromSeconds(1); var invocation = new FaultedInvocation( - expectedName, expectedType, expectedException, expectedDuration); + expectedId, + expectedName, + expectedType, + expectedException, + expectedDuration); - var (name, type, exception, duration) = invocation; + var (id, name, type, exception, duration) = invocation; + Assert.Equal(expectedId, id); Assert.Equal(expectedName, name); Assert.Equal(expectedType, type); Assert.Equal(expectedException, exception); diff --git a/tests/NScatterGather.Tests/Invocations/IncompleteInvocationTests.cs b/tests/NScatterGather.Tests/Invocations/IncompleteInvocationTests.cs index 24d5181..771ae84 100644 --- a/tests/NScatterGather.Tests/Invocations/IncompleteInvocationTests.cs +++ b/tests/NScatterGather.Tests/Invocations/IncompleteInvocationTests.cs @@ -1,4 +1,5 @@ -using Xunit; +using System; +using Xunit; namespace NScatterGather.Invocations { @@ -7,12 +8,14 @@ public class IncompleteInvocationTests [Fact] public void Can_be_deconstructed() { + var expectedId = Guid.NewGuid(); var expectedName = "foo"; var expectedType = typeof(int); - var invocation = new IncompleteInvocation(expectedName, expectedType); - var (name, type) = invocation; + var invocation = new IncompleteInvocation(expectedId, expectedName, expectedType); + var (id, name, type) = invocation; + Assert.Equal(expectedId, id); Assert.Equal(expectedName, name); Assert.Equal(expectedType, type); } diff --git a/tests/NScatterGather.Tests/Recipients/Collection/RecipientsCollectionTests.cs b/tests/NScatterGather.Tests/Recipients/Collection/RecipientsCollectionTests.cs index ddaba0c..06279ea 100644 --- a/tests/NScatterGather.Tests/Recipients/Collection/RecipientsCollectionTests.cs +++ b/tests/NScatterGather.Tests/Recipients/Collection/RecipientsCollectionTests.cs @@ -1,5 +1,4 @@ using System; -using System.Threading.Tasks; using Xunit; namespace NScatterGather.Recipients.Collection @@ -16,33 +15,42 @@ public RecipientsCollectionTests() [Fact] public void Can_add_generic_type() { - _collection.Add(); + var id = _collection.Add(); + Assert.NotEqual(default, id); } [Fact] public void Can_add_generic_type_with_name() { - _collection.Add("My name is"); + var id = _collection.Add("My name is"); + Assert.NotEqual(default, id); } [Fact] public void Can_add_generic_type_with_lifetime() { - _collection.Add(Lifetime.Transient); - _collection.Add(Lifetime.Scoped); - _collection.Add(Lifetime.Singleton); + var id1 = _collection.Add(Lifetime.Transient); + Assert.NotEqual(default, id1); + + var id2 = _collection.Add(Lifetime.Scoped); + Assert.NotEqual(default, id2); + + var id3 = _collection.Add(Lifetime.Singleton); + Assert.NotEqual(default, id3); } [Fact] public void Can_add_generic_type_with_factory_method() { - _collection.Add(() => new SomeType()); + var id = _collection.Add(() => new SomeType()); + Assert.NotEqual(default, id); } [Fact] public void Can_add_generic_type_with_collision_strategy() { - _collection.Add(CollisionStrategy.UseAllMethodsMatching); + var id = _collection.Add(CollisionStrategy.UseAllMethodsMatching); + Assert.NotEqual(default, id); } [Fact] @@ -62,7 +70,8 @@ public void Error_if_lifetime_is_not_valid() [Fact] public void Can_add_instance() { - _collection.Add(new SomeType()); + var id = _collection.Add(new SomeType()); + Assert.NotEqual(default, id); } [Fact] @@ -100,7 +109,8 @@ public void Error_if_type_is_null() [Fact] public void Can_add_recipient_instance() { - _collection.Add(new SomeType()); + var id = _collection.Add(new SomeType()); + Assert.NotEqual(default, id); } [Fact] From b4194ffab0f06276783d42e44660877877040a35 Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Tue, 5 Jan 2021 11:36:58 +0100 Subject: [PATCH 12/17] Ensures readonly recipient Id. --- src/NScatterGather/Recipients/Recipient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NScatterGather/Recipients/Recipient.cs b/src/NScatterGather/Recipients/Recipient.cs index 2bb113a..d9f9b2e 100644 --- a/src/NScatterGather/Recipients/Recipient.cs +++ b/src/NScatterGather/Recipients/Recipient.cs @@ -9,7 +9,7 @@ namespace NScatterGather.Recipients { internal abstract class Recipient { - public Guid Id { get; set; } = Guid.NewGuid(); + public Guid Id { get; } = Guid.NewGuid(); public string? Name { get; } From 7b9f4880a5e3c1848a002fbf70d81e5b39220184 Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Tue, 5 Jan 2021 11:39:05 +0100 Subject: [PATCH 13/17] Introduces RecipientDescription. A readonly description of the recipient that was invoked. --- .../Invocations/CompletedInvocation.cs | 25 +++-------- .../Invocations/FaultedInvocation.cs | 25 +++-------- .../Invocations/IncompleteInvocation.cs | 30 ++----------- src/NScatterGather/Invocations/Invocation.cs | 12 +++++ .../Invocations/RecipientDescription.cs | 45 +++++++++++++++++++ .../RecipientDescriptionFactory.cs | 21 +++++++++ .../Responses/AggregatedResponseExtensions.cs | 7 +-- .../Responses/AggregatedResponseFactory.cs | 20 +++------ 8 files changed, 100 insertions(+), 85 deletions(-) create mode 100644 src/NScatterGather/Invocations/Invocation.cs create mode 100644 src/NScatterGather/Invocations/RecipientDescription.cs create mode 100644 src/NScatterGather/Invocations/RecipientDescriptionFactory.cs diff --git a/src/NScatterGather/Invocations/CompletedInvocation.cs b/src/NScatterGather/Invocations/CompletedInvocation.cs index 412e61a..5ef3bb9 100644 --- a/src/NScatterGather/Invocations/CompletedInvocation.cs +++ b/src/NScatterGather/Invocations/CompletedInvocation.cs @@ -3,43 +3,28 @@ namespace NScatterGather { - public class CompletedInvocation + public class CompletedInvocation : Invocation { - public Guid RecipientId { get; } - - public string? RecipientName { get; } - - public Type? RecipientType { get; } - [AllowNull, MaybeNull] public TResponse Result { get; } public TimeSpan Duration { get; } internal CompletedInvocation( - Guid recipientId, - string? recipientName, - Type? recipientType, + RecipientDescription recipient, [AllowNull] TResponse result, - TimeSpan duration) + TimeSpan duration) : base(recipient) { - RecipientId = recipientId; - RecipientName = recipientName; - RecipientType = recipientType; Result = result; Duration = duration; } public void Deconstruct( - out Guid recipientId, - out string? recipientName, - out Type? recipientType, + out RecipientDescription recipient, [MaybeNull] out TResponse result, out TimeSpan duration) { - recipientId = RecipientId; - recipientName = RecipientName; - recipientType = RecipientType; + recipient = Recipient; result = Result; duration = Duration; } diff --git a/src/NScatterGather/Invocations/FaultedInvocation.cs b/src/NScatterGather/Invocations/FaultedInvocation.cs index 9a38b86..3526c2d 100644 --- a/src/NScatterGather/Invocations/FaultedInvocation.cs +++ b/src/NScatterGather/Invocations/FaultedInvocation.cs @@ -2,42 +2,27 @@ namespace NScatterGather { - public class FaultedInvocation + public class FaultedInvocation : Invocation { - public Guid RecipientId { get; } - - public string? RecipientName { get; } - - public Type? RecipientType { get; } - public Exception? Exception { get; } public TimeSpan Duration { get; } internal FaultedInvocation( - Guid recipientId, - string? recipientName, - Type? recipientType, + RecipientDescription recipient, Exception? exception, - TimeSpan duration) + TimeSpan duration) : base(recipient) { - RecipientId = recipientId; - RecipientName = recipientName; - RecipientType = recipientType; Exception = exception; Duration = duration; } public void Deconstruct( - out Guid recipientId, - out string? recipientName, - out Type? recipientType, + out RecipientDescription recipient, out Exception? exception, out TimeSpan duration) { - recipientId = RecipientId; - recipientName = RecipientName; - recipientType = RecipientType; + recipient = Recipient; exception = Exception; duration = Duration; } diff --git a/src/NScatterGather/Invocations/IncompleteInvocation.cs b/src/NScatterGather/Invocations/IncompleteInvocation.cs index b0b347a..2e28b99 100644 --- a/src/NScatterGather/Invocations/IncompleteInvocation.cs +++ b/src/NScatterGather/Invocations/IncompleteInvocation.cs @@ -1,33 +1,9 @@ -using System; - -namespace NScatterGather +namespace NScatterGather { - public class IncompleteInvocation + public class IncompleteInvocation : Invocation { - public Guid RecipientId { get; } - - public string? RecipientName { get; } - - public Type? RecipientType { get; } - - internal IncompleteInvocation( - Guid recipientId, - string? recipientName, - Type? recipientType) + internal IncompleteInvocation(RecipientDescription recipient) : base(recipient) { - RecipientId = recipientId; - RecipientName = recipientName; - RecipientType = recipientType; - } - - public void Deconstruct( - out Guid recipientId, - out string? recipientName, - out Type? recipientType) - { - recipientId = RecipientId; - recipientName = RecipientName; - recipientType = RecipientType; } } } diff --git a/src/NScatterGather/Invocations/Invocation.cs b/src/NScatterGather/Invocations/Invocation.cs new file mode 100644 index 0000000..c57621b --- /dev/null +++ b/src/NScatterGather/Invocations/Invocation.cs @@ -0,0 +1,12 @@ +namespace NScatterGather +{ + public abstract class Invocation + { + public RecipientDescription Recipient { get; } + + protected Invocation(RecipientDescription recipient) + { + Recipient = recipient; + } + } +} diff --git a/src/NScatterGather/Invocations/RecipientDescription.cs b/src/NScatterGather/Invocations/RecipientDescription.cs new file mode 100644 index 0000000..1029601 --- /dev/null +++ b/src/NScatterGather/Invocations/RecipientDescription.cs @@ -0,0 +1,45 @@ +using System; + +namespace NScatterGather +{ + public class RecipientDescription + { + public Guid Id { get; } + + public string? Name { get; } + + public Type? Type { get; } + + public Lifetime Lifetime { get; } + + public CollisionStrategy CollisionStrategy { get; } + + internal RecipientDescription( + Guid id, + string? name, + Type? type, + Lifetime lifetime, + CollisionStrategy collisionStrategy) + { + Id = id; + Name = name; + Type = type; + Lifetime = lifetime; + CollisionStrategy = collisionStrategy; + } + + public void Deconstruct( + out Guid id, + out string? name, + out Type? type, + out Lifetime lifetime, + out CollisionStrategy collisionStrategy) + { + id = Id; + name = Name; + type = Type; + lifetime = Lifetime; + collisionStrategy = CollisionStrategy; + } + } +} diff --git a/src/NScatterGather/Invocations/RecipientDescriptionFactory.cs b/src/NScatterGather/Invocations/RecipientDescriptionFactory.cs new file mode 100644 index 0000000..9823874 --- /dev/null +++ b/src/NScatterGather/Invocations/RecipientDescriptionFactory.cs @@ -0,0 +1,21 @@ +using NScatterGather.Recipients; + +namespace NScatterGather.Invocations +{ + internal class RecipientDescriptionFactory + { + public static RecipientDescription CreateFrom(Recipient recipient) + { + var recipientType = recipient is TypeRecipient ir ? ir.Type : null; + + var description = new RecipientDescription( + recipient.Id, + recipient.Name, + recipientType, + recipient.Lifetime, + recipient.CollisionStrategy); + + return description; + } + } +} diff --git a/src/NScatterGather/Responses/AggregatedResponseExtensions.cs b/src/NScatterGather/Responses/AggregatedResponseExtensions.cs index 38b5cff..1e59129 100644 --- a/src/NScatterGather/Responses/AggregatedResponseExtensions.cs +++ b/src/NScatterGather/Responses/AggregatedResponseExtensions.cs @@ -6,17 +6,14 @@ namespace NScatterGather { public static class AggregatedResponseExtensions { - public static IReadOnlyDictionary AsResultsDictionary( + public static IReadOnlyDictionary AsResultsDictionary( this AggregatedResponse aggregatedResponse) { if (aggregatedResponse is null) throw new ArgumentNullException(nameof(aggregatedResponse)); var dictionary = aggregatedResponse.Completed - .Where(x => x.RecipientType is not null) - .ToDictionary( - x => x.RecipientType!, - x => x.Result!); + .ToDictionary(keySelector: i => i.Recipient, elementSelector: i => i.Result!); return dictionary; } diff --git a/src/NScatterGather/Responses/AggregatedResponseFactory.cs b/src/NScatterGather/Responses/AggregatedResponseFactory.cs index 6d887fb..14dd6a3 100644 --- a/src/NScatterGather/Responses/AggregatedResponseFactory.cs +++ b/src/NScatterGather/Responses/AggregatedResponseFactory.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using NScatterGather.Recipients; +using NScatterGather.Invocations; using NScatterGather.Recipients.Run; namespace NScatterGather.Responses @@ -15,15 +15,12 @@ public static AggregatedResponse CreateFrom( foreach (var invocation in invocations) { - var recipient = invocation.Recipient; - var recipientType = recipient is TypeRecipient ir ? ir.Type : null; + var recipientDescription = RecipientDescriptionFactory.CreateFrom(invocation.Recipient); if (invocation.CompletedSuccessfully) { var completedInvocation = new CompletedInvocation( - recipient.Id, - recipient.Name, - recipientType, + recipientDescription, invocation.Result, invocation.Duration); @@ -32,19 +29,16 @@ public static AggregatedResponse CreateFrom( else if (invocation.Faulted) { var faultedInvocation = new FaultedInvocation( - recipient.Id, - recipient.Name, - recipientType, + recipientDescription, invocation.Exception, invocation.Duration); faulted.Add(faultedInvocation); } else - incomplete.Add(new IncompleteInvocation( - recipient.Id, - recipient.Name, - recipientType)); + { + incomplete.Add(new IncompleteInvocation(recipientDescription)); + } } return new AggregatedResponse(completed, faulted, incomplete); From f5a376df264f88061c94ed6da0595e2052a490c1 Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Tue, 5 Jan 2021 11:39:31 +0100 Subject: [PATCH 14/17] Updates samples with RecipientDescription. --- samples/NScatterGather.Samples.CompetingTasks/Program.cs | 2 +- samples/NScatterGather.Samples/Samples/4.HandleErrors.cs | 4 ++-- samples/NScatterGather.Samples/Samples/5.Timeout.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/NScatterGather.Samples.CompetingTasks/Program.cs b/samples/NScatterGather.Samples.CompetingTasks/Program.cs index c7c1463..71a4064 100644 --- a/samples/NScatterGather.Samples.CompetingTasks/Program.cs +++ b/samples/NScatterGather.Samples.CompetingTasks/Program.cs @@ -71,7 +71,7 @@ static void PrettyPrint(IReadOnlyList evaluations) foreach (var invocation in resultsWithPrice.OrderBy(x => x.Duration)) { var productName = isFirstPrice ? evaluation.Product.Name : string.Empty; - var supplierName = invocation.RecipientName; + var supplierName = invocation.Recipient.Name; var supplierPrice = invocation.Result!.Value; var isBestPrice = supplierPrice == bestPrice; var resultColor = isBestPrice ? "green3_1" : "red"; diff --git a/samples/NScatterGather.Samples/Samples/4.HandleErrors.cs b/samples/NScatterGather.Samples/Samples/4.HandleErrors.cs index 8cfaa3f..9ded89b 100644 --- a/samples/NScatterGather.Samples/Samples/4.HandleErrors.cs +++ b/samples/NScatterGather.Samples/Samples/4.HandleErrors.cs @@ -22,7 +22,7 @@ public async Task Run() Console.WriteLine($"Completed {response.Completed.Count}"); Console.WriteLine( $"Faulted {response.Faulted.Count}: " + - $"{response.Faulted[0].RecipientType?.Name} => " + + $"{response.Faulted[0].Recipient.Type?.Name} => " + $"{response.Faulted[0].Exception?.Message}"); } @@ -34,7 +34,7 @@ public string Get(string s) => class Bar { - public string Todo(string s) => + public string Todo(string _) => throw new NotImplementedException("TODO"); } } diff --git a/samples/NScatterGather.Samples/Samples/5.Timeout.cs b/samples/NScatterGather.Samples/Samples/5.Timeout.cs index 89e8c43..45f6fb8 100644 --- a/samples/NScatterGather.Samples/Samples/5.Timeout.cs +++ b/samples/NScatterGather.Samples/Samples/5.Timeout.cs @@ -26,8 +26,8 @@ public async Task Run() Console.WriteLine($"Completed {response.Completed.Count}"); Console.WriteLine( $"Incomplete {response.Incomplete.Count}: " + - $"{response.Incomplete[0].RecipientType?.Name}, " + - $"{response.Incomplete[1].RecipientType?.Name}"); + $"{response.Incomplete[0].Recipient.Type?.Name}, " + + $"{response.Incomplete[1].Recipient.Type?.Name}"); } class Foo @@ -46,7 +46,7 @@ public async Task Long(int n) class Baz { - public Task Block(int n) + public Task Block(int _) { var tcs = new TaskCompletionSource(); return tcs.Task; // It will never complete. From c974065e637e35780c0028a9cec100a7c2a261cf Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Tue, 5 Jan 2021 11:40:13 +0100 Subject: [PATCH 15/17] Updates tests with RecipientDescription. --- tests/NScatterGather.Tests/AggregatorTests.cs | 36 +++++++++---------- .../Invocations/CompletedInvocationTests.cs | 24 +++++++------ .../Invocations/FaultedInvocation.cs | 24 +++++++------ .../Invocations/IncompleteInvocationTests.cs | 21 ++++++----- .../AggregatedResponseExtensionsTests.cs | 2 +- .../Responses/AggregatedResponseTests.cs | 6 ++-- 6 files changed, 63 insertions(+), 50 deletions(-) diff --git a/tests/NScatterGather.Tests/AggregatorTests.cs b/tests/NScatterGather.Tests/AggregatorTests.cs index efabb6f..56d3c10 100644 --- a/tests/NScatterGather.Tests/AggregatorTests.cs +++ b/tests/NScatterGather.Tests/AggregatorTests.cs @@ -29,15 +29,15 @@ public async Task Sends_request_and_aggregates_responses() Assert.NotNull(result); Assert.Equal(3, result.Completed.Count); - Assert.Contains(typeof(SomeType), result.Completed.Select(x => x.RecipientType)); - Assert.Contains(typeof(SomeAsyncType), result.Completed.Select(x => x.RecipientType)); - Assert.Contains(typeof(SomePossiblyAsyncType), result.Completed.Select(x => x.RecipientType)); + Assert.Contains(typeof(SomeType), result.Completed.Select(x => x.Recipient.Type)); + Assert.Contains(typeof(SomeAsyncType), result.Completed.Select(x => x.Recipient.Type)); + Assert.Contains(typeof(SomePossiblyAsyncType), result.Completed.Select(x => x.Recipient.Type)); Assert.Single(result.Faulted); - Assert.Contains(typeof(SomeFaultingType), result.Faulted.Select(x => x.RecipientType)); + Assert.Contains(typeof(SomeFaultingType), result.Faulted.Select(x => x.Recipient.Type)); Assert.Single(result.Incomplete); - Assert.Contains(typeof(SomeNeverEndingType), result.Incomplete.Select(x => x.RecipientType)); + Assert.Contains(typeof(SomeNeverEndingType), result.Incomplete.Select(x => x.Recipient.Type)); } [Fact(Timeout = 5_000)] @@ -47,15 +47,15 @@ public async Task Receives_expected_response_types() Assert.NotNull(result); Assert.Equal(3, result.Completed.Count); - Assert.Contains(typeof(SomeType), result.Completed.Select(x => x.RecipientType)); - Assert.Contains(typeof(SomeAsyncType), result.Completed.Select(x => x.RecipientType)); - Assert.Contains(typeof(SomePossiblyAsyncType), result.Completed.Select(x => x.RecipientType)); + Assert.Contains(typeof(SomeType), result.Completed.Select(x => x.Recipient.Type)); + Assert.Contains(typeof(SomeAsyncType), result.Completed.Select(x => x.Recipient.Type)); + Assert.Contains(typeof(SomePossiblyAsyncType), result.Completed.Select(x => x.Recipient.Type)); Assert.Single(result.Faulted); - Assert.Contains(typeof(SomeFaultingType), result.Faulted.Select(x => x.RecipientType)); + Assert.Contains(typeof(SomeFaultingType), result.Faulted.Select(x => x.Recipient.Type)); Assert.Single(result.Incomplete); - Assert.Contains(typeof(SomeNeverEndingType), result.Incomplete.Select(x => x.RecipientType)); + Assert.Contains(typeof(SomeNeverEndingType), result.Incomplete.Select(x => x.Recipient.Type)); } [Fact] @@ -72,16 +72,16 @@ public async Task Responses_expose_the_recipient_name_and_type() Assert.NotNull(result); Assert.Equal(1, result.Completed.Count); - Assert.Equal("Some delegate", result.Completed[0].RecipientName); - Assert.Null(result.Completed[0].RecipientType); + Assert.Equal("Some delegate", result.Completed[0].Recipient.Name); + Assert.Null(result.Completed[0].Recipient.Type); Assert.Equal(1, result.Faulted.Count); - Assert.Equal("Some faulting type", result.Faulted[0].RecipientName); - Assert.Equal(typeof(SomeFaultingType), result.Faulted[0].RecipientType); + Assert.Equal("Some faulting type", result.Faulted[0].Recipient.Name); + Assert.Equal(typeof(SomeFaultingType), result.Faulted[0].Recipient.Type); Assert.Equal(1, result.Incomplete.Count); - Assert.Equal("Some never ending type", result.Incomplete[0].RecipientName); - Assert.Equal(typeof(SomeNeverEndingType), result.Incomplete[0].RecipientType); + Assert.Equal("Some never ending type", result.Incomplete[0].Recipient.Name); + Assert.Equal(typeof(SomeNeverEndingType), result.Incomplete[0].Recipient.Type); } [Fact] @@ -141,7 +141,7 @@ public async Task Recipients_can_return_null() Assert.Empty(response.Faulted); var completed = response.Completed[0]; - Assert.Equal(typeof(SomeTypeReturningNull), completed.RecipientType); + Assert.Equal(typeof(SomeTypeReturningNull), completed.Recipient.Type); Assert.Null(completed.Result); } @@ -160,7 +160,7 @@ public async Task Recipients_can_return_nullable() Assert.Empty(response.Faulted); var completed = response.Completed[0]; - Assert.Equal(typeof(SomeTypeReturningNullable), completed.RecipientType); + Assert.Equal(typeof(SomeTypeReturningNullable), completed.Recipient.Type); Assert.Null(completed.Result); } diff --git a/tests/NScatterGather.Tests/Invocations/CompletedInvocationTests.cs b/tests/NScatterGather.Tests/Invocations/CompletedInvocationTests.cs index 318f158..a1d0cd9 100644 --- a/tests/NScatterGather.Tests/Invocations/CompletedInvocationTests.cs +++ b/tests/NScatterGather.Tests/Invocations/CompletedInvocationTests.cs @@ -8,24 +8,28 @@ public class CompletedInvocationTests [Fact] public void Can_be_deconstructed() { - var expectedId = Guid.NewGuid(); - var expectedName = "foo"; - var expectedType = typeof(int); + var expectedDescription = new RecipientDescription( + Guid.NewGuid(), + "My name is", + typeof(SomeType), + Lifetime.Singleton, + CollisionStrategy.UseAllMethodsMatching); + var expectedResult = 42; var expectedDuration = TimeSpan.FromSeconds(1); var invocation = new CompletedInvocation( - expectedId, - expectedName, - expectedType, + expectedDescription, expectedResult, expectedDuration); - var (id, name, type, result, duration) = invocation; + var ((id, name, type, lifetime, strategy), result, duration) = invocation; - Assert.Equal(expectedId, id); - Assert.Equal(expectedName, name); - Assert.Equal(expectedType, type); + Assert.Equal(expectedDescription.Id, id); + Assert.Equal(expectedDescription.Name, name); + Assert.Equal(expectedDescription.Type, type); + Assert.Equal(expectedDescription.Lifetime, lifetime); + Assert.Equal(expectedDescription.CollisionStrategy, strategy); Assert.Equal(expectedResult, result); Assert.Equal(expectedDuration, duration); } diff --git a/tests/NScatterGather.Tests/Invocations/FaultedInvocation.cs b/tests/NScatterGather.Tests/Invocations/FaultedInvocation.cs index 995bf49..ba94e09 100644 --- a/tests/NScatterGather.Tests/Invocations/FaultedInvocation.cs +++ b/tests/NScatterGather.Tests/Invocations/FaultedInvocation.cs @@ -8,24 +8,28 @@ public class FaultedInvocationTests [Fact] public void Can_be_deconstructed() { - var expectedId = Guid.NewGuid(); - var expectedName = "foo"; - var expectedType = typeof(int); + var expectedDescription = new RecipientDescription( + Guid.NewGuid(), + "My name is", + typeof(SomeType), + Lifetime.Singleton, + CollisionStrategy.UseAllMethodsMatching); + var expectedException = new Exception(); var expectedDuration = TimeSpan.FromSeconds(1); var invocation = new FaultedInvocation( - expectedId, - expectedName, - expectedType, + expectedDescription, expectedException, expectedDuration); - var (id, name, type, exception, duration) = invocation; + var ((id, name, type, lifetime, strategy), exception, duration) = invocation; - Assert.Equal(expectedId, id); - Assert.Equal(expectedName, name); - Assert.Equal(expectedType, type); + Assert.Equal(expectedDescription.Id, id); + Assert.Equal(expectedDescription.Name, name); + Assert.Equal(expectedDescription.Type, type); + Assert.Equal(expectedDescription.Lifetime, lifetime); + Assert.Equal(expectedDescription.CollisionStrategy, strategy); Assert.Equal(expectedException, exception); Assert.Equal(expectedDuration, duration); } diff --git a/tests/NScatterGather.Tests/Invocations/IncompleteInvocationTests.cs b/tests/NScatterGather.Tests/Invocations/IncompleteInvocationTests.cs index 771ae84..f3c732c 100644 --- a/tests/NScatterGather.Tests/Invocations/IncompleteInvocationTests.cs +++ b/tests/NScatterGather.Tests/Invocations/IncompleteInvocationTests.cs @@ -8,16 +8,21 @@ public class IncompleteInvocationTests [Fact] public void Can_be_deconstructed() { - var expectedId = Guid.NewGuid(); - var expectedName = "foo"; - var expectedType = typeof(int); + var expectedDescription = new RecipientDescription( + Guid.NewGuid(), + "My name is", + typeof(SomeType), + Lifetime.Singleton, + CollisionStrategy.UseAllMethodsMatching); - var invocation = new IncompleteInvocation(expectedId, expectedName, expectedType); - var (id, name, type) = invocation; + var invocation = new IncompleteInvocation(expectedDescription); + var (id, name, type, lifetime, strategy) = invocation.Recipient; - Assert.Equal(expectedId, id); - Assert.Equal(expectedName, name); - Assert.Equal(expectedType, type); + Assert.Equal(expectedDescription.Id, id); + Assert.Equal(expectedDescription.Name, name); + Assert.Equal(expectedDescription.Type, type); + Assert.Equal(expectedDescription.Lifetime, lifetime); + Assert.Equal(expectedDescription.CollisionStrategy, strategy); } } } diff --git a/tests/NScatterGather.Tests/Responses/AggregatedResponseExtensionsTests.cs b/tests/NScatterGather.Tests/Responses/AggregatedResponseExtensionsTests.cs index f90d872..31e9b99 100644 --- a/tests/NScatterGather.Tests/Responses/AggregatedResponseExtensionsTests.cs +++ b/tests/NScatterGather.Tests/Responses/AggregatedResponseExtensionsTests.cs @@ -51,7 +51,7 @@ public void Can_be_projected_onto_results_dictionary() var results = response.AsResultsDictionary(); Assert.NotNull(results); Assert.Single(results.Keys); - Assert.Equal(typeof(SomeType), results.Keys.First()); + Assert.Equal(typeof(SomeType), results.Keys.First().Type); Assert.Single(results.Values); Assert.Equal("42", results.Values.First()); } diff --git a/tests/NScatterGather.Tests/Responses/AggregatedResponseTests.cs b/tests/NScatterGather.Tests/Responses/AggregatedResponseTests.cs index 97ade03..5d5622c 100644 --- a/tests/NScatterGather.Tests/Responses/AggregatedResponseTests.cs +++ b/tests/NScatterGather.Tests/Responses/AggregatedResponseTests.cs @@ -47,13 +47,13 @@ public void Invocations_are_grouped_correctly() { var response = AggregatedResponseFactory.CreateFrom(_runners); - Assert.Equal(typeof(SomeType), response.Completed[0].RecipientType); + Assert.Equal(typeof(SomeType), response.Completed[0].Recipient.Type); Assert.Equal("42", response.Completed[0].Result); - Assert.Equal(typeof(SomeFaultingType), response.Faulted[0].RecipientType); + Assert.Equal(typeof(SomeFaultingType), response.Faulted[0].Recipient.Type); Assert.Equal("A failure.", response.Faulted[0].Exception?.Message); - Assert.Equal(typeof(SomeNeverEndingType), response.Incomplete[0].RecipientType); + Assert.Equal(typeof(SomeNeverEndingType), response.Incomplete[0].Recipient.Type); } [Fact] From 0b242f41d14dde5a770ce8a9a5a3d7c5c46d1e93 Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Tue, 5 Jan 2021 11:43:09 +0100 Subject: [PATCH 16/17] Simplifies extraction of AggregateException. --- src/NScatterGather/Recipients/Run/RecipientRunner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NScatterGather/Recipients/Run/RecipientRunner.cs b/src/NScatterGather/Recipients/Run/RecipientRunner.cs index 0c938a6..4d60e9e 100644 --- a/src/NScatterGather/Recipients/Run/RecipientRunner.cs +++ b/src/NScatterGather/Recipients/Run/RecipientRunner.cs @@ -84,8 +84,8 @@ private void InspectAndExtract(Task task) private Exception? ExtractException(Exception exception) { - if (exception is AggregateException aEx && aEx.InnerExceptions.Count == 1) - return aEx.InnerException is null ? aEx : ExtractException(aEx.InnerException); + if (exception is AggregateException aEx) + return aEx.InnerExceptions.Count == 1 ? ExtractException(aEx.InnerException!) : aEx; if (exception is TargetInvocationException tIEx) return tIEx.InnerException is null ? tIEx : ExtractException(tIEx.InnerException); From dd163da28122e40a349ca34a4cec320454807fbc Mon Sep 17 00:00:00 2001 From: Tommaso Bertoni Date: Tue, 5 Jan 2021 11:44:08 +0100 Subject: [PATCH 17/17] Adds specific tests for RecipientRunner's exception extraction. --- .../Recipients/Run/RecipientRunnerTests.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/NScatterGather.Tests/Recipients/Run/RecipientRunnerTests.cs b/tests/NScatterGather.Tests/Recipients/Run/RecipientRunnerTests.cs index 50afa01..5a0c0dc 100644 --- a/tests/NScatterGather.Tests/Recipients/Run/RecipientRunnerTests.cs +++ b/tests/NScatterGather.Tests/Recipients/Run/RecipientRunnerTests.cs @@ -1,7 +1,10 @@ using System; +using System.Reflection; using System.Threading.Tasks; using NScatterGather.Inspection; using NScatterGather.Recipients; +using NScatterGather.Recipients.Invokers; +using NScatterGather.Recipients.Run; using Xunit; using static NScatterGather.CollisionStrategy; @@ -155,5 +158,59 @@ public async Task Reflection_exception_are_decomposed() Assert.NotNull(runner.Exception); Assert.Equal("An invocation failure.", runner.Exception!.Message); } + + [Fact] + public async Task Aggregate_exception_without_inner_is_handled() + { + var aggEx = new AggregateException("Empty inner exceptions"); + var runner = new RecipientRunner(_recipient, new PreparedInvocation(() => throw aggEx)); + + await runner.Start(); + Assert.Same(aggEx, runner.Exception); + } + + [Fact] + public async Task Aggregate_exception_with_inner_is_handled() + { + var ex1 = new Exception(); + var ex2 = new Exception(); + var aggEx = new AggregateException(ex1, ex2); + var runner = new RecipientRunner(_recipient, new PreparedInvocation(() => throw aggEx)); + + await runner.Start(); + Assert.Same(aggEx, runner.Exception); + } + + [Fact] + public async Task Aggregate_exception_with_one_inner_is_handled() + { + var ex = new Exception(); + var aggEx = new AggregateException(ex); + var runner = new RecipientRunner(_recipient, new PreparedInvocation(() => throw aggEx)); + + await runner.Start(); + Assert.Same(ex, runner.Exception); + } + + [Fact] + public async Task Target_invocation_exception_without_inner_is_handled() + { + var tiEx = new TargetInvocationException("Empty inner exception", null); + var runner = new RecipientRunner(_recipient, new PreparedInvocation(() => throw tiEx)); + + await runner.Start(); + Assert.Same(tiEx, runner.Exception); + } + + [Fact] + public async Task Target_invocation_exception_with_inner_is_handled() + { + var ex = new Exception(); + var tiEx = new TargetInvocationException(ex); + var runner = new RecipientRunner(_recipient, new PreparedInvocation(() => throw tiEx)); + + await runner.Start(); + Assert.Same(ex, runner.Exception); + } } }