1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Linq ;
4+ using System . Reflection ;
5+ using NexusRpc ;
6+ using NexusRpc . Handlers ;
7+
8+ namespace Temporalio . Extensions . Hosting
9+ {
10+ /// <summary>
11+ /// Helper for contructing <see cref="ServiceHandlerInstance"/>.
12+ /// </summary>
13+ /// <remarks>
14+ /// This is internal and should be moved to NexusRpc in the future.
15+ /// </remarks>
16+ internal static class ServiceHandlerInstanceHelper
17+ {
18+ /// <summary>
19+ /// Create a service handler instance from the given service handler type and handler factory.
20+ /// </summary>
21+ /// <param name="serviceHandlerType">The type of the Nexus service handler.</param>
22+ /// <param name="handlerFactory">A factory that converts method information into an operation handler.</param>
23+ /// <returns>A <see cref="ServiceHandlerInstance"/> for the given <paramref name="serviceHandlerType"/> type.</returns>
24+ public static ServiceHandlerInstance FromType ( Type serviceHandlerType , Func < MethodInfo , IOperationHandler < object ? , object ? > > handlerFactory )
25+ {
26+ var serviceDef = GetServiceDefinition ( serviceHandlerType ) ;
27+
28+ return new ServiceHandlerInstance (
29+ serviceDef ,
30+ CreateHandlers (
31+ serviceDef ,
32+ serviceHandlerType ,
33+ handlerFactory ) ) ;
34+ }
35+
36+ /// <summary>
37+ /// Creates a <see cref="ServiceDefinition"/> for the given service handler type.
38+ /// </summary>
39+ /// <param name="serviceHandlerType">The type of the Nexus service handler.</param>
40+ /// <returns>A <see cref="ServiceDefinition"/> for the given <paramref name="serviceHandlerType"/> type.</returns>
41+ private static ServiceDefinition GetServiceDefinition ( Type serviceHandlerType )
42+ {
43+ // Make sure the attribute is on the declaring type of the instance
44+ var handlerAttr = serviceHandlerType . GetCustomAttribute < NexusServiceHandlerAttribute > ( ) ??
45+ throw new ArgumentException ( "Missing NexusServiceHandler attribute" ) ;
46+ return ServiceDefinition . FromType ( handlerAttr . ServiceType ) ;
47+ }
48+
49+ /// <summary>
50+ /// Collects all public methods from the given type and its base types recursively.
51+ /// </summary>
52+ /// <param name="serviceHandlerType">The type of the Nexus service handler.</param>
53+ /// <param name="methods">The list to which discovered methods are added.</param>
54+ private static void CollectTypeMethods ( Type serviceHandlerType , List < MethodInfo > methods )
55+ {
56+ // Add all declared public static/instance methods that do not already have one like
57+ // it present
58+ foreach ( var method in serviceHandlerType . GetMethods (
59+ BindingFlags . Public | BindingFlags . Instance | BindingFlags . Static | BindingFlags . DeclaredOnly ) )
60+ {
61+ // Only add if there isn't already one that matches the base definition
62+ var baseDef = method . GetBaseDefinition ( ) ;
63+ if ( ! methods . Any ( m => baseDef == m . GetBaseDefinition ( ) ) )
64+ {
65+ methods . Add ( method ) ;
66+ }
67+ }
68+ if ( serviceHandlerType . BaseType is { } baseType )
69+ {
70+ CollectTypeMethods ( baseType , methods ) ;
71+ }
72+ }
73+
74+ /// <summary>
75+ /// Validates and adds an operation handler created from the given operation handler method.
76+ /// </summary>
77+ /// <param name="serviceDef">A <see cref="ServiceDefinition"/> for the given service handler type.</param>
78+ /// <param name="method">The method from which an operation hander is created.</param>
79+ /// <param name="handlerFactory">A factory that creates an operation handler for a given method.</param>
80+ /// <param name="opHandlers">The mapping of operation names to operation handlers.</param>
81+ private static void AddOperationHandler (
82+ ServiceDefinition serviceDef ,
83+ MethodInfo method ,
84+ Func < MethodInfo , IOperationHandler < object ? , object ? > > handlerFactory ,
85+ Dictionary < string , IOperationHandler < object ? , object ? > > opHandlers )
86+ {
87+ // Validate
88+ if ( method . GetParameters ( ) . Length != 0 )
89+ {
90+ throw new ArgumentException ( "Cannot have parameters" ) ;
91+ }
92+ if ( method . ContainsGenericParameters )
93+ {
94+ throw new ArgumentException ( "Cannot be generic" ) ;
95+ }
96+ if ( ! method . IsPublic )
97+ {
98+ throw new ArgumentException ( "Must be public" ) ;
99+ }
100+
101+ // Find definition by the method name
102+ var opDef = serviceDef . Operations . Values . FirstOrDefault ( o => o . MethodInfo ? . Name == method . Name ) ??
103+ throw new ArgumentException ( "No matching NexusOperation on the service interface" ) ;
104+
105+ // Check return
106+ var goodReturn = false ;
107+ if ( method . ReturnType . IsGenericType &&
108+ method . ReturnType . GetGenericTypeDefinition ( ) == typeof ( IOperationHandler < , > ) )
109+ {
110+ var args = method . ReturnType . GetGenericArguments ( ) ;
111+ goodReturn = args . Length == 2 &&
112+ NoValue . NormalizeVoidType ( args [ 0 ] ) == opDef . InputType &&
113+ NoValue . NormalizeVoidType ( args [ 1 ] ) == opDef . OutputType ;
114+ }
115+ if ( ! goodReturn )
116+ {
117+ var inType = opDef . InputType == typeof ( void ) ? typeof ( NoValue ) : opDef . InputType ;
118+ var outType = opDef . OutputType == typeof ( void ) ? typeof ( NoValue ) : opDef . OutputType ;
119+ throw new ArgumentException (
120+ $ "Expected return type of IOperationHandler<{ inType . Name } , { outType . Name } >") ;
121+ }
122+
123+ // Confirm not present already
124+ if ( opHandlers . ContainsKey ( opDef . Name ) )
125+ {
126+ throw new ArgumentException ( $ "Duplicate operation handler named ${ opDef . Name } ") ;
127+ }
128+
129+ opHandlers [ opDef . Name ] = handlerFactory ( method ) ;
130+ }
131+
132+ /// <summary>
133+ /// Creates a mapping of operation names to operation handlers for the given service handler type.
134+ /// </summary>
135+ /// <param name="serviceDef">A <see cref="ServiceDefinition"/> for the given service handler type.</param>
136+ /// <param name="serviceHandlerType">The type of the Nexus service handler.</param>
137+ /// <param name="handlerFactory">A factory that creates an operation handler for a given method.</param>
138+ /// <returns>A mapping of operation names to operation handlers.</returns>
139+ private static Dictionary < string , IOperationHandler < object ? , object ? > > CreateHandlers (
140+ ServiceDefinition serviceDef ,
141+ Type serviceHandlerType ,
142+ Func < MethodInfo , IOperationHandler < object ? , object ? > > handlerFactory )
143+ {
144+ // Collect all methods recursively
145+ var methods = new List < MethodInfo > ( ) ;
146+ CollectTypeMethods ( serviceHandlerType , methods ) ;
147+
148+ // Collect handlers from the method list
149+ var opHandlers = new Dictionary < string , IOperationHandler < object ? , object ? > > ( ) ;
150+ foreach ( var method in methods )
151+ {
152+ // Only care about ones with operation attribute
153+ if ( method . GetCustomAttribute < NexusOperationHandlerAttribute > ( ) == null )
154+ {
155+ continue ;
156+ }
157+
158+ try
159+ {
160+ AddOperationHandler ( serviceDef , method , handlerFactory , opHandlers ) ;
161+ }
162+ catch ( Exception e )
163+ {
164+ throw new ArgumentException (
165+ $ "Failed obtaining operation handler from { method . Name } ", e ) ;
166+ }
167+ }
168+
169+ return opHandlers ;
170+ }
171+ }
172+ }
0 commit comments