@@ -214,7 +214,7 @@ func (pii *processInstanceInfo) MarshalJSON() ([]byte, error) {
214214 case * eventBasedGatewayActivity :
215215 piia .ActivityAdapters = append (piia .ActivityAdapters , createEventBasedGatewayActivityAdapter (activity ))
216216 default :
217- panic ( fmt .Sprintf ("[invariant check] missing activity adapter for the type %T" , a ) )
217+ return nil , fmt .Errorf ("[invariant check] missing activity adapter for the type %T" , a )
218218 }
219219 }
220220 return json .Marshal (piia )
@@ -229,8 +229,7 @@ func (pii *processInstanceInfo) UnmarshalJSON(data []byte) error {
229229 }
230230 pii .ProcessInfo = & ProcessInfo {ProcessKey : adapter .ProcessKey }
231231 pii .VariableHolder = adapter .VariableHolder
232- recoverProcessInstanceActivitiesPart1 (pii , adapter )
233- return nil
232+ return recoverProcessInstanceActivitiesPart1 (pii , adapter )
234233}
235234
236235func createEventBasedGatewayActivityAdapter (ebga * eventBasedGatewayActivity ) * activityAdapter {
@@ -276,31 +275,182 @@ func (a activitySurrogate) Element() *BPMN20.BaseElement {
276275
277276// ----------------------------------------------------------------------------
278277
279- func (state * BpmnEngineState ) Marshal () []byte {
278+ // VariableWrapFunc function to wrap variables before marshalling
279+ //
280+ // Parameters:
281+ // - key: Variables key
282+ // - value: Wrapped value of the variable
283+ type VariableWrapFunc func (string , any ) (any , error )
284+
285+ // marshalOptions Options that will be used while marshalling the engine
286+ type marshalOptions struct {
287+ marshalVariablesFunc VariableWrapFunc
288+ }
289+
290+ // MarshalOption is a function that modifies the marshalOptions
291+ type MarshalOption func (* marshalOptions ) error
292+
293+ // WithMarshalVariableFunc sets a function that will be called for each variable in the engine's VarHolder
294+ // This allows you to customize variables before they are marshalled, e.g. to convert them to a different type
295+ func WithMarshalVariableFunc (fun VariableWrapFunc ) MarshalOption {
296+ return func (opts * marshalOptions ) error {
297+ opts .marshalVariablesFunc = fun
298+ return nil
299+ }
300+ }
301+
302+ // applyMarshalOptions Applies the given options and returns the applied marshalOptions
303+ func applyMarshalOptions (options ... MarshalOption ) (* marshalOptions , error ) {
304+ opts := & marshalOptions {}
305+ for _ , o := range options {
306+ err := o (opts )
307+ if err != nil {
308+ return nil , fmt .Errorf ("could not apply option: %w" , err )
309+ }
310+ }
311+
312+ return opts , nil
313+ }
314+
315+ // Marshal marshals the engine into a byte array.
316+ // Options may be provided to configure the marshalling process.
317+ // It returns a byte array containing the marshalled engine state.
318+ // If there is an error applying the options, it will panic.
319+ //
320+ // Example:
321+ //
322+ // ```go
323+ // // Marshal with default options
324+ // data, err := bpmn_engine.Marshal()
325+ //
326+ // // Marshal with type information for complex variables
327+ // data, err := bpmn_engine.Marshal(WithMarshalComplexTypes())
328+ // ```
329+ func (state * BpmnEngineState ) Marshal (options ... MarshalOption ) ([]byte , error ) {
330+ opts , err := applyMarshalOptions (options ... )
331+ if err != nil {
332+ return nil , err
333+ }
334+ pis , err := createProcessInstances (state .processInstances , opts )
335+ if err != nil {
336+ return nil , err
337+ }
338+
280339 m := serializedBpmnEngine {
281340 Version : CurrentSerializerVersion ,
282341 Name : state .name ,
283342 MessageSubscriptions : state .messageSubscriptions ,
284343 ProcessReferences : createReferences (state .processes ),
285- ProcessInstances : state . processInstances ,
344+ ProcessInstances : pis ,
286345 Timers : state .timers ,
287346 Jobs : state .jobs ,
288347 }
289348 bytes , err := json .Marshal (m )
290349 if err != nil {
291- panic (err )
350+ return nil , err
351+ }
352+ return bytes , nil
353+ }
354+
355+ // wrapVariables takes a variable holder and wraps each variable with a complexVariable if the variable is a
356+ // struct or pointer
357+ func wrapVariables (vh VariableHolder , f VariableWrapFunc ) (VariableHolder , error ) {
358+ for k , v := range vh .variables {
359+ val , err := f (k , v )
360+ if err != nil {
361+ return vh , err
362+ }
363+ vh .variables [k ] = val
364+ }
365+ // If there is a parent, create complex variables for it as well
366+ if vh .parent != nil {
367+ parent , err := wrapVariables (* vh .parent , f )
368+ if err != nil {
369+ return VariableHolder {}, err
370+ }
371+ vh .parent = & parent
372+ }
373+ return vh , nil
374+ }
375+
376+ // createProcessInstances Creates process instances that can be marshalled to JSON
377+ func createProcessInstances (pii []* processInstanceInfo , opts * marshalOptions ) ([]* processInstanceInfo , error ) {
378+
379+ // If exporting types is not enable, there is nothing extra to do
380+ if opts .marshalVariablesFunc == nil {
381+ return pii , nil
382+ }
383+
384+ // Create complex variables for each process instance
385+ for _ , pi := range pii {
386+ cvs , err := wrapVariables (pi .VariableHolder , opts .marshalVariablesFunc )
387+ if err != nil {
388+ return nil , err
389+ }
390+ pi .VariableHolder = cvs
292391 }
293- return bytes
392+ return pii , nil
393+ }
394+
395+ // VariableUnwrapFunc function to unwrap variables from marshalled state
396+ //
397+ // Parameters:
398+ // - key: Variables key
399+ // - value: Wrapped value of the variable
400+ type VariableUnwrapFunc func (key string , value any ) (any , error )
401+
402+ // unmarshalOptions Options that will be used while unmarshalling the engine
403+ type unmarshalOptions struct {
404+ // variableUnwrapFunc function that can be called to restore a variable from a marshalled state
405+ variableUnwrapFunc VariableUnwrapFunc
406+ }
407+
408+ // UnmarshalOption is a function that modifies the unmarshalOptions
409+ type UnmarshalOption func (* unmarshalOptions ) error
410+
411+ // WithUnmarshalVariableFunc sets a function that will be called for each variable in the engine's VarHolder
412+ // This allows you to customize variables after they are unmarshalled, e.g. to convert them to a different type
413+ func WithUnmarshalVariableFunc (fun VariableUnwrapFunc ) UnmarshalOption {
414+ return func (opts * unmarshalOptions ) error {
415+ opts .variableUnwrapFunc = fun
416+ return nil
417+ }
418+ }
419+
420+ // applyUnmarshalOptions Applies the given options and returns the applied unmarshalOptions
421+ func applyUnmarshalOptions (options ... UnmarshalOption ) (* unmarshalOptions , error ) {
422+ opts := & unmarshalOptions {}
423+ for _ , o := range options {
424+ err := o (opts )
425+ if err != nil {
426+ return nil , fmt .Errorf ("could not apply option: %w" , err )
427+ }
428+ }
429+
430+ return opts , nil
294431}
295432
296433// Unmarshal loads the data byte array and creates a new instance of the BPMN Engine
297434// Will return an BpmnEngineUnmarshallingError, if there was an issue AND in case of error,
298435// the engine return object is only partially initialized and likely not usable
299- func Unmarshal (data []byte ) (BpmnEngineState , error ) {
436+ func Unmarshal (data []byte , opts ... UnmarshalOption ) (BpmnEngineState , error ) {
437+
438+ // Build an unmarshalOptions object from the provided options
439+ options , err := applyUnmarshalOptions (opts ... )
440+ if err != nil {
441+ return BpmnEngineState {}, & BpmnEngineUnmarshallingError {
442+ Msg : "Failed to apply unmarshalling options" ,
443+ Err : err ,
444+ }
445+ }
446+
300447 eng := serializedBpmnEngine {}
301- err : = json .Unmarshal (data , & eng )
448+ err = json .Unmarshal (data , & eng )
302449 if err != nil {
303- panic (err )
450+ return BpmnEngineState {}, & BpmnEngineUnmarshallingError {
451+ Msg : "Failed to unmarshall engine data" ,
452+ Err : err ,
453+ }
304454 }
305455 state := New ()
306456 state .name = eng .Name
@@ -327,12 +477,15 @@ func Unmarshal(data []byte) (BpmnEngineState, error) {
327477 }
328478 if eng .ProcessInstances != nil {
329479 state .processInstances = eng .ProcessInstances
330- err : = recoverProcessInstances (& state )
480+ err = recoverProcessInstances (& state , options )
331481 if err != nil {
332482 return state , err
333483 }
334484 }
335- recoverProcessInstanceActivitiesPart2 (& state )
485+ err = recoverProcessInstanceActivitiesPart2 (& state )
486+ if err != nil {
487+ return BpmnEngineState {}, err
488+ }
336489 if eng .MessageSubscriptions != nil {
337490 state .messageSubscriptions = eng .MessageSubscriptions
338491 err = recoverMessageSubscriptions (& state )
@@ -357,7 +510,7 @@ func Unmarshal(data []byte) (BpmnEngineState, error) {
357510 return state , nil
358511}
359512
360- func recoverProcessInstanceActivitiesPart1 (pii * processInstanceInfo , adapter * processInstanceInfoAdapter ) {
513+ func recoverProcessInstanceActivitiesPart1 (pii * processInstanceInfo , adapter * processInstanceInfoAdapter ) error {
361514 for _ , aa := range adapter .ActivityAdapters {
362515 switch aa .Type {
363516 case gatewayActivityAdapterType :
@@ -378,12 +531,13 @@ func recoverProcessInstanceActivitiesPart1(pii *processInstanceInfo, adapter *pr
378531 OutboundActivityCompleted : aa .OutboundActivityCompleted ,
379532 })
380533 default :
381- panic ( fmt .Sprintf ("[invariant check] missing recovery code for actictyAdapter.Type=%d" , aa .Type ) )
534+ return fmt .Errorf ("[invariant check] missing recovery code for actictyAdapter.Type=%d" , aa .Type )
382535 }
383536 }
537+ return nil
384538}
385539
386- func recoverProcessInstanceActivitiesPart2 (state * BpmnEngineState ) {
540+ func recoverProcessInstanceActivitiesPart2 (state * BpmnEngineState ) error {
387541 for _ , pi := range state .processInstances {
388542 for _ , a := range pi .activities {
389543 switch activity := a .(type ) {
@@ -392,15 +546,33 @@ func recoverProcessInstanceActivitiesPart2(state *BpmnEngineState) {
392546 case * gatewayActivity :
393547 activity .element = BPMN20 .FindBaseElementsById (pi .ProcessInfo .definitions .Process , (* a .Element ()).GetId ())[0 ]
394548 default :
395- panic ( fmt .Sprintf ("[invariant check] missing case for activity type=%T" , a ) )
549+ return fmt .Errorf ("[invariant check] missing case for activity type=%T" , a )
396550 }
397551 }
398552 }
553+ return nil
399554}
400555
401- // ----------------------------------------------------------------------------
556+ // recoverVariableInstances recovers the variable instances from the given VariableHolder
557+ func recoverVariableInstances (vh VariableHolder , opts * unmarshalOptions ) (VariableHolder , error ) {
558+ if opts .variableUnwrapFunc == nil {
559+ // Nothing additional to do
560+ return vh , nil
561+ }
402562
403- func recoverProcessInstances (state * BpmnEngineState ) error {
563+ for k , v := range vh .variables {
564+ val , err := opts .variableUnwrapFunc (k , v )
565+ if err != nil {
566+ return vh , err
567+ }
568+
569+ // Replace the variable with the proper instance
570+ vh .variables [k ] = val
571+ }
572+ return vh , nil
573+ }
574+
575+ func recoverProcessInstances (state * BpmnEngineState , opts * unmarshalOptions ) error {
404576 for i , pi := range state .processInstances {
405577 process := state .findProcess (pi .ProcessInfo .ProcessKey )
406578 if process == nil {
@@ -410,7 +582,11 @@ func recoverProcessInstances(state *BpmnEngineState) error {
410582 }
411583 }
412584 state .processInstances [i ].ProcessInfo = process
413- state .processInstances [i ].VariableHolder = pi .VariableHolder
585+ vars , err := recoverVariableInstances (pi .VariableHolder , opts )
586+ if err != nil {
587+ return err
588+ }
589+ state .processInstances [i ].VariableHolder = vars
414590 }
415591 return nil
416592}
0 commit comments