Skip to content

Commit 9ac2bb0

Browse files
authored
Merge pull request #492 from hjotha/submit/boolean-split-case-values
fix: emit expression case values for IF flows
2 parents 58d907a + ee8d0a8 commit 9ac2bb0

7 files changed

Lines changed: 126 additions & 31 deletions

mdl/executor/cmd_microflows_builder_enum_split_test.go

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

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: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ func (w *Writer) serializeMicroflow(mf *microflows.Microflow) ([]byte, error) {
170170
func serializeSequenceFlow(flow *microflows.SequenceFlow, majorVersion int) bson.D {
171171
// Build the case document. Every sequence flow needs a case — NoCase is the
172172
// default when no branch condition has been set.
173-
caseDoc := buildSequenceFlowCase(flow.CaseValue)
173+
caseDoc := buildSequenceFlowCase(flow.CaseValue, majorVersion)
174174

175175
originCV := flow.OriginControlVector
176176
if originCV == "" {
@@ -221,13 +221,15 @@ func serializeSequenceFlow(flow *microflows.SequenceFlow, majorVersion int) bson
221221
// buildSequenceFlowCase renders the case document for a sequence flow.
222222
// When no case has been set on the flow, a NoCase document is synthesised —
223223
// Studio Pro requires every SequenceFlow to carry an explicit case object.
224-
func buildSequenceFlowCase(cv microflows.CaseValue) bson.D {
224+
func buildSequenceFlowCase(cv microflows.CaseValue, majorVersion int) bson.D {
225225
// Normalise value receivers to pointers so each case is handled once.
226226
switch c := cv.(type) {
227227
case microflows.EnumerationCase:
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,23 @@ 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+
if majorVersion <= 9 {
261+
return bson.D{
262+
{Key: "$ID", Value: idToBsonBinary(id)},
263+
{Key: "$Type", Value: "Microflows$EnumerationCase"},
264+
{Key: "Value", Value: c.Expression},
265+
}
266+
}
267+
return bson.D{
268+
{Key: "$ID", Value: idToBsonBinary(id)},
269+
{Key: "$Type", Value: "Microflows$ExpressionCase"},
270+
{Key: "Expression", Value: c.Expression},
271+
}
253272
}
254273
// Default: synthesise a NoCase document with a fresh ID.
255274
return bson.D{

sdk/mpr/writer_microflow_version_test.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,38 @@ func TestSerializeMicroflowParameter_Mx9_OmitsMx10OnlyKeys(t *testing.T) {
161161

162162
func TestBuildSequenceFlowCase_NormalisesValueReceiver(t *testing.T) {
163163
// A value-receiver NoCase must produce the same shape as a pointer.
164-
fromValue := buildSequenceFlowCase(microflows.NoCase{BaseElement: model.BaseElement{ID: "x"}})
165-
fromPointer := buildSequenceFlowCase(&microflows.NoCase{BaseElement: model.BaseElement{ID: "x"}})
164+
fromValue := buildSequenceFlowCase(microflows.NoCase{BaseElement: model.BaseElement{ID: "x"}}, 9)
165+
fromPointer := buildSequenceFlowCase(&microflows.NoCase{BaseElement: model.BaseElement{ID: "x"}}, 9)
166166

167167
if bsonGetKey(fromValue, "$Type") != bsonGetKey(fromPointer, "$Type") {
168168
t.Error("value and pointer NoCase must produce identical $Type")
169169
}
170170
}
171+
172+
func TestBuildSequenceFlowCase_ExpressionCase_Mx10(t *testing.T) {
173+
doc := buildSequenceFlowCase(microflows.ExpressionCase{
174+
BaseElement: model.BaseElement{ID: "case-false"},
175+
Expression: "false",
176+
}, 10)
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+
}
185+
186+
func TestBuildSequenceFlowCase_ExpressionCase_Mx9UsesEnumerationCase(t *testing.T) {
187+
doc := buildSequenceFlowCase(microflows.ExpressionCase{
188+
BaseElement: model.BaseElement{ID: "case-false"},
189+
Expression: "false",
190+
}, 9)
191+
192+
if got := bsonGetKey(doc, "$Type"); got != "Microflows$EnumerationCase" {
193+
t.Fatalf("$Type = %v, want Microflows$EnumerationCase", got)
194+
}
195+
if got := bsonGetKey(doc, "Value"); got != "false" {
196+
t.Fatalf("Value = %v, want false", got)
197+
}
198+
}

0 commit comments

Comments
 (0)