Skip to content

Commit

Permalink
Create Release 1.2.0 (#36)
Browse files Browse the repository at this point in the history
* [FEATURE]: Binder base (#31)

* Implement abstract binders
* Refactor existing binders to use abstract base

---------

Co-authored-by: Brenton Farmer <brent.farmer@wagglebee.net>

* [FEATURE]: Implement dedicated builder and binder for CallIfBlock and PipeIfBlock (#33)
* Factor CallIfBlock* and PipeIfBlock* into their own builders and binders.

---------

Co-authored-by: Brenton Farmer <brent.farmer@wagglebee.net>

* [FEATURE]: Improve builder pattern (#35)
* Refactor builders to eliminate reliance on partial interfaces and classes

---------

Co-authored-by: Brenton Farmer <brent.farmer@wagglebee.net>

* Fix footer
* Previous version was 'v1.1.6'. Version now 'v1.2.0'.

---------

Co-authored-by: Brenton Farmer <brent.farmer@wagglebee.net>
  • Loading branch information
bfarmer67 and bfarmer67 authored Aug 9, 2024
1 parent b11d002 commit 5c970a9
Show file tree
Hide file tree
Showing 37 changed files with 766 additions and 506 deletions.
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<!-- Solution version numbers -->
<PropertyGroup>
<MajorVersion>1</MajorVersion>
<MinorVersion>1</MinorVersion>
<PatchVersion>6</PatchVersion>
<MinorVersion>2</MinorVersion>
<PatchVersion>0</PatchVersion>
</PropertyGroup>
<!-- Disable automatic package publishing -->
<PropertyGroup>
Expand Down
2 changes: 0 additions & 2 deletions Hyperbee.Pipeline.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
LICENSE = LICENSE
README.md = README.md
solution-helper.psm1 = solution-helper.psm1
EndProjectSection
EndProject
Expand Down
2 changes: 1 addition & 1 deletion docs/_includes/nav_footer_custom.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<footer class="site-footer">
Hyperbee Json Docs
Hyperbee Pipeline Docs
</footer>
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
26 changes: 26 additions & 0 deletions src/Hyperbee.Pipeline/Binders/Abstractions/Binder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Hyperbee.Pipeline.Context;
using Hyperbee.Pipeline.Extensions.Implementation;

namespace Hyperbee.Pipeline.Binders.Abstractions;

internal abstract class Binder<TInput, TOutput>
{
protected FunctionAsync<TInput, TOutput> Pipeline { get; }
protected Action<IPipelineContext> Configure { get; }

protected Binder( FunctionAsync<TInput, TOutput> function, Action<IPipelineContext> configure )
{
Pipeline = function;
Configure = configure;
}

protected virtual async Task<(TOutput Result, bool Canceled)> ProcessPipelineAsync( IPipelineContext context, TInput argument )
{
var result = await Pipeline( context, argument ).ConfigureAwait( false );

var contextControl = (IPipelineContextControl) context;
var canceled = contextControl.HandleCancellationRequested( result );

return (canceled ? default : result, canceled);
}
}
20 changes: 20 additions & 0 deletions src/Hyperbee.Pipeline/Binders/Abstractions/BlockBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Hyperbee.Pipeline.Context;

namespace Hyperbee.Pipeline.Binders.Abstractions;

internal abstract class BlockBinder<TInput, TOutput> : Binder<TInput, TOutput>
{
protected BlockBinder( FunctionAsync<TInput, TOutput> function, Action<IPipelineContext> configure )
: base( function, configure )
{
}

// Using TArgument instead of TOutput allows more capabilities for special
// use cases where the next argument is not the same as the output type
// like ReduceBlockBinder and ForEachBlockBinder

protected virtual async Task<TNext> ProcessBlockAsync<TArgument, TNext>( FunctionAsync<TArgument, TNext> blockFunction, IPipelineContext context, TArgument nextArgument )
{
return await blockFunction( context, nextArgument ).ConfigureAwait( false );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Runtime.CompilerServices;
using Hyperbee.Pipeline.Context;

namespace Hyperbee.Pipeline.Binders.Abstractions;

internal abstract class ConditionalBlockBinder<TInput, TOutput> : BlockBinder<TInput, TOutput>
{
protected Function<TOutput, bool> Condition { get; }

protected ConditionalBlockBinder( Function<TOutput, bool> condition, FunctionAsync<TInput, TOutput> function, Action<IPipelineContext> configure )
: base( function, configure )
{
Condition = condition;
}

protected override async Task<TNext> ProcessBlockAsync<TArgument, TNext>( FunctionAsync<TArgument, TNext> blockFunction, IPipelineContext context, TArgument nextArgument )
{
if ( Condition != null && !Condition( context, CastTypeArg<TArgument, TOutput>( nextArgument ) ) )
{
return CastTypeArg<TArgument, TNext>( nextArgument );
}

return await base.ProcessBlockAsync( blockFunction, context, nextArgument ).ConfigureAwait( false );
}

[MethodImpl( MethodImplOptions.AggressiveInlining )]
private static TResult CastTypeArg<TType, TResult>( TType input )
{
return (TResult) (object) input;
}
}
31 changes: 31 additions & 0 deletions src/Hyperbee.Pipeline/Binders/Abstractions/StatementBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Hyperbee.Pipeline.Context;
using Hyperbee.Pipeline.Extensions.Implementation;

namespace Hyperbee.Pipeline.Binders.Abstractions;

internal abstract class StatementBinder<TInput, TOutput> : Binder<TInput, TOutput>
{
protected MiddlewareAsync<object, object> Middleware { get; }

protected StatementBinder( FunctionAsync<TInput, TOutput> function, MiddlewareAsync<object, object> middleware, Action<IPipelineContext> configure )
: base( function, configure )
{
Middleware = middleware;
}

protected virtual async Task<TNext> ProcessStatementAsync<TNext>( FunctionAsync<TOutput, TNext> nextFunction, IPipelineContext context, TOutput nextArgument, string frameName )
{
var contextControl = (IPipelineContextControl) context;

using var _ = contextControl.CreateFrame( context, Configure, frameName );

if ( Middleware == null )
return await nextFunction( context, nextArgument ).ConfigureAwait( false );

return (TNext) await Middleware(
context,
nextArgument,
async ( context1, argument1 ) => await nextFunction( context1, (TOutput) argument1 ).ConfigureAwait( false )
).ConfigureAwait( false );
}
}
24 changes: 9 additions & 15 deletions src/Hyperbee.Pipeline/Binders/CallBlockBinder.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
namespace Hyperbee.Pipeline.Binders;
using Hyperbee.Pipeline.Binders.Abstractions;

internal class CallBlockBinder<TInput, TOutput>
{
private FunctionAsync<TInput, TOutput> Pipeline { get; }
private Function<TOutput, bool> Condition { get; }
namespace Hyperbee.Pipeline.Binders;

internal class CallBlockBinder<TInput, TOutput> : BlockBinder<TInput, TOutput>
{
public CallBlockBinder( FunctionAsync<TInput, TOutput> function )
: this( null, function )
{
}

public CallBlockBinder( Function<TOutput, bool> condition, FunctionAsync<TInput, TOutput> function )
: base( function, default )
{
Condition = condition;
Pipeline = function;
}

public FunctionAsync<TInput, TOutput> Bind( FunctionAsync<TOutput, object> next )
{
return async ( context, argument ) =>
{
var nextArgument = await Pipeline( context, argument ).ConfigureAwait( false );
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false );
if ( Condition == null || Condition( context, nextArgument ) )
await next( context, nextArgument ).ConfigureAwait( false );
if ( canceled )
return default;
await ProcessBlockAsync( next, context, nextArgument ).ConfigureAwait( false );
return nextArgument;
};
}
Expand Down
25 changes: 25 additions & 0 deletions src/Hyperbee.Pipeline/Binders/CallIfBlockBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Hyperbee.Pipeline.Binders.Abstractions;

namespace Hyperbee.Pipeline.Binders;

internal class CallIfBlockBinder<TInput, TOutput> : ConditionalBlockBinder<TInput, TOutput>
{
public CallIfBlockBinder( Function<TOutput, bool> condition, FunctionAsync<TInput, TOutput> function )
: base( condition, function, default )
{
}

public FunctionAsync<TInput, TOutput> Bind( FunctionAsync<TOutput, object> next )
{
return async ( context, argument ) =>
{
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false );
if ( canceled )
return default;
await ProcessBlockAsync( next, context, nextArgument ).ConfigureAwait( false );
return nextArgument;
};
}
}
50 changes: 12 additions & 38 deletions src/Hyperbee.Pipeline/Binders/CallStatementBinder.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
using System.Reflection;
using Hyperbee.Pipeline.Binders.Abstractions;
using Hyperbee.Pipeline.Context;
using Hyperbee.Pipeline.Extensions.Implementation;

namespace Hyperbee.Pipeline.Binders;

internal class CallStatementBinder<TInput, TOutput>
internal class CallStatementBinder<TInput, TOutput> : StatementBinder<TInput, TOutput>
{
private FunctionAsync<TInput, TOutput> Pipeline { get; }
private MiddlewareAsync<object, object> Middleware { get; }
private Action<IPipelineContext> Configure { get; }

public CallStatementBinder( FunctionAsync<TInput, TOutput> function, MiddlewareAsync<object, object> middleware, Action<IPipelineContext> configure )
: base( function, middleware, configure )
{
Pipeline = function;
Middleware = middleware;
Configure = configure;
}

public FunctionAsync<TInput, TOutput> Bind( ProcedureAsync<TOutput> next, MethodInfo method = null )
Expand All @@ -23,38 +17,18 @@ public FunctionAsync<TInput, TOutput> Bind( ProcedureAsync<TOutput> next, Method

return async ( context, argument ) =>
{
var nextArgument = await Pipeline( context, argument ).ConfigureAwait( false );
var contextControl = (IPipelineContextControl) context;
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false );
if ( contextControl.HandleCancellationRequested( nextArgument ) )
if ( canceled )
return default;
using ( contextControl.CreateFrame( context, Configure, defaultName ) )
{
return await Next( next, context, nextArgument ).ConfigureAwait( false );
}
return await ProcessStatementAsync(
async ( ctx, arg ) =>
{
await next( ctx, arg ).ConfigureAwait( false );
return arg;
}, context, nextArgument, defaultName ).ConfigureAwait( false );
};
}

private async Task<TOutput> Next( ProcedureAsync<TOutput> next, IPipelineContext context, TOutput nextArgument )
{
if ( Middleware == null )
{
await next( context, nextArgument ).ConfigureAwait( false );
return nextArgument;
}

await Middleware(
context,
nextArgument,
async ( context1, argument1 ) =>
{
await next( context1, (TOutput) argument1 ).ConfigureAwait( false );
return nextArgument;
}
).ConfigureAwait( false );

return nextArgument;
}
}

20 changes: 13 additions & 7 deletions src/Hyperbee.Pipeline/Binders/ForEachBlockBinder.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
namespace Hyperbee.Pipeline.Binders;
using Hyperbee.Pipeline.Binders.Abstractions;

internal class ForEachBlockBinder<TInput, TOutput, TElement>
{
private FunctionAsync<TInput, TOutput> Pipeline { get; }
namespace Hyperbee.Pipeline.Binders;

internal class ForEachBlockBinder<TInput, TOutput, TElement> : BlockBinder<TInput, TOutput>
{
public ForEachBlockBinder( FunctionAsync<TInput, TOutput> function )
: base( function, default )
{
Pipeline = function;
}

public FunctionAsync<TInput, TOutput> Bind( FunctionAsync<TElement, object> next )

{
return async ( context, argument ) =>
{
var nextArgument = await Pipeline( context, argument ).ConfigureAwait( false );
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false );
if ( canceled )
return default;
var nextArguments = (IEnumerable<TElement>) nextArgument;
foreach ( var elementArgument in nextArguments )
{
await next( context, elementArgument ).ConfigureAwait( false );
await ProcessBlockAsync( next, context, elementArgument ).ConfigureAwait( false );
}
return nextArgument;
};
}
}

8 changes: 4 additions & 4 deletions src/Hyperbee.Pipeline/Binders/HookBinder.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
namespace Hyperbee.Pipeline.Binders;

internal class HookBinder<TInput1, TOutput1> // explicit Type Args due to <object,object> usage
internal class HookBinder<TInput, TOutput> // explicit Type Args due to <object,object> usage
{
private MiddlewareAsync<TInput1, TOutput1> Middleware { get; }
private MiddlewareAsync<TInput, TOutput> Middleware { get; }

public HookBinder( MiddlewareAsync<TInput1, TOutput1> middleware )
public HookBinder( MiddlewareAsync<TInput, TOutput> middleware )
{
Middleware = middleware ?? (async ( context, argument, next ) => await next( context, argument ).ConfigureAwait( false ));
}

public MiddlewareAsync<TInput1, TOutput1> Bind( MiddlewareAsync<TInput1, TOutput1> middleware )
public MiddlewareAsync<TInput, TOutput> Bind( MiddlewareAsync<TInput, TOutput> middleware )
{
return async ( context, argument, function ) =>
await middleware(
Expand Down
Loading

0 comments on commit 5c970a9

Please sign in to comment.