diff --git a/.claude/skills/mendix/write-microflows.md b/.claude/skills/mendix/write-microflows.md index 06158796..3f235f5e 100644 --- a/.claude/skills/mendix/write-microflows.md +++ b/.claude/skills/mendix/write-microflows.md @@ -836,6 +836,26 @@ download file $GeneratedReport show in browser; download file $GeneratedExport; ``` +## Empty Java-Action Argument (`empty`) + +When `describe` round-trips a Java-action call that has an unbound parameter +in Studio Pro, it emits `empty` as the argument value. In this Java-action +argument context, `empty` preserves the +underlying empty `BasicCodeActionParameterValue.Argument` so that the next +`describe → exec → describe` cycle stays symmetric. + +```mdl +$Total = call java action SampleModule.Recalculate( + CompanyId = empty, + RecalculateAll = true, + ItemList = empty +); +``` + +New scripts should bind every parameter to a real expression. Use `empty` +for a Java-action argument only when regenerating MDL from an existing project +that already had an unbound parameter. + ## Error Handling MDL supports error handling for activities that may fail (microflow calls, commits, external service calls, etc.). diff --git a/docs/01-project/MDL_QUICK_REFERENCE.md b/docs/01-project/MDL_QUICK_REFERENCE.md index 2197cad2..c5b8515d 100644 --- a/docs/01-project/MDL_QUICK_REFERENCE.md +++ b/docs/01-project/MDL_QUICK_REFERENCE.md @@ -784,6 +784,7 @@ Module.OrderResponse_CustomerInfo/Module.CustomerInfo as customer { | Create exposed action | `... exposed as 'caption' in 'Category' as $$ ... $$;` | Toolbox-visible in Studio Pro | | Drop Java action | `drop java action Module.Name;` | Delete a Java action | | Call from microflow | `$Result = call java action Module.Name(Param = value);` | Inside BEGIN...END | +| Empty argument | `call java action Module.Name(Param = empty);` | Unbound code-action parameter preserved as empty mapping | **Parameter Types:** `string`, `integer`, `long`, `decimal`, `boolean`, `datetime`, `Module.Entity`, `list of Module.Entity`, `enum Module.EnumName`, `enumeration(Module.EnumName)`, `stringtemplate(sql)`, `stringtemplate(Oql)`, `entity ` (type parameter declaration), bare `pEntity` (type parameter reference). diff --git a/docs/11-proposals/PROPOSAL_microflow_empty_java_action_argument.md b/docs/11-proposals/PROPOSAL_microflow_empty_java_action_argument.md new file mode 100644 index 00000000..1b4bf188 --- /dev/null +++ b/docs/11-proposals/PROPOSAL_microflow_empty_java_action_argument.md @@ -0,0 +1,73 @@ +# Empty Java Action Argument + +Status: Implemented + +## Summary + +Use the existing MDL `empty` literal to represent an intentionally unbound Java +action argument in microflow call statements. + +```mdl +$Total = call java action SampleModule.Recalculate( + CompanyId = empty, + RecalculateAll = true, + ItemList = empty +); +``` + +In this Java-action argument context, `empty` produces a parameter binding with +an empty `Argument` string in the serialized BSON +(`Microflows$BasicCodeActionParameterValue.Argument = ""`). Re-executing the +script reproduces the same empty binding, so `describe -> exec -> describe` +stays symmetric for existing Studio Pro projects that have unbound code-action +parameters. + +## Motivation + +Studio Pro's Java-action call dialog allows a developer to leave individual +parameters empty. The on-disk representation is a +`Microflows$JavaActionParameterMapping` whose value is a +`BasicCodeActionParameterValue` with `Argument: ""`. + +Emitting `''` would create a literal empty string expression, not an unbound +parameter. Dropping the parameter would lose the original mapping. The existing +`empty` literal is already valid MDL expression syntax and is clearer than +introducing a new placeholder token for this one case. + +## Semantics + +- In Java-action call arguments, `empty` maps to an empty + `BasicCodeActionParameterValue.Argument`. +- If the Java action parameter type is a microflow callback, `empty` maps to a + `Microflows$MicroflowParameterValue` with an empty `Microflow` reference. +- Outside Java-action call arguments, `empty` keeps its normal MDL literal + meaning. + +## Examples + +```mdl +-- Java action call with two unbound and one bound argument. +$Total = call java action SampleModule.Recalculate( + CompanyId = empty, + RecalculateAll = true, + ItemList = empty +); +``` + +The Mendix BSON for the unbound arguments is: + +```text +JavaActionParameterMapping { + Parameter: 'SampleModule.Recalculate.CompanyId', + Value: BasicCodeActionParameterValue { Argument: '' } +} +``` + +## Tests And Examples + +- Builder coverage: `TestBuildJavaAction_EmptyArgumentPreservesEmptyBasicValue` + and `TestBuildJavaAction_EmptyMicroflowArgumentUsesMicroflowParameterValue` + in `mdl/executor/cmd_microflows_builder_java_action_test.go`. +- Example script: + `mdl-examples/doctype-tests/empty_java_action_argument.test.mdl`. + diff --git a/docs/11-proposals/README.md b/docs/11-proposals/README.md index e4c25f4a..07c367a2 100644 --- a/docs/11-proposals/README.md +++ b/docs/11-proposals/README.md @@ -45,6 +45,7 @@ BSON schema Registry ◄──── multi-version Support | [MDL Syntax Improvements v2](PROPOSAL_mdl_syntax_improvements_v2.md) | Proposed | Consolidated v2: unified variable declaration, C-style braces, fluent list ops | Syntax Improvements v1 | | [Microflow Free Annotation](PROPOSAL_microflow_free_annotation.md) | Draft | Order-sensitive `@annotation` handling for free-floating visual notes in microflows | — | | [Microflow Download File Statement](PROPOSAL_microflow_download_file_statement.md) | Draft | `download file $FileDocument [show in browser]` for `DownloadFileAction` round-trip and authoring | — | +| [Empty Java Action Argument](PROPOSAL_microflow_empty_java_action_argument.md) | Implemented | `empty` for unbound Java-action parameters; round-trip preservation of empty `BasicCodeActionParameterValue` bindings | — | | [Page Syntax V2](PROPOSAL_page_syntax_v2.md) | Superseded | Page/widget syntax with `{}` blocks and `->` binding. Superseded by V3 (archived) | — | | [Page Styling Support](page-styling-support.md) | Partial | CSS classes, inline styles, dynamic classes, design properties. Phase 1 (Class/Style) done | — | | [Page Composition](proposal_page_composition.md) | Proposed | Fragment definitions and ALTER PAGE for partial page editing | Page Syntax V2, Page Styling | diff --git a/mdl-examples/doctype-tests/empty_java_action_argument.mdl b/mdl-examples/doctype-tests/empty_java_action_argument.mdl new file mode 100644 index 00000000..30b4cf26 --- /dev/null +++ b/mdl-examples/doctype-tests/empty_java_action_argument.mdl @@ -0,0 +1,25 @@ +create microflow SampleModule.ACT_RecalculateOpenItems () +returns Void +begin + call java action SampleModule.Recalculate( + CompanyId = empty, + RecalculateAll = true, + ItemList = empty + ); + return; +end; +/ + +create microflow SampleModule.ACT_RecalculateForCompany ( + $CompanyId: String +) +returns Void +begin + call java action SampleModule.Recalculate( + CompanyId = $CompanyId, + RecalculateAll = false, + ItemList = empty + ); + return; +end; +/ diff --git a/mdl/executor/cmd_microflows_builder_calls.go b/mdl/executor/cmd_microflows_builder_calls.go index eb9cb6a9..d03bd901 100644 --- a/mdl/executor/cmd_microflows_builder_calls.go +++ b/mdl/executor/cmd_microflows_builder_calls.go @@ -244,10 +244,13 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model. // Build a map of parameter name -> param type for the Java action entityTypeParams := make(map[string]bool) + microflowTypeParams := make(map[string]bool) if jaDef != nil { for _, p := range jaDef.Parameters { if _, ok := p.ParameterType.(*javaactions.EntityTypeParameterType); ok { entityTypeParams[p.Name] = true + } else if _, ok := p.ParameterType.(*javaactions.MicroflowType); ok { + microflowTypeParams[p.Name] = true } } } @@ -276,12 +279,31 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model. BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())}, Entity: entityName, } + } else if isPlaceholderExpression(arg.Value) { + if microflowTypeParams[arg.Name] { + value = µflows.MicroflowParameterValue{ + BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())}, + Microflow: "", + } + } else { + value = µflows.BasicCodeActionParameterValue{ + BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())}, + Argument: "", + } + } } else { // Regular parameter: expression-based value valueExpr := fb.exprToString(arg.Value) - value = µflows.BasicCodeActionParameterValue{ - BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())}, - Argument: valueExpr, + if microflowTypeParams[arg.Name] { + value = µflows.MicroflowParameterValue{ + BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())}, + Microflow: strings.Trim(valueExpr, "'"), + } + } else { + value = µflows.BasicCodeActionParameterValue{ + BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())}, + Argument: valueExpr, + } } } @@ -437,6 +459,11 @@ func (fb *flowBuilder) addCallJavaScriptActionAction(s *ast.CallJavaScriptAction return activity.ID } +func isPlaceholderExpression(expr ast.Expression) bool { + lit, ok := expr.(*ast.LiteralExpr) + return ok && (lit.Kind == ast.LiteralEmpty || lit.Kind == ast.LiteralNull) +} + // addCallExternalActionAction creates a CALL EXTERNAL ACTION statement. func (fb *flowBuilder) addCallExternalActionAction(s *ast.CallExternalActionStmt) model.ID { serviceQN := s.ServiceName.Module + "." + s.ServiceName.Name diff --git a/mdl/executor/cmd_microflows_builder_java_action_test.go b/mdl/executor/cmd_microflows_builder_java_action_test.go new file mode 100644 index 00000000..aabcddbd --- /dev/null +++ b/mdl/executor/cmd_microflows_builder_java_action_test.go @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/javaactions" + "github.com/mendixlabs/mxcli/sdk/microflows" +) + +func TestBuildJavaAction_EmptyArgumentPreservesEmptyBasicValue(t *testing.T) { + fb := &flowBuilder{posX: 100, posY: 100, spacing: HorizontalSpacing} + stmt := &ast.CallJavaActionStmt{ + ActionName: ast.QualifiedName{Module: "SampleModule", Name: "Recalculate"}, + Arguments: []ast.CallArgument{ + {Name: "CompanyId", Value: &ast.LiteralExpr{Kind: ast.LiteralEmpty}}, + {Name: "RecalculateAll", Value: &ast.LiteralExpr{Kind: ast.LiteralBoolean, Value: true}}, + {Name: "ItemList", Value: &ast.LiteralExpr{Kind: ast.LiteralEmpty}}, + }, + } + + id := fb.addCallJavaActionAction(stmt) + var activity *microflows.ActionActivity + for _, obj := range fb.objects { + if obj.GetID() == id { + activity, _ = obj.(*microflows.ActionActivity) + break + } + } + if activity == nil { + t.Fatal("expected Java action activity") + } + action, ok := activity.Action.(*microflows.JavaActionCallAction) + if !ok { + t.Fatalf("action = %T, want *JavaActionCallAction", activity.Action) + } + if len(action.ParameterMappings) != 3 { + t.Fatalf("parameter mappings = %d, want 3", len(action.ParameterMappings)) + } + + for _, idx := range []int{0, 2} { + value, ok := action.ParameterMappings[idx].Value.(*microflows.BasicCodeActionParameterValue) + if !ok { + t.Fatalf("mapping %d value = %T, want *BasicCodeActionParameterValue", idx, action.ParameterMappings[idx].Value) + } + if value.Argument != "" { + t.Fatalf("mapping %d argument = %q, want empty string", idx, value.Argument) + } + } + + value, ok := action.ParameterMappings[1].Value.(*microflows.BasicCodeActionParameterValue) + if !ok { + t.Fatalf("boolean mapping value = %T, want *BasicCodeActionParameterValue", action.ParameterMappings[1].Value) + } + if value.Argument != "true" { + t.Fatalf("boolean argument = %q, want true", value.Argument) + } +} + +func TestBuildJavaAction_EmptyMicroflowArgumentUsesMicroflowParameterValue(t *testing.T) { + fb := &flowBuilder{ + posX: 100, + posY: 100, + spacing: HorizontalSpacing, + backend: &mock.MockBackend{ + ReadJavaActionByNameFunc: func(qualifiedName string) (*javaactions.JavaAction, error) { + if qualifiedName != "SampleModule.StartAsync" { + t.Fatalf("java action lookup = %q", qualifiedName) + } + return &javaactions.JavaAction{ + Parameters: []*javaactions.JavaActionParameter{ + { + Name: "Callback", + ParameterType: &javaactions.MicroflowType{ + BaseElement: model.BaseElement{ID: "param-type"}, + }, + }, + }, + }, nil + }, + }, + } + stmt := &ast.CallJavaActionStmt{ + ActionName: ast.QualifiedName{Module: "SampleModule", Name: "StartAsync"}, + Arguments: []ast.CallArgument{ + {Name: "Callback", Value: &ast.LiteralExpr{Kind: ast.LiteralEmpty}}, + }, + } + + id := fb.addCallJavaActionAction(stmt) + var activity *microflows.ActionActivity + for _, obj := range fb.objects { + if obj.GetID() == id { + activity, _ = obj.(*microflows.ActionActivity) + break + } + } + if activity == nil { + t.Fatal("expected Java action activity") + } + action, ok := activity.Action.(*microflows.JavaActionCallAction) + if !ok { + t.Fatalf("action = %T, want *JavaActionCallAction", activity.Action) + } + value, ok := action.ParameterMappings[0].Value.(*microflows.MicroflowParameterValue) + if !ok { + t.Fatalf("mapping value = %T, want *MicroflowParameterValue", action.ParameterMappings[0].Value) + } + if value.Microflow != "" { + t.Fatalf("placeholder microflow = %q, want empty string", value.Microflow) + } +} diff --git a/mdl/executor/cmd_microflows_format_action.go b/mdl/executor/cmd_microflows_format_action.go index 89e76296..05016d28 100644 --- a/mdl/executor/cmd_microflows_format_action.go +++ b/mdl/executor/cmd_microflows_format_action.go @@ -540,7 +540,17 @@ func formatAction( case *microflows.ExpressionBasedCodeActionParameterValue: valueStr = v.Expression case *microflows.BasicCodeActionParameterValue: - valueStr = v.Argument + if v.Argument == "" { + valueStr = "empty" + } else { + valueStr = v.Argument + } + case *microflows.MicroflowParameterValue: + if v.Microflow != "" { + valueStr = mdlQuote(v.Microflow) + } else { + valueStr = "empty" + } case *microflows.EntityTypeCodeActionParameterValue: if v.Entity != "" { valueStr = mdlQuote(v.Entity) diff --git a/mdl/executor/cmd_microflows_format_action_test.go b/mdl/executor/cmd_microflows_format_action_test.go index 20660a5b..c29a598b 100644 --- a/mdl/executor/cmd_microflows_format_action_test.go +++ b/mdl/executor/cmd_microflows_format_action_test.go @@ -389,6 +389,32 @@ func TestFormatAction_JavaActionCall(t *testing.T) { } } +func TestFormatAction_JavaActionCall_EmptyParameterValues(t *testing.T) { + e := newTestExecutor() + action := µflows.JavaActionCallAction{ + JavaAction: "MyModule.Recalculate", + ParameterMappings: []*microflows.JavaActionParameterMapping{ + { + Parameter: "MyModule.Recalculate.CompanyId", + Value: µflows.BasicCodeActionParameterValue{Argument: ""}, + }, + { + Parameter: "MyModule.Recalculate.RecalculateAll", + Value: µflows.BasicCodeActionParameterValue{Argument: "true"}, + }, + { + Parameter: "MyModule.Recalculate.Callback", + Value: µflows.MicroflowParameterValue{Microflow: ""}, + }, + }, + } + got := e.formatAction(action, nil, nil) + want := "call java action MyModule.Recalculate(CompanyId = empty, RecalculateAll = true, Callback = empty);" + if got != want { + t.Errorf("got %q, want %q", got, want) + } +} + func TestFormatAction_CallExternal(t *testing.T) { e := newTestExecutor() action := µflows.CallExternalAction{ diff --git a/mdl/grammar/parser/mdl_lexer.go b/mdl/grammar/parser/mdl_lexer.go index 14ee68a2..5595c3dc 100644 --- a/mdl/grammar/parser/mdl_lexer.go +++ b/mdl/grammar/parser/mdl_lexer.go @@ -1,4 +1,4 @@ -// Code generated from MDLLexer.g4 by ANTLR 4.13.2. DO NOT EDIT. +// Code generated from MDLLexer.g4 by ANTLR 4.13.1. DO NOT EDIT. package parser diff --git a/mdl/grammar/parser/mdl_parser.go b/mdl/grammar/parser/mdl_parser.go index 2c2733b1..bdf28702 100644 --- a/mdl/grammar/parser/mdl_parser.go +++ b/mdl/grammar/parser/mdl_parser.go @@ -1,4 +1,4 @@ -// Code generated from MDLParser.g4 by ANTLR 4.13.2. DO NOT EDIT. +// Code generated from MDLParser.g4 by ANTLR 4.13.1. DO NOT EDIT. package parser // MDLParser import ( diff --git a/mdl/grammar/parser/mdlparser_base_listener.go b/mdl/grammar/parser/mdlparser_base_listener.go index 384d0178..b3303a5b 100644 --- a/mdl/grammar/parser/mdlparser_base_listener.go +++ b/mdl/grammar/parser/mdlparser_base_listener.go @@ -1,4 +1,4 @@ -// Code generated from MDLParser.g4 by ANTLR 4.13.2. DO NOT EDIT. +// Code generated from MDLParser.g4 by ANTLR 4.13.1. DO NOT EDIT. package parser // MDLParser import "github.com/antlr4-go/antlr/v4" diff --git a/mdl/grammar/parser/mdlparser_listener.go b/mdl/grammar/parser/mdlparser_listener.go index 7b065e50..c6fdad4b 100644 --- a/mdl/grammar/parser/mdlparser_listener.go +++ b/mdl/grammar/parser/mdlparser_listener.go @@ -1,4 +1,4 @@ -// Code generated from MDLParser.g4 by ANTLR 4.13.2. DO NOT EDIT. +// Code generated from MDLParser.g4 by ANTLR 4.13.1. DO NOT EDIT. package parser // MDLParser import "github.com/antlr4-go/antlr/v4" diff --git a/mdl/visitor/visitor_test.go b/mdl/visitor/visitor_test.go index 4012678c..621ca00b 100644 --- a/mdl/visitor/visitor_test.go +++ b/mdl/visitor/visitor_test.go @@ -1686,6 +1686,35 @@ END;` } } +func TestCallJavaActionAcceptsEmptyArguments(t *testing.T) { + input := `CREATE MICROFLOW Synthetic.Check () +RETURNS Boolean AS $Success +BEGIN + $Total = CALL JAVA ACTION Synthetic.Recalculate(CompanyId = empty, RecalculateAll = true, ItemList = empty); + RETURN true; +END;` + + prog, errs := Build(input) + if len(errs) > 0 { + t.Fatalf("unexpected parse errors: %v", errs) + } + + stmt := prog.Statements[0].(*ast.CreateMicroflowStmt) + callStmt, ok := stmt.Body[0].(*ast.CallJavaActionStmt) + if !ok { + t.Fatalf("body[0] = %T, want *ast.CallJavaActionStmt", stmt.Body[0]) + } + for _, idx := range []int{0, 2} { + lit, ok := callStmt.Arguments[idx].Value.(*ast.LiteralExpr) + if !ok { + t.Fatalf("argument %d value = %T, want *ast.LiteralExpr", idx, callStmt.Arguments[idx].Value) + } + if lit.Kind != ast.LiteralNull { + t.Fatalf("argument %d kind = %v, want LiteralNull", idx, lit.Kind) + } + } +} + func TestDeclareAndLogTemplatePreserveMultilineSourceWhitespace(t *testing.T) { input := `CREATE MICROFLOW Synthetic.Check () RETURNS Boolean AS $Success diff --git a/sdk/microflows/microflows_actions.go b/sdk/microflows/microflows_actions.go index ccc6f10e..56983edf 100644 --- a/sdk/microflows/microflows_actions.go +++ b/sdk/microflows/microflows_actions.go @@ -616,6 +616,15 @@ type BasicCodeActionParameterValue struct { func (BasicCodeActionParameterValue) isCodeActionParameterValue() {} +// MicroflowParameterValue is a microflow reference passed to a Java action +// microflow parameter. An empty Microflow keeps Studio Pro's placeholder value. +type MicroflowParameterValue struct { + model.BaseElement + Microflow string `json:"microflow,omitempty"` // BY_NAME_REFERENCE: qualified microflow name +} + +func (MicroflowParameterValue) isCodeActionParameterValue() {} + // EntityTypeCodeActionParameterValue is an entity type passed at a call site for a type parameter. type EntityTypeCodeActionParameterValue struct { model.BaseElement diff --git a/sdk/mpr/parser_javaactions.go b/sdk/mpr/parser_javaactions.go index bee9392b..11ac125f 100644 --- a/sdk/mpr/parser_javaactions.go +++ b/sdk/mpr/parser_javaactions.go @@ -402,7 +402,7 @@ func parseCodeActionParameterType(raw map[string]any) javaactions.CodeActionPara } et.Enumeration = extractString(raw["Enumeration"]) return et - case "CodeActions$MicroflowType": + case "CodeActions$MicroflowType", "JavaActions$MicroflowJavaActionParameterType": return &javaactions.MicroflowType{ BaseElement: model.BaseElement{ID: model.ID(extractBsonID(raw["$ID"]))}, } @@ -461,6 +461,10 @@ func parseInnerParameterType(raw map[string]any) javaactions.CodeActionParameter return &javaactions.DateTimeType{ BaseElement: model.BaseElement{ID: model.ID(extractBsonID(raw["$ID"]))}, } + case "CodeActions$MicroflowType", "JavaActions$MicroflowJavaActionParameterType": + return &javaactions.MicroflowType{ + BaseElement: model.BaseElement{ID: model.ID(extractBsonID(raw["$ID"]))}, + } case "CodeActions$ConcreteEntityType", "CodeActions$EntityType": et := &javaactions.EntityType{ BaseElement: model.BaseElement{ID: model.ID(extractBsonID(raw["$ID"]))}, diff --git a/sdk/mpr/parser_javaactions_test.go b/sdk/mpr/parser_javaactions_test.go new file mode 100644 index 00000000..8ac50f0a --- /dev/null +++ b/sdk/mpr/parser_javaactions_test.go @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 + +package mpr + +import ( + "testing" + + "github.com/mendixlabs/mxcli/sdk/javaactions" +) + +func TestParseCodeActionParameterType_JavaActionMicroflowParameter(t *testing.T) { + value := parseCodeActionParameterType(map[string]any{ + "$ID": "type-1", + "$Type": "JavaActions$MicroflowJavaActionParameterType", + }) + + if _, ok := value.(*javaactions.MicroflowType); !ok { + t.Fatalf("value = %T, want *MicroflowType", value) + } +} diff --git a/sdk/mpr/parser_microflow_actions.go b/sdk/mpr/parser_microflow_actions.go index d7095bd9..3b75886a 100644 --- a/sdk/mpr/parser_microflow_actions.go +++ b/sdk/mpr/parser_microflow_actions.go @@ -175,6 +175,11 @@ func parseCodeActionParameterValue(raw map[string]any) microflows.CodeActionPara value.ID = model.ID(extractBsonID(raw["$ID"])) value.Argument = extractString(raw["Argument"]) return value + case "Microflows$MicroflowParameterValue": + value := µflows.MicroflowParameterValue{} + value.ID = model.ID(extractBsonID(raw["$ID"])) + value.Microflow = extractString(raw["Microflow"]) + return value case "Microflows$EntityTypeCodeActionParameterValue": value := µflows.EntityTypeCodeActionParameterValue{} value.ID = model.ID(extractBsonID(raw["$ID"])) diff --git a/sdk/mpr/parser_microflow_test.go b/sdk/mpr/parser_microflow_test.go index 41307a84..2ae378a3 100644 --- a/sdk/mpr/parser_microflow_test.go +++ b/sdk/mpr/parser_microflow_test.go @@ -81,3 +81,19 @@ func TestParseCommitAction_ErrorHandlingTypeDefaultsToRollback(t *testing.T) { t.Errorf("expected default Rollback, got %q", action.ErrorHandlingType) } } + +func TestParseCodeActionParameterValue_MicroflowParameterValue(t *testing.T) { + value := parseCodeActionParameterValue(map[string]any{ + "$ID": "value-1", + "$Type": "Microflows$MicroflowParameterValue", + "Microflow": "SyntheticModule.Callback", + }) + + got, ok := value.(*microflows.MicroflowParameterValue) + if !ok { + t.Fatalf("value = %T, want *MicroflowParameterValue", value) + } + if got.Microflow != "SyntheticModule.Callback" { + t.Fatalf("microflow = %q", got.Microflow) + } +} diff --git a/sdk/mpr/writer_microflow_actions.go b/sdk/mpr/writer_microflow_actions.go index 5fdf6cda..1b3dadfb 100644 --- a/sdk/mpr/writer_microflow_actions.go +++ b/sdk/mpr/writer_microflow_actions.go @@ -1181,6 +1181,12 @@ func serializeCodeActionParameterValue(v microflows.CodeActionParameterValue) bs {Key: "$Type", Value: "Microflows$BasicCodeActionParameterValue"}, {Key: "Argument", Value: value.Argument}, } + case *microflows.MicroflowParameterValue: + return bson.D{ + {Key: "$ID", Value: idToBsonBinary(string(value.ID))}, + {Key: "$Type", Value: "Microflows$MicroflowParameterValue"}, + {Key: "Microflow", Value: value.Microflow}, + } case *microflows.EntityTypeCodeActionParameterValue: return bson.D{ {Key: "$ID", Value: idToBsonBinary(string(value.ID))},