.NET: [BREAKING] .NET: Add Workflow on-Build() Orphan Validation (#1943)

* fix: Workflow Validation

* adds orphan validation to workflow builder
* adds tests for workflow validation
* expands on the underlying reasoning why type validation is not supported

* fixup: CodeGen template
This commit is contained in:
Jacob Alber
2025-11-05 22:12:27 +00:00
committed by GitHub
parent d701e796cb
commit 0bf6d437d8
27 changed files with 201 additions and 92 deletions
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -114,6 +114,6 @@ public static class WorkflowProvider
builder.AddEdge(workflowTest, addMessage);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -80,6 +80,6 @@ public static class WorkflowProvider
builder.AddEdge(myWorkflow, clearAll);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -175,6 +175,6 @@ public static class WorkflowProvider
builder.AddEdge(conditionItemEvenactionsPost, conditionItemEvenPost);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -167,6 +167,6 @@ public static class WorkflowProvider
builder.AddEdge(conditionGroupTestelseactionsPost, conditionGroupTestPost);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -90,6 +90,6 @@ public static class WorkflowProvider
builder.AddEdge(workflowTest, copyMessages);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -83,6 +83,6 @@ public static class WorkflowProvider
builder.AddEdge(workflowTest, conversationCreate);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -82,6 +82,6 @@ public static class WorkflowProvider
builder.AddEdge(myWorkflow, setVar);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -82,6 +82,6 @@ public static class WorkflowProvider
builder.AddEdge(myWorkflow, setVar);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -89,6 +89,6 @@ public static class WorkflowProvider
builder.AddEdge(endAllRestart, sendActivity1);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -89,6 +89,6 @@ public static class WorkflowProvider
builder.AddEdge(endAllRestart, sendActivity1);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -138,6 +138,6 @@ public static class WorkflowProvider
builder.AddEdge(sendActivity3, endAll);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -109,6 +109,6 @@ public static class WorkflowProvider
builder.AddEdge(myWorkflow, invokeAgent);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -180,6 +180,6 @@ public static class WorkflowProvider
builder.AddEdge(foreachLoopEnd, foreachLoopNext);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -180,6 +180,6 @@ public static class WorkflowProvider
builder.AddEdge(foreachLoopEnd, foreachLoopNext);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -176,6 +176,6 @@ public static class WorkflowProvider
builder.AddEdge(foreachLoopEnd, foreachLoopNext);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -101,6 +101,6 @@ public static class WorkflowProvider
builder.AddEdge(setVar, parseVar);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -98,6 +98,6 @@ public static class WorkflowProvider
builder.AddEdge(setVar, clearVar);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -86,6 +86,6 @@ public static class WorkflowProvider
builder.AddEdge(workflowTest, getMessageSingle);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -99,6 +99,6 @@ public static class WorkflowProvider
builder.AddEdge(workflowTest, getMessagesAll);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -105,6 +105,6 @@ public static class WorkflowProvider
builder.AddEdge(setInput, activityInput);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -85,6 +85,6 @@ public static class WorkflowProvider
builder.AddEdge(myWorkflow, setText);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -82,6 +82,6 @@ public static class WorkflowProvider
builder.AddEdge(myWorkflow, setVar);
// Build the workflow
return builder.Build();
return builder.Build(validateOrphans: false);
}
}
}
@@ -21,6 +21,51 @@ public partial class WorkflowBuilderSmokeTests
(msg, ctx) => ctx.SendMessageAsync(msg));
}
[Fact]
public void Test_Validation_FailsWhenUnboundExecutors()
{
Func<Workflow> act = () =>
{
return new WorkflowBuilder("start")
.AddEdge(new NoOpExecutor("start"), "unbound")
.Build();
};
act.Should().Throw<InvalidOperationException>();
}
[Fact]
public void Test_Validation_FailsWhenUnreachableExecutors()
{
Func<Workflow> act = () =>
{
return new WorkflowBuilder("start")
.BindExecutor(new NoOpExecutor("start"))
.AddEdge(new NoOpExecutor("unreachable"), new NoOpExecutor("also-unreachable"))
.Build();
};
act.Should().Throw<InvalidOperationException>();
}
[Fact]
public void Test_Validation_AddEdgesOutOfOrderDoesNotImpactReachability()
{
Workflow workflow = new WorkflowBuilder("start")
.BindExecutor(new NoOpExecutor("start"))
.AddEdge(new NoOpExecutor("not-unreachable"), new NoOpExecutor("also-not-unreachable"))
.AddEdge("start", "not-unreachable")
.Build();
workflow.StartExecutorId.Should().Be("start");
workflow.ExecutorBindings.Should().HaveCount(3);
workflow.ExecutorBindings.Should().ContainKey("start");
workflow.ExecutorBindings.Should().ContainKey("not-unreachable");
workflow.ExecutorBindings.Should().ContainKey("also-not-unreachable");
workflow.ExecutorBindings.Values.Should().AllSatisfy(binding => binding.ExecutorType.Should().Be<NoOpExecutor>());
}
[Fact]
public void Test_LateBinding_Executor()
{