diff --git a/src/ConductorSharp.Engine/Util/ExpressionUtil.cs b/src/ConductorSharp.Engine/Util/ExpressionUtil.cs index 66b4249d..7385541b 100644 --- a/src/ConductorSharp.Engine/Util/ExpressionUtil.cs +++ b/src/ConductorSharp.Engine/Util/ExpressionUtil.cs @@ -29,7 +29,59 @@ public static Type ParseToType(Expression expression) return ((PropertyInfo)taskSelectExpression.Member).PropertyType; } - public static JObject ParseToParameters(Expression expression) + public static JObject ParseToParameters(Expression expression) => ParseObjectInitialization(expression); + + private static object ParseExpression(Expression expression) + { + if (expression is ConstantExpression cex) + return Convert.ToString(cex.Value); + + if (expression is UnaryExpression uex && uex.Operand is ConstantExpression ccex) + { + var converted = Convert.ToString(ccex.Value); + + //if (ccex.Value is bool) + // return converted.ToLowerInvariant(); + + return ccex.Value; + } + + // Handle boxing + if (expression is UnaryExpression unaryEx && unaryEx.NodeType == ExpressionType.Convert) + return CreateExpressionString(unaryEx.Operand); + + if ( + expression is MethodCallExpression methodExpression + && methodExpression.Method.Name == nameof(string.Format) + && methodExpression.Method.DeclaringType == typeof(string) + ) + { + var expressionStrings = methodExpression.Arguments.Skip(1).Select(CompileMemberOrNameExpressions).ToArray(); + var formatExpr = methodExpression.Arguments[0] as ConstantExpression; + if (formatExpr == null) + throw new Exception("string.Format with non constant format string is not supported"); + var formatString = (string)formatExpr.Value; + return string.Format(formatString, expressionStrings); + } + + if (expression is NewExpression || expression is MemberInitExpression) + return ParseObjectInitialization(expression); + + if (expression is NewArrayExpression newArrayExpression) + return ParseArrayInitalization(newArrayExpression); + + return CompileMemberOrNameExpressions(expression); + } + + private static JArray ParseArrayInitalization(NewArrayExpression newArrayExpression) + { + if (newArrayExpression.NodeType != ExpressionType.NewArrayInit) + throw new Exception("Only dimensionless array initialization is supported"); + + return new JArray(newArrayExpression.Expressions.Select(ParseExpression)); + } + + private static JObject ParseObjectInitialization(Expression expression) { var inputParams = new JObject(); @@ -41,7 +93,7 @@ public static JObject ParseToParameters(Expression expression) throw new Exception($"Only {nameof(MemberBindingType.Assignment)} binding type supported"); var assignmentBinding = (MemberAssignment)binding; - var assignmentValue = ParseToAssignmentString(assignmentBinding.Expression); + var assignmentValue = ParseExpression(assignmentBinding.Expression); var assignmentKey = GetMemberName(binding.Member as PropertyInfo); inputParams.Add(new JProperty(assignmentKey, assignmentValue)); @@ -62,7 +114,7 @@ var member in newExpression.Arguments.Zip( ) ) { - var assignmentValue = ParseToAssignmentString(member.expression); + var assignmentValue = ParseExpression(member.expression); var assignmentKey = GetMemberName(member.memberInfo as PropertyInfo); inputParams.Add(new JProperty(assignmentKey, assignmentValue)); @@ -76,45 +128,6 @@ var member in newExpression.Arguments.Zip( return inputParams; } - private static object ParseToAssignmentString(Expression assignmentExpression) - { - if (assignmentExpression is ConstantExpression cex) - return Convert.ToString(cex.Value); - - if (assignmentExpression is UnaryExpression uex && uex.Operand is ConstantExpression ccex) - { - var converted = Convert.ToString(ccex.Value); - - //if (ccex.Value is bool) - // return converted.ToLowerInvariant(); - - return ccex.Value; - } - - // Handle boxing - if (assignmentExpression is UnaryExpression unaryEx && unaryEx.NodeType == ExpressionType.Convert) - return CreateExpressionString(unaryEx.Operand); - - if ( - assignmentExpression is MethodCallExpression methodExpression - && methodExpression.Method.Name == nameof(string.Format) - && methodExpression.Method.DeclaringType == typeof(string) - ) - { - var expressionStrings = methodExpression.Arguments.Skip(1).Select(CompileMemberOrNameExpressions).ToArray(); - var formatExpr = methodExpression.Arguments[0] as ConstantExpression; - if (formatExpr == null) - throw new Exception("string.Format with non constant format string is not supported"); - var formatString = (string)formatExpr.Value; - return string.Format(formatString, expressionStrings); - } - - if (assignmentExpression is NewExpression || assignmentExpression is MemberInitExpression) - return ParseToParameters(assignmentExpression); - - return CompileMemberOrNameExpressions(assignmentExpression); - } - private static string CompileMemberOrNameExpressions(Expression expr) { if (expr is MemberExpression) diff --git a/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj b/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj index 376ccd49..d5811d3b 100644 --- a/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj +++ b/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj @@ -9,6 +9,7 @@ + @@ -17,6 +18,7 @@ + diff --git a/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs index 570674e2..0b704f4c 100644 --- a/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs +++ b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs @@ -36,9 +36,18 @@ public void BuilderReturnsCorrectDefinitionNestedObjects() public void BuildersReturnSameDefinitionsTaskInitialization() { var explicitDef = SerializationUtil.SerializeObject(new TaskInputInitializationNew().GetDefinition()); - var targetTypedDef = SerializationUtil.SerializeObject(new TaskInputInitializationMemberInit().GetDefinition()); + var memberInitDef = SerializationUtil.SerializeObject(new TaskInputInitializationMemberInit().GetDefinition()); - Assert.True(explicitDef == targetTypedDef, "Definitions are not equal"); + Assert.True(explicitDef == memberInitDef, "Definitions are not equal"); + } + + [Fact] + public void BuilderReturnsCorrectDefinitionArrayTask() + { + var definition = SerializationUtil.SerializeObject(new Arrays().GetDefinition()); + var expectedDefinition = EmbeddedFileHelper.GetLinesFromEmbeddedFile("~/Samples/Workflows/Arrays.json"); + + Assert.Equal(expectedDefinition, definition); } } } diff --git a/test/ConductorSharp.Engine.Tests/Samples/Tasks/ArrayTask.cs b/test/ConductorSharp.Engine.Tests/Samples/Tasks/ArrayTask.cs new file mode 100644 index 00000000..7c65a3c5 --- /dev/null +++ b/test/ConductorSharp.Engine.Tests/Samples/Tasks/ArrayTask.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ConductorSharp.Engine.Tests.Samples.Tasks +{ + public class ArrayTaskInput : IRequest + { + public class TestModel + { + public string String { get; set; } + } + + public int[] Integers { get; set; } + public TestModel[] Models { get; set; } + public object Objects { get; set; } + } + + public class ArrayTaskOutput { } + + public class ArrayTask : SimpleTaskModel { } +} diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workflows/Arrays.cs b/test/ConductorSharp.Engine.Tests/Samples/Workflows/Arrays.cs new file mode 100644 index 00000000..8dbc3010 --- /dev/null +++ b/test/ConductorSharp.Engine.Tests/Samples/Workflows/Arrays.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ConductorSharp.Engine.Tests.Samples.Workflows +{ + public class ArrayInput : WorkflowInput { } + + public class ArrayOutput : WorkflowOutput { } + + public class Arrays : Workflow + { + public ArrayTask ArrayTask1 { get; set; } + public ArrayTask ArrayTask2 { get; set; } + + public override WorkflowDefinition GetDefinition() + { + var builder = new WorkflowDefinitionBuilder(); + + builder.AddTask( + wf => wf.ArrayTask1, + wf => + new() + { + Integers = new[] { 1, 2, 3 }, + Models = new[] + { + new ArrayTaskInput.TestModel { String = "Test1" }, + new ArrayTaskInput.TestModel { String = "Test2" } + }, + Objects = new dynamic[] { new { AnonymousObjProp = "Prop" }, new { Test = "Prop" } } + } + ); + + builder.AddTask( + wf => wf.ArrayTask2, + wf => + new() + { + Integers = new int[] { }, + Models = new ArrayTaskInput.TestModel[] { }, + Objects = new dynamic[] { } + } + ); + + return builder.Build(opts => opts.Version = 1); + } + } +} diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workflows/Arrays.json b/test/ConductorSharp.Engine.Tests/Samples/Workflows/Arrays.json new file mode 100644 index 00000000..406ec2a9 --- /dev/null +++ b/test/ConductorSharp.Engine.Tests/Samples/Workflows/Arrays.json @@ -0,0 +1,112 @@ +{ + "ownerApp": null, + "createTime": 0, + "updateTime": 0, + "createdBy": null, + "updatedBy": null, + "name": "arrays", + "description": "{\"description\":null,\"labels\":null}", + "version": 1, + "tasks": [ + { + "queryExpression": null, + "name": "array_task", + "taskReferenceName": "array_task1", + "description": "{\"description\":null}", + "inputParameters": { + "integers": [ + "1", + "2", + "3" + ], + "models": [ + { + "string": "Test1" + }, + { + "string": "Test2" + } + ], + "objects": [ + { + "anonymous_obj_prop": "Prop" + }, + { + "test": "Prop" + } + ] + }, + "type": "SIMPLE", + "dynamicTaskNameParam": null, + "caseValueParam": null, + "caseExpression": null, + "expression": null, + "evaluatorType": null, + "scriptExpression": null, + "decisionCases": null, + "dynamicForkJoinTasksParam": null, + "dynamicForkTasksParam": null, + "dynamicForkTasksInputParamName": null, + "defaultCase": null, + "forkTasks": null, + "startDelay": 0, + "subWorkflowParam": null, + "joinOn": null, + "sink": null, + "optional": false, + "taskDefinition": null, + "rateLimited": false, + "defaultExclusiveJoinTask": null, + "asyncComplete": false, + "loopCondition": null, + "loopOver": null + }, + { + "queryExpression": null, + "name": "array_task", + "taskReferenceName": "array_task2", + "description": "{\"description\":null}", + "inputParameters": { + "integers": [], + "models": [], + "objects": [] + }, + "type": "SIMPLE", + "dynamicTaskNameParam": null, + "caseValueParam": null, + "caseExpression": null, + "expression": null, + "evaluatorType": null, + "scriptExpression": null, + "decisionCases": null, + "dynamicForkJoinTasksParam": null, + "dynamicForkTasksParam": null, + "dynamicForkTasksInputParamName": null, + "defaultCase": null, + "forkTasks": null, + "startDelay": 0, + "subWorkflowParam": null, + "joinOn": null, + "sink": null, + "optional": false, + "taskDefinition": null, + "rateLimited": false, + "defaultExclusiveJoinTask": null, + "asyncComplete": false, + "loopCondition": null, + "loopOver": null + } + ], + "inputParameters": [ + "{}" + ], + "outputParameters": null, + "failureWorkflow": null, + "schemaVersion": 2, + "restartable": true, + "workflowStatusListenerEnabled": true, + "ownerEmail": null, + "timeoutPolicy": null, + "timeoutSeconds": 0, + "variables": null +} \ No newline at end of file