@@ -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,172 @@ func (a activitySurrogate) Element() *BPMN20.BaseElement {
276275
277276// ----------------------------------------------------------------------------
278277
279- func (state * BpmnEngineState ) Marshal () []byte {
278+ type VariableWrapFunc func (any ) (any , error )
279+
280+ // marshalOptions Options that will be used while marshalling the engine
281+ type marshalOptions struct {
282+ marshalVariablesFunc VariableWrapFunc
283+ }
284+
285+ // MarshalOption is a function that modifies the marshalOptions
286+ type MarshalOption func (* marshalOptions ) error
287+
288+ // WithMarshalVariableFunc sets a function that will be called for each variable in the engine's VarHolder
289+ // This allows you to customize variables before they are marshalled, e.g. to convert them to a different type
290+ func WithMarshalVariableFunc (fun VariableWrapFunc ) MarshalOption {
291+ return func (opts * marshalOptions ) error {
292+ opts .marshalVariablesFunc = fun
293+ return nil
294+ }
295+ }
296+
297+ // applyMarshalOptions Applies the given options and returns the applied marshalOptions
298+ func applyMarshalOptions (options ... MarshalOption ) (* marshalOptions , error ) {
299+ opts := & marshalOptions {}
300+ for _ , o := range options {
301+ err := o (opts )
302+ if err != nil {
303+ return nil , fmt .Errorf ("could not apply option: %w" , err )
304+ }
305+ }
306+
307+ return opts , nil
308+ }
309+
310+ // Marshal marshals the engine into a byte array.
311+ // Options may be provided to configure the marshalling process.
312+ // It returns a byte array containing the marshalled engine state.
313+ // If there is an error applying the options, it will panic.
314+ //
315+ // Example:
316+ //
317+ // ```go
318+ // // Marshal with default options
319+ // data, err := bpmn_engine.Marshal()
320+ //
321+ // // Marshal with type information for complex variables
322+ // data, err := bpmn_engine.Marshal(WithMarshalComplexTypes())
323+ // ```
324+ func (state * BpmnEngineState ) Marshal (options ... MarshalOption ) ([]byte , error ) {
325+ opts , err := applyMarshalOptions (options ... )
326+ if err != nil {
327+ return nil , err
328+ }
329+ pis , err := createProcessInstances (state .processInstances , opts )
330+ if err != nil {
331+ return nil , err
332+ }
333+
280334 m := serializedBpmnEngine {
281335 Version : CurrentSerializerVersion ,
282336 Name : state .name ,
283337 MessageSubscriptions : state .messageSubscriptions ,
284338 ProcessReferences : createReferences (state .processes ),
285- ProcessInstances : state . processInstances ,
339+ ProcessInstances : pis ,
286340 Timers : state .timers ,
287341 Jobs : state .jobs ,
288342 }
289343 bytes , err := json .Marshal (m )
290344 if err != nil {
291- panic (err )
345+ return nil , err
346+ }
347+ return bytes , nil
348+ }
349+
350+ // wrapVariables takes a variable holder and wraps each variable with a complexVariable if the variable is a
351+ // struct or pointer
352+ func wrapVariables (vh VariableHolder , f VariableWrapFunc ) (VariableHolder , error ) {
353+ for k , v := range vh .variables {
354+ val , err := f (v )
355+ if err != nil {
356+ return vh , err
357+ }
358+ vh .variables [k ] = val
359+ }
360+ // If there is a parent, create complex variables for it as well
361+ if vh .parent != nil {
362+ parent , err := wrapVariables (* vh .parent , f )
363+ if err != nil {
364+ return VariableHolder {}, err
365+ }
366+ vh .parent = & parent
367+ }
368+ return vh , nil
369+ }
370+
371+ // createProcessInstances Creates process instances that can be marshalled to JSON
372+ func createProcessInstances (pii []* processInstanceInfo , opts * marshalOptions ) ([]* processInstanceInfo , error ) {
373+
374+ // If exporting types is not enable, there is nothing extra to do
375+ if opts .marshalVariablesFunc == nil {
376+ return pii , nil
377+ }
378+
379+ // Create complex variables for each process instance
380+ for _ , pi := range pii {
381+ cvs , err := wrapVariables (pi .VariableHolder , opts .marshalVariablesFunc )
382+ if err != nil {
383+ return nil , err
384+ }
385+ pi .VariableHolder = cvs
292386 }
293- return bytes
387+ return pii , nil
388+ }
389+
390+ type VariableUnwrapFunc func (any ) (any , error )
391+
392+ // unmarshalOptions Options that will be used while unmarshalling the engine
393+ type unmarshalOptions struct {
394+ // variableUnwrapFunc function that can be called to restore a variable from a marshalled state
395+ variableUnwrapFunc VariableUnwrapFunc
396+ }
397+
398+ // UnmarshalOption is a function that modifies the unmarshalOptions
399+ type UnmarshalOption func (* unmarshalOptions ) error
400+
401+ // WithUnmarshalVariableFunc sets a function that will be called for each variable in the engine's VarHolder
402+ // This allows you to customize variables after they are unmarshalled, e.g. to convert them to a different type
403+ func WithUnmarshalVariableFunc (fun VariableUnwrapFunc ) UnmarshalOption {
404+ return func (opts * unmarshalOptions ) error {
405+ opts .variableUnwrapFunc = fun
406+ return nil
407+ }
408+ }
409+
410+ // applyUnmarshalOptions Applies the given options and returns the applied unmarshalOptions
411+ func applyUnmarshalOptions (options ... UnmarshalOption ) (* unmarshalOptions , error ) {
412+ opts := & unmarshalOptions {}
413+ for _ , o := range options {
414+ err := o (opts )
415+ if err != nil {
416+ return nil , fmt .Errorf ("could not apply option: %w" , err )
417+ }
418+ }
419+
420+ return opts , nil
294421}
295422
296423// Unmarshal loads the data byte array and creates a new instance of the BPMN Engine
297424// Will return an BpmnEngineUnmarshallingError, if there was an issue AND in case of error,
298425// the engine return object is only partially initialized and likely not usable
299- func Unmarshal (data []byte ) (BpmnEngineState , error ) {
426+ func Unmarshal (data []byte , opts ... UnmarshalOption ) (BpmnEngineState , error ) {
427+
428+ // Build an unmarshalOptions object from the provided options
429+ options , err := applyUnmarshalOptions (opts ... )
430+ if err != nil {
431+ return BpmnEngineState {}, & BpmnEngineUnmarshallingError {
432+ Msg : "Failed to apply unmarshalling options" ,
433+ Err : err ,
434+ }
435+ }
436+
300437 eng := serializedBpmnEngine {}
301- err : = json .Unmarshal (data , & eng )
438+ err = json .Unmarshal (data , & eng )
302439 if err != nil {
303- panic (err )
440+ return BpmnEngineState {}, & BpmnEngineUnmarshallingError {
441+ Msg : "Failed to unmarshall engine data" ,
442+ Err : err ,
443+ }
304444 }
305445 state := New ()
306446 state .name = eng .Name
@@ -327,12 +467,15 @@ func Unmarshal(data []byte) (BpmnEngineState, error) {
327467 }
328468 if eng .ProcessInstances != nil {
329469 state .processInstances = eng .ProcessInstances
330- err : = recoverProcessInstances (& state )
470+ err = recoverProcessInstances (& state , options )
331471 if err != nil {
332472 return state , err
333473 }
334474 }
335- recoverProcessInstanceActivitiesPart2 (& state )
475+ err = recoverProcessInstanceActivitiesPart2 (& state )
476+ if err != nil {
477+ return BpmnEngineState {}, err
478+ }
336479 if eng .MessageSubscriptions != nil {
337480 state .messageSubscriptions = eng .MessageSubscriptions
338481 err = recoverMessageSubscriptions (& state )
@@ -357,7 +500,7 @@ func Unmarshal(data []byte) (BpmnEngineState, error) {
357500 return state , nil
358501}
359502
360- func recoverProcessInstanceActivitiesPart1 (pii * processInstanceInfo , adapter * processInstanceInfoAdapter ) {
503+ func recoverProcessInstanceActivitiesPart1 (pii * processInstanceInfo , adapter * processInstanceInfoAdapter ) error {
361504 for _ , aa := range adapter .ActivityAdapters {
362505 switch aa .Type {
363506 case gatewayActivityAdapterType :
@@ -378,12 +521,13 @@ func recoverProcessInstanceActivitiesPart1(pii *processInstanceInfo, adapter *pr
378521 OutboundActivityCompleted : aa .OutboundActivityCompleted ,
379522 })
380523 default :
381- panic ( fmt .Sprintf ("[invariant check] missing recovery code for actictyAdapter.Type=%d" , aa .Type ) )
524+ return fmt .Errorf ("[invariant check] missing recovery code for actictyAdapter.Type=%d" , aa .Type )
382525 }
383526 }
527+ return nil
384528}
385529
386- func recoverProcessInstanceActivitiesPart2 (state * BpmnEngineState ) {
530+ func recoverProcessInstanceActivitiesPart2 (state * BpmnEngineState ) error {
387531 for _ , pi := range state .processInstances {
388532 for _ , a := range pi .activities {
389533 switch activity := a .(type ) {
@@ -392,15 +536,33 @@ func recoverProcessInstanceActivitiesPart2(state *BpmnEngineState) {
392536 case * gatewayActivity :
393537 activity .element = BPMN20 .FindBaseElementsById (pi .ProcessInfo .definitions .Process , (* a .Element ()).GetId ())[0 ]
394538 default :
395- panic ( fmt .Sprintf ("[invariant check] missing case for activity type=%T" , a ) )
539+ return fmt .Errorf ("[invariant check] missing case for activity type=%T" , a )
396540 }
397541 }
398542 }
543+ return nil
399544}
400545
401- // ----------------------------------------------------------------------------
546+ // recoverVariableInstances recovers the variable instances from the given VariableHolder
547+ func recoverVariableInstances (vh VariableHolder , opts * unmarshalOptions ) (VariableHolder , error ) {
548+ if opts .variableUnwrapFunc == nil {
549+ // Nothing additional to do
550+ return vh , nil
551+ }
402552
403- func recoverProcessInstances (state * BpmnEngineState ) error {
553+ for k , v := range vh .variables {
554+ val , err := opts .variableUnwrapFunc (v )
555+ if err != nil {
556+ return vh , err
557+ }
558+
559+ // Replace the variable with the proper instance
560+ vh .variables [k ] = val
561+ }
562+ return vh , nil
563+ }
564+
565+ func recoverProcessInstances (state * BpmnEngineState , opts * unmarshalOptions ) error {
404566 for i , pi := range state .processInstances {
405567 process := state .findProcess (pi .ProcessInfo .ProcessKey )
406568 if process == nil {
@@ -410,7 +572,11 @@ func recoverProcessInstances(state *BpmnEngineState) error {
410572 }
411573 }
412574 state .processInstances [i ].ProcessInfo = process
413- state .processInstances [i ].VariableHolder = pi .VariableHolder
575+ vars , err := recoverVariableInstances (pi .VariableHolder , opts )
576+ if err != nil {
577+ return err
578+ }
579+ state .processInstances [i ].VariableHolder = vars
414580 }
415581 return nil
416582}
0 commit comments