Skip to content
Draft
11 changes: 6 additions & 5 deletions src/Hyperbee.Pipeline/Binders/Abstractions/Binder.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
using Hyperbee.Pipeline.Context;
using System.Linq.Expressions;
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 Expression<FunctionAsync<TInput, TOutput>> Pipeline { get; }
protected Action<IPipelineContext> Configure { get; }

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

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

var contextControl = (IPipelineContextControl) context;
var canceled = contextControl.HandleCancellationRequested( result );
Expand Down
5 changes: 3 additions & 2 deletions src/Hyperbee.Pipeline/Binders/Abstractions/BlockBinder.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using Hyperbee.Pipeline.Context;
using System.Linq.Expressions;
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 )
protected BlockBinder( Expression<FunctionAsync<TInput, TOutput>> function, Action<IPipelineContext> configure )
: base( function, configure )
{
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using Hyperbee.Pipeline.Context;

namespace Hyperbee.Pipeline.Binders.Abstractions;
Expand All @@ -7,7 +8,7 @@ internal abstract class ConditionalBlockBinder<TInput, TOutput> : BlockBinder<TI
{
protected Function<TOutput, bool> Condition { get; }

protected ConditionalBlockBinder( Function<TOutput, bool> condition, FunctionAsync<TInput, TOutput> function, Action<IPipelineContext> configure )
protected ConditionalBlockBinder( Function<TOutput, bool> condition, Expression<FunctionAsync<TInput, TOutput>> function, Action<IPipelineContext> configure )
: base( function, configure )
{
Condition = condition;
Expand Down
5 changes: 3 additions & 2 deletions src/Hyperbee.Pipeline/Binders/Abstractions/StatementBinder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Hyperbee.Pipeline.Context;
using System.Linq.Expressions;
using Hyperbee.Pipeline.Context;
using Hyperbee.Pipeline.Extensions.Implementation;

namespace Hyperbee.Pipeline.Binders.Abstractions;
Expand All @@ -7,7 +8,7 @@ internal abstract class StatementBinder<TInput, TOutput> : Binder<TInput, TOutpu
{
protected MiddlewareAsync<object, object> Middleware { get; }

protected StatementBinder( FunctionAsync<TInput, TOutput> function, MiddlewareAsync<object, object> middleware, Action<IPipelineContext> configure )
protected StatementBinder( Expression<FunctionAsync<TInput, TOutput>> function, MiddlewareAsync<object, object> middleware, Action<IPipelineContext> configure )
: base( function, configure )
{
Middleware = middleware;
Expand Down
51 changes: 40 additions & 11 deletions src/Hyperbee.Pipeline/Binders/CallBlockBinder.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,54 @@
using Hyperbee.Pipeline.Binders.Abstractions;
using System.Linq.Expressions;
using System.Reflection;
using Hyperbee.Pipeline.Binders.Abstractions;
using Hyperbee.Pipeline.Context;

namespace Hyperbee.Pipeline.Binders;

internal class CallBlockBinder<TInput, TOutput> : BlockBinder<TInput, TOutput>
{
public CallBlockBinder( FunctionAsync<TInput, TOutput> function )
public CallBlockBinder( Expression<FunctionAsync<TInput, TOutput>> function )
: base( function, default )
{
}

public FunctionAsync<TInput, TOutput> Bind( FunctionAsync<TOutput, object> next )
public Expression<FunctionAsync<TInput, TOutput>> Bind( Expression<FunctionAsync<TOutput, object>> next )
{
return async ( context, argument ) =>
{
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false );
// Get the MethodInfo for the helper method
var bindImplAsyncMethodInfo = typeof( CallBlockBinder<TInput, TOutput> )
.GetMethod( nameof( BindImplAsync ), BindingFlags.NonPublic | BindingFlags.Instance )!;

if ( canceled )
return default;
// Create parameters for the lambda expression
var paramContext = Expression.Parameter( typeof( IPipelineContext ), "context" );
var paramArgument = Expression.Parameter( typeof( TInput ), "argument" );

await ProcessBlockAsync( next, context, nextArgument ).ConfigureAwait( false );
return nextArgument;
};
// Create a call expression to the helper method
var callBindImplAsync = Expression.Call(
Expression.Constant( this ),
bindImplAsyncMethodInfo,
next,
Pipeline,
paramContext,
paramArgument
);

// Create and return the final expression
return Expression.Lambda<FunctionAsync<TInput, TOutput>>( callBindImplAsync, paramContext, paramArgument );
}

private async Task<TOutput> BindImplAsync(
FunctionAsync<TOutput, object> next,
FunctionAsync<TInput, TOutput> pipeline,
IPipelineContext context,
TInput argument )
{
var (nextArgument, canceled) =
await ProcessPipelineAsync( context, argument, pipeline ).ConfigureAwait( false );

if ( canceled )
return default;

await ProcessBlockAsync( next, context, nextArgument ).ConfigureAwait( false );
return nextArgument;
}
}
52 changes: 41 additions & 11 deletions src/Hyperbee.Pipeline/Binders/CallIfBlockBinder.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,55 @@
using Hyperbee.Pipeline.Binders.Abstractions;
using System.Linq.Expressions;
using System.Reflection;
using Hyperbee.Pipeline.Binders.Abstractions;
using Hyperbee.Pipeline.Context;

namespace Hyperbee.Pipeline.Binders;

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

public FunctionAsync<TInput, TOutput> Bind( FunctionAsync<TOutput, object> next )
public Expression<FunctionAsync<TInput, TOutput>> Bind( Expression<FunctionAsync<TOutput, object>> next )
{
return async ( context, argument ) =>
{
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false );
// Get the MethodInfo for the helper method
var bindImplAsyncMethodInfo = typeof( CallIfBlockBinder<TInput, TOutput> )
.GetMethod( nameof( BindImplAsync ), BindingFlags.NonPublic | BindingFlags.Instance )!;

if ( canceled )
return default;
// Create parameters for the lambda expression
var paramContext = Expression.Parameter( typeof( IPipelineContext ), "context" );
var paramArgument = Expression.Parameter( typeof( TInput ), "argument" );

await ProcessBlockAsync( next, context, nextArgument ).ConfigureAwait( false );
return nextArgument;
};
// Create a call expression to the helper method
var callBindImplAsync = Expression.Call(
Expression.Constant( this ),
bindImplAsyncMethodInfo,
next,
Pipeline,
paramContext,
paramArgument
);

// Create and return the final expression
return Expression.Lambda<FunctionAsync<TInput, TOutput>>( callBindImplAsync, paramContext, paramArgument );
}

private async Task<TOutput> BindImplAsync(
FunctionAsync<TOutput, object> next,
FunctionAsync<TInput, TOutput> pipeline,
IPipelineContext context,
TInput argument )
{
var (nextArgument, canceled) =
await ProcessPipelineAsync( context, argument, pipeline ).ConfigureAwait( false );

if ( canceled )
return default;

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

}
87 changes: 70 additions & 17 deletions src/Hyperbee.Pipeline/Binders/CallStatementBinder.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,87 @@
using System.Reflection;
using System.Linq.Expressions;
using System.Reflection;
using Hyperbee.Pipeline.Binders.Abstractions;
using Hyperbee.Pipeline.Context;

namespace Hyperbee.Pipeline.Binders;

internal class CallStatementBinder<TInput, TOutput> : StatementBinder<TInput, TOutput>
{
public CallStatementBinder( FunctionAsync<TInput, TOutput> function, MiddlewareAsync<object, object> middleware, Action<IPipelineContext> configure )
public CallStatementBinder( Expression<FunctionAsync<TInput, TOutput>> function, MiddlewareAsync<object, object> middleware, Action<IPipelineContext> configure )
: base( function, middleware, configure )
{
}

public FunctionAsync<TInput, TOutput> Bind( ProcedureAsync<TOutput> next, MethodInfo method = null )
public Expression<FunctionAsync<TInput, TOutput>> Bind( ProcedureAsync<TOutput> next, MethodInfo method = null )
{
var defaultName = (method ?? next.Method).Name;

return async ( context, argument ) =>
{
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false );

if ( canceled )
return default;

return await ProcessStatementAsync(
async ( ctx, arg ) =>
{
await next( ctx, arg ).ConfigureAwait( false );
return arg;
}, context, nextArgument, defaultName ).ConfigureAwait( false );
};
// Get the MethodInfo for the helper method
var bindImplAsyncMethodInfo = typeof( CallStatementBinder<TInput, TOutput> )
.GetMethod( nameof( BindImplAsync ), BindingFlags.NonPublic | BindingFlags.Instance )!;

// Create parameters for the lambda expression
var paramContext = Expression.Parameter( typeof( IPipelineContext ), "context" );
var paramArgument = Expression.Parameter( typeof( TInput ), "argument" );

// Create a call expression to the helper method
var callBindImplAsync = Expression.Call(
Expression.Constant( this ),
bindImplAsyncMethodInfo,
ExpressionBinder.ToExpression( next ),
Pipeline,
paramContext,
paramArgument,
Expression.Constant( defaultName )
);

// Create and return the final expression
return Expression.Lambda<FunctionAsync<TInput, TOutput>>( callBindImplAsync, paramContext, paramArgument );
}

private async Task<TOutput> BindImplAsync(
ProcedureAsync<TOutput> next,
FunctionAsync<TInput, TOutput> pipeline,
IPipelineContext context,
TInput argument,
string defaultName )
{
var (nextArgument, canceled) =
await ProcessPipelineAsync( context, argument, pipeline ).ConfigureAwait( false );

if ( canceled )
return default;

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, MiddlewareAsync<object, object> middleware, 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;
}*/

}

57 changes: 42 additions & 15 deletions src/Hyperbee.Pipeline/Binders/ForEachBlockBinder.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,60 @@
using Hyperbee.Pipeline.Binders.Abstractions;
using System.Linq.Expressions;
using System.Reflection;
using Hyperbee.Pipeline.Binders.Abstractions;
using Hyperbee.Pipeline.Context;

namespace Hyperbee.Pipeline.Binders;

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

public FunctionAsync<TInput, TOutput> Bind( FunctionAsync<TElement, object> next )
public Expression<FunctionAsync<TInput, TOutput>> Bind( Expression<FunctionAsync<TElement, object>> next )
{
// Get the MethodInfo for the helper method
var bindImplAsyncMethodInfo = typeof( ForEachBlockBinder<TInput, TOutput, TElement> )
.GetMethod( nameof( BindImplAsync ), BindingFlags.NonPublic | BindingFlags.Instance )!;

// Create parameters for the lambda expression
var paramContext = Expression.Parameter( typeof( IPipelineContext ), "context" );
var paramArgument = Expression.Parameter( typeof( TInput ), "argument" );

// Create a call expression to the helper method
var callBindImplAsync = Expression.Call(
Expression.Constant( this ),
bindImplAsyncMethodInfo,
next,
Pipeline,
paramContext,
paramArgument
);

// Create and return the final expression
return Expression.Lambda<FunctionAsync<TInput, TOutput>>( callBindImplAsync, paramContext, paramArgument );
}

private async Task<TOutput> BindImplAsync(
FunctionAsync<TElement, object> next,
FunctionAsync<TInput, TOutput> pipeline,
IPipelineContext context,
TInput argument )
{
return async ( context, argument ) =>
{
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false );
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument, pipeline ).ConfigureAwait( false );

if ( canceled )
return default;
if ( canceled )
return default;

var nextArguments = (IEnumerable<TElement>) nextArgument;
var nextArguments = (IEnumerable<TElement>) nextArgument;

foreach ( var elementArgument in nextArguments )
{
await ProcessBlockAsync( next, context, elementArgument ).ConfigureAwait( false );
}
foreach ( var elementArgument in nextArguments )
{
await ProcessBlockAsync( next, context, elementArgument ).ConfigureAwait( false );
}

return nextArgument;
};
return nextArgument;
}
}

3 changes: 2 additions & 1 deletion src/Hyperbee.Pipeline/Binders/HookBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ internal class HookBinder<TInput, TOutput> // explicit Type Args due to <object,

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

public MiddlewareAsync<TInput, TOutput> Bind( MiddlewareAsync<TInput, TOutput> middleware )
Expand Down
Loading
Loading