113 lines
3.9 KiB
C#
113 lines
3.9 KiB
C#
using w4c_workflows.Models;
|
|
using w4c_workflows.Models.Nodes;
|
|
|
|
namespace w4c_workflows.Services.Nodes;
|
|
|
|
/// <summary>
|
|
/// Lowers a legacy script definition (<c>language</c> + <c>entry</c> tasks driven
|
|
/// by the linear <c>next</c>/<c>onError</c> chain) into an equivalent node-mode
|
|
/// definition where every step is a synthetic <see cref="CodeNodeType"/> node.
|
|
/// The result is an ordinary <see cref="WorkflowDefinition"/> and can be compiled
|
|
/// by <see cref="NodeGraphCompiler"/>.
|
|
///
|
|
/// Script execution is unchanged: the <c>core.code</c> executor delegates to the
|
|
/// existing <see cref="Execution.IScriptExecutor"/> runtime, so the subprocess
|
|
/// path is preserved while the edge kernel owns scheduling.
|
|
/// </summary>
|
|
public static class LegacyWorkflowLowerer
|
|
{
|
|
public const string CodeNodeType = "core.code";
|
|
|
|
/// <summary>Id of the synthetic node representing the header's entry script.</summary>
|
|
public const string EntryStepId = "__entry";
|
|
|
|
/// <summary>
|
|
/// Builds the node-mode definition. The header entry becomes a synthetic node
|
|
/// whose <c>next</c> is the workflow's chain head; each task keeps its id,
|
|
/// <c>next</c> and <c>onError</c>, which the node compiler lowers into ports.
|
|
/// </summary>
|
|
public static WorkflowDefinition ToNodeDefinition(WorkflowDefinition def)
|
|
{
|
|
var tasks = def.Tasks ?? new List<TaskDefinition>();
|
|
var entryId = UniqueId(EntryStepId, tasks);
|
|
var headId = WorkflowGraph.Compute(def).HeadId;
|
|
|
|
var lowered = new WorkflowDefinition
|
|
{
|
|
Name = def.Name,
|
|
Version = def.Version,
|
|
Mode = def.Mode,
|
|
Target = def.Target,
|
|
Server = def.Server,
|
|
Trigger = def.Trigger,
|
|
Env = def.Env,
|
|
Entry = null,
|
|
Language = null,
|
|
Tasks = new List<TaskDefinition>(tasks.Count + 1),
|
|
};
|
|
|
|
if (def.Entry != null)
|
|
{
|
|
lowered.Tasks.Add(new TaskDefinition
|
|
{
|
|
Id = entryId,
|
|
Parent = "root",
|
|
Next = headId,
|
|
Node = new NodeTaskDefinition { Type = CodeNodeType },
|
|
Parameters = CodeParameters(def.Language, def.Entry),
|
|
});
|
|
}
|
|
|
|
foreach (var task in tasks)
|
|
{
|
|
lowered.Tasks.Add(new TaskDefinition
|
|
{
|
|
Id = task.Id,
|
|
Parent = "root",
|
|
Next = task.Next,
|
|
OnError = task.OnError,
|
|
Node = new NodeTaskDefinition { Type = CodeNodeType },
|
|
Parameters = CodeParameters(task.Language ?? def.Language, task.Entry),
|
|
});
|
|
}
|
|
|
|
return lowered;
|
|
}
|
|
|
|
private static Dictionary<string, object?> CodeParameters(string? language, EntryDefinition? entry)
|
|
{
|
|
var parameters = new Dictionary<string, object?>
|
|
{
|
|
[ParameterNames.Language] = language,
|
|
[ParameterNames.EntryFile] = entry?.File,
|
|
};
|
|
|
|
if (!string.IsNullOrWhiteSpace(entry?.Function))
|
|
parameters[ParameterNames.EntryFunction] = entry.Function;
|
|
|
|
return parameters;
|
|
}
|
|
|
|
private static string UniqueId(string preferred, IReadOnlyList<TaskDefinition> tasks)
|
|
{
|
|
var taken = tasks.Select(t => t.Id).Where(id => id != null).ToHashSet(StringComparer.Ordinal);
|
|
if (!taken.Contains(preferred))
|
|
return preferred;
|
|
|
|
for (var i = 1; ; i++)
|
|
{
|
|
var candidate = $"{preferred}{i}";
|
|
if (!taken.Contains(candidate))
|
|
return candidate;
|
|
}
|
|
}
|
|
|
|
/// <summary>Parameter names shared with the <c>core.code</c> blueprint.</summary>
|
|
private static class ParameterNames
|
|
{
|
|
public const string Language = "language";
|
|
public const string EntryFile = "entryFile";
|
|
public const string EntryFunction = "entryFunction";
|
|
}
|
|
}
|