@@ -105,13 +105,18 @@ func (w *Writer) serializeMicroflow(mf *microflows.Microflow) ([]byte, error) {
105105 }
106106
107107 // Add Flows array (SequenceFlows and AnnotationFlows go here, not in ObjectCollection)
108+ // The serialized shape depends on the project's Mendix major version.
109+ majorVersion := 11 // modern default when version metadata is unavailable (e.g. in-memory tests)
110+ if pv := w .reader .ProjectVersion (); pv != nil {
111+ majorVersion = pv .MajorVersion
112+ }
108113 flows := bson.A {int32 (3 )} // Start with array type marker
109114 if mf .ObjectCollection != nil {
110115 for _ , flow := range mf .ObjectCollection .Flows {
111- flows = append (flows , serializeSequenceFlow (flow ))
116+ flows = append (flows , serializeSequenceFlow (flow , majorVersion ))
112117 }
113118 for _ , af := range mf .ObjectCollection .AnnotationFlows {
114- flows = append (flows , serializeAnnotationFlow (af ))
119+ flows = append (flows , serializeAnnotationFlow (af , majorVersion ))
115120 }
116121 }
117122 doc = append (doc , bson.E {Key : "Flows" , Value : flows })
@@ -133,63 +138,37 @@ func (w *Writer) serializeMicroflow(mf *microflows.Microflow) ([]byte, error) {
133138 // Add object collection (without flows - they're in Flows array)
134139 // Parameters go in ObjectCollection.Objects, pass them here
135140 if mf .ObjectCollection != nil {
136- doc = append (doc , bson.E {Key : "ObjectCollection" , Value : serializeMicroflowObjectCollectionWithoutFlows (mf .ObjectCollection , mf .Parameters )})
141+ doc = append (doc , bson.E {Key : "ObjectCollection" , Value : serializeMicroflowObjectCollectionWithoutFlows (mf .ObjectCollection , mf .Parameters , majorVersion )})
137142 }
138143
139- // Add remaining optional fields
140- // ReturnVariableName is "" by default (Studio Pro convention).
141- // Only set a custom name when explicitly specified via "RETURNS xxx AS $VarName".
142- doc = append (doc , bson.E {Key : "ReturnVariableName" , Value : mf .ReturnVariableName })
143- doc = append (doc , bson.E {Key : "StableId" , Value : idToBsonBinary (generateUUID ())})
144- doc = append (doc , bson.E {Key : "Url" , Value : "" })
145- doc = append (doc , bson.E {Key : "UrlSearchParameters" , Value : bson.A {int32 (1 )}})
144+ // ReturnVariableName, StableId, Url, and UrlSearchParameters were added in
145+ // Mendix 10; Mendix 9 projects do not know about these fields and Studio Pro
146+ // raises metamodel errors if they're present.
147+ if majorVersion >= 10 {
148+ // ReturnVariableName is "" by default (Studio Pro convention).
149+ // Only set a custom name when explicitly specified via "RETURNS xxx AS $VarName".
150+ doc = append (doc , bson.E {Key : "ReturnVariableName" , Value : mf .ReturnVariableName })
151+ doc = append (doc , bson.E {Key : "StableId" , Value : idToBsonBinary (generateUUID ())})
152+ doc = append (doc , bson.E {Key : "Url" , Value : "" })
153+ doc = append (doc , bson.E {Key : "UrlSearchParameters" , Value : bson.A {int32 (1 )}})
154+ }
146155 doc = append (doc , bson.E {Key : "WorkflowActionInfo" , Value : nil })
147156
148157 return bson .Marshal (doc )
149158}
150159
151160// serializeSequenceFlow serializes a SequenceFlow to BSON with correct structure.
152- func serializeSequenceFlow (flow * microflows.SequenceFlow ) bson.D {
153- // Serialize CaseValues
154- caseValues := bson.A {int32 (2 )} // Default empty array marker
155- if flow .CaseValue != nil {
156- switch cv := flow .CaseValue .(type ) {
157- case microflows.EnumerationCase :
158- caseValues = bson.A {
159- int32 (2 ),
160- bson.D {
161- {Key : "$ID" , Value : idToBsonBinary (string (cv .ID ))},
162- {Key : "$Type" , Value : "Microflows$EnumerationCase" },
163- {Key : "Value" , Value : cv .Value },
164- },
165- }
166- case * microflows.EnumerationCase :
167- caseValues = bson.A {
168- int32 (2 ),
169- bson.D {
170- {Key : "$ID" , Value : idToBsonBinary (string (cv .ID ))},
171- {Key : "$Type" , Value : "Microflows$EnumerationCase" },
172- {Key : "Value" , Value : cv .Value },
173- },
174- }
175- case microflows.NoCase :
176- caseValues = bson.A {
177- int32 (2 ),
178- bson.D {
179- {Key : "$ID" , Value : idToBsonBinary (string (cv .ID ))},
180- {Key : "$Type" , Value : "Microflows$NoCase" },
181- },
182- }
183- case * microflows.NoCase :
184- caseValues = bson.A {
185- int32 (2 ),
186- bson.D {
187- {Key : "$ID" , Value : idToBsonBinary (string (cv .ID ))},
188- {Key : "$Type" , Value : "Microflows$NoCase" },
189- },
190- }
191- }
192- }
161+ //
162+ // The case value shape is version-specific:
163+ // - Mendix 9: inline `NewCaseValue` document (NoCase for non-decision flows,
164+ // EnumerationCase for decision branches). `CaseValues` is omitted.
165+ // - Mendix 10+: `CaseValues = [marker, case]` where the case is always present
166+ // (at minimum a NoCase object). Studio Pro rejects `CaseValues = [marker]`
167+ // alone with CE0079/CE0773 "condition value must be configured".
168+ func serializeSequenceFlow (flow * microflows.SequenceFlow , majorVersion int ) bson.D {
169+ // Build the case document. Every sequence flow needs a case — NoCase is the
170+ // default when no branch condition has been set.
171+ caseDoc := buildSequenceFlowCase (flow .CaseValue )
193172
194173 originCV := flow .OriginControlVector
195174 if originCV == "" {
@@ -200,26 +179,111 @@ func serializeSequenceFlow(flow *microflows.SequenceFlow) bson.D {
200179 destCV = "0;0"
201180 }
202181
203- return bson.D {
182+ doc := bson.D {
204183 {Key : "$ID" , Value : idToBsonBinary (string (flow .ID ))},
205184 {Key : "$Type" , Value : "Microflows$SequenceFlow" },
206- {Key : "CaseValues" , Value : caseValues },
207- {Key : "DestinationConnectionIndex" , Value : int32 (flow .DestinationConnectionIndex )},
208- {Key : "DestinationPointer" , Value : idToBsonBinary (string (flow .DestinationID ))},
209- {Key : "IsErrorHandler" , Value : flow .IsErrorHandler },
210- {Key : "Line" , Value : bson.D {
211- {Key : "$ID" , Value : idToBsonBinary (generateUUID ())},
212- {Key : "$Type" , Value : "Microflows$BezierCurve" },
213- {Key : "DestinationControlVector" , Value : destCV },
214- {Key : "OriginControlVector" , Value : originCV },
215- }},
216- {Key : "OriginConnectionIndex" , Value : int32 (flow .OriginConnectionIndex )},
217- {Key : "OriginPointer" , Value : idToBsonBinary (string (flow .OriginID ))},
185+ }
186+
187+ if majorVersion <= 9 {
188+ // Legacy Mendix 9 shape:
189+ // - inline NewCaseValue (no CaseValues array)
190+ // - OriginBezierVector / DestinationBezierVector are top-level strings
191+ // (no nested Line: Microflows$BezierCurve document)
192+ doc = append (doc , bson.E {Key : "DestinationBezierVector" , Value : destCV })
193+ doc = append (doc , bson.E {Key : "DestinationConnectionIndex" , Value : int32 (flow .DestinationConnectionIndex )})
194+ doc = append (doc , bson.E {Key : "DestinationPointer" , Value : idToBsonBinary (string (flow .DestinationID ))})
195+ doc = append (doc , bson.E {Key : "IsErrorHandler" , Value : flow .IsErrorHandler })
196+ doc = append (doc , bson.E {Key : "NewCaseValue" , Value : caseDoc })
197+ doc = append (doc , bson.E {Key : "OriginBezierVector" , Value : originCV })
198+ doc = append (doc , bson.E {Key : "OriginConnectionIndex" , Value : int32 (flow .OriginConnectionIndex )})
199+ doc = append (doc , bson.E {Key : "OriginPointer" , Value : idToBsonBinary (string (flow .OriginID ))})
200+ return doc
201+ }
202+
203+ // Modern format (Mx 10+): CaseValues = [marker, caseDoc].
204+ doc = append (doc , bson.E {Key : "CaseValues" , Value : bson.A {int32 (2 ), caseDoc }})
205+ doc = append (doc , bson.E {Key : "DestinationConnectionIndex" , Value : int32 (flow .DestinationConnectionIndex )})
206+ doc = append (doc , bson.E {Key : "DestinationPointer" , Value : idToBsonBinary (string (flow .DestinationID ))})
207+ doc = append (doc , bson.E {Key : "IsErrorHandler" , Value : flow .IsErrorHandler })
208+ doc = append (doc , bson.E {Key : "Line" , Value : bson.D {
209+ {Key : "$ID" , Value : idToBsonBinary (generateUUID ())},
210+ {Key : "$Type" , Value : "Microflows$BezierCurve" },
211+ {Key : "DestinationControlVector" , Value : destCV },
212+ {Key : "OriginControlVector" , Value : originCV },
213+ }})
214+ doc = append (doc , bson.E {Key : "OriginConnectionIndex" , Value : int32 (flow .OriginConnectionIndex )})
215+ doc = append (doc , bson.E {Key : "OriginPointer" , Value : idToBsonBinary (string (flow .OriginID ))})
216+ return doc
217+ }
218+
219+ // buildSequenceFlowCase renders the case document for a sequence flow.
220+ // When no case has been set on the flow, a NoCase document is synthesised —
221+ // Studio Pro requires every SequenceFlow to carry an explicit case object.
222+ func buildSequenceFlowCase (cv microflows.CaseValue ) bson.D {
223+ switch c := cv .(type ) {
224+ case * microflows.EnumerationCase :
225+ id := string (c .ID )
226+ if id == "" {
227+ id = generateUUID ()
228+ }
229+ return bson.D {
230+ {Key : "$ID" , Value : idToBsonBinary (id )},
231+ {Key : "$Type" , Value : "Microflows$EnumerationCase" },
232+ {Key : "Value" , Value : c .Value },
233+ }
234+ case microflows.EnumerationCase :
235+ id := string (c .ID )
236+ if id == "" {
237+ id = generateUUID ()
238+ }
239+ return bson.D {
240+ {Key : "$ID" , Value : idToBsonBinary (id )},
241+ {Key : "$Type" , Value : "Microflows$EnumerationCase" },
242+ {Key : "Value" , Value : c .Value },
243+ }
244+ case * microflows.NoCase :
245+ id := string (c .ID )
246+ if id == "" {
247+ id = generateUUID ()
248+ }
249+ return bson.D {
250+ {Key : "$ID" , Value : idToBsonBinary (id )},
251+ {Key : "$Type" , Value : "Microflows$NoCase" },
252+ }
253+ case microflows.NoCase :
254+ id := string (c .ID )
255+ if id == "" {
256+ id = generateUUID ()
257+ }
258+ return bson.D {
259+ {Key : "$ID" , Value : idToBsonBinary (id )},
260+ {Key : "$Type" , Value : "Microflows$NoCase" },
261+ }
262+ }
263+ // Default: synthesise a NoCase document with a fresh ID.
264+ return bson.D {
265+ {Key : "$ID" , Value : idToBsonBinary (generateUUID ())},
266+ {Key : "$Type" , Value : "Microflows$NoCase" },
218267 }
219268}
220269
221270// serializeAnnotationFlow serializes an AnnotationFlow to BSON.
222- func serializeAnnotationFlow (af * microflows.AnnotationFlow ) bson.D {
271+ // The line shape is version-specific: Mendix 9 stores OriginBezierVector /
272+ // DestinationBezierVector as top-level strings, while Mendix 10+ nests them
273+ // inside a Microflows$BezierCurve document under `Line`.
274+ func serializeAnnotationFlow (af * microflows.AnnotationFlow , majorVersion int ) bson.D {
275+ if majorVersion <= 9 {
276+ return bson.D {
277+ {Key : "$ID" , Value : idToBsonBinary (string (af .ID ))},
278+ {Key : "$Type" , Value : "Microflows$AnnotationFlow" },
279+ {Key : "DestinationBezierVector" , Value : "0;0" },
280+ {Key : "DestinationConnectionIndex" , Value : int32 (0 )},
281+ {Key : "DestinationPointer" , Value : idToBsonBinary (string (af .DestinationID ))},
282+ {Key : "OriginBezierVector" , Value : "0;0" },
283+ {Key : "OriginConnectionIndex" , Value : int32 (0 )},
284+ {Key : "OriginPointer" , Value : idToBsonBinary (string (af .OriginID ))},
285+ }
286+ }
223287 return bson.D {
224288 {Key : "$ID" , Value : idToBsonBinary (string (af .ID ))},
225289 {Key : "$Type" , Value : "Microflows$AnnotationFlow" },
@@ -238,21 +302,28 @@ func serializeAnnotationFlow(af *microflows.AnnotationFlow) bson.D {
238302
239303// serializeMicroflowParameter serializes a MicroflowParameter to BSON.
240304// Parameters go in ObjectCollection.Objects, not in a separate collection.
241- func serializeMicroflowParameter (p * microflows.MicroflowParameter , posX int ) bson.D {
305+ //
306+ // DefaultValue and IsRequired were introduced in Mendix 10; emitting them on a
307+ // Mendix 9 project trips the Studio Pro metamodel checker, so they are gated.
308+ func serializeMicroflowParameter (p * microflows.MicroflowParameter , posX int , majorVersion int ) bson.D {
242309 // Calculate position based on index - parameters appear at the top of the microflow
243310 relativeMiddlePoint := fmt .Sprintf ("%d;53" , 200 + posX * 100 )
244311
245312 doc := bson.D {
246313 {Key : "$ID" , Value : idToBsonBinary (string (p .ID ))},
247314 {Key : "$Type" , Value : "Microflows$MicroflowParameter" },
248- {Key : "DefaultValue" , Value : "" },
249- {Key : "Documentation" , Value : p .Documentation },
250- {Key : "HasVariableNameBeenChanged" , Value : false },
251- {Key : "IsRequired" , Value : true },
252- {Key : "Name" , Value : p .Name },
253- {Key : "RelativeMiddlePoint" , Value : relativeMiddlePoint },
254- {Key : "Size" , Value : "30;30" },
255315 }
316+ if majorVersion >= 10 {
317+ doc = append (doc , bson.E {Key : "DefaultValue" , Value : "" })
318+ }
319+ doc = append (doc , bson.E {Key : "Documentation" , Value : p .Documentation })
320+ doc = append (doc , bson.E {Key : "HasVariableNameBeenChanged" , Value : false })
321+ if majorVersion >= 10 {
322+ doc = append (doc , bson.E {Key : "IsRequired" , Value : true })
323+ }
324+ doc = append (doc , bson.E {Key : "Name" , Value : p .Name })
325+ doc = append (doc , bson.E {Key : "RelativeMiddlePoint" , Value : relativeMiddlePoint })
326+ doc = append (doc , bson.E {Key : "Size" , Value : "30;30" })
256327 if p .Type != nil {
257328 doc = append (doc , bson.E {Key : "VariableType" , Value : serializeMicroflowDataType (p .Type )})
258329 }
@@ -350,13 +421,13 @@ func serializeMicroflowDataType(dt microflows.DataType) bson.D {
350421
351422// serializeMicroflowObjectCollectionWithoutFlows serializes the object collection to BSON (flows are in separate Flows array).
352423// Parameters are also included in the Objects array.
353- func serializeMicroflowObjectCollectionWithoutFlows (oc * microflows.MicroflowObjectCollection , params []* microflows.MicroflowParameter ) bson.D {
424+ func serializeMicroflowObjectCollectionWithoutFlows (oc * microflows.MicroflowObjectCollection , params []* microflows.MicroflowParameter , majorVersion int ) bson.D {
354425 // Start with array type marker, then serialize objects (NOT flows)
355426 objects := bson.A {int32 (3 )} // Array type marker
356427
357428 // Add parameters first (they appear at the top of the microflow)
358429 for i , p := range params {
359- objects = append (objects , serializeMicroflowParameter (p , i ))
430+ objects = append (objects , serializeMicroflowParameter (p , i , majorVersion ))
360431 }
361432
362433 // Add regular microflow objects
@@ -404,16 +475,21 @@ func serializeMicroflowObject(obj microflows.MicroflowObject) bson.D {
404475 }
405476
406477 case * microflows.EndEvent :
478+ // Pristine EndEvents always carry `ReturnValue` (empty string for void
479+ // microflows; expression + "\n" when a value is returned). Omitting it
480+ // diverges from the pristine key set on Mx 9 roundtrips.
481+ returnValue := ""
482+ if o .ReturnValue != "" {
483+ returnValue = o .ReturnValue + "\n "
484+ }
407485 doc := bson.D {
408486 {Key : "$ID" , Value : idToBsonBinary (string (o .ID ))},
409487 {Key : "$Type" , Value : "Microflows$EndEvent" },
410488 {Key : "Documentation" , Value : "" },
411489 {Key : "RelativeMiddlePoint" , Value : pointToString (o .Position )},
490+ {Key : "ReturnValue" , Value : returnValue },
491+ {Key : "Size" , Value : sizeToString (o .Size )},
412492 }
413- if o .ReturnValue != "" {
414- doc = append (doc , bson.E {Key : "ReturnValue" , Value : o .ReturnValue + "\n " })
415- }
416- doc = append (doc , bson.E {Key : "Size" , Value : sizeToString (o .Size )})
417493 return doc
418494
419495 case * microflows.ErrorEvent :
@@ -450,6 +526,7 @@ func serializeMicroflowObject(obj microflows.MicroflowObject) bson.D {
450526 {Key : "$ID" , Value : idToBsonBinary (string (o .ID ))},
451527 {Key : "$Type" , Value : "Microflows$ExclusiveSplit" },
452528 {Key : "Caption" , Value : o .Caption },
529+ {Key : "Documentation" , Value : o .Documentation },
453530 {Key : "ErrorHandlingType" , Value : string (o .ErrorHandlingType )},
454531 {Key : "RelativeMiddlePoint" , Value : pointToString (o .Position )},
455532 {Key : "Size" , Value : sizeToString (o .Size )},
0 commit comments