Skip to content
Closed
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ dist
*.user
.env
*.xmldocs.xml
**/CONDUCTORSHARP_HEALTH.json
**/CONDUCTORSHARP_HEALTH.json
.idea
Empty file modified .husky/pre-commit
100644 → 100755
Empty file.
51 changes: 42 additions & 9 deletions src/ConductorSharp.Engine/Util/ExpressionUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
<EmbeddedResource Include="Samples\Workflows\HumanTaskWorkflow.json" />
<EmbeddedResource Include="Samples\Workflows\ListInitializationWorkflow.json" />
<EmbeddedResource Include="Samples\Workflows\EvaluateExpressionWorkflow.json" />
<EmbeddedResource Include="Samples\Workflows\StaticFormatter.json" />
<EmbeddedResource Include="Samples\Workflows\TaskPropertiesWorkflow.json" />
<EmbeddedResource Include="Samples\Workflows\WaitTask.json" />
<EmbeddedResource Include="Samples\Workflows\CSharpLambdaWorkflow.json">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,15 @@ public void BuilderReturnsCorrectDefinitionDictionaryInitializationWorkflow()
Assert.Equal(expectedDefinition, definition);
}

[Fact]
public void BuilderReturnsCorrectDefinitionStaticFormatter()
{
var definition = GetDefinitionFromWorkflow<StaticFormatter>();
var expectedDefinition = EmbeddedFileHelper.GetLinesFromEmbeddedFile("~/Samples/Workflows/StaticFormatter.json");

Assert.Equal(expectedDefinition, definition);
}

private static string GetDefinitionFromWorkflow<TWorkflow>()
where TWorkflow : IConfigurableWorkflow
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ public class NonEvaluatableWorkflow : Workflow<NonEvaluatableWorkflow, NonEvalua

public NonEvaluatableWorkflow(
WorkflowDefinitionBuilder<NonEvaluatableWorkflow, NonEvaluatableWorkflowInput, NonEvaluatableWorkflowOutput> 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(), });
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace ConductorSharp.Engine.Tests.Samples.Workflows
{
public class StaticFormatterInput : WorkflowInput<StaticFormatterOutput>
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

public class StaticFormatterOutput : WorkflowOutput
{
public object EmailBody { get; set; }
}

public class StaticFormatter : Workflow<StaticFormatter, StaticFormatterInput, StaticFormatterOutput>
{
public StaticFormatter(WorkflowDefinitionBuilder<StaticFormatter, StaticFormatterInput, StaticFormatterOutput> 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<StringInterpolation>()}"
}
);

_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";
}
}
}
Original file line number Diff line number Diff line change
@@ -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
}