Skip to content

Commit 0eedb66

Browse files
committed
fix: preserve enum split case order
Symptom: enum split cases could be reordered after describe/exec/describe, especially empty or body-less cases sharing the same split destination shape. Root cause: the builder emitted enum case sequence flows with identical default connection metadata, and the describer only had that metadata available after MPR serialization. Cases with equivalent flow shape could therefore come back in a different order. Fix: encode the authoring case order into valid split flow connection pairs and make the enum split describer sort by that encoded order before grouping case values by destination. Tests: added describe coverage that shuffles the stored flows but expects the encoded enum case order to be preserved; existing enum split builder tests still cover case value emission.
1 parent 5afbf6a commit 0eedb66

3 files changed

Lines changed: 117 additions & 6 deletions

File tree

mdl/executor/cmd_microflows_builder_actions.go

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ func (fb *flowBuilder) addEnumSplit(s *ast.EnumSplitStmt) model.ID {
353353
fb.pendingAnnotations = nil
354354
}
355355
if lastID == "" {
356-
fb.addEnumSplitFlows(splitID, actID, br.values)
356+
fb.addEnumSplitFlows(splitID, actID, br.values, i)
357357
} else {
358358
if pendingCase != "" {
359359
fb.flows = append(fb.flows, newHorizontalFlowWithCase(lastID, actID, pendingCase))
@@ -377,7 +377,7 @@ func (fb *flowBuilder) addEnumSplit(s *ast.EnumSplitStmt) model.ID {
377377
}
378378
allBranchesReturn = false
379379
if lastID == "" {
380-
fb.addEnumSplitFlows(splitID, merge.ID, br.values)
380+
fb.addEnumSplitFlows(splitID, merge.ID, br.values, i)
381381
} else {
382382
if pendingCase != "" {
383383
fb.flows = append(fb.flows, newHorizontalFlowWithCase(lastID, merge.ID, pendingCase))
@@ -398,16 +398,53 @@ func (fb *flowBuilder) addEnumSplit(s *ast.EnumSplitStmt) model.ID {
398398
return splitID
399399
}
400400

401-
func (fb *flowBuilder) addEnumSplitFlows(originID, destinationID model.ID, values []string) {
401+
func (fb *flowBuilder) addEnumSplitFlows(originID, destinationID model.ID, values []string, order int) {
402402
if len(values) == 0 {
403-
fb.flows = append(fb.flows, newHorizontalFlow(originID, destinationID))
403+
flow := newHorizontalFlow(originID, destinationID)
404+
applySplitCaseOrder(flow, order)
405+
fb.flows = append(fb.flows, flow)
404406
return
405407
}
406408
for _, value := range values {
407-
fb.flows = append(fb.flows, newHorizontalFlowWithEnumCase(originID, destinationID, value))
409+
flow := newHorizontalFlowWithEnumCase(originID, destinationID, value)
410+
applySplitCaseOrder(flow, order)
411+
fb.flows = append(fb.flows, flow)
408412
}
409413
}
410414

415+
type splitCaseOrderAnchor struct {
416+
origin int
417+
destination int
418+
}
419+
420+
var splitCaseOrderAnchors = []splitCaseOrderAnchor{
421+
{AnchorTop, AnchorLeft},
422+
{AnchorRight, AnchorLeft},
423+
{AnchorBottom, AnchorLeft},
424+
{AnchorLeft, AnchorLeft},
425+
{AnchorTop, AnchorTop},
426+
{AnchorRight, AnchorTop},
427+
{AnchorBottom, AnchorTop},
428+
{AnchorLeft, AnchorTop},
429+
{AnchorTop, AnchorRight},
430+
{AnchorRight, AnchorRight},
431+
{AnchorBottom, AnchorRight},
432+
{AnchorLeft, AnchorRight},
433+
{AnchorTop, AnchorBottom},
434+
{AnchorRight, AnchorBottom},
435+
{AnchorBottom, AnchorBottom},
436+
{AnchorLeft, AnchorBottom},
437+
}
438+
439+
func applySplitCaseOrder(flow *microflows.SequenceFlow, order int) {
440+
if flow == nil || order < 0 || order >= len(splitCaseOrderAnchors) {
441+
return
442+
}
443+
pair := splitCaseOrderAnchors[order]
444+
flow.OriginConnectionIndex = pair.origin
445+
flow.DestinationConnectionIndex = pair.destination
446+
}
447+
411448
func enumSplitCaseValues(c ast.EnumSplitCase) []string {
412449
if len(c.Values) > 0 {
413450
return append([]string(nil), c.Values...)

mdl/executor/cmd_microflows_describe_enum_split_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package executor
44

55
import (
6+
"strings"
67
"testing"
78

89
"github.com/mendixlabs/mxcli/model"
@@ -55,6 +56,58 @@ func TestTraverseFlow_EnumSplit(t *testing.T) {
5556
assertContainsAny(t, lines, "end split;")
5657
}
5758

59+
func TestTraverseFlow_EnumSplitPreservesExplicitCaseOrder(t *testing.T) {
60+
e := newTestExecutor()
61+
62+
activityMap := map[model.ID]microflows.MicroflowObject{
63+
mkID("split"): &microflows.ExclusiveSplit{
64+
BaseMicroflowObject: mkObj("split"),
65+
SplitCondition: &microflows.ExpressionSplitCondition{Expression: "$Status"},
66+
},
67+
mkID("empty"): &microflows.ActionActivity{
68+
BaseActivity: microflows.BaseActivity{BaseMicroflowObject: mkObj("empty")},
69+
Action: &microflows.LogMessageAction{LogLevel: "Info", MessageTemplate: &model.Text{Translations: map[string]string{"en_US": "empty"}}},
70+
},
71+
mkID("ready"): &microflows.ActionActivity{
72+
BaseActivity: microflows.BaseActivity{BaseMicroflowObject: mkObj("ready")},
73+
Action: &microflows.LogMessageAction{LogLevel: "Info", MessageTemplate: &model.Text{Translations: map[string]string{"en_US": "ready"}}},
74+
},
75+
mkID("blocked"): &microflows.ActionActivity{
76+
BaseActivity: microflows.BaseActivity{BaseMicroflowObject: mkObj("blocked")},
77+
Action: &microflows.LogMessageAction{LogLevel: "Info", MessageTemplate: &model.Text{Translations: map[string]string{"en_US": "blocked"}}},
78+
},
79+
mkID("merge"): &microflows.ExclusiveMerge{BaseMicroflowObject: mkObj("merge")},
80+
}
81+
emptyFlow := mkBranchFlow("split", "empty", microflows.EnumerationCase{Value: ""})
82+
readyFlow := mkBranchFlow("split", "ready", microflows.EnumerationCase{Value: "Ready"})
83+
blockedFlow := mkBranchFlow("split", "blocked", microflows.EnumerationCase{Value: "Blocked"})
84+
applySplitCaseOrder(emptyFlow, 0)
85+
applySplitCaseOrder(readyFlow, 1)
86+
applySplitCaseOrder(blockedFlow, 2)
87+
flowsByOrigin := map[model.ID][]*microflows.SequenceFlow{
88+
mkID("split"): {blockedFlow, readyFlow, emptyFlow},
89+
mkID("empty"): {mkFlow("empty", "merge")},
90+
mkID("ready"): {mkFlow("ready", "merge")},
91+
mkID("blocked"): {mkFlow("blocked", "merge")},
92+
}
93+
splitMergeMap := map[model.ID]model.ID{mkID("split"): mkID("merge")}
94+
95+
var lines []string
96+
visited := make(map[model.ID]bool)
97+
e.traverseFlow(mkID("split"), activityMap, flowsByOrigin, splitMergeMap, visited, nil, nil, &lines, 1, nil, 0, nil)
98+
99+
out := strings.Join(lines, "\n")
100+
emptyIdx := strings.Index(out, "case (empty)")
101+
readyIdx := strings.Index(out, "case Ready")
102+
blockedIdx := strings.Index(out, "case Blocked")
103+
if emptyIdx == -1 || readyIdx == -1 || blockedIdx == -1 {
104+
t.Fatalf("missing expected cases:\n%s", out)
105+
}
106+
if !(emptyIdx < readyIdx && readyIdx < blockedIdx) {
107+
t.Fatalf("case order was not preserved:\n%s", out)
108+
}
109+
}
110+
58111
func assertContainsAny(t *testing.T, lines []string, want string) {
59112
t.Helper()
60113
for _, line := range lines {

mdl/executor/cmd_microflows_show_helpers.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package executor
66
import (
77
"context"
88
"fmt"
9+
"sort"
910
"strings"
1011

1112
"github.com/mendixlabs/mxcli/model"
@@ -960,7 +961,7 @@ func emitEnumSplitStatement(
960961
branches := []enumBranch{}
961962
branchByDestination := map[model.ID]int{}
962963
var elseFlow *microflows.SequenceFlow
963-
for _, flow := range findNormalFlows(flowsByOrigin[currentID]) {
964+
for _, flow := range orderedEnumSplitFlows(findNormalFlows(flowsByOrigin[currentID])) {
964965
caseValue, ok := enumCaseValue(flow)
965966
if !ok {
966967
elseFlow = flow
@@ -1024,6 +1025,26 @@ func enumCaseValue(flow *microflows.SequenceFlow) (string, bool) {
10241025
}
10251026
}
10261027

1028+
func orderedEnumSplitFlows(flows []*microflows.SequenceFlow) []*microflows.SequenceFlow {
1029+
ordered := append([]*microflows.SequenceFlow(nil), flows...)
1030+
sort.SliceStable(ordered, func(i, j int) bool {
1031+
return splitCaseOrder(ordered[i]) < splitCaseOrder(ordered[j])
1032+
})
1033+
return ordered
1034+
}
1035+
1036+
func splitCaseOrder(flow *microflows.SequenceFlow) int {
1037+
if flow == nil {
1038+
return 1 << 20
1039+
}
1040+
for i, pair := range splitCaseOrderAnchors {
1041+
if flow.OriginConnectionIndex == pair.origin && flow.DestinationConnectionIndex == pair.destination {
1042+
return i
1043+
}
1044+
}
1045+
return (1 << 10) + flow.OriginConnectionIndex*4 + flow.DestinationConnectionIndex
1046+
}
1047+
10271048
func formatEnumSplitCaseValue(value string) string {
10281049
if value == "" || value == "(empty)" {
10291050
return "(empty)"

0 commit comments

Comments
 (0)