-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* PipelineBuilder<T> is now immutable * completed refactor * Decided against continuing when TOut matches default(T) as this will not work for non-nullable value types * Added CancellationToken to handler interfaces for future proofing. Rest of pipeline will be trickier
- Loading branch information
1 parent
fd6fcbb
commit ae0c8ec
Showing
25 changed files
with
711 additions
and
939 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,115 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Flo | ||
{ | ||
/// <summary> | ||
/// Provides linking and building functionality for chain of responsibility pipelines | ||
/// </summary> | ||
public abstract class Builder<TInput, TOutput> | ||
public abstract class Builder<TIn, TOut, TBuilder> | ||
where TBuilder : Builder<TIn, TOut, TBuilder> | ||
{ | ||
private readonly List<Func<Func<TInput, TOutput>, Func<TInput, TOutput>>> _handlers | ||
= new List<Func<Func<TInput, TOutput>, Func<TInput, TOutput>>>(); | ||
private readonly TBuilder _builderInstance; | ||
private readonly List<Func<TIn, Func<TIn, Task<TOut>>, Task<TOut>>> _handlers | ||
= new List<Func<TIn, Func<TIn, Task<TOut>>, Task<TOut>>>(); | ||
|
||
protected Func<TInput, TOutput> InnerHandler { get; set; } | ||
protected Func<Type, object> ServiceProvider { get; set; } | ||
protected Func<TIn, Task<TOut>> InnerHandler { get; set; } | ||
|
||
public Builder(Func<TInput, TOutput> innerHandler) | ||
public Builder(Func<TIn, Task<TOut>> innerHandler, Func<Type, object> serviceProvider) | ||
{ | ||
InnerHandler = innerHandler ?? throw new ArgumentNullException(nameof(innerHandler)); | ||
ServiceProvider = serviceProvider ?? DefaultServiceProvider; | ||
|
||
_builderInstance = (TBuilder)this; | ||
} | ||
|
||
public TBuilder Fork( | ||
Func<TIn, bool> predicate, | ||
Action<TBuilder> configurePipeline) | ||
{ | ||
return When(predicate, async (input, innerPipeline, next) => | ||
{ | ||
return await innerPipeline.Invoke(input); | ||
}, | ||
configurePipeline); | ||
} | ||
|
||
public TBuilder When( | ||
Func<TIn, bool> predicate, | ||
Func<TIn, Func<TIn, Task<TOut>>, Func<TIn, Task<TOut>>, Task<TOut>> handler, | ||
Action<TBuilder> configurePipeline) | ||
{ | ||
if (predicate == null) throw new ArgumentNullException(nameof(predicate)); | ||
if (handler == null) throw new ArgumentNullException(nameof(handler)); | ||
if (configurePipeline == null) throw new ArgumentNullException(nameof(configurePipeline)); | ||
|
||
return Add((input, next) => | ||
{ | ||
if (predicate.Invoke(input)) | ||
{ | ||
var builder = CreateBuilder(); | ||
configurePipeline(builder); | ||
return handler.Invoke(input, builder.Build(), next); | ||
} | ||
|
||
return next.Invoke(input); | ||
}); | ||
} | ||
|
||
protected abstract TBuilder CreateBuilder(); | ||
|
||
public TBuilder Final(Func<TIn, Task<TOut>> handler) | ||
{ | ||
if (handler == null) throw new ArgumentNullException(nameof(handler)); | ||
return Add((input, next) => handler.Invoke(input)); | ||
} | ||
|
||
public TBuilder Add<THandler>() where THandler : class, IHandler<TIn, TOut> | ||
{ | ||
Func<THandler> handlerFactory = () => ServiceProvider.Invoke(typeof(THandler)) as THandler; | ||
return Add(handlerFactory); | ||
} | ||
|
||
protected virtual void AddHandler(Func<TInput, Func<TInput, TOutput>, TOutput> handler) | ||
public TBuilder Add(Func<IHandler<TIn, TOut>> handlerFactory) | ||
{ | ||
if (handlerFactory == null) throw new ArgumentNullException(nameof(handlerFactory)); | ||
|
||
return Add((input, next) => | ||
{ | ||
var handler = handlerFactory.Invoke(); | ||
|
||
if (handler != null) | ||
{ | ||
return handler.HandleAsync(input, next, CancellationToken.None); | ||
} | ||
|
||
return next.Invoke(input); | ||
}); | ||
} | ||
|
||
public TBuilder Add(Func<TIn, Func<TIn, Task<TOut>>, Task<TOut>> handler) | ||
{ | ||
if (handler == null) throw new ArgumentNullException(nameof(handler)); | ||
_handlers.Add(next => input => handler.Invoke(input, next)); | ||
_handlers.Add(handler); | ||
return _builderInstance; | ||
} | ||
|
||
public virtual Func<TInput, TOutput> Build() | ||
public virtual Func<TIn, Task<TOut>> Build() | ||
{ | ||
Func<TInput, TOutput> pipeline = InnerHandler; | ||
Func<TIn, Task<TOut>> pipeline = InnerHandler; | ||
for (int i = _handlers.Count - 1; i >= 0; i--) | ||
{ | ||
var handler = _handlers[i]; | ||
pipeline = handler.Invoke(pipeline); | ||
var prev = pipeline; | ||
pipeline = input => handler.Invoke(input, prev); | ||
} | ||
|
||
return pipeline; | ||
} | ||
|
||
private static object DefaultServiceProvider(Type type) => Activator.CreateInstance(type); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,15 @@ | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Flo | ||
{ | ||
public interface IHandler<TInput> | ||
public interface IHandler<TIn, TOut> | ||
{ | ||
Task<TOut> HandleAsync(TIn input, Func<TIn, Task<TOut>> next, CancellationToken token); | ||
} | ||
|
||
public interface IHandler<T> : IHandler<T, T> | ||
{ | ||
Task HandleAsync(TInput input, Func<TInput, Task> next); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
|
||
namespace Flo | ||
{ | ||
public class OutputPipelineBuilder<TIn, TOut> | ||
: Builder<TIn, TOut, OutputPipelineBuilder<TIn, TOut>> | ||
{ | ||
private static Func<TOut, bool> DefaultContinuation = output => output == null; | ||
|
||
public OutputPipelineBuilder(Func<Type, object> serviceProvider = null) | ||
: base(input => Task.FromResult(default(TOut)), serviceProvider) | ||
{ | ||
} | ||
|
||
public OutputPipelineBuilder<TIn, TOut> When( | ||
Func<TIn, bool> predicate, | ||
Action<OutputPipelineBuilder<TIn, TOut>> configurePipeline, | ||
Func<TOut, bool> continueIf = null) | ||
{ | ||
return When(predicate, async (input, innerPipeline, next) => | ||
{ | ||
var result = await innerPipeline.Invoke(input); | ||
|
||
// Continue on to the parent pipeline if the continueIf output predicate matches | ||
if ((continueIf ?? DefaultContinuation).Invoke(result)) | ||
return await next.Invoke(input); | ||
|
||
return result; | ||
}, | ||
configurePipeline); | ||
} | ||
|
||
protected override OutputPipelineBuilder<TIn, TOut> CreateBuilder() => new OutputPipelineBuilder<TIn, TOut>(ServiceProvider); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.