w4c-workflows-api/Services/Nodes/Executors/ExecuteWorkflowNodeExecutor.cs
2026-09-12 01:02:46 +03:00

73 lines
3.1 KiB
C#

using System.Text.Json.Nodes;
using w4c_workflows.Models.Nodes;
namespace w4c_workflows.Services.Nodes.Executors;
/// <summary>
/// Runs another workflow in-process and turns its output into this node's items
/// (the <c>core.executeWorkflow</c> step). The node itself only reads its
/// parameters and shapes the result: resolving the child definition, the
/// recursion/depth guard and child-run history live behind
/// <see cref="ISubWorkflowInvoker"/>.
///
/// The blueprint runs <c>allItems</c>, so the node always sees the whole input
/// batch and chooses per-item or single-run semantics itself.
/// </summary>
public sealed class ExecuteWorkflowNodeExecutor : INodeExecutor
{
public string Type => "core.executeWorkflow";
public async Task<NodeExecutionOutcome> RunAsync(NodeExecutionContext context, CancellationToken ct)
{
var invoker = context.SubWorkflows;
if (invoker == null)
{
return NodeExecutionOutcome.Failed(
"sub-workflow execution is not available on this runtime", "subworkflow_unavailable");
}
var workflow = ReadString(context, "workflow");
if (string.IsNullOrWhiteSpace(workflow))
return NodeExecutionOutcome.Failed(
"core.executeWorkflow requires a 'workflow' parameter", "invalid_parameter");
var mode = ReadString(context, "mode") ?? SubWorkflowMode.AllItems;
if (!string.Equals(mode, SubWorkflowMode.AllItems, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(mode, SubWorkflowMode.EachItem, StringComparison.OrdinalIgnoreCase))
{
return NodeExecutionOutcome.Failed(
$"core.executeWorkflow mode '{mode}' is invalid", "invalid_parameter");
}
var invocation = new SubWorkflowInvocation
{
Workflow = workflow,
Input = context.Input(0).ToList(),
Mode = mode,
RecordHistory = ReadBool(context, "recordHistory", defaultValue: true),
WaitForCompletion = ReadBool(context, "waitForCompletion", defaultValue: true),
ParentNodeId = context.TaskId,
WorkingDirectory = context.WorkingDirectory,
};
var result = await invoker.InvokeAsync(invocation, ct);
if (!result.Succeeded)
{
return NodeExecutionOutcome.Failed(
result.Failure!.Message, result.Failure.Code, result.Failure.Description);
}
// With waiting disabled the child still runs, but its items are not handed
// downstream — a fire-and-forget call.
return invocation.WaitForCompletion
? NodeExecutionOutcome.Single(result.Items)
: NodeExecutionOutcome.Empty;
}
private static string? ReadString(NodeExecutionContext context, string name)
=> context.Parameters[name] is JsonValue value && value.TryGetValue<string>(out var text) ? text : null;
private static bool ReadBool(NodeExecutionContext context, string name, bool defaultValue)
=> context.Parameters[name] is JsonValue value && value.TryGetValue<bool>(out var flag) ? flag : defaultValue;
}