Skip to content

Commit

Permalink
[FEATURE]: Improve builder pattern (#35)
Browse files Browse the repository at this point in the history
* Refactor builders to eliminate reliance on partial interfaces and classes

---------

Co-authored-by: Brenton Farmer <[email protected]>
  • Loading branch information
github-actions[bot] and bfarmer67 authored Aug 9, 2024
1 parent e5a01f2 commit de4801b
Show file tree
Hide file tree
Showing 17 changed files with 468 additions and 178 deletions.
17 changes: 16 additions & 1 deletion docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ var count = 0;
var command = PipelineFactory
.Start<string>()
.Pipe( ( ctx, arg ) => arg.Split( ' ' ) )
.ForEach<string>( builder => builder
.ForEach().Type<string>( builder => builder
.Pipe( ( ctx, arg ) => count += 10 )
)
.Pipe( ( ctx, arg ) => count += 5 )
Expand All @@ -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<string>()
.Pipe( ( ctx, arg ) => arg.Split( ' ' ) )
.Reduce().Type<string, int>( ( 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
Expand Down
1 change: 1 addition & 0 deletions src/Hyperbee.Pipeline/Binders/Abstractions/Binder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Hyperbee.Pipeline.Extensions.Implementation;

namespace Hyperbee.Pipeline.Binders.Abstractions;

internal abstract class Binder<TInput, TOutput>
{
protected FunctionAsync<TInput, TOutput> Pipeline { get; }
Expand Down
42 changes: 28 additions & 14 deletions src/Hyperbee.Pipeline/Builders/CallBlockBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
using Hyperbee.Pipeline.Binders;
using Hyperbee.Pipeline.Extensions.Implementation;

namespace Hyperbee.Pipeline;

public partial interface IPipelineBuilder<TInput, TOutput>
public static class CallBlockBuilder
{
IPipelineBuilder<TInput, TOutput> Call( Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder );
IPipelineBuilder<TInput, TOutput> Call( bool inheritMiddleware, Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder );
}

public partial class PipelineBuilder<TInput, TOutput>
{
// Call an inner builder discarding the final result. Acts like an Action.
public static IPipelineBuilder<TInput, TOutput> Call<TInput, TOutput>(
this IPipelineBuilder<TInput, TOutput> parent,
Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder
)
{
return CallBlockBuilder<TInput, TOutput>.Call( parent, true, builder );
}

public IPipelineBuilder<TInput, TOutput> Call( Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder )
public static IPipelineBuilder<TInput, TOutput> Call<TInput, TOutput>(
this IPipelineBuilder<TInput, TOutput> parent,
bool inheritMiddleware,
Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder
)
{
return Call( true, builder );
return CallBlockBuilder<TInput, TOutput>.Call( parent, inheritMiddleware, builder );
}
}

public IPipelineBuilder<TInput, TOutput> Call( bool inheritMiddleware, Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder )
internal static class CallBlockBuilder<TInput, TOutput>
{
public static IPipelineBuilder<TInput, TOutput> Call(
IPipelineBuilder<TInput, TOutput> parent,
bool inheritMiddleware,
Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder
)
{
ArgumentNullException.ThrowIfNull( builder );

var block = PipelineFactory.Start<TOutput>( inheritMiddleware ? Middleware : null );
var (parentFunction, parentMiddleware) = parent.GetPipelineFunction();

var block = PipelineFactory.Start<TOutput>( inheritMiddleware ? parentMiddleware : null );
var function = builder( block ).CastFunction<TOutput, object>(); // cast because we don't know the final Pipe output value

return new PipelineBuilder<TInput, TOutput>
{
Function = new CallBlockBinder<TInput, TOutput>( Function ).Bind( function ),
Middleware = Middleware
Function = new CallBlockBinder<TInput, TOutput>( parentFunction ).Bind( function ),
Middleware = parentMiddleware
};
}
}
44 changes: 31 additions & 13 deletions src/Hyperbee.Pipeline/Builders/CallIfBlockBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,50 @@
using Hyperbee.Pipeline.Binders;
using Hyperbee.Pipeline.Extensions.Implementation;

namespace Hyperbee.Pipeline;

public partial interface IPipelineBuilder<TInput, TOutput>
public static class CallIfBuilder
{
IPipelineBuilder<TInput, TOutput> CallIf( Function<TOutput, bool> condition, Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder );
IPipelineBuilder<TInput, TOutput> CallIf( Function<TOutput, bool> condition, bool inheritMiddleware, Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder );
}

public partial class PipelineBuilder<TInput, TOutput>
{
public IPipelineBuilder<TInput, TOutput> CallIf( Function<TOutput, bool> condition, Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder )
public static IPipelineBuilder<TInput, TOutput> CallIf<TInput, TOutput>(
this IPipelineBuilder<TInput, TOutput> parent,
Function<TOutput, bool> condition,
Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder
)
{
return CallIfBuilder<TInput, TOutput>.CallIf( parent, condition, true, builder );
}
public static IPipelineBuilder<TInput, TOutput> CallIf<TInput, TOutput>(
this IPipelineBuilder<TInput, TOutput> parent,
Function<TOutput, bool> condition,
bool inheritMiddleware,
Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder
)
{
return CallIf( condition, true, builder );
return CallIfBuilder<TInput, TOutput>.CallIf( parent, condition, inheritMiddleware, builder );
}
}

public IPipelineBuilder<TInput, TOutput> CallIf( Function<TOutput, bool> condition, bool inheritMiddleware, Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder )
internal static class CallIfBuilder<TInput, TOutput>
{
public static IPipelineBuilder<TInput, TOutput> CallIf(
IPipelineBuilder<TInput, TOutput> parent,
Function<TOutput, bool> condition,
bool inheritMiddleware,
Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder> builder
)
{
ArgumentNullException.ThrowIfNull( builder );
ArgumentNullException.ThrowIfNull( condition );

var block = PipelineFactory.Start<TOutput>( inheritMiddleware ? Middleware : null );
var (parentFunction, parentMiddleware) = parent.GetPipelineFunction();

var block = PipelineFactory.Start<TOutput>( inheritMiddleware ? parentMiddleware : null );
var function = builder( block ).CastFunction<TOutput, object>(); // cast because we don't know the final Pipe output value

return new PipelineBuilder<TInput, TOutput>
{
Function = new CallIfBlockBinder<TInput, TOutput>( condition, Function ).Bind( function ),
Middleware = Middleware
Function = new CallIfBlockBinder<TInput, TOutput>( condition, parentFunction ).Bind( function ),
Middleware = parentMiddleware
};
}
}
74 changes: 57 additions & 17 deletions src/Hyperbee.Pipeline/Builders/CallStatementBuilder.cs
Original file line number Diff line number Diff line change
@@ -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<TInput, TOutput>
public static class CallStatementBuilder
{
IPipelineBuilder<TInput, TOutput> Call( Procedure<TOutput> next, string name );
IPipelineBuilder<TInput, TOutput> Call( Procedure<TOutput> next, Action<IPipelineContext> config = null );
IPipelineBuilder<TInput, TOutput> CallAsync( ProcedureAsync<TOutput> next, string name );
IPipelineBuilder<TInput, TOutput> CallAsync( ProcedureAsync<TOutput> next, Action<IPipelineContext> config = null );
public static IPipelineBuilder<TInput, TOutput> Call<TInput, TOutput>(
this IPipelineBuilder<TInput, TOutput> parent,
Procedure<TOutput> next, string name
)
{
return CallStatementBuilder<TInput, TOutput>.Call( parent, next, config => config.Name = name );
}

public static IPipelineBuilder<TInput, TOutput> Call<TInput, TOutput>(
this IPipelineBuilder<TInput, TOutput> parent,
Procedure<TOutput> next,
Action<IPipelineContext> config = null
)
{
return CallStatementBuilder<TInput, TOutput>.Call( parent, next, config );
}

public static IPipelineBuilder<TInput, TOutput> CallAsync<TInput, TOutput>(
this IPipelineBuilder<TInput, TOutput> parent,
ProcedureAsync<TOutput> next,
string name
)
{
return CallStatementBuilder<TInput, TOutput>.CallAsync( parent, next, config => config.Name = name );
}

public static IPipelineBuilder<TInput, TOutput> CallAsync<TInput, TOutput>(
this IPipelineBuilder<TInput, TOutput> parent,
ProcedureAsync<TOutput> next,
Action<IPipelineContext> config = null
)
{
return CallStatementBuilder<TInput, TOutput>.CallAsync( parent, next, config );
}
}

public partial class PipelineBuilder<TInput, TOutput>
internal static class CallStatementBuilder<TInput, TOutput>
{
public IPipelineBuilder<TInput, TOutput> Call( Procedure<TOutput> next, string name ) => Call( next, config => config.Name = name );

public IPipelineBuilder<TInput, TOutput> Call( Procedure<TOutput> next, Action<IPipelineContext> config = null )
public static IPipelineBuilder<TInput, TOutput> Call(
IPipelineBuilder<TInput, TOutput> parent,
Procedure<TOutput> next,
Action<IPipelineContext> config = null
)
{
ArgumentNullException.ThrowIfNull( next );

var (parentFunction, parentMiddleware) = parent.GetPipelineFunction();

return new PipelineBuilder<TInput, TOutput>
{
Function = new CallStatementBinder<TInput, TOutput>( Function, Middleware, config ).Bind( AsyncNext, next.Method ),
Middleware = Middleware
Function = new CallStatementBinder<TInput, TOutput>( parentFunction, parentMiddleware, config ).Bind( AsyncNext, next.Method ),
Middleware = parentMiddleware
};

// task wrapper
Expand All @@ -34,16 +70,20 @@ Task AsyncNext( IPipelineContext context, TOutput argument )
}
}

public IPipelineBuilder<TInput, TOutput> CallAsync( ProcedureAsync<TOutput> next, string name ) => CallAsync( next, config => config.Name = name );

public IPipelineBuilder<TInput, TOutput> CallAsync( ProcedureAsync<TOutput> next, Action<IPipelineContext> config = null )
public static IPipelineBuilder<TInput, TOutput> CallAsync(
IPipelineBuilder<TInput, TOutput> parent,
ProcedureAsync<TOutput> next,
Action<IPipelineContext> config = null
)
{
ArgumentNullException.ThrowIfNull( next );

var (parentFunction, parentMiddleware) = parent.GetPipelineFunction();

return new PipelineBuilder<TInput, TOutput>
{
Function = new CallStatementBinder<TInput, TOutput>( Function, Middleware, config ).Bind( next ),
Middleware = Middleware
Function = new CallStatementBinder<TInput, TOutput>( parentFunction, parentMiddleware, config ).Bind( next ),
Middleware = parentMiddleware
};
}
}
42 changes: 30 additions & 12 deletions src/Hyperbee.Pipeline/Builders/ForEachBlockBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,49 @@
using Hyperbee.Pipeline.Binders;
using Hyperbee.Pipeline.Extensions.Implementation;

namespace Hyperbee.Pipeline;

public partial interface IPipelineBuilder<TInput, TOutput>
public static class ForEachBlockBuilder
{
IPipelineBuilder<TInput, TOutput> ForEach<TElement>( Func<IPipelineStartBuilder<TElement, TElement>, IPipelineBuilder> builder );
IPipelineBuilder<TInput, TOutput> ForEachAsync<TElement>( bool inheritMiddleware, Func<IPipelineStartBuilder<TElement, TElement>, IPipelineBuilder> builder );
}
public static ForEachBlockBuilderWrapper<TInput, TOutput> ForEach<TInput, TOutput>( this IPipelineBuilder<TInput, TOutput> parent )
{
return new ForEachBlockBuilderWrapper<TInput, TOutput>( parent );
}

public partial class PipelineBuilder<TInput, TOutput>
{
public IPipelineBuilder<TInput, TOutput> ForEach<TElement>( Func<IPipelineStartBuilder<TElement, TElement>, IPipelineBuilder> builder )
public class ForEachBlockBuilderWrapper<TInput, TOutput>( IPipelineBuilder<TInput, TOutput> parent )
{
return ForEachAsync( true, builder );
public IPipelineBuilder<TInput, TOutput> Type<TElement>( Func<IPipelineStartBuilder<TElement, TElement>, IPipelineBuilder> builder )
{
return ForEachBlockBuilder<TInput, TOutput, TElement>.ForEach( parent, true, builder );
}

public IPipelineBuilder<TInput, TOutput> Type<TElement>( bool inheritMiddleware, Func<IPipelineStartBuilder<TElement, TElement>, IPipelineBuilder> builder )
{
return ForEachBlockBuilder<TInput, TOutput, TElement>.ForEach( parent, inheritMiddleware, builder );
}
}
}

public IPipelineBuilder<TInput, TOutput> ForEachAsync<TElement>( bool inheritMiddleware, Func<IPipelineStartBuilder<TElement, TElement>, IPipelineBuilder> builder )
internal static class ForEachBlockBuilder<TInput, TOutput, TElement>
{
public static IPipelineBuilder<TInput, TOutput> ForEach(
IPipelineBuilder<TInput, TOutput> parent,
bool inheritMiddleware,
Func<IPipelineStartBuilder<TElement, TElement>, IPipelineBuilder> builder
)
{
ArgumentNullException.ThrowIfNull( builder );

var block = PipelineFactory.Start<TElement>( inheritMiddleware ? Middleware : null );
var (parentFunction, parentMiddleware) = parent.GetPipelineFunction();

var block = PipelineFactory.Start<TElement>( inheritMiddleware ? parentMiddleware : null );
var function = builder( block ).CastFunction<TElement, object>(); // cast because we don't know the final Pipe output value

return new PipelineBuilder<TInput, TOutput>
{
Function = new ForEachBlockBinder<TInput, TOutput, TElement>( Function ).Bind( function ),
Middleware = Middleware
Function = new ForEachBlockBinder<TInput, TOutput, TElement>( parentFunction ).Bind( function ),
Middleware = parentMiddleware
};
}
}

Loading

0 comments on commit de4801b

Please sign in to comment.