feat(executor): parse pinned_input/pinned_output on WorkflowNode + WrapPin helper
This commit is contained in:
@@ -6,7 +6,10 @@ using VolcanicCore.Persistence.Repositories;
|
||||
|
||||
namespace VolcanicCore.Api.Engine;
|
||||
|
||||
public sealed record WorkflowNode(string Id, string TypeId, string? ConnectionId, string ConfigJson);
|
||||
public sealed record WorkflowNode(
|
||||
string Id, string TypeId, string? ConnectionId, string ConfigJson,
|
||||
string? PinnedInputJson = null,
|
||||
string? PinnedOutputJson = null);
|
||||
public sealed record WorkflowEdge(string FromNode, int FromPort, string ToNode, int ToPort);
|
||||
public sealed record WorkflowGraph
|
||||
{
|
||||
@@ -31,7 +34,9 @@ public sealed record WorkflowGraph
|
||||
: n.TryGetProperty("connectionId", out var c2) ? c2.GetString() : null;
|
||||
var cfg = n.TryGetProperty("config_json", out var g1) ? g1.GetRawText()
|
||||
: n.TryGetProperty("config", out var g2) ? g2.GetRawText() : "{}";
|
||||
nodes.Add(new WorkflowNode(id, typeId!, connId, cfg));
|
||||
var pinIn = ReadPin(n, "pinned_input");
|
||||
var pinOut = ReadPin(n, "pinned_output");
|
||||
nodes.Add(new WorkflowNode(id, typeId!, connId, cfg, pinIn, pinOut));
|
||||
}
|
||||
var edges = new List<WorkflowEdge>();
|
||||
if (root.TryGetProperty("edges", out var edgesEl))
|
||||
@@ -47,6 +52,13 @@ public sealed record WorkflowGraph
|
||||
}
|
||||
return new WorkflowGraph { Nodes = nodes, Edges = edges };
|
||||
}
|
||||
|
||||
private static string? ReadPin(JsonElement node, string name)
|
||||
{
|
||||
if (!node.TryGetProperty(name, out var v)) return null;
|
||||
if (v.ValueKind == JsonValueKind.Null) return null;
|
||||
return v.GetRawText();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Executor
|
||||
@@ -407,6 +419,22 @@ public sealed class Executor
|
||||
}
|
||||
}
|
||||
|
||||
// Wraps a pinned JSON value into a port-shape Item[] on port 0.
|
||||
// - Array → one item per element (each item's Json = the element)
|
||||
// - Anything else (object, scalar, null) → single item wrapping the value
|
||||
// `internal` so unit tests can reach it without going through a full run.
|
||||
internal static IReadOnlyList<Item> WrapPin(JsonElement pin)
|
||||
{
|
||||
if (pin.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
var arr = new List<Item>(pin.GetArrayLength());
|
||||
foreach (var el in pin.EnumerateArray())
|
||||
arr.Add(new Item(el.Clone()));
|
||||
return arr;
|
||||
}
|
||||
return new[] { new Item(pin.Clone()) };
|
||||
}
|
||||
|
||||
private static string SerializeInputs(IReadOnlyList<IReadOnlyList<Item>> ports)
|
||||
=> JsonSerializer.Serialize(ports.Select(port => port.Select(i => new
|
||||
{
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="VolcanicCore.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="wwwroot\**\*" CopyToOutputDirectory="Never" CopyToPublishDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -377,4 +377,43 @@ public class ExecutorTests
|
||||
Assert.Contains("status", row.Error!);
|
||||
Assert.Empty(rec.Calls);
|
||||
}
|
||||
|
||||
// --- WrapPin helper --------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void WrapPin_object_produces_a_single_item_wrapping_the_object()
|
||||
{
|
||||
var pin = JsonDocument.Parse("""{"a":1}""").RootElement;
|
||||
var items = Executor.WrapPin(pin);
|
||||
Assert.Single(items);
|
||||
Assert.Equal("""{"a":1}""", items[0].Json.GetRawText());
|
||||
Assert.Null(items[0].Binary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WrapPin_array_of_objects_produces_one_item_per_element()
|
||||
{
|
||||
var pin = JsonDocument.Parse("""[{"a":1},{"a":2}]""").RootElement;
|
||||
var items = Executor.WrapPin(pin);
|
||||
Assert.Equal(2, items.Count);
|
||||
Assert.Equal("""{"a":1}""", items[0].Json.GetRawText());
|
||||
Assert.Equal("""{"a":2}""", items[1].Json.GetRawText());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WrapPin_empty_array_produces_zero_items()
|
||||
{
|
||||
var pin = JsonDocument.Parse("[]").RootElement;
|
||||
var items = Executor.WrapPin(pin);
|
||||
Assert.Empty(items);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WrapPin_scalar_produces_a_single_item_with_the_scalar()
|
||||
{
|
||||
var pin = JsonDocument.Parse("42").RootElement;
|
||||
var items = Executor.WrapPin(pin);
|
||||
Assert.Single(items);
|
||||
Assert.Equal("42", items[0].Json.GetRawText());
|
||||
}
|
||||
}
|
||||
|
||||
76
tests/VolcanicCore.Tests/WorkflowGraphParseTests.cs
Normal file
76
tests/VolcanicCore.Tests/WorkflowGraphParseTests.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System.Text.Json;
|
||||
using VolcanicCore.Api.Engine;
|
||||
|
||||
namespace VolcanicCore.Tests;
|
||||
|
||||
public class WorkflowGraphParseTests
|
||||
{
|
||||
[Fact]
|
||||
public void Nodes_without_pin_fields_get_null_pins()
|
||||
{
|
||||
var g = WorkflowGraph.Parse("""
|
||||
{
|
||||
"nodes": [{ "id": "n1", "type_id": "test.a", "config": {} }],
|
||||
"edges": []
|
||||
}
|
||||
""");
|
||||
var n = g.Nodes.Single();
|
||||
Assert.Null(n.PinnedInputJson);
|
||||
Assert.Null(n.PinnedOutputJson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pinned_input_object_is_stored_as_raw_json_text()
|
||||
{
|
||||
var g = WorkflowGraph.Parse("""
|
||||
{
|
||||
"nodes": [{
|
||||
"id": "n1", "type_id": "test.a", "config": {},
|
||||
"pinned_input": { "a": 1 }
|
||||
}],
|
||||
"edges": []
|
||||
}
|
||||
""");
|
||||
Assert.Equal("""{"a":1}""",
|
||||
Compact(g.Nodes.Single().PinnedInputJson!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pinned_output_array_is_stored_as_raw_json_text()
|
||||
{
|
||||
var g = WorkflowGraph.Parse("""
|
||||
{
|
||||
"nodes": [{
|
||||
"id": "t", "type_id": "test.trigger", "config": {},
|
||||
"pinned_output": [{ "b": 1 }, { "b": 2 }]
|
||||
}],
|
||||
"edges": []
|
||||
}
|
||||
""");
|
||||
Assert.Equal("""[{"b":1},{"b":2}]""",
|
||||
Compact(g.Nodes.Single().PinnedOutputJson!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Explicit_null_pin_is_stored_as_null()
|
||||
{
|
||||
var g = WorkflowGraph.Parse("""
|
||||
{
|
||||
"nodes": [{
|
||||
"id": "n1", "type_id": "test.a", "config": {},
|
||||
"pinned_input": null, "pinned_output": null
|
||||
}],
|
||||
"edges": []
|
||||
}
|
||||
""");
|
||||
var n = g.Nodes.Single();
|
||||
Assert.Null(n.PinnedInputJson);
|
||||
Assert.Null(n.PinnedOutputJson);
|
||||
}
|
||||
|
||||
private static string Compact(string raw)
|
||||
{
|
||||
using var doc = JsonDocument.Parse(raw);
|
||||
return JsonSerializer.Serialize(doc.RootElement);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user