Skip to content

Commit 557016e

Browse files
committed
fix: emit expression case values for IF flows
Symptom: MPR validation can report duplicate output variables for values declared in mutually exclusive IF branches after exec builds the graph. A targeted validation pass also showed that emitting Microflows$BooleanCase breaks Mendix 9 projects because that metamodel type is not available there. Root cause: IF sequence flows used EnumerationCase values with the strings "true" and "false". Studio Pro's IF branches are represented as expression cases, while enumeration cases are for enum split values. Fix: emit ExpressionCase for true/false IF branch flows, keep EnumerationCase for real enum branch values, and teach the MPR writer to serialize ExpressionCase so it does not degrade to NoCase. Tests: make build, make test, make lint-go.
1 parent da6f250 commit 557016e

7 files changed

Lines changed: 101 additions & 27 deletions

mdl/executor/cmd_microflows_builder_enum_split_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func TestEnumSplitNestedEmptyThenBranchKeepsContinuationCase(t *testing.T) {
111111
if flow.OriginID != nestedSplitID {
112112
continue
113113
}
114-
if value, ok := enumCaseValue(flow); ok && value == "true" {
114+
if flowCaseString(flow.CaseValue) == "true" {
115115
if _, ok := objects[flow.DestinationID].(*microflows.ExclusiveMerge); ok {
116116
return
117117
}

mdl/executor/cmd_microflows_builder_flows.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -653,10 +653,7 @@ func newHorizontalFlow(originID, destinationID model.ID) *microflows.SequenceFlo
653653
// newHorizontalFlowWithCase creates a horizontal SequenceFlow with a boolean case value (for splits)
654654
func newHorizontalFlowWithCase(originID, destinationID model.ID, caseValue string) *microflows.SequenceFlow {
655655
flow := newHorizontalFlow(originID, destinationID)
656-
flow.CaseValue = microflows.EnumerationCase{
657-
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
658-
Value: caseValue, // "true" or "false" as string
659-
}
656+
flow.CaseValue = caseValueForFlow(caseValue)
660657
return flow
661658
}
662659

@@ -678,10 +675,27 @@ func newDownwardFlowWithCase(originID, destinationID model.ID, caseValue string)
678675
DestinationID: destinationID,
679676
OriginConnectionIndex: AnchorBottom, // Connect from bottom of origin (going down)
680677
DestinationConnectionIndex: AnchorLeft, // Connect to left side of destination
681-
CaseValue: microflows.EnumerationCase{
678+
CaseValue: caseValueForFlow(caseValue),
679+
}
680+
}
681+
682+
func caseValueForFlow(caseValue string) microflows.CaseValue {
683+
switch caseValue {
684+
case "true":
685+
return &microflows.ExpressionCase{
682686
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
683-
Value: caseValue, // "true" or "false" as string
684-
},
687+
Expression: "true",
688+
}
689+
case "false":
690+
return &microflows.ExpressionCase{
691+
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
692+
Expression: "false",
693+
}
694+
default:
695+
return microflows.EnumerationCase{
696+
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
697+
Value: caseValue,
698+
}
685699
}
686700
}
687701

mdl/executor/cmd_microflows_builder_terminal_test.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -892,17 +892,9 @@ func flowCaseString(caseValue microflows.CaseValue) string {
892892
if c != nil {
893893
return c.Value
894894
}
895-
case microflows.BooleanCase:
896-
if c.Value {
897-
return "true"
898-
}
899-
return "false"
900-
case *microflows.BooleanCase:
901-
if c != nil && c.Value {
902-
return "true"
903-
}
895+
case *microflows.ExpressionCase:
904896
if c != nil {
905-
return "false"
897+
return c.Expression
906898
}
907899
}
908900
return ""

mdl/executor/cmd_microflows_guard_pattern_test.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,10 @@ func TestBuilder_GuardPatternPreservesFalseBranchAnchor(t *testing.T) {
4747
oc := fb.buildFlowGraph(body, nil)
4848

4949
// Find the flow from the split to the tail log. It's the only one with
50-
// an EnumerationCase Value=="false" that doesn't target an EndEvent.
50+
// a false branch case that doesn't target an EndEvent.
5151
var found *microflows.SequenceFlow
5252
for _, f := range oc.Flows {
53-
cv, ok := f.CaseValue.(microflows.EnumerationCase)
54-
if !ok {
55-
if p, okp := f.CaseValue.(*microflows.EnumerationCase); okp {
56-
cv = *p
57-
ok = true
58-
}
59-
}
60-
if !ok || cv.Value != "false" {
53+
if flowCaseString(f.CaseValue) != "false" {
6154
continue
6255
}
6356
// Exclude flows pointing at an EndEvent.
@@ -85,3 +78,31 @@ func TestBuilder_GuardPatternPreservesFalseBranchAnchor(t *testing.T) {
8578
t.Errorf("destination: got %d, want %d (Top)", found.DestinationConnectionIndex, AnchorTop)
8679
}
8780
}
81+
82+
func TestCaseValueForFlowUsesExpressionCaseForBooleanBranches(t *testing.T) {
83+
for _, tc := range []struct {
84+
value string
85+
want string
86+
}{
87+
{value: "true", want: "true"},
88+
{value: "false", want: "false"},
89+
} {
90+
got, ok := caseValueForFlow(tc.value).(*microflows.ExpressionCase)
91+
if !ok {
92+
t.Fatalf("caseValueForFlow(%q) = %T, want *ExpressionCase", tc.value, caseValueForFlow(tc.value))
93+
}
94+
if got.Expression != tc.want {
95+
t.Fatalf("caseValueForFlow(%q).Expression = %q, want %q", tc.value, got.Expression, tc.want)
96+
}
97+
}
98+
}
99+
100+
func TestCaseValueForFlowKeepsEnumValuesAsEnumerationCase(t *testing.T) {
101+
got, ok := caseValueForFlow("Submitted").(microflows.EnumerationCase)
102+
if !ok {
103+
t.Fatalf("caseValueForFlow(enum) = %T, want EnumerationCase", caseValueForFlow("Submitted"))
104+
}
105+
if got.Value != "Submitted" {
106+
t.Fatalf("enum case value = %q, want Submitted", got.Value)
107+
}
108+
}

sdk/mpr/parser_microflow_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,27 @@ func TestParseSequenceFlow_NewCaseValueEnumerationCase(t *testing.T) {
3535
}
3636
}
3737

38+
func TestParseSequenceFlow_NewCaseValueExpressionCase(t *testing.T) {
39+
flow := parseSequenceFlow(map[string]any{
40+
"$ID": "flow-1",
41+
"OriginPointer": "start-1",
42+
"DestinationPointer": "dest-1",
43+
"NewCaseValue": primitive.D{
44+
{Key: "$ID", Value: "case-1"},
45+
{Key: "$Type", Value: "Microflows$ExpressionCase"},
46+
{Key: "Expression", Value: "false"},
47+
},
48+
})
49+
50+
got, ok := flow.CaseValue.(*microflows.ExpressionCase)
51+
if !ok {
52+
t.Fatalf("expected *ExpressionCase, got %T", flow.CaseValue)
53+
}
54+
if got.Expression != "false" {
55+
t.Fatalf("expected false branch, got %q", got.Expression)
56+
}
57+
}
58+
3859
func TestParseSequenceFlow_NewCaseValueNoCase(t *testing.T) {
3960
flow := parseSequenceFlow(map[string]any{
4061
"$ID": "flow-1",

sdk/mpr/writer_microflow.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ func buildSequenceFlowCase(cv microflows.CaseValue) bson.D {
228228
cv = &c
229229
case microflows.NoCase:
230230
cv = &c
231+
case microflows.ExpressionCase:
232+
cv = &c
231233
}
232234

233235
switch c := cv.(type) {
@@ -250,6 +252,16 @@ func buildSequenceFlowCase(cv microflows.CaseValue) bson.D {
250252
{Key: "$ID", Value: idToBsonBinary(id)},
251253
{Key: "$Type", Value: "Microflows$NoCase"},
252254
}
255+
case *microflows.ExpressionCase:
256+
id := string(c.ID)
257+
if id == "" {
258+
id = generateUUID()
259+
}
260+
return bson.D{
261+
{Key: "$ID", Value: idToBsonBinary(id)},
262+
{Key: "$Type", Value: "Microflows$ExpressionCase"},
263+
{Key: "Expression", Value: c.Expression},
264+
}
253265
}
254266
// Default: synthesise a NoCase document with a fresh ID.
255267
return bson.D{

sdk/mpr/writer_microflow_version_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,17 @@ func TestBuildSequenceFlowCase_NormalisesValueReceiver(t *testing.T) {
168168
t.Error("value and pointer NoCase must produce identical $Type")
169169
}
170170
}
171+
172+
func TestBuildSequenceFlowCase_ExpressionCase(t *testing.T) {
173+
doc := buildSequenceFlowCase(microflows.ExpressionCase{
174+
BaseElement: model.BaseElement{ID: "case-false"},
175+
Expression: "false",
176+
})
177+
178+
if got := bsonGetKey(doc, "$Type"); got != "Microflows$ExpressionCase" {
179+
t.Fatalf("$Type = %v, want Microflows$ExpressionCase", got)
180+
}
181+
if got := bsonGetKey(doc, "Expression"); got != "false" {
182+
t.Fatalf("Expression = %v, want false", got)
183+
}
184+
}

0 commit comments

Comments
 (0)