diff --git a/.gitignore b/.gitignore
index fd2d83a8..673f996f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,5 @@ dist
*.user
.env
*.xmldocs.xml
-**/CONDUCTORSHARP_HEALTH.json
\ No newline at end of file
+**/CONDUCTORSHARP_HEALTH.json
+.idea
diff --git a/.husky/pre-commit b/.husky/pre-commit
old mode 100644
new mode 100755
diff --git a/src/ConductorSharp.Engine/Util/ExpressionUtil.cs b/src/ConductorSharp.Engine/Util/ExpressionUtil.cs
index b93b1272..3f8554ac 100644
--- a/src/ConductorSharp.Engine/Util/ExpressionUtil.cs
+++ b/src/ConductorSharp.Engine/Util/ExpressionUtil.cs
@@ -71,30 +71,63 @@ private static bool IsStringInterpolation(Expression expression) =>
&& methodExpression.Method.DeclaringType == typeof(string);
private static object EvaluateExpression(Expression expr) =>
- IsEvaluatable(expr) ? Expression.Lambda(expr).Compile().DynamicInvoke() : throw new NonEvaluatableExpressionException(expr);
+ GetEvaluatableExpression(expr) is { } evaluatableExpression
+ ? Expression.Lambda(evaluatableExpression).Compile().DynamicInvoke()
+ : throw new NonEvaluatableExpressionException(expr);
- private static bool IsEvaluatable(Expression expr)
+ private static Expression GetEvaluatableExpression(Expression expr, Expression parentExpr = null)
{
switch (expr)
{
case MemberExpression memExpr:
+ bool shouldCompileToJsonPath = ShouldCompileToJsonPathExpression(expr);
+ if (shouldCompileToJsonPath && parentExpr is MethodCallExpression methodCallExpression && methodCallExpression.Object == expr)
+ return null;
+
+ if (shouldCompileToJsonPath)
+ return Expression.Constant(CreateExpressionString(expr), typeof(string));
+
if (
typeof(ITaskModel).IsAssignableFrom(memExpr.Type)
|| typeof(WorkflowId).IsAssignableFrom(memExpr.Type)
|| typeof(IWorkflowInput).IsAssignableFrom(memExpr.Type)
)
- return false;
- return IsEvaluatable(memExpr.Expression);
+ return null;
+
+ if (memExpr.Member is PropertyInfo { GetMethod.IsStatic: true })
+ return memExpr;
+
+ if (memExpr.Member is PropertyInfo propertyInfo && propertyInfo.DeclaringType == typeof(Type))
+ return memExpr;
+
+ var subExpr = GetEvaluatableExpression(memExpr.Expression, parentExpr);
+ return memExpr.Expression is null || subExpr is not null ? memExpr.Update(subExpr) : null;
case MethodCallExpression methodExpr:
- return IsEvaluatable(methodExpr.Object) && methodExpr.Arguments.All(IsEvaluatable);
+ var argumentExpressions = methodExpr
+ .Arguments.Select(arg => GetEvaluatableExpression(arg, methodExpr))
+ .Where(argExpr => argExpr is not null)
+ .ToArray();
+ var objectExpression = GetEvaluatableExpression(methodExpr.Object, methodExpr);
+ return (methodExpr.Object is null || objectExpression is not null) && argumentExpressions.Length == methodExpr.Arguments.Count
+ ? methodExpr.Update(objectExpression, argumentExpressions)
+ : null;
case BinaryExpression binaryExpr:
- return IsEvaluatable(binaryExpr.Left) && IsEvaluatable(binaryExpr.Right);
+ return
+ GetEvaluatableExpression(binaryExpr.Left, binaryExpr) is { } left
+ && GetEvaluatableExpression(binaryExpr.Right, binaryExpr) is { } right
+ ? binaryExpr.Update(left, binaryExpr.Conversion, right)
+ : null;
case UnaryExpression unaryExpr:
- return IsEvaluatable(unaryExpr.Operand);
+ return unaryExpr.Update(GetEvaluatableExpression(unaryExpr.Operand, unaryExpr));
case ConditionalExpression condExpr:
- return IsEvaluatable(condExpr.Test) && IsEvaluatable(condExpr.IfTrue) && IsEvaluatable(condExpr.IfFalse);
+ return
+ GetEvaluatableExpression(condExpr.Test, condExpr) is { } test
+ && GetEvaluatableExpression(condExpr.IfTrue, condExpr) is { } ifTrue
+ && GetEvaluatableExpression(condExpr.IfFalse, condExpr) is { } ifFalse
+ ? condExpr.Update(test, ifTrue, ifFalse)
+ : null;
default:
- return true;
+ return expr;
}
}
diff --git a/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj b/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj
index 87ec8a06..f58b0ef4 100644
--- a/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj
+++ b/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj
@@ -44,6 +44,7 @@
+
diff --git a/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
index 727affcc..a1e60a11 100644
--- a/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
+++ b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
@@ -271,6 +271,15 @@ public void BuilderReturnsCorrectDefinitionDictionaryInitializationWorkflow()
Assert.Equal(expectedDefinition, definition);
}
+ [Fact]
+ public void BuilderReturnsCorrectDefinitionStaticFormatter()
+ {
+ var definition = GetDefinitionFromWorkflow();
+ var expectedDefinition = EmbeddedFileHelper.GetLinesFromEmbeddedFile("~/Samples/Workflows/StaticFormatter.json");
+
+ Assert.Equal(expectedDefinition, definition);
+ }
+
private static string GetDefinitionFromWorkflow()
where TWorkflow : IConfigurableWorkflow
{
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workflows/NonEvaluatableWorkflow.cs b/test/ConductorSharp.Engine.Tests/Samples/Workflows/NonEvaluatableWorkflow.cs
index f8ba3ee5..99ee0e63 100644
--- a/test/ConductorSharp.Engine.Tests/Samples/Workflows/NonEvaluatableWorkflow.cs
+++ b/test/ConductorSharp.Engine.Tests/Samples/Workflows/NonEvaluatableWorkflow.cs
@@ -20,13 +20,14 @@ public class NonEvaluatableWorkflow : Workflow builder
- ) : base(builder) { }
+ )
+ : base(builder) { }
public override void BuildDefinition()
{
_builder.AddTask(wf => wf.GetCustomer, wf => new() { CustomerId = wf.WorkflowInput.Input });
- _builder.AddTask(wf => wf.PrepareEmail, wf => new() { Address = $"{wf.GetCustomer.Output.Address}".ToUpperInvariant(), });
+ _builder.AddTask(wf => wf.PrepareEmail, wf => new() { Address = wf.GetCustomer.Output.Address.ToString().ToUpperInvariant(), });
}
}
}
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workflows/StaticFormatter.cs b/test/ConductorSharp.Engine.Tests/Samples/Workflows/StaticFormatter.cs
new file mode 100644
index 00000000..b171e228
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Samples/Workflows/StaticFormatter.cs
@@ -0,0 +1,43 @@
+namespace ConductorSharp.Engine.Tests.Samples.Workflows
+{
+ public class StaticFormatterInput : WorkflowInput
+ {
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ }
+
+ public class StaticFormatterOutput : WorkflowOutput
+ {
+ public object EmailBody { get; set; }
+ }
+
+ public class StaticFormatter : Workflow
+ {
+ public StaticFormatter(WorkflowDefinitionBuilder builder)
+ : base(builder) { }
+
+ public EmailPrepareV1 EmailPrepare { get; set; }
+
+ public override void BuildDefinition()
+ {
+ _builder.AddTask(
+ wf => wf.EmailPrepare,
+ wf =>
+ new()
+ {
+ Address = SmartEmailConverter.Format(wf.Input.FirstName, wf.Input.LastName),
+ Name = $"Workflow name: {NamingUtil.NameOf()}"
+ }
+ );
+
+ _builder.SetOutput(a => new() { EmailBody = a.EmailPrepare.Output.EmailBody });
+ }
+
+ public static EmailConverter SmartEmailConverter => new EmailConverter();
+
+ public class EmailConverter
+ {
+ public string Format(string firstName, string lastName) => $"{firstName}.{lastName}@example.com";
+ }
+ }
+}
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workflows/StaticFormatter.json b/test/ConductorSharp.Engine.Tests/Samples/Workflows/StaticFormatter.json
new file mode 100644
index 00000000..da204307
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Samples/Workflows/StaticFormatter.json
@@ -0,0 +1,26 @@
+{
+ "name": "static_formatter",
+ "version": 1,
+ "tasks": [
+ {
+ "name": "EMAIL_prepare",
+ "taskReferenceName": "email_prepare",
+ "inputParameters": {
+ "address": "${workflow.input.first_name}.${workflow.input.last_name}@example.com",
+ "name": "Workflow name: TEST_StringInterpolation"
+ },
+ "type": "SIMPLE",
+ "optional": false,
+ "workflowTaskType": "SIMPLE"
+ }
+ ],
+ "inputParameters": [
+ "first_name",
+ "last_name"
+ ],
+ "outputParameters": {
+ "email_body": "${email_prepare.output.email_body}"
+ },
+ "schemaVersion": 2,
+ "timeoutSeconds": 0
+}
\ No newline at end of file