From 6ae408fc3ed1a33a695d6c41352844fbf946b452 Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Mon, 27 Apr 2026 15:11:21 +0200 Subject: [PATCH 1/2] fix: register import mapping result variable types Import-from-mapping actions infer their result cardinality from the referenced mapping and JSON structure, but the builder did not copy that inferred result type into the variable scope. Later activities that depended on the imported result could therefore rebuild with missing object/list type information. The builder now records the output variable as either the mapped entity or a list of that entity after constructing the mapping result handling. This keeps the variable scope aligned with the BSON result handling that is already written for the action. Tests cover single-object and array-root mappings with synthetic backend metadata, and make build, make lint-go, and make test pass locally. --- mdl/executor/cmd_microflows_builder_calls.go | 13 +++- ..._microflows_builder_import_mapping_test.go | 72 +++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 mdl/executor/cmd_microflows_builder_import_mapping_test.go diff --git a/mdl/executor/cmd_microflows_builder_calls.go b/mdl/executor/cmd_microflows_builder_calls.go index a19f9a33..362254e3 100644 --- a/mdl/executor/cmd_microflows_builder_calls.go +++ b/mdl/executor/cmd_microflows_builder_calls.go @@ -1012,7 +1012,8 @@ func (fb *flowBuilder) addImportFromMappingAction(s *ast.ImportFromMappingStmt) SingleObject: true, } - // Determine single vs list and result entity from the import mapping + // Determine single vs list and result entity from the import mapping. + resultEntityQN := "" if fb.backend != nil { if im, err := fb.backend.GetImportMappingByQualifiedName(s.Mapping.Module, s.Mapping.Name); err == nil { if im.JsonStructure != "" { @@ -1026,7 +1027,8 @@ func (fb *flowBuilder) addImportFromMappingAction(s *ast.ImportFromMappingStmt) } } if len(im.Elements) > 0 && im.Elements[0].Entity != "" { - resultHandling.ResultEntityID = model.ID(im.Elements[0].Entity) + resultEntityQN = im.Elements[0].Entity + resultHandling.ResultEntityID = model.ID(resultEntityQN) } } } @@ -1047,6 +1049,13 @@ func (fb *flowBuilder) addImportFromMappingAction(s *ast.ImportFromMappingStmt) fb.objects = append(fb.objects, activity) fb.posX += fb.spacing + if s.OutputVariable != "" && resultEntityQN != "" && fb.varTypes != nil { + if resultHandling.SingleObject { + fb.varTypes[s.OutputVariable] = resultEntityQN + } else { + fb.varTypes[s.OutputVariable] = "List of " + resultEntityQN + } + } if s.ErrorHandling != nil && len(s.ErrorHandling.Body) > 0 { errorY := fb.posY + VerticalSpacing diff --git a/mdl/executor/cmd_microflows_builder_import_mapping_test.go b/mdl/executor/cmd_microflows_builder_import_mapping_test.go new file mode 100644 index 00000000..dc1a957a --- /dev/null +++ b/mdl/executor/cmd_microflows_builder_import_mapping_test.go @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "fmt" + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + mdltypes "github.com/mendixlabs/mxcli/mdl/types" + "github.com/mendixlabs/mxcli/model" +) + +func TestAddImportFromMappingRegistersSingleResultType(t *testing.T) { + fb := importMappingFlowBuilder(t, "Object") + + fb.addImportFromMappingAction(&ast.ImportFromMappingStmt{ + OutputVariable: "ImportedOrder", + SourceVariable: "Payload", + Mapping: ast.QualifiedName{Module: "Integration", Name: "ImportOrder"}, + }) + + if got := fb.varTypes["ImportedOrder"]; got != "Sales.Order" { + t.Fatalf("ImportedOrder type = %q, want Sales.Order", got) + } +} + +func TestAddImportFromMappingRegistersListResultType(t *testing.T) { + fb := importMappingFlowBuilder(t, "Array") + + fb.addImportFromMappingAction(&ast.ImportFromMappingStmt{ + OutputVariable: "ImportedOrders", + SourceVariable: "Payload", + Mapping: ast.QualifiedName{Module: "Integration", Name: "ImportOrderList"}, + }) + + if got := fb.varTypes["ImportedOrders"]; got != "List of Sales.Order" { + t.Fatalf("ImportedOrders type = %q, want list of Sales.Order", got) + } +} + +func importMappingFlowBuilder(t *testing.T, rootElementType string) *flowBuilder { + t.Helper() + + return &flowBuilder{ + varTypes: map[string]string{}, + backend: &mock.MockBackend{ + GetImportMappingByQualifiedNameFunc: func(moduleName, name string) (*model.ImportMapping, error) { + if moduleName != "Integration" { + return nil, fmt.Errorf("unexpected module %q", moduleName) + } + return &model.ImportMapping{ + JsonStructure: "Integration.OrderPayload", + Elements: []*model.ImportMappingElement{ + {Entity: "Sales.Order"}, + }, + }, nil + }, + GetJsonStructureByQualifiedNameFunc: func(moduleName, name string) (*mdltypes.JsonStructure, error) { + if moduleName != "Integration" || name != "OrderPayload" { + return nil, fmt.Errorf("unexpected json structure %s.%s", moduleName, name) + } + return &mdltypes.JsonStructure{ + Elements: []*mdltypes.JsonElement{ + {ElementType: rootElementType}, + }, + }, nil + }, + }, + } +} From e5d6418670b3c47febbfa6e2ca2f725a2c2d27d0 Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Mon, 27 Apr 2026 18:34:58 +0200 Subject: [PATCH 2/2] test: add bug-test reproducer for import-from-mapping result type Adds an MDL script under mdl-examples/bug-tests/ exercising `import from mapping` followed by a `change` on the imported result. After exec, `mx check` reports 0 errors, confirming the result variable type is registered so the downstream change resolves the entity attribute. Co-Authored-By: Claude Opus 4.7 --- .../360-import-mapping-result-type.mdl | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 mdl-examples/bug-tests/360-import-mapping-result-type.mdl diff --git a/mdl-examples/bug-tests/360-import-mapping-result-type.mdl b/mdl-examples/bug-tests/360-import-mapping-result-type.mdl new file mode 100644 index 00000000..ad53d9b1 --- /dev/null +++ b/mdl-examples/bug-tests/360-import-mapping-result-type.mdl @@ -0,0 +1,66 @@ +-- ============================================================================ +-- Bug #360: Import-from-mapping result variable type was not registered +-- ============================================================================ +-- +-- Symptom (before fix): +-- `import from mapping` actions wrote the correct +-- `Forms$ResultHandlingMapping` BSON, but the builder did not register +-- the inferred result type onto the output variable in its variable +-- scope. Subsequent activities that touched the imported result +-- (`change`, `commit`, `$Var/Attribute`, association paths) ran without +-- type info, which caused: +-- - the writer to fall back to untyped expressions +-- - re-describes to lose the right declaration shape +-- - downstream activities to fail with CE0117 in Studio Pro +-- +-- After fix: +-- `addImportFromMappingAction` now also writes the result type into +-- `flowBuilder.varTypes`. When the mapping's JSON structure produces a +-- single object, the variable becomes `Module.Entity`. When it produces +-- an array, the variable becomes `List of Module.Entity`. The +-- ResultHandlingMapping BSON path is unchanged. +-- +-- Usage: +-- mxcli exec mdl-examples/bug-tests/360-import-mapping-result-type.mdl -p app.mpr +-- mxcli -p app.mpr -c "describe microflow BugTest360.MF_ImportPet" +-- `mx check` against the resulting MPR must report 0 errors and the +-- `change $Pet (...)` statement after the import must resolve. +-- ============================================================================ + +create module BugTest360; + +create json structure BugTest360.JSON_Pet +snippet '{"id": 1, "name": "Fido", "status": "available"}'; + +create non-persistent entity BugTest360.PetResponse ( + PetId : integer, + Name : string, + Status : string +); +/ + +create import mapping BugTest360.IMM_Pet + with json structure BugTest360.JSON_Pet +{ + create BugTest360.PetResponse { + PetId = id, + Name = name, + Status = status + } +}; + +-- Caller — uses the imported `$Pet` in a downstream `change`. Without +-- the result-type registration, the change activity has no resolved +-- entity for `Status` and Studio Pro surfaces CE0117. +create microflow BugTest360.MF_ImportPet ( + $Json: string +) +returns BugTest360.PetResponse as $Pet +begin + $Pet = import from mapping BugTest360.IMM_Pet ($Json); + + if $Pet != empty then + change $Pet (Status = $Pet/Status + ' (processed)'); + end if; +end; +/