Skip to content
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
99 changes: 99 additions & 0 deletions src/ConductorSharp.Engine/Builders/DoWhileTaskBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ConductorSharp.Client.Generated;
using ConductorSharp.Engine.Interface;
using ConductorSharp.Engine.Model;
using ConductorSharp.Engine.Util;
using ConductorSharp.Engine.Util.Builders;

namespace ConductorSharp.Engine.Builders
{
/// <summary>
/// Extension methods to add a DO_WHILE loop with multiple inner tasks, based on BaseTaskBuilder.
/// </summary>
public static class DoWhileTaskExtensions
{
/// <summary>
/// Adds a DO_WHILE loop to the workflow definition.
/// </summary>
/// <typeparam name="TWorkflow">Workflow type</typeparam>
/// <param name="builder">Parent sequence builder</param>
/// <param name="reference">Expression selecting the workflow property holding the loop's reference name</param>
/// <param name="input">Expression creating the input</param>
/// <param name="loopCondition">Loop condition</param>
/// <param name="configureBody">Delegate to add tasks inside the loop</param>
public static ITaskOptionsBuilder AddTask<TWorkflow>(
this ITaskSequenceBuilder<TWorkflow> builder,
Expression<Func<TWorkflow, DoWhileTaskModel>> reference,
Expression<Func<TWorkflow, DoWhileInput>> input,
string loopCondition,
Action<ITaskSequenceBuilder<TWorkflow>> configureBody
)
where TWorkflow : ITypedWorkflow
{
// Extract expressions
var taskBuilder = new DoWhileTaskBuilder<TWorkflow>(reference.Body, input.Body, loopCondition, builder.BuildConfiguration);
// Register it with the outer sequence
builder.AddTaskBuilderToSequence(taskBuilder);
// Allow the caller to configure inner loop body tasks
configureBody(taskBuilder);
return taskBuilder;
}
}

/// <summary>
/// Builder for the DO_WHILE task, extending BaseTaskBuilder for consistent patterns.
/// </summary>
internal sealed class DoWhileTaskBuilder<TWorkflow> : BaseTaskBuilder<DoWhileInput, NoOutput>, ITaskSequenceBuilder<TWorkflow>
where TWorkflow : ITypedWorkflow
{
private readonly string _loopCondition;
private readonly DoWhileInput _doWhileInput;
private readonly List<WorkflowTask> _innerTasks = new();
public BuildContext BuildContext { get; } = new();
public BuildConfiguration BuildConfiguration { get; }
public WorkflowBuildItemRegistry WorkflowBuildRegistry { get; } = new();
public IEnumerable<ConfigurationProperty> ConfigurationProperties { get; } = new List<ConfigurationProperty>();

/// <inheritdoc />
public DoWhileTaskBuilder(Expression taskExpression, Expression inputExpression, string loopCondition, BuildConfiguration buildConfiguration)
: base(taskExpression, inputExpression, buildConfiguration)
{
_loopCondition = loopCondition;
BuildConfiguration = buildConfiguration;
}

/// <inheritdoc/>
public override WorkflowTask[] Build()
{
var refName = _taskRefferenceName;

if (_innerTasks.Any(t => t.WorkflowTaskType == WorkflowTaskType.DO_WHILE))
{
throw new InvalidOperationException("Nested DO_WHILE tasks are not allowed.");
}

var loopTask = new WorkflowTask
{
Name = refName,
TaskReferenceName = refName,
WorkflowTaskType = WorkflowTaskType.DO_WHILE,
Type = nameof(WorkflowTaskType.DO_WHILE),
InputParameters = _inputParameters.ToObject<IDictionary<string, object>>(),
LoopCondition = _loopCondition,
LoopOver = _innerTasks,
};
return [loopTask];
}

public void AddTaskBuilderToSequence(ITaskBuilder builder)
{
foreach (var task in builder.Build())
{
_innerTasks.Add(task);
}
}
}
}
18 changes: 18 additions & 0 deletions src/ConductorSharp.Engine/Model/DoWhileTaskModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using ConductorSharp.Engine.Builders;
using MediatR;

namespace ConductorSharp.Engine.Model
{
/// <summary>
/// Input for configuration of the DO_WHILE task.
/// </summary>
public class DoWhileInput : IRequest<NoOutput>, IWorkflowInput
{
public object Value { get; set; } = null;
}

/// <summary>
/// Task Model to reference the do while task in the workflow builders
/// </summary>
public class DoWhileTaskModel : TaskModel<DoWhileInput, NoOutput> { }
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

<ItemGroup>
<EmbeddedResource Include="Samples\Workflows\DictionaryInitializationWorkflow.json" />
<EmbeddedResource Include="Samples\Workflows\DoWhileTask.json" />
<EmbeddedResource Include="Samples\Workflows\FormatterWorkflow.json" />
<EmbeddedResource Include="Samples\Workflows\HumanTaskWorkflow.json" />
<EmbeddedResource Include="Samples\Workflows\ListInitializationWorkflow.json" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,15 @@ public void BuilderReturnsCorrectDefinitionDecisionTask()
Assert.Equal(expectedDefinition, definition);
}

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

Assert.Equal(expectedDefinition, definition);
}

[Fact]
public void BuilderReturnsCorrectDefinitionSwitchTask()
{
Expand Down
31 changes: 31 additions & 0 deletions test/ConductorSharp.Engine.Tests/Samples/Workflows/DoWhileTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace ConductorSharp.Engine.Tests.Samples.Workflows
{
public sealed class DoWhileTaskInput : WorkflowInput<DoWhileTaskOutput>
{
public int Loops { get; set; }
}

public sealed class DoWhileTaskOutput : WorkflowOutput { }

public sealed class DoWhileTask : Workflow<DoWhileTask, DoWhileTaskInput, DoWhileTaskOutput>
{
public DoWhileTaskModel DoWhile { get; set; }
public CustomerGetV1 GetCustomer { get; set; }

public DoWhileTask(WorkflowDefinitionBuilder<DoWhileTask, DoWhileTaskInput, DoWhileTaskOutput> builder)
: base(builder) { }

public override void BuildDefinition()
{
_builder.AddTask(
wf => wf.DoWhile,
wf => new() { Value = wf.Input.Loops },
"$.do_while.iteration < $.value",
builder =>
{
builder.AddTask(wf => wf.GetCustomer, wf => new CustomerGetV1Input() { CustomerId = "CUSTOMER-1" });
}
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "do_while_task",
"version": 1,
"tasks": [
{
"name": "do_while",
"taskReferenceName": "do_while",
"inputParameters": {
"value": "${workflow.input.loops}"
},
"type": "DO_WHILE",
"loopCondition": "$.do_while.iteration < $.value",
"loopOver": [
{
"name": "CUSTOMER_get",
"taskReferenceName": "get_customer",
"inputParameters": {
"customer_id": "CUSTOMER-1"
},
"type": "SIMPLE",
"optional": false,
"workflowTaskType": "SIMPLE"
}
],
"workflowTaskType": "DO_WHILE"
}
],
"inputParameters": [
"loops"
],
"outputParameters": {},
"schemaVersion": 2,
"timeoutSeconds": 0
}
4 changes: 2 additions & 2 deletions test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Newtonsoft.Json;
using System;
using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace ConductorSharp.Engine.Tests.Util
{
Expand Down
Loading