/
WorkflowQueryDefinition.cs
162 lines (149 loc) · 6.4 KB
/
WorkflowQueryDefinition.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Threading.Tasks;
namespace Temporalio.Workflows
{
/// <summary>
/// Definition of a workflow query.
/// </summary>
public class WorkflowQueryDefinition
{
private static readonly ConcurrentDictionary<MethodInfo, WorkflowQueryDefinition> MethodDefinitions = new();
private static readonly ConcurrentDictionary<PropertyInfo, WorkflowQueryDefinition> PropertyDefinitions = new();
private WorkflowQueryDefinition(string? name, MethodInfo? method, Delegate? del)
{
Name = name;
Method = method;
Delegate = del;
}
/// <summary>
/// Gets the query name. This is null if the query is dynamic.
/// </summary>
public string? Name { get; private init; }
/// <summary>
/// Gets a value indicating whether the query is dynamic.
/// </summary>
public bool Dynamic => Name == null;
/// <summary>
/// Gets the query method.
/// </summary>
internal MethodInfo? Method { get; private init; }
/// <summary>
/// Gets the query method if done with delegate.
/// </summary>
internal Delegate? Delegate { get; private init; }
/// <summary>
/// Get a query definition from a method or fail. The result is cached.
/// </summary>
/// <param name="method">Query method.</param>
/// <returns>Query definition.</returns>
public static WorkflowQueryDefinition FromMethod(MethodInfo method)
{
if (!method.IsPublic)
{
throw new ArgumentException($"WorkflowQuery method {method} must be public");
}
if (method.IsStatic)
{
throw new ArgumentException($"WorkflowQuery method {method} cannot be static");
}
return MethodDefinitions.GetOrAdd(method, CreateFromMethod);
}
/// <summary>
/// Get a query definition from a property getter or fail. The result is cached.
/// </summary>
/// <param name="property">Query property.</param>
/// <returns>Query definition.</returns>
public static WorkflowQueryDefinition FromProperty(PropertyInfo property) =>
PropertyDefinitions.GetOrAdd(property, _ =>
{
var attr = property.GetCustomAttribute<WorkflowQueryAttribute>(false) ??
throw new ArgumentException($"{property} missing WorkflowQuery attribute");
var method = property.GetGetMethod();
if (method == null)
{
throw new ArgumentException($"WorkflowQuery property {property} must have public getter");
}
else if (method.IsStatic)
{
throw new ArgumentException($"WorkflowQuery property {property} cannot be static");
}
else if (attr.Dynamic)
{
throw new ArgumentException($"WorkflowQuery property {property} cannot be dynamic");
}
return new(attr.Name ?? property.Name, method, null);
});
/// <summary>
/// Creates a query definition from an explicit name and method. Most users should use
/// <see cref="FromMethod" /> with attributes instead.
/// </summary>
/// <param name="name">Query name. Null for dynamic query.</param>
/// <param name="del">Query delegate.</param>
/// <returns>Query definition.</returns>
public static WorkflowQueryDefinition CreateWithoutAttribute(string? name, Delegate del)
{
AssertValid(del.Method, dynamic: name == null);
return new(name, null, del);
}
/// <summary>
/// Gets the query name for calling or fail if no attribute or if dynamic.
/// </summary>
/// <param name="method">Method to get name from.</param>
/// <returns>Name.</returns>
internal static string NameFromMethodForCall(MethodInfo method)
{
var defn = FromMethod(method);
return defn.Name ??
throw new ArgumentException(
$"{method} cannot be used directly since it is a dynamic query");
}
/// <summary>
/// Gets the query name for calling or fail if no attribute or if dynamic.
/// </summary>
/// <param name="property">Property to get name from.</param>
/// <returns>Name.</returns>
internal static string NameFromPropertyForCall(PropertyInfo property)
{
var defn = FromProperty(property);
return defn.Name ??
throw new ArgumentException(
$"{property} cannot be used directly since it is a dynamic query");
}
private static WorkflowQueryDefinition CreateFromMethod(MethodInfo method)
{
var attr = method.GetCustomAttribute<WorkflowQueryAttribute>(false) ??
throw new ArgumentException($"{method} missing WorkflowQuery attribute");
AssertValid(method, attr.Dynamic);
var name = attr.Name;
if (attr.Dynamic && name != null)
{
throw new ArgumentException($"WorkflowQuery method {method} cannot be dynamic with custom name");
}
else if (!attr.Dynamic && name == null)
{
name = method.Name;
}
return new(name, method, null);
}
private static void AssertValid(MethodInfo method, bool dynamic)
{
// Method must not return void or a Task
if (method.ReturnType == typeof(void))
{
throw new ArgumentException($"WorkflowQuery method {method} must return a value");
}
if (typeof(Task).IsAssignableFrom(method.ReturnType))
{
throw new ArgumentException($"WorkflowQuery method {method} cannot return a Task");
}
// If it's dynamic, must have specific signature
if (dynamic && !WorkflowDefinition.HasValidDynamicParameters(method, requireNameFirst: true))
{
throw new ArgumentException(
$"WorkflowQuery method {method} must accept string and an array of IRawValue");
}
}
}
}