Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store MethodInfo when creating an ActivityDefinition #369

Merged
merged 3 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 46 additions & 27 deletions src/Temporalio/Activities/ActivityDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ private ActivityDefinition(
Type returnType,
IReadOnlyCollection<Type> parameterTypes,
int requiredParameterCount,
Func<object?[], object?> invoker)
Func<object?[], object?> invoker,
MethodInfo? methodInfo)
{
Name = name;
ReturnType = returnType;
ParameterTypes = parameterTypes;
RequiredParameterCount = requiredParameterCount;
this.invoker = invoker;
MethodInfo = methodInfo;
}

/// <summary>
Expand Down Expand Up @@ -56,6 +58,11 @@ private ActivityDefinition(
/// </summary>
public bool Dynamic => Name == null;

/// <summary>
/// Gets the <see cref="MethodInfo"/>. Will only have a value if one was used to create this <see cref="ActivityDefinition"/>.
/// </summary>
public MethodInfo? MethodInfo { get; private init; }

/// <summary>
/// Create an activity definition from a delegate. <see cref="Delegate.DynamicInvoke" /> is
/// called on this delegate. The delegate must have an associated method and that method
Expand Down Expand Up @@ -89,31 +96,7 @@ public static ActivityDefinition Create(
int requiredParameterCount,
Func<object?[], object?> invoker)
{
// If there is a null name, which means dynamic, there must only be one parameter type
// and it must be varargs IRawValue
if (name == null && (
requiredParameterCount != 1 ||
parameterTypes.SingleOrDefault() != typeof(Converters.IRawValue[])))
{
throw new ArgumentException(
$"Dynamic activity must accept a required array of IRawValue");
}

if (requiredParameterCount > parameterTypes.Count)
{
throw new ArgumentException(
$"Activity {name} has more required parameters than parameters",
nameof(requiredParameterCount));
}
foreach (var parameterType in parameterTypes)
{
if (parameterType.IsByRef)
{
throw new ArgumentException(
$"Activity {name} has disallowed ref/out parameter");
}
}
return new(name, returnType, parameterTypes, requiredParameterCount, invoker);
return Create(name, returnType, parameterTypes, requiredParameterCount, invoker, methodInfo: null);
}

/// <summary>
Expand All @@ -136,7 +119,8 @@ public static ActivityDefinition Create(MethodInfo method, Func<object?[], objec
method.ReturnType,
parms.Select(p => p.ParameterType).ToArray(),
parms.Count(p => !p.HasDefaultValue),
parameters => invoker.Invoke(ParametersWithDefaults(parms, parameters)));
parameters => invoker.Invoke(ParametersWithDefaults(parms, parameters)),
method);
}

/// <summary>
Expand Down Expand Up @@ -300,5 +284,40 @@ internal static string NameFromMethodForCall(MethodInfo method)
}
return name;
}

private static ActivityDefinition Create(
string? name,
Type returnType,
IReadOnlyCollection<Type> parameterTypes,
int requiredParameterCount,
Func<object?[], object?> invoker,
MethodInfo? methodInfo)
{
// If there is a null name, which means dynamic, there must only be one parameter type
// and it must be varargs IRawValue
if (name == null && (
requiredParameterCount != 1 ||
parameterTypes.SingleOrDefault() != typeof(Converters.IRawValue[])))
{
throw new ArgumentException(
$"Dynamic activity must accept a required array of IRawValue");
}

if (requiredParameterCount > parameterTypes.Count)
{
throw new ArgumentException(
$"Activity {name} has more required parameters than parameters",
nameof(requiredParameterCount));
}
foreach (var parameterType in parameterTypes)
{
if (parameterType.IsByRef)
{
throw new ArgumentException(
$"Activity {name} has disallowed ref/out parameter");
}
}
return new(name, returnType, parameterTypes, requiredParameterCount, invoker, methodInfo);
}
}
}
35 changes: 35 additions & 0 deletions tests/Temporalio.Tests/Activities/ActivityDefinitionTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Temporalio.Tests.Activities;

using System.Reflection;
using System.Threading.Tasks;
using Temporalio.Activities;
using Temporalio.Converters;
Expand Down Expand Up @@ -173,6 +174,40 @@ public async Task CreateAll_ClosedGeneric_CanInvoke()
Assert.Equal("some-val", await defn.InvokeAsync(Array.Empty<object?>()));
}

[Fact]
public async Task Create_WithMethodInfo_HasValidMethodInfo()
{
var methodInfo = typeof(ActivityDefinitionTests)
.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
.Single(mi => mi.Name.Equals(nameof(GoodAct1Async), StringComparison.Ordinal));

var defn = ActivityDefinition.Create(methodInfo, objects => methodInfo!.Invoke(this, objects));
Assert.Equal(methodInfo, defn.MethodInfo);
}

[Fact]
public async Task Create_WithDelegate_HasValidMethodInfo()
{
var methodInfo = typeof(ActivityDefinitionTests)
.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
.Single(mi => mi.Name.Equals(nameof(GoodAct1Async), StringComparison.Ordinal));

var defn = ActivityDefinition.Create(GoodAct1Async);
Assert.Equal(methodInfo, defn.MethodInfo);
}

[Fact]
public async Task Create_WithLambda_DoesNotHaveValidMethodInfo()
{
var defn = ActivityDefinition.Create(
"some-name",
typeof(int),
new Type[] { typeof(int) },
1,
parameters => ((int)parameters[0]!) + 5);
Assert.Null(defn.MethodInfo);
}

protected static void BadAct1()
{
}
Expand Down
Loading