2017-02-07 16:13:33 +01:00
using System ;
2022-11-23 09:13:38 +01:00
using System.Diagnostics ;
2021-12-09 23:52:25 +01:00
using System.Linq ;
2017-02-07 16:13:33 +01:00
using System.Reflection ;
using System.Threading.Tasks ;
2018-12-19 20:50:56 +01:00
using Tapeti.Annotations ;
2017-02-07 16:13:33 +01:00
using Tapeti.Config ;
using Tapeti.Flow.Annotations ;
using Tapeti.Helpers ;
namespace Tapeti.Flow.Default
{
2022-11-23 09:25:46 +01: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 20:30:04 +02:00
internal class FlowBindingMiddleware : IControllerBindingMiddleware
2017-02-07 16:13:33 +01:00
{
2019-08-13 20:30:04 +02:00
public void Handle ( IControllerBindingContext context , Action next )
2017-02-07 16:13:33 +01:00
{
2017-02-15 22:05:01 +01:00
if ( context . Method . GetCustomAttribute < StartAttribute > ( ) ! = null )
return ;
2017-02-07 16:13:33 +01:00
RegisterYieldPointResult ( context ) ;
2017-02-12 19:04:26 +01:00
RegisterContinuationFilter ( context ) ;
2017-02-07 16:13:33 +01:00
next ( ) ;
ValidateRequestResponse ( context ) ;
}
2019-08-13 20:30:04 +02:00
private static void RegisterContinuationFilter ( IControllerBindingContext context )
2017-02-07 16:13:33 +01:00
{
var continuationAttribute = context . Method . GetCustomAttribute < ContinuationAttribute > ( ) ;
if ( continuationAttribute = = null )
return ;
2022-02-09 11:26:56 +01: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 20:48:40 +02:00
context . SetBindingTargetMode ( BindingTargetMode . Direct ) ;
context . Use ( new FlowContinuationMiddleware ( ) ) ;
2017-02-12 19:04:26 +01: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 09:13:38 +01:00
if ( value = = null )
throw new InvalidOperationException ( "Return value should be a Task, not null" ) ;
2024-04-08 14:20:15 +02:00
await ( ( Task ) value ) . ConfigureAwait ( false ) ;
await HandleParallelResponse ( messageContext ) . ConfigureAwait ( false ) ;
2017-02-12 19:04:26 +01:00
} ) ;
}
2023-04-06 14:03:34 +02:00
else if ( context . Result . Info . ParameterType = = typeof ( ValueTask ) )
2022-02-09 12:19:05 +01:00
{
context . Result . SetHandler ( async ( messageContext , value ) = >
{
2022-11-23 09:13:38 +01: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 14:20:15 +02:00
await ( ( ValueTask ) value ) . ConfigureAwait ( false ) ;
await HandleParallelResponse ( messageContext ) . ConfigureAwait ( false ) ;
2022-02-09 12:19:05 +01:00
} ) ;
}
2017-02-12 19:04:26 +01:00
else if ( context . Result . Info . ParameterType = = typeof ( void ) )
{
2022-11-22 13:20:47 +01:00
context . Result . SetHandler ( ( messageContext , _ ) = > HandleParallelResponse ( messageContext ) ) ;
2017-02-12 19:04:26 +01:00
}
else
2022-02-09 11:26:56 +01: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 23:52:25 +01:00
foreach ( var parameter in context . Parameters . Where ( p = > ! p . HasBinding & & p . Info . ParameterType = = typeof ( IFlowParallelRequest ) ) )
parameter . SetBinding ( ParallelRequestParameterFactory ) ;
2017-02-07 16:13:33 +01:00
}
2019-08-13 20:30:04 +02:00
private static void RegisterYieldPointResult ( IControllerBindingContext context )
2017-02-07 16:13:33 +01:00
{
2022-02-09 11:26:56 +01:00
if ( ! context . Result . Info . ParameterType . IsTypeOrTaskOf ( typeof ( IYieldPoint ) , out var taskType ) )
2017-02-07 16:13:33 +01:00
return ;
2022-02-09 11:26:56 +01: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 16:13:33 +01:00
{
2022-02-09 11:26:56 +01:00
case TaskType . None :
2022-11-23 09:13:38 +01: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 11:26:56 +01:00
break ;
case TaskType . Task :
context . Result . SetHandler ( async ( messageContext , value ) = >
{
2022-11-23 09:13:38 +01:00
if ( value = = null )
throw new InvalidOperationException ( "Return value should be a Task<IYieldPoint>, not null" ) ;
2024-04-08 14:20:15 +02:00
var yieldPoint = await ( ( Task < IYieldPoint > ) value ) . ConfigureAwait ( false ) ;
await HandleYieldPoint ( messageContext , yieldPoint ) . ConfigureAwait ( false ) ;
2022-02-09 11:26:56 +01:00
} ) ;
break ;
case TaskType . ValueTask :
context . Result . SetHandler ( async ( messageContext , value ) = >
{
2022-11-23 09:13:38 +01: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 14:20:15 +02:00
var yieldPoint = await ( ( ValueTask < IYieldPoint > ) value ) . ConfigureAwait ( false ) ;
await HandleYieldPoint ( messageContext , yieldPoint ) . ConfigureAwait ( false ) ;
2022-02-09 11:26:56 +01:00
} ) ;
break ;
default :
throw new ArgumentOutOfRangeException ( ) ;
2017-02-07 16:13:33 +01:00
}
}
2022-02-09 11:26:56 +01:00
private static ValueTask HandleYieldPoint ( IMessageContext context , IYieldPoint yieldPoint )
2017-02-07 16:13:33 +01:00
{
2019-08-13 20:30:04 +02:00
var flowHandler = context . Config . DependencyResolver . Resolve < IFlowHandler > ( ) ;
2019-08-15 12:04:03 +02:00
return flowHandler . Execute ( new FlowHandlerContext ( context ) , yieldPoint ) ;
2017-02-07 16:13:33 +01:00
}
2022-02-09 11:26:56 +01:00
private static ValueTask HandleParallelResponse ( IMessageContext context )
2017-02-12 19:04:26 +01:00
{
2024-03-13 13:43:43 +01:00
if ( ! context . TryGet < FlowMessageContextPayload > ( out var flowPayload ) )
return default ;
if ( flowPayload . FlowIsConverging )
2022-02-09 11:26:56 +01:00
return default ;
2020-01-20 16:47:59 +01:00
2019-08-13 20:30:04 +02:00
var flowHandler = context . Config . DependencyResolver . Resolve < IFlowHandler > ( ) ;
2019-08-15 12:04:03 +02:00
return flowHandler . Execute ( new FlowHandlerContext ( context ) , new DelegateYieldPoint ( async flowContext = >
2018-12-19 20:20:08 +01:00
{
2023-04-20 10:40:13 +02:00
// IFlowParallelRequest.AddRequest will store the flow immediately
if ( ! flowPayload . FlowContext . IsStoredOrDeleted ( ) )
2024-04-08 14:20:15 +02:00
await flowContext . Store ( context . Binding . QueueType = = QueueType . Durable ) . ConfigureAwait ( false ) ;
2018-12-19 20:20:08 +01:00
} ) ) ;
2017-02-12 19:04:26 +01:00
}
2019-08-13 20:30:04 +02:00
private static void ValidateRequestResponse ( IControllerBindingContext context )
2017-02-07 16:13:33 +01:00
{
var request = context . MessageClass ? . GetCustomAttribute < RequestAttribute > ( ) ;
if ( request ? . Response = = null )
return ;
2018-12-19 20:50:56 +01:00
if ( ! context . Result . Info . ParameterType . IsTypeOrTaskOf ( t = > t = = request . Response | | t = = typeof ( IYieldPoint ) , out _ ) )
2017-02-07 16:13:33 +01:00
throw new ResponseExpectedException ( $"Response of class {request.Response.FullName} expected in controller {context.Method.DeclaringType?.FullName}, method {context.Method.Name}" ) ;
}
2021-12-09 23:52:25 +01:00
2022-11-23 09:13:38 +01:00
private static object? ParallelRequestParameterFactory ( IMessageContext context )
2021-12-09 23:52:25 +01:00
{
var flowHandler = context . Config . DependencyResolver . Resolve < IFlowHandler > ( ) ;
return flowHandler . GetParallelRequest ( new FlowHandlerContext ( context ) ) ;
}
2017-02-07 16:13:33 +01:00
}
}