using System.Text.Json.Nodes; using w4c_workflows.Models.Nodes; namespace w4c_workflows.Services.Nodes.Executors; /// /// Runs another workflow in-process and turns its output into this node's items /// (the core.executeWorkflow 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 /// . /// /// The blueprint runs allItems, so the node always sees the whole input /// batch and chooses per-item or single-run semantics itself. /// public sealed class ExecuteWorkflowNodeExecutor : INodeExecutor { public string Type => "core.executeWorkflow"; public async Task 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(out var text) ? text : null; private static bool ReadBool(NodeExecutionContext context, string name, bool defaultValue) => context.Parameters[name] is JsonValue value && value.TryGetValue(out var flag) ? flag : defaultValue; }