2017-02-07 15:13:33 +00:00
using System ;
2022-11-23 08:13:38 +00:00
using System.Diagnostics ;
2021-12-09 22:52:25 +00:00
using System.Linq ;
2017-02-07 15:13:33 +00:00
using System.Reflection ;
using System.Threading.Tasks ;
2018-12-19 19:50:56 +00:00
using Tapeti.Annotations ;
2017-02-07 15:13:33 +00:00
using Tapeti.Config ;
using Tapeti.Flow.Annotations ;
using Tapeti.Helpers ;
namespace Tapeti.Flow.Default
{
2022-11-23 08:25:46 +00:00
#if ! NET7_0_OR_GREATER
#pragma warning disable CS1591
public class UnreachableException : Exception
{
public UnreachableException ( string message ) : base ( message )
{
}
}
#pragma warning restore CS1591
#endif
2019-08-13 18:30:04 +00:00
internal class FlowBindingMiddleware : IControllerBindingMiddleware
2017-02-07 15:13:33 +00:00
{
2019-08-13 18:30:04 +00:00
public void Handle ( IControllerBindingContext context , Action next )
2017-02-07 15:13:33 +00:00
{
2017-02-15 21:05:01 +00:00
if ( context . Method . GetCustomAttribute < StartAttribute > ( ) ! = null )
return ;
2017-02-07 15:13:33 +00:00
RegisterYieldPointResult ( context ) ;
2017-02-12 18:04:26 +00:00
RegisterContinuationFilter ( context ) ;
2017-02-07 15:13:33 +00:00
next ( ) ;
ValidateRequestResponse ( context ) ;
}
2019-08-13 18:30:04 +00:00
private static void RegisterContinuationFilter ( IControllerBindingContext context )
2017-02-07 15:13:33 +00:00
{
var continuationAttribute = context . Method . GetCustomAttribute < ContinuationAttribute > ( ) ;
if ( continuationAttribute = = null )
return ;
2022-02-09 10:26:56 +00:00
if ( context . Method . IsStatic )
throw new ArgumentException ( $"Continuation attribute is not valid on static methods in controller {context.Method.DeclaringType?.FullName}, method {context.Method.Name}" ) ;
2019-08-14 18:48:40 +00:00
context . SetBindingTargetMode ( BindingTargetMode . Direct ) ;
context . Use ( new FlowContinuationMiddleware ( ) ) ;
2017-02-12 18:04:26 +00:00
if ( context . Result . HasHandler )
return ;
// Continuation without IYieldPoint indicates a ParallelRequestBuilder response handler,
// make sure to store it's state as well
if ( context . Result . Info . ParameterType = = typeof ( Task ) )
{
context . Result . SetHandler ( async ( messageContext , value ) = >
{
2022-11-23 08:13:38 +00:00
if ( value = = null )
throw new InvalidOperationException ( "Return value should be a Task, not null" ) ;
2024-04-08 12:20:15 +00:00
await ( ( Task ) value ) . ConfigureAwait ( false ) ;
await HandleParallelResponse ( messageContext ) . ConfigureAwait ( false ) ;
2017-02-12 18:04:26 +00:00
} ) ;
}
2023-04-06 12:03:34 +00:00
else if ( context . Result . Info . ParameterType = = typeof ( ValueTask ) )
2022-02-09 11:19:05 +00:00
{
context . Result . SetHandler ( async ( messageContext , value ) = >
{
2022-11-23 08:13:38 +00:00
if ( value = = null )
// ValueTask is a struct and should never be null
throw new UnreachableException ( "Return value should be a ValueTask, not null" ) ;
2024-04-08 12:20:15 +00:00
await ( ( ValueTask ) value ) . ConfigureAwait ( false ) ;
await HandleParallelResponse ( messageContext ) . ConfigureAwait ( false ) ;
2022-02-09 11:19:05 +00:00
} ) ;
}
2017-02-12 18:04:26 +00:00
else if ( context . Result . Info . ParameterType = = typeof ( void ) )
{
2022-11-22 12:20:47 +00:00
context . Result . SetHandler ( ( messageContext , _ ) = > HandleParallelResponse ( messageContext ) ) ;
2017-02-12 18:04:26 +00:00
}
else
2022-02-09 10:26:56 +00:00
throw new ArgumentException ( $"Result type must be IYieldPoint, Task or void in controller {context.Method.DeclaringType?.FullName}, method {context.Method.Name}" ) ;
2021-12-09 22:52:25 +00:00
foreach ( var parameter in context . Parameters . Where ( p = > ! p . HasBinding & & p . Info . ParameterType = = typeof ( IFlowParallelRequest ) ) )
parameter . SetBinding ( ParallelRequestParameterFactory ) ;
2017-02-07 15:13:33 +00:00
}
2019-08-13 18:30:04 +00:00
private static void RegisterYieldPointResult ( IControllerBindingContext context )
2017-02-07 15:13:33 +00:00
{
2022-02-09 10:26:56 +00:00
if ( ! context . Result . Info . ParameterType . IsTypeOrTaskOf ( typeof ( IYieldPoint ) , out var taskType ) )
2017-02-07 15:13:33 +00:00
return ;
2022-02-09 10:26:56 +00:00
if ( context . Method . IsStatic )
throw new ArgumentException ( $"Yield points are not valid on static methods in controller {context.Method.DeclaringType?.FullName}, method {context.Method.Name}" ) ;
switch ( taskType )
2017-02-07 15:13:33 +00:00
{
2022-02-09 10:26:56 +00:00
case TaskType . None :
2022-11-23 08:13:38 +00:00
context . Result . SetHandler ( ( messageContext , value ) = >
{
if ( value = = null )
throw new InvalidOperationException ( "Return value should be an IYieldPoint, not null" ) ;
return HandleYieldPoint ( messageContext , ( IYieldPoint ) value ) ;
} ) ;
2022-02-09 10:26:56 +00:00
break ;
case TaskType . Task :
context . Result . SetHandler ( async ( messageContext , value ) = >
{
2022-11-23 08:13:38 +00:00
if ( value = = null )
throw new InvalidOperationException ( "Return value should be a Task<IYieldPoint>, not null" ) ;
2024-04-08 12:20:15 +00:00
var yieldPoint = await ( ( Task < IYieldPoint > ) value ) . ConfigureAwait ( false ) ;
await HandleYieldPoint ( messageContext , yieldPoint ) . ConfigureAwait ( false ) ;
2022-02-09 10:26:56 +00:00
} ) ;
break ;
case TaskType . ValueTask :
context . Result . SetHandler ( async ( messageContext , value ) = >
{
2022-11-23 08:13:38 +00:00
if ( value = = null )
// ValueTask is a struct and should never be null
throw new UnreachableException ( "Return value should be a ValueTask<IYieldPoint>, not null" ) ;
2024-04-08 12:20:15 +00:00
var yieldPoint = await ( ( ValueTask < IYieldPoint > ) value ) . ConfigureAwait ( false ) ;
await HandleYieldPoint ( messageContext , yieldPoint ) . ConfigureAwait ( false ) ;
2022-02-09 10:26:56 +00:00
} ) ;
break ;
default :
throw new ArgumentOutOfRangeException ( ) ;
2017-02-07 15:13:33 +00:00
}
}
2022-02-09 10:26:56 +00:00
private static ValueTask HandleYieldPoint ( IMessageContext context , IYieldPoint yieldPoint )
2017-02-07 15:13:33 +00:00
{
2019-08-13 18:30:04 +00:00
var flowHandler = context . Config . DependencyResolver . Resolve < IFlowHandler > ( ) ;
2019-08-15 10:04:03 +00:00
return flowHandler . Execute ( new FlowHandlerContext ( context ) , yieldPoint ) ;
2017-02-07 15:13:33 +00:00
}
2022-02-09 10:26:56 +00:00
private static ValueTask HandleParallelResponse ( IMessageContext context )
2017-02-12 18:04:26 +00:00
{
2024-03-13 12:43:43 +00:00
if ( ! context . TryGet < FlowMessageContextPayload > ( out var flowPayload ) )
return default ;
if ( flowPayload . FlowIsConverging )
2022-02-09 10:26:56 +00:00
return default ;
2020-01-20 15:47:59 +00:00
2019-08-13 18:30:04 +00:00
var flowHandler = context . Config . DependencyResolver . Resolve < IFlowHandler > ( ) ;
2019-08-15 10:04:03 +00:00
return flowHandler . Execute ( new FlowHandlerContext ( context ) , new DelegateYieldPoint ( async flowContext = >
2018-12-19 19:20:08 +00:00
{
2023-04-20 08:40:13 +00:00
// IFlowParallelRequest.AddRequest will store the flow immediately
if ( ! flowPayload . FlowContext . IsStoredOrDeleted ( ) )
2024-04-08 12:20:15 +00:00
await flowContext . Store ( context . Binding . QueueType = = QueueType . Durable ) . ConfigureAwait ( false ) ;
2018-12-19 19:20:08 +00:00
} ) ) ;
2017-02-12 18:04:26 +00:00
}
2019-08-13 18:30:04 +00:00
private static void ValidateRequestResponse ( IControllerBindingContext context )
2017-02-07 15:13:33 +00:00
{
var request = context . MessageClass ? . GetCustomAttribute < RequestAttribute > ( ) ;
if ( request ? . Response = = null )
return ;
2018-12-19 19:50:56 +00:00
if ( ! context . Result . Info . ParameterType . IsTypeOrTaskOf ( t = > t = = request . Response | | t = = typeof ( IYieldPoint ) , out _ ) )
2017-02-07 15:13:33 +00:00
throw new ResponseExpectedException ( $"Response of class {request.Response.FullName} expected in controller {context.Method.DeclaringType?.FullName}, method {context.Method.Name}" ) ;
}
2021-12-09 22:52:25 +00:00
2022-11-23 08:13:38 +00:00
private static object? ParallelRequestParameterFactory ( IMessageContext context )
2021-12-09 22:52:25 +00:00
{
var flowHandler = context . Config . DependencyResolver . Resolve < IFlowHandler > ( ) ;
return flowHandler . GetParallelRequest ( new FlowHandlerContext ( context ) ) ;
}
2017-02-07 15:13:33 +00:00
}
}