-
Notifications
You must be signed in to change notification settings - Fork 62
/
MethodInfoExtensions.cs
219 lines (194 loc) · 9.64 KB
/
MethodInfoExtensions.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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Mono.Cecil;
using Newtonsoft.Json.Linq;
namespace MakeFunctionJson
{
internal static class MethodInfoExtensions
{
/// <summary>
/// A method is an SDK method if it has a FunctionNameAttribute AND at least one parameter has an SDK attribute or the method has a NoAutomaticTriggerAttribute.
/// </summary>
/// <param name="method">method to check if an SDK method or not.</param>
/// <returns>true if <paramref name="method"/> is a WebJobs SDK method. False otherwise.</returns>
public static bool IsWebJobsSdkMethod(this MethodDefinition method)
{
return method.HasFunctionNameAttribute() && method.HasValidWebJobSdkTriggerAttribute();
}
public static bool HasValidWebJobSdkTriggerAttribute(this MethodDefinition method)
{
var hasNoAutomaticTrigger = method.HasNoAutomaticTriggerAttribute();
var hasTrigger = method.HasTriggerAttribute();
return (hasNoAutomaticTrigger || hasTrigger) && !(hasNoAutomaticTrigger && hasTrigger);
}
public static bool HasFunctionNameAttribute(this MethodDefinition method)
{
return method.CustomAttributes.FirstOrDefault(d => d.AttributeType.FullName == "Microsoft.Azure.WebJobs.FunctionNameAttribute") != null;
}
public static bool HasNoAutomaticTriggerAttribute(this MethodDefinition method)
{
return method.CustomAttributes.FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.NoAutomaticTriggerAttribute") != null;
}
public static bool HasTriggerAttribute(this MethodDefinition method)
{
return method.Parameters.Any(p => p.IsWebJobSdkTriggerParameter());
}
public static JObject ManualTriggerBinding(this MethodDefinition method)
{
var binding = new JObject { ["type"] = "manualTrigger", ["direction"] = "in" };
var stringParameter = method.Parameters.FirstOrDefault(p => p.ParameterType.FullName == typeof(string).FullName);
if (stringParameter != null)
{
binding["name"] = stringParameter.Name;
}
return binding;
}
/// <summary>
/// Converts a <see cref="MethodInfo"/> to a <see cref="FunctionJsonSchema"/>
/// </summary>
/// <param name="method">method to convert to a <see cref="FunctionJsonSchema"/> object. The method has to be <see cref="IsWebJobsSdkMethod(MethodInfo)"/> </param>
/// <param name="assemblyPath">This will be the value of <see cref="FunctionJsonSchema.ScriptFile"/> on the returned value.</param>
/// <returns><see cref="FunctionJsonSchema"/> object that represents the passed in <paramref name="method"/>.</returns>
public static FunctionJsonSchema ToFunctionJson(this MethodDefinition method, string assemblyPath)
{
// For every SDK parameter, convert it to a FunctionJson bindings.
// Every parameter can potentially contain more than 1 attribute that will be converted into a binding object.
var bindingsFromParameters = method.HasNoAutomaticTriggerAttribute() ? new[] { method.ManualTriggerBinding() } : method.Parameters
.Select(p => p.ToFunctionJsonBindings())
.SelectMany(i => i);
// Get binding if a return attribute is used.
// Ex: [return: Queue("myqueue-items-a", Connection = "MyStorageConnStr")]
var returnBindings = GetOutputBindingsFromReturnAttribute(method);
var allBindings = bindingsFromParameters.Concat(returnBindings).ToArray();
return new FunctionJsonSchema
{
Bindings = allBindings,
// Entry point is the fully qualified name of the function
EntryPoint = $"{method.DeclaringType.FullName}.{method.Name}",
ScriptFile = assemblyPath,
// A method is disabled is any of it's parameters have [Disabled] attribute
// or if the method itself or class have the [Disabled] attribute.
Disabled = method.GetDisabled()
};
}
/// <summary>
/// Gets bindings from return expression used with a binding expression.
/// Ex:
/// [FunctionName("HttpTriggerWriteToQueue1")]
/// [return: Queue("myqueue-items-a", Connection = "MyStorageConnStra")]
/// public static string Run([HttpTrigger] HttpRequestMessage request) => "foo";
/// </summary>
private static JObject[] GetOutputBindingsFromReturnAttribute(MethodDefinition method)
{
if (method.MethodReturnType == null)
{
return Array.Empty<JObject>();
}
var outputBindings = new List<JObject>();
foreach (var attribute in method.MethodReturnType.CustomAttributes.Where(a=>a.IsWebJobsAttribute()))
{
var bindingJObject = attribute.ToReflection().ToJObject();
// return binding must have the direction attribute set to out.
// https://github.com/Azure/azure-functions-host/blob/dev/src/WebJobs.Script/Utility.cs#L561
bindingJObject["name"] = "$return";
bindingJObject["direction"] = "out";
outputBindings.Add(bindingJObject);
}
return outputBindings.ToArray();
}
/// <summary>
/// Gets a function name from a <paramref name="method"/>
/// </summary>
/// <param name="method">method has to be a WebJobs SDK method. <see cref="IsWebJobsSdkMethod(MethodInfo)"/></param>
/// <returns>Function name.</returns>
public static string GetSdkFunctionName(this MethodDefinition method)
{
if (!method.IsWebJobsSdkMethod())
{
throw new ArgumentException($"{nameof(method)} has to be a WebJob SDK function");
}
string functionName = method.CustomAttributes.FirstOrDefault(a => a.AttributeType.Name == "FunctionNameAttribute")?.ConstructorArguments[0].Value.ToString();
if (functionName != null)
{
return functionName;
}
else
{
throw new InvalidOperationException("Missing FunctionNameAttribute");
}
}
/// <summary>
/// A method is disabled is any of it's parameters have [Disabled] attribute
/// or if the method itself or class have the [Disabled] attribute. The overloads
/// are stringified so that the ScriptHost will do its job.
/// </summary>
/// <param name="method"></param>
/// <returns>a boolean true or false if the outcome is fixed, a string if the ScriptHost should interpret it</returns>
public static object GetDisabled(this MethodDefinition method)
{
var customAttribute = method.Parameters.Select(p => p.GetDisabledAttribute()).Where(a => a != null).FirstOrDefault() ??
method.GetDisabledAttribute() ??
method.DeclaringType.GetDisabledAttribute();
if (customAttribute != null)
{
var attribute = customAttribute.ToReflection();
// With a SettingName defined, just put that as string. The ScriptHost will evaluate it.
var settingName = attribute.GetValue<string>("SettingName");
if (!string.IsNullOrEmpty(settingName))
{
return settingName;
}
var providerType = attribute.GetValue<Type>("ProviderType");
if (providerType != null)
{
return providerType;
}
// With neither settingName or providerType, no arguments were given and it should always be true
return true;
}
// No attribute means not disabled
return false;
}
/// <summary>
///
/// </summary>
/// <param name="method"></param>
/// <returns></returns>
public static CustomAttribute GetDisabledAttribute(this MethodDefinition method)
{
return method.CustomAttributes.FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.DisableAttribute");
}
/// <summary>
/// A method has an unsupported attributes if it has any of the following:
/// 1) [Disabled("%settingName%")]
/// 2) [Disabled(typeof(TypeName))]
/// However this [Disabled("settingName")] is valid.
/// </summary>
/// <param name="method"></param>
/// <param name="error"></param>
/// <returns></returns>
public static bool HasUnsuportedAttributes(this MethodDefinition method, out string error)
{
error = string.Empty;
var disabled = method.GetDisabled();
if (disabled is string disabledStr &&
disabledStr.StartsWith("%") &&
disabledStr.EndsWith("%"))
{
error = "'%' expressions are not supported for 'Disable'. Use 'Disable(\"settingName\") instead of 'Disable(\"%settingName%\")'";
return true;
}
else if (disabled is Type)
{
error = "the constructor 'DisableAttribute(Type)' is not supported.";
return true;
}
else
{
return false;
}
}
}
}