From de4801bdc735dd2c9643813af2a5e82b395c7895 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:21:58 -0700 Subject: [PATCH] [FEATURE]: Improve builder pattern (#35) * Refactor builders to eliminate reliance on partial interfaces and classes --------- Co-authored-by: Brenton Farmer --- docs/syntax.md | 17 +++- .../Binders/Abstractions/Binder.cs | 1 + .../Builders/CallBlockBuilder.cs | 42 ++++++---- .../Builders/CallIfBlockBuilder.cs | 44 +++++++--- .../Builders/CallStatementBuilder.cs | 74 +++++++++++++---- .../Builders/ForEachBlockBuilder.cs | 42 +++++++--- src/Hyperbee.Pipeline/Builders/HookBuilder.cs | 46 +++++++--- .../Builders/PipeBlockBuilder.cs | 43 ++++++---- .../Builders/PipeIfBlockBuilder.cs | 43 +++++++--- .../Builders/PipeStatementBuilder.cs | 72 ++++++++++++---- .../Builders/ReduceBlockBuilder.cs | 49 ++++++++--- .../Builders/WaitAllBlockBuilder.cs | 83 +++++++++++++------ src/Hyperbee.Pipeline/Builders/WrapBuilder.cs | 62 ++++++++++---- src/Hyperbee.Pipeline/IPipelineBuilder.cs | 4 +- src/Hyperbee.Pipeline/IPipelineFunction.cs | 10 ++- src/Hyperbee.Pipeline/PipelineBuilder.cs | 10 +-- .../PipelineEnumerationTests.cs | 4 +- 17 files changed, 468 insertions(+), 178 deletions(-) diff --git a/docs/syntax.md b/docs/syntax.md index a449bd7..ead930b 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -101,7 +101,7 @@ var count = 0; var command = PipelineFactory .Start() .Pipe( ( ctx, arg ) => arg.Split( ' ' ) ) - .ForEach( builder => builder + .ForEach().Type( builder => builder .Pipe( ( ctx, arg ) => count += 10 ) ) .Pipe( ( ctx, arg ) => count += 5 ) @@ -117,6 +117,21 @@ Assert.AreEqual( count, 25 ); `Reduce` and `ReduceAync` allow you to transform an enumerable pipeline input to a single value. You can specify a reducer function that defines how the elements should be combined, and a builder function that creates the pipeline for processing the elements.### Cancel +```csharp +var command = PipelineFactory + .Start() + .Pipe( ( ctx, arg ) => arg.Split( ' ' ) ) + .Reduce().Type( ( aggregate, value ) => aggregate + value, builder => builder + .Pipe( ( ctx, arg ) => int.Parse( arg ) + 10 ) + ) + .Pipe( ( ctx, arg ) => arg + 5 ) + .Build(); + +var result = await command( new PipelineContext(), "1 2 3 4 5" ); + +Assert.AreEqual( result, 70 ); +``` + ### WaitAll `WaitAll` allows you to wait for concurrent pipelines to complete before continuing. You can specify a set of builders that create diff --git a/src/Hyperbee.Pipeline/Binders/Abstractions/Binder.cs b/src/Hyperbee.Pipeline/Binders/Abstractions/Binder.cs index 599b072..eb97279 100644 --- a/src/Hyperbee.Pipeline/Binders/Abstractions/Binder.cs +++ b/src/Hyperbee.Pipeline/Binders/Abstractions/Binder.cs @@ -2,6 +2,7 @@ using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline.Binders.Abstractions; + internal abstract class Binder { protected FunctionAsync Pipeline { get; } diff --git a/src/Hyperbee.Pipeline/Builders/CallBlockBuilder.cs b/src/Hyperbee.Pipeline/Builders/CallBlockBuilder.cs index 39ec168..3d57e25 100644 --- a/src/Hyperbee.Pipeline/Builders/CallBlockBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/CallBlockBuilder.cs @@ -1,33 +1,47 @@ using Hyperbee.Pipeline.Binders; +using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline; -public partial interface IPipelineBuilder +public static class CallBlockBuilder { - IPipelineBuilder Call( Func, IPipelineBuilder> builder ); - IPipelineBuilder Call( bool inheritMiddleware, Func, IPipelineBuilder> builder ); -} - -public partial class PipelineBuilder -{ - // Call an inner builder discarding the final result. Acts like an Action. + public static IPipelineBuilder Call( + this IPipelineBuilder parent, + Func, IPipelineBuilder> builder + ) + { + return CallBlockBuilder.Call( parent, true, builder ); + } - public IPipelineBuilder Call( Func, IPipelineBuilder> builder ) + public static IPipelineBuilder Call( + this IPipelineBuilder parent, + bool inheritMiddleware, + Func, IPipelineBuilder> builder + ) { - return Call( true, builder ); + return CallBlockBuilder.Call( parent, inheritMiddleware, builder ); } +} - public IPipelineBuilder Call( bool inheritMiddleware, Func, IPipelineBuilder> builder ) +internal static class CallBlockBuilder +{ + public static IPipelineBuilder Call( + IPipelineBuilder parent, + bool inheritMiddleware, + Func, IPipelineBuilder> builder + ) { ArgumentNullException.ThrowIfNull( builder ); - var block = PipelineFactory.Start( inheritMiddleware ? Middleware : null ); + var (parentFunction, parentMiddleware) = parent.GetPipelineFunction(); + + var block = PipelineFactory.Start( inheritMiddleware ? parentMiddleware : null ); var function = builder( block ).CastFunction(); // cast because we don't know the final Pipe output value return new PipelineBuilder { - Function = new CallBlockBinder( Function ).Bind( function ), - Middleware = Middleware + Function = new CallBlockBinder( parentFunction ).Bind( function ), + Middleware = parentMiddleware }; } } diff --git a/src/Hyperbee.Pipeline/Builders/CallIfBlockBuilder.cs b/src/Hyperbee.Pipeline/Builders/CallIfBlockBuilder.cs index c2e8cfa..90b7993 100644 --- a/src/Hyperbee.Pipeline/Builders/CallIfBlockBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/CallIfBlockBuilder.cs @@ -1,32 +1,50 @@ using Hyperbee.Pipeline.Binders; +using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline; -public partial interface IPipelineBuilder +public static class CallIfBuilder { - IPipelineBuilder CallIf( Function condition, Func, IPipelineBuilder> builder ); - IPipelineBuilder CallIf( Function condition, bool inheritMiddleware, Func, IPipelineBuilder> builder ); -} - -public partial class PipelineBuilder -{ - public IPipelineBuilder CallIf( Function condition, Func, IPipelineBuilder> builder ) + public static IPipelineBuilder CallIf( + this IPipelineBuilder parent, + Function condition, + Func, IPipelineBuilder> builder + ) + { + return CallIfBuilder.CallIf( parent, condition, true, builder ); + } + public static IPipelineBuilder CallIf( + this IPipelineBuilder parent, + Function condition, + bool inheritMiddleware, + Func, IPipelineBuilder> builder + ) { - return CallIf( condition, true, builder ); + return CallIfBuilder.CallIf( parent, condition, inheritMiddleware, builder ); } +} - public IPipelineBuilder CallIf( Function condition, bool inheritMiddleware, Func, IPipelineBuilder> builder ) +internal static class CallIfBuilder +{ + public static IPipelineBuilder CallIf( + IPipelineBuilder parent, + Function condition, + bool inheritMiddleware, + Func, IPipelineBuilder> builder + ) { ArgumentNullException.ThrowIfNull( builder ); ArgumentNullException.ThrowIfNull( condition ); - var block = PipelineFactory.Start( inheritMiddleware ? Middleware : null ); + var (parentFunction, parentMiddleware) = parent.GetPipelineFunction(); + + var block = PipelineFactory.Start( inheritMiddleware ? parentMiddleware : null ); var function = builder( block ).CastFunction(); // cast because we don't know the final Pipe output value return new PipelineBuilder { - Function = new CallIfBlockBinder( condition, Function ).Bind( function ), - Middleware = Middleware + Function = new CallIfBlockBinder( condition, parentFunction ).Bind( function ), + Middleware = parentMiddleware }; } } diff --git a/src/Hyperbee.Pipeline/Builders/CallStatementBuilder.cs b/src/Hyperbee.Pipeline/Builders/CallStatementBuilder.cs index f9c8e0c..e061065 100644 --- a/src/Hyperbee.Pipeline/Builders/CallStatementBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/CallStatementBuilder.cs @@ -1,28 +1,64 @@ -using Hyperbee.Pipeline.Binders; +using System.Xml.Linq; +using Hyperbee.Pipeline.Binders; using Hyperbee.Pipeline.Context; +using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline; -public partial interface IPipelineBuilder +public static class CallStatementBuilder { - IPipelineBuilder Call( Procedure next, string name ); - IPipelineBuilder Call( Procedure next, Action config = null ); - IPipelineBuilder CallAsync( ProcedureAsync next, string name ); - IPipelineBuilder CallAsync( ProcedureAsync next, Action config = null ); + public static IPipelineBuilder Call( + this IPipelineBuilder parent, + Procedure next, string name + ) + { + return CallStatementBuilder.Call( parent, next, config => config.Name = name ); + } + + public static IPipelineBuilder Call( + this IPipelineBuilder parent, + Procedure next, + Action config = null + ) + { + return CallStatementBuilder.Call( parent, next, config ); + } + + public static IPipelineBuilder CallAsync( + this IPipelineBuilder parent, + ProcedureAsync next, + string name + ) + { + return CallStatementBuilder.CallAsync( parent, next, config => config.Name = name ); + } + + public static IPipelineBuilder CallAsync( + this IPipelineBuilder parent, + ProcedureAsync next, + Action config = null + ) + { + return CallStatementBuilder.CallAsync( parent, next, config ); + } } -public partial class PipelineBuilder +internal static class CallStatementBuilder { - public IPipelineBuilder Call( Procedure next, string name ) => Call( next, config => config.Name = name ); - - public IPipelineBuilder Call( Procedure next, Action config = null ) + public static IPipelineBuilder Call( + IPipelineBuilder parent, + Procedure next, + Action config = null + ) { ArgumentNullException.ThrowIfNull( next ); + var (parentFunction, parentMiddleware) = parent.GetPipelineFunction(); + return new PipelineBuilder { - Function = new CallStatementBinder( Function, Middleware, config ).Bind( AsyncNext, next.Method ), - Middleware = Middleware + Function = new CallStatementBinder( parentFunction, parentMiddleware, config ).Bind( AsyncNext, next.Method ), + Middleware = parentMiddleware }; // task wrapper @@ -34,16 +70,20 @@ Task AsyncNext( IPipelineContext context, TOutput argument ) } } - public IPipelineBuilder CallAsync( ProcedureAsync next, string name ) => CallAsync( next, config => config.Name = name ); - - public IPipelineBuilder CallAsync( ProcedureAsync next, Action config = null ) + public static IPipelineBuilder CallAsync( + IPipelineBuilder parent, + ProcedureAsync next, + Action config = null + ) { ArgumentNullException.ThrowIfNull( next ); + var (parentFunction, parentMiddleware) = parent.GetPipelineFunction(); + return new PipelineBuilder { - Function = new CallStatementBinder( Function, Middleware, config ).Bind( next ), - Middleware = Middleware + Function = new CallStatementBinder( parentFunction, parentMiddleware, config ).Bind( next ), + Middleware = parentMiddleware }; } } diff --git a/src/Hyperbee.Pipeline/Builders/ForEachBlockBuilder.cs b/src/Hyperbee.Pipeline/Builders/ForEachBlockBuilder.cs index 384c1bf..2456159 100644 --- a/src/Hyperbee.Pipeline/Builders/ForEachBlockBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/ForEachBlockBuilder.cs @@ -1,31 +1,49 @@ using Hyperbee.Pipeline.Binders; +using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline; -public partial interface IPipelineBuilder +public static class ForEachBlockBuilder { - IPipelineBuilder ForEach( Func, IPipelineBuilder> builder ); - IPipelineBuilder ForEachAsync( bool inheritMiddleware, Func, IPipelineBuilder> builder ); -} + public static ForEachBlockBuilderWrapper ForEach( this IPipelineBuilder parent ) + { + return new ForEachBlockBuilderWrapper( parent ); + } -public partial class PipelineBuilder -{ - public IPipelineBuilder ForEach( Func, IPipelineBuilder> builder ) + public class ForEachBlockBuilderWrapper( IPipelineBuilder parent ) { - return ForEachAsync( true, builder ); + public IPipelineBuilder Type( Func, IPipelineBuilder> builder ) + { + return ForEachBlockBuilder.ForEach( parent, true, builder ); + } + + public IPipelineBuilder Type( bool inheritMiddleware, Func, IPipelineBuilder> builder ) + { + return ForEachBlockBuilder.ForEach( parent, inheritMiddleware, builder ); + } } +} - public IPipelineBuilder ForEachAsync( bool inheritMiddleware, Func, IPipelineBuilder> builder ) +internal static class ForEachBlockBuilder +{ + public static IPipelineBuilder ForEach( + IPipelineBuilder parent, + bool inheritMiddleware, + Func, IPipelineBuilder> builder + ) { ArgumentNullException.ThrowIfNull( builder ); - var block = PipelineFactory.Start( inheritMiddleware ? Middleware : null ); + var (parentFunction, parentMiddleware) = parent.GetPipelineFunction(); + + var block = PipelineFactory.Start( inheritMiddleware ? parentMiddleware : null ); var function = builder( block ).CastFunction(); // cast because we don't know the final Pipe output value return new PipelineBuilder { - Function = new ForEachBlockBinder( Function ).Bind( function ), - Middleware = Middleware + Function = new ForEachBlockBinder( parentFunction ).Bind( function ), + Middleware = parentMiddleware }; } } + diff --git a/src/Hyperbee.Pipeline/Builders/HookBuilder.cs b/src/Hyperbee.Pipeline/Builders/HookBuilder.cs index e28d8a7..41ae3ee 100644 --- a/src/Hyperbee.Pipeline/Builders/HookBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/HookBuilder.cs @@ -1,37 +1,59 @@ using Hyperbee.Pipeline.Binders; +using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline; -public partial interface IPipelineStartBuilder +public static class HookBuilder { - IPipelineStartBuilder HookAsync( MiddlewareAsync functionMiddleware ); - IPipelineStartBuilder HookAsync( IEnumerable> functionMiddleware ); + public static IPipelineStartBuilder HookAsync( + this IPipelineStartBuilder parent, + MiddlewareAsync functionMiddleware + ) + { + return HookBuilder.HookAsync( parent, functionMiddleware ); + } + + public static IPipelineStartBuilder HookAsync( + this IPipelineStartBuilder parent, + IEnumerable> functionMiddleware + ) + { + return HookBuilder.HookAsync( parent, functionMiddleware ); + } } -public partial class PipelineBuilder +internal static class HookBuilder { - public IPipelineStartBuilder HookAsync( MiddlewareAsync functionMiddleware ) + public static IPipelineStartBuilder HookAsync( + IPipelineStartBuilder parent, + MiddlewareAsync functionMiddleware + ) { if ( functionMiddleware == null ) - return this; + return parent; + + var (parentFunction, parentMiddleware) = parent.GetPipelineFunction(); return new PipelineBuilder { - Function = Function, - Middleware = new HookBinder( Middleware ).Bind( functionMiddleware ) + Function = parentFunction, + Middleware = new HookBinder( parentMiddleware ).Bind( functionMiddleware ) }; } - public IPipelineStartBuilder HookAsync( IEnumerable> functionMiddleware ) + public static IPipelineStartBuilder HookAsync( + IPipelineStartBuilder parent, + IEnumerable> functionMiddleware + ) { if ( functionMiddleware == null ) - return this; + return parent; - var builder = this as IPipelineStartBuilder; + var builder = parent; foreach ( var middleware in functionMiddleware ) { - builder = HookAsync( middleware ); + builder = HookAsync( parent, middleware ); } return builder; diff --git a/src/Hyperbee.Pipeline/Builders/PipeBlockBuilder.cs b/src/Hyperbee.Pipeline/Builders/PipeBlockBuilder.cs index 334a28d..aa9adbd 100644 --- a/src/Hyperbee.Pipeline/Builders/PipeBlockBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/PipeBlockBuilder.cs @@ -1,33 +1,48 @@ using Hyperbee.Pipeline.Binders; +using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline; -public partial interface IPipelineBuilder +public static class PipeBlockBuilderExtensions { - IPipelineBuilder Pipe( Func, IPipelineBuilder> builder ); - IPipelineBuilder Pipe( bool inheritMiddleware, Func, IPipelineBuilder> builder ); -} - -public partial class PipelineBuilder -{ - // Pipe the result of an inner builder to the next pipeline step. Acts like a Func. + public static IPipelineBuilder Pipe( + this IPipelineBuilder parent, + Func, IPipelineBuilder> builder + ) + { + return PipeBlockBuilder.Pipe( parent, true, builder ); + } - public IPipelineBuilder Pipe( Func, IPipelineBuilder> builder ) + public static IPipelineBuilder Pipe( + this IPipelineBuilder parent, + bool inheritMiddleware, + Func, IPipelineBuilder> builder + ) { - return Pipe( true, builder ); + return PipeBlockBuilder.Pipe( parent, inheritMiddleware, builder ); } +} - public IPipelineBuilder Pipe( bool inheritMiddleware, Func, IPipelineBuilder> builder ) +internal static class PipeBlockBuilder +{ + public static IPipelineBuilder Pipe( + IPipelineBuilder parent, + bool inheritMiddleware, + Func, IPipelineBuilder> builder + ) { ArgumentNullException.ThrowIfNull( builder ); - var block = PipelineFactory.Start( inheritMiddleware ? Middleware : null ); + var (parentFunction, parentMiddleware) = parent.GetPipelineFunction(); + + var block = PipelineFactory.Start( inheritMiddleware ? parentMiddleware : null ); var function = ((PipelineBuilder) builder( block )).Function; return new PipelineBuilder { - Function = new PipeBlockBinder( Function ).Bind( function ), - Middleware = Middleware + Function = new PipeBlockBinder( parentFunction ).Bind( function ), + Middleware = parentMiddleware }; } } + diff --git a/src/Hyperbee.Pipeline/Builders/PipeIfBlockBuilder.cs b/src/Hyperbee.Pipeline/Builders/PipeIfBlockBuilder.cs index 311391d..596744e 100644 --- a/src/Hyperbee.Pipeline/Builders/PipeIfBlockBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/PipeIfBlockBuilder.cs @@ -1,32 +1,51 @@ using Hyperbee.Pipeline.Binders; +using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline; -public partial interface IPipelineBuilder +public static class PipeIfBlockBuilder { - IPipelineBuilder PipeIf( Function condition, Func, IPipelineBuilder> builder ); - IPipelineBuilder PipeIf( Function condition, bool inheritMiddleware, Func, IPipelineBuilder> builder ); -} + public static IPipelineBuilder PipeIf( + this IPipelineBuilder parent, + Function condition, + Func, IPipelineBuilder> builder + ) + { + return PipeIfBlockBuilder.PipeIf( parent, condition, true, builder ); + } -public partial class PipelineBuilder -{ - public IPipelineBuilder PipeIf( Function condition, Func, IPipelineBuilder> builder ) + public static IPipelineBuilder PipeIf( + this IPipelineBuilder parent, + Function condition, + bool inheritMiddleware, + Func, IPipelineBuilder> builder + ) { - return PipeIf( condition, true, builder ); + return PipeIfBlockBuilder.PipeIf( parent, condition, inheritMiddleware, builder ); } +} - public IPipelineBuilder PipeIf( Function condition, bool inheritMiddleware, Func, IPipelineBuilder> builder ) +internal static class PipeIfBlockBuilder +{ + public static IPipelineBuilder PipeIf( + IPipelineBuilder parent, + Function condition, + bool inheritMiddleware, + Func, IPipelineBuilder> builder + ) { ArgumentNullException.ThrowIfNull( builder ); ArgumentNullException.ThrowIfNull( condition ); - var block = PipelineFactory.Start( inheritMiddleware ? Middleware : null ); + var (parentFunction, parentMiddleware) = parent.GetPipelineFunction(); + + var block = PipelineFactory.Start( inheritMiddleware ? parentMiddleware : null ); var function = ((PipelineBuilder) builder( block )).Function; return new PipelineBuilder { - Function = new PipeIfBlockBinder( condition, Function ).Bind( function ), - Middleware = Middleware + Function = new PipeIfBlockBinder( condition, parentFunction ).Bind( function ), + Middleware = parentMiddleware }; } } diff --git a/src/Hyperbee.Pipeline/Builders/PipeStatementBuilder.cs b/src/Hyperbee.Pipeline/Builders/PipeStatementBuilder.cs index 146f1da..28f4846 100644 --- a/src/Hyperbee.Pipeline/Builders/PipeStatementBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/PipeStatementBuilder.cs @@ -1,28 +1,64 @@ using Hyperbee.Pipeline.Binders; using Hyperbee.Pipeline.Context; +using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline; -public partial interface IPipelineBuilder +public static class PipeStatementBuilder { - IPipelineBuilder Pipe( Function next, string name ); - IPipelineBuilder Pipe( Function next, Action config = null ); - IPipelineBuilder PipeAsync( FunctionAsync next, string name ); - IPipelineBuilder PipeAsync( FunctionAsync next, Action config = null ); + public static IPipelineBuilder Pipe( + this IPipelineBuilder parent, + Function next, + string name + ) + { + return PipeStatementBuilder.Pipe( parent, next, config => config.Name = name ); + } + + public static IPipelineBuilder Pipe( + this IPipelineBuilder parent, + Function next, + Action config = null + ) + { + return PipeStatementBuilder.Pipe( parent, next, config ); + } + + public static IPipelineBuilder PipeAsync( + this IPipelineBuilder parent, + FunctionAsync next, + string name + ) + { + return PipeStatementBuilder.PipeAsync( parent, next, config => config.Name = name ); + } + + public static IPipelineBuilder PipeAsync( + this IPipelineBuilder parent, + FunctionAsync next, + Action config = null + ) + { + return PipeStatementBuilder.PipeAsync( parent, next, config ); + } } -public partial class PipelineBuilder +internal static class PipeStatementBuilder { - public IPipelineBuilder Pipe( Function next, string name ) => Pipe( next, config => config.Name = name ); - - public IPipelineBuilder Pipe( Function next, Action config = null ) + public static IPipelineBuilder Pipe( + IPipelineBuilder parent, + Function next, + Action config = null + ) { ArgumentNullException.ThrowIfNull( next ); + var (parentFunction, parentMiddleware) = parent.GetPipelineFunction(); + return new PipelineBuilder { - Function = new PipeStatementBinder( Function, Middleware, config ).Bind( AsyncNext, next.Method ), - Middleware = Middleware + Function = new PipeStatementBinder( parentFunction, parentMiddleware, config ).Bind( AsyncNext, next.Method ), + Middleware = parentMiddleware }; // task wrapper @@ -33,16 +69,20 @@ Task AsyncNext( IPipelineContext context, TOutput argument ) } } - public IPipelineBuilder PipeAsync( FunctionAsync next, string name ) => PipeAsync( next, config => config.Name = name ); - - public IPipelineBuilder PipeAsync( FunctionAsync next, Action config = null ) + public static IPipelineBuilder PipeAsync( + IPipelineBuilder parent, + FunctionAsync next, + Action config = null + ) { ArgumentNullException.ThrowIfNull( next ); + var (parentFunction, parentMiddleware) = parent.GetPipelineFunction(); + return new PipelineBuilder { - Function = new PipeStatementBinder( Function, Middleware, config ).Bind( next ), - Middleware = Middleware + Function = new PipeStatementBinder( parentFunction, parentMiddleware, config ).Bind( next ), + Middleware = parentMiddleware }; } } diff --git a/src/Hyperbee.Pipeline/Builders/ReduceBlockBuilder.cs b/src/Hyperbee.Pipeline/Builders/ReduceBlockBuilder.cs index 2910aad..da7dc2c 100644 --- a/src/Hyperbee.Pipeline/Builders/ReduceBlockBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/ReduceBlockBuilder.cs @@ -1,32 +1,55 @@ using Hyperbee.Pipeline.Binders; +using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline; -public partial interface IPipelineBuilder +public static class ReduceBlockBuilder { - IPipelineBuilder Reduce( Func reducer, Func, IPipelineBuilder> builder ); - IPipelineBuilder ReduceAsync( bool inheritMiddleware, Func reducer, Func, IPipelineBuilder> builder ); -} + public static ReduceBlockBuilderWrapper Reduce( this IPipelineBuilder parent ) + { + return new ReduceBlockBuilderWrapper( parent ); + } -public partial class PipelineBuilder -{ - public IPipelineBuilder Reduce( Func reducer, Func, IPipelineBuilder> builder ) + public class ReduceBlockBuilderWrapper( IPipelineBuilder parent ) { - return ReduceAsync( true, reducer, builder ); + public IPipelineBuilder Type( + Func reducer, + Func, IPipelineBuilder> builder + ) + { + return ReduceBlockBuilder.ReduceAsync( parent, true, reducer, builder ); + } + + public IPipelineBuilder Type( + bool inheritMiddleware, + Func reducer, + Func, IPipelineBuilder> builder + ) + { + return ReduceBlockBuilder.ReduceAsync( parent, inheritMiddleware, reducer, builder ); + } } +} - public IPipelineBuilder ReduceAsync( bool inheritMiddleware, Func reducer, Func, IPipelineBuilder> builder ) +internal static class ReduceBlockBuilder +{ + public static IPipelineBuilder ReduceAsync( + IPipelineBuilder parent, + bool inheritMiddleware, + Func reducer, + Func, IPipelineBuilder> builder ) { ArgumentNullException.ThrowIfNull( builder ); - ArgumentNullException.ThrowIfNull( reducer ); - var block = PipelineFactory.Start( inheritMiddleware ? Middleware : null ); + var (parentFunction, parentMiddleware) = parent.GetPipelineFunction(); + + var block = PipelineFactory.Start( inheritMiddleware ? parentMiddleware : null ); var function = ((PipelineBuilder) builder( block )).Function; return new PipelineBuilder { - Function = new ReduceBlockBinder( reducer, Function ).Bind( function ), - Middleware = Middleware + Function = new ReduceBlockBinder( reducer, parentFunction ).Bind( function ), + Middleware = parentMiddleware }; } } diff --git a/src/Hyperbee.Pipeline/Builders/WaitAllBlockBuilder.cs b/src/Hyperbee.Pipeline/Builders/WaitAllBlockBuilder.cs index 3e8cb82..3e1327a 100644 --- a/src/Hyperbee.Pipeline/Builders/WaitAllBlockBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/WaitAllBlockBuilder.cs @@ -1,54 +1,85 @@ using Hyperbee.Pipeline.Binders; using Hyperbee.Pipeline.Context; +using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline; public delegate TOutput WaitAllReducer( IPipelineContext context, TInput input, WaitAllResult[] results ); -public partial interface IPipelineBuilder +public static class WaitAllBlockBuilder { - IPipelineBuilder WaitAll( Func, Func, IPipelineBuilder>[]> builders, WaitAllReducer reducer, Action config = null ); - IPipelineBuilder WaitAll( bool inheritMiddleware, Func, Func, IPipelineBuilder>[]> builders, WaitAllReducer reducer, Action config = null ); - IPipelineBuilder WaitAll( Func, Func, IPipelineBuilder>[]> builders, Action config = null ); - IPipelineBuilder WaitAll( bool inheritMiddleware, Func, Func, IPipelineBuilder>[]> builders, Action config = null ); + public static IPipelineBuilder WaitAll( + this IPipelineBuilder parent, + Func, Func, IPipelineBuilder>[]> builders, + WaitAllReducer reducer, + Action config = null ) + { + return WaitAllBlockBuilder.WaitAll( parent, true, builders, reducer, config ); + } + + public static IPipelineBuilder WaitAll( + this IPipelineBuilder parent, + bool inheritMiddleware, + Func, Func, IPipelineBuilder>[]> builders, + WaitAllReducer reducer, + Action config = null ) + { + return WaitAllBlockBuilder.WaitAll( parent, inheritMiddleware, builders, reducer, config ); + } + + public static IPipelineBuilder WaitAll( + this IPipelineBuilder parent, + Func, Func, IPipelineBuilder>[]> builders, + Action config = null ) + { + return WaitAllBlockBuilder.WaitAll( parent, true, builders, config ); + } + + public static IPipelineBuilder WaitAll( + this IPipelineBuilder parent, + bool inheritMiddleware, + Func, Func, IPipelineBuilder>[]> builders, + Action config = null ) + { + return WaitAllBlockBuilder.WaitAll( parent, inheritMiddleware, builders, config ); + } } -public partial class PipelineBuilder +internal static class WaitAllBlockBuilder { - public IPipelineBuilder WaitAll( - Func, - Func, IPipelineBuilder>[]> builders, + public static IPipelineBuilder WaitAll( + IPipelineBuilder parent, + Func, Func, IPipelineBuilder>[]> builders, WaitAllReducer reducer, Action config = null ) { - return WaitAll( true, builders, reducer, config ); + return WaitAll( parent, true, builders, reducer, config ); } - public IPipelineBuilder WaitAll( - Func, - Func, - IPipelineBuilder>[]> builders, + public static IPipelineBuilder WaitAll( + IPipelineBuilder parent, + Func, Func, IPipelineBuilder>[]> builders, Action config = null ) { - return WaitAll( true, builders, config ); + return WaitAll( parent, true, builders, config ); } - public IPipelineBuilder WaitAll( + public static IPipelineBuilder WaitAll( + IPipelineBuilder parent, bool inheritMiddleware, - Func, - Func, IPipelineBuilder>[]> builders, + Func, Func, IPipelineBuilder>[]> builders, Action config = null ) { - return WaitAll( true, builders, DefaultReducer, config ); + return WaitAll( parent, inheritMiddleware, builders, DefaultReducer, config ); // create a default reducer that returns the arg from the previous step static TOutput DefaultReducer( IPipelineContext ctx, TOutput arg, WaitAllResult[] results ) => arg; } - public IPipelineBuilder WaitAll( + public static IPipelineBuilder WaitAll( + IPipelineBuilder parent, bool inheritMiddleware, - Func, - Func, IPipelineBuilder>[]> builders, + Func, Func, IPipelineBuilder>[]> builders, WaitAllReducer reducer, Action config = null ) { @@ -59,15 +90,17 @@ public IPipelineBuilder WaitAll( if ( builderInstances.Length == 0 ) throw new ArgumentOutOfRangeException( nameof( builders ) ); + var (parentFunction, parentMiddleware) = parent.GetPipelineFunction(); + var functions = builderInstances - .Select( builder => new { builder, block = PipelineFactory.Start( inheritMiddleware ? Middleware : null ) } ) + .Select( builder => new { builder, block = PipelineFactory.Start( inheritMiddleware ? parentMiddleware : null ) } ) .Select( x => x.builder( x.block ).CastFunction() ) .ToArray(); return new PipelineBuilder { - Function = new WaitAllBlockBinder( Function, Middleware, config ).Bind( functions, reducer ), - Middleware = Middleware + Function = new WaitAllBlockBinder( parentFunction, parentMiddleware, config ).Bind( functions, reducer ), + Middleware = parentMiddleware }; } } diff --git a/src/Hyperbee.Pipeline/Builders/WrapBuilder.cs b/src/Hyperbee.Pipeline/Builders/WrapBuilder.cs index 8e292e4..d3b91fe 100644 --- a/src/Hyperbee.Pipeline/Builders/WrapBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/WrapBuilder.cs @@ -1,41 +1,73 @@ using Hyperbee.Pipeline.Binders; using Hyperbee.Pipeline.Context; +using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline; -public partial interface IPipelineBuilder +public static class WrapBuilder { - IPipelineBuilder WrapAsync( MiddlewareAsync pipelineMiddleware, string name ); - IPipelineBuilder WrapAsync( MiddlewareAsync pipelineMiddleware, Action config = null ); - IPipelineBuilder WrapAsync( IEnumerable> pipelineMiddleware, Action config = null ); + public static IPipelineBuilder WrapAsync( + this IPipelineBuilder parent, + MiddlewareAsync pipelineMiddleware, + string name + ) + { + return WrapBuilder.WrapAsync( parent, pipelineMiddleware, config => config.Name = name ); + } + + public static IPipelineBuilder WrapAsync( + this IPipelineBuilder parent, + MiddlewareAsync pipelineMiddleware, + Action config = null + ) + { + return WrapBuilder.WrapAsync( parent, pipelineMiddleware, config ); + } + + public static IPipelineBuilder WrapAsync( + this IPipelineBuilder parent, + IEnumerable> pipelineMiddleware, + Action config = null + ) + { + return WrapBuilder.WrapAsync( parent, pipelineMiddleware, config ); + } } -public partial class PipelineBuilder +internal static class WrapBuilder { - public IPipelineBuilder WrapAsync( MiddlewareAsync pipelineMiddleware, string name ) => WrapAsync( pipelineMiddleware, config => config.Name = name ); - - public IPipelineBuilder WrapAsync( MiddlewareAsync pipelineMiddleware, Action config = null ) + public static IPipelineBuilder WrapAsync( + IPipelineBuilder parent, + MiddlewareAsync pipelineMiddleware, + Action config = null + ) { if ( pipelineMiddleware == null ) - return this; + return parent; + + var (parentFunction, parentMiddleware) = parent.GetPipelineFunction(); return new PipelineBuilder { - Function = new WrapBinder( pipelineMiddleware, config ).Bind( Function ), - Middleware = Middleware + Function = new WrapBinder( pipelineMiddleware, config ).Bind( parentFunction ), + Middleware = parentMiddleware }; } - public IPipelineBuilder WrapAsync( IEnumerable> pipelineMiddleware, Action config = null ) + public static IPipelineBuilder WrapAsync( + IPipelineBuilder parent, + IEnumerable> pipelineMiddleware, + Action config = null + ) { if ( pipelineMiddleware == null ) - return this; + return parent; - var builder = this as IPipelineBuilder; + var builder = parent; foreach ( var middleware in pipelineMiddleware ) { - builder = WrapAsync( middleware, config ); + builder = WrapAsync( parent, middleware, config ); } return builder; diff --git a/src/Hyperbee.Pipeline/IPipelineBuilder.cs b/src/Hyperbee.Pipeline/IPipelineBuilder.cs index eb7120f..86b2c1c 100644 --- a/src/Hyperbee.Pipeline/IPipelineBuilder.cs +++ b/src/Hyperbee.Pipeline/IPipelineBuilder.cs @@ -33,12 +33,12 @@ public struct Empty; // // we solve for this using interfaces. -public partial interface IPipelineStartBuilder : IPipelineBuilder +public interface IPipelineStartBuilder : IPipelineBuilder { // head actions: (e.g. Hook) that are only valid at the start of the pipeline } -public partial interface IPipelineBuilder : IPipelineFinalBuilder +public interface IPipelineBuilder : IPipelineFinalBuilder { // normal actions } diff --git a/src/Hyperbee.Pipeline/IPipelineFunction.cs b/src/Hyperbee.Pipeline/IPipelineFunction.cs index be6f538..e4b6b04 100644 --- a/src/Hyperbee.Pipeline/IPipelineFunction.cs +++ b/src/Hyperbee.Pipeline/IPipelineFunction.cs @@ -1,14 +1,20 @@ namespace Hyperbee.Pipeline; -public interface IPipelineFunctionProvider +public interface IPipelineFunctionProvider { // Provide access to the Function and the Middleware // so people can implement their own custom binders. IPipelineFunction GetPipelineFunction(); } -public interface IPipelineFunction +public interface IPipelineFunction { FunctionAsync Function { get; } MiddlewareAsync Middleware { get; } + + void Deconstruct( out FunctionAsync function, out MiddlewareAsync middleware ) + { + function = Function; + middleware = Middleware; + } } diff --git a/src/Hyperbee.Pipeline/PipelineBuilder.cs b/src/Hyperbee.Pipeline/PipelineBuilder.cs index 1323464..2dfe12d 100644 --- a/src/Hyperbee.Pipeline/PipelineBuilder.cs +++ b/src/Hyperbee.Pipeline/PipelineBuilder.cs @@ -2,7 +2,7 @@ namespace Hyperbee.Pipeline; -public partial class PipelineBuilder : PipelineFactory, IPipelineStartBuilder, IPipelineFunctionProvider +public class PipelineBuilder : PipelineFactory, IPipelineStartBuilder, IPipelineFunctionProvider { internal FunctionAsync Function { get; init; } internal MiddlewareAsync Middleware { get; init; } @@ -67,7 +67,7 @@ FunctionAsync IPipelineBuilder.CastFunction() static TType Cast( object value ) => (TType) value; } - // custom builder and binders need access to Function and Middleware + // custom builders and binders need access to Function and Middleware IPipelineFunction IPipelineFunctionProvider.GetPipelineFunction() { return new PipelineFunction @@ -81,11 +81,5 @@ public record PipelineFunction : IPipelineFunction { public FunctionAsync Function { get; init; } public MiddlewareAsync Middleware { get; init; } - - public void Deconstruct( out FunctionAsync function, out MiddlewareAsync middleware ) - { - function = Function; - middleware = Middleware; - } } } diff --git a/test/Hyperbee.Pipeline.Tests/PipelineEnumerationTests.cs b/test/Hyperbee.Pipeline.Tests/PipelineEnumerationTests.cs index d76adef..6eb7a07 100644 --- a/test/Hyperbee.Pipeline.Tests/PipelineEnumerationTests.cs +++ b/test/Hyperbee.Pipeline.Tests/PipelineEnumerationTests.cs @@ -44,7 +44,7 @@ public async Task Pipeline_should_enumerate() var command = PipelineFactory .Start() .Pipe( ( ctx, arg ) => arg.Split( ' ' ) ) - .ForEach( builder => builder + .ForEach().Type( builder => builder .Pipe( ( ctx, arg ) => arg + "!" ) .Pipe( ( ctx, arg ) => count += 10 ) ) @@ -62,7 +62,7 @@ public async Task Pipeline_should_reduce() var command = PipelineFactory .Start() .Pipe( ( ctx, arg ) => arg.Split( ' ' ) ) - .Reduce( ( a, v ) => a + v, builder => builder + .Reduce().Type( ( a, v ) => a + v, builder => builder .Pipe( ( ctx, arg ) => int.Parse( arg ) + 10 ) ) .Pipe( ( ctx, arg ) => arg + 5 )