Skip to content

Commit 80dffe3

Browse files
Ylberclaude
andcommitted
fix: route path/query params correctly and suppress BodyVariable for JSON bodies in SEND REST REQUEST
Fixes CE7054 ("parameters updated") and CE7067 ("does not support body entity") errors produced by mx check after mxcli generates a Microflows$RestOperationCallAction. Two root causes: - All WITH-clause params were emitted as QueryParameterMappings regardless of whether they are path or query params on the operation definition. - BodyVariable was always serialised when BODY $var was present, but for Rest$JsonBody / Rest$StringBody operations the body lives on the operation template and BodyVariable must be nil. Fix: look up the ConsumedRestService operation at build time via a new restServices field on flowBuilder (populated from loadRestServices()), then use lookupRestOperation / buildRestParameterMappings / shouldSetBodyVariable helpers to emit the correct BSON. When the operation is not found we fall back to the previous behaviour. Closes #193 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9a807c3 commit 80dffe3

File tree

7 files changed

+374
-16
lines changed

7 files changed

+374
-16
lines changed

.claude/skills/fix-issue.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ to the symptom table below, so the next similar issue costs fewer reads.
2525
| CE0463 "widget definition changed" | Object property structure doesn't match Type PropertyTypes | `sdk/widgets/templates/` | Re-extract template from Studio Pro; see `sdk/widgets/templates/README.md` |
2626
| Parser returns `nil` for a known BSON type | Unhandled `default` in a `parseXxx()` switch | `sdk/mpr/parser_microflow.go` or `parser_page.go` | Find the switch by grepping for `default: return nil`; add the missing case |
2727
| MDL check gives "unexpected token" on valid-looking syntax | Grammar missing rule or token | `mdl/grammar/MDLParser.g4` + `MDLLexer.g4` | Add rule/token, run `make grammar` |
28+
| CE7054 "parameters updated" / CE7067 "does not support body entity" after `SEND REST REQUEST` | `addSendRestRequestAction` emitted wrong BSON: all params as query params, BodyVariable set for JSON bodies | `mdl/executor/cmd_microflows_builder_calls.go``addSendRestRequestAction` | Look up operation via `fb.restServices`; route path/query params with `buildRestParameterMappings`; suppress BodyVariable for JSON/TEMPLATE/FILE via `shouldSetBodyVariable` |
2829

2930
---
3031

mdl/executor/cmd_microflows_builder.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type flowBuilder struct {
3333
reader *mpr.Reader // For looking up page/microflow references
3434
hierarchy *ContainerHierarchy // For resolving container IDs to module names
3535
pendingAnnotations *ast.ActivityAnnotations // Pending annotations to attach to next activity
36+
restServices []*model.ConsumedRestService // Cached REST services for parameter classification
3637
}
3738

3839
// addError records a validation error during flow building.

mdl/executor/cmd_microflows_builder_calls.go

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,16 @@ func (fb *flowBuilder) addSendRestRequestAction(s *ast.SendRestRequestStmt) mode
769769
// Build operation reference: Module.Service.Operation
770770
operationQN := s.Operation.String()
771771

772+
// Look up the operation definition to classify parameters and body kind.
773+
// s.Operation.Module = "MfTest", s.Operation.Name = "RC_TestApi.PostJsonTemplate"
774+
var opDef *model.RestClientOperation
775+
if fb.restServices != nil && s.Operation.Module != "" && strings.Contains(s.Operation.Name, ".") {
776+
dotIdx := strings.Index(s.Operation.Name, ".")
777+
serviceName := s.Operation.Name[:dotIdx]
778+
opName := s.Operation.Name[dotIdx+1:]
779+
opDef = lookupRestOperation(fb.restServices, serviceName, opName)
780+
}
781+
772782
// Build OutputVariable
773783
var outputVar *microflows.RestOutputVar
774784
if s.OutputVariable != "" {
@@ -778,29 +788,20 @@ func (fb *flowBuilder) addSendRestRequestAction(s *ast.SendRestRequestStmt) mode
778788
}
779789
}
780790

781-
// Build BodyVariable
791+
// Build BodyVariable only for EXPORT_MAPPING body kind.
792+
// For JSON / TEMPLATE / FILE bodies, the body expression lives on the
793+
// operation definition itself and must NOT be set here (CE7067).
782794
var bodyVar *microflows.RestBodyVar
783-
if s.BodyVariable != "" {
795+
if s.BodyVariable != "" && shouldSetBodyVariable(opDef) {
784796
bodyVar = &microflows.RestBodyVar{
785797
BaseElement: model.BaseElement{ID: model.ID(mpr.GenerateID())},
786798
VariableName: s.BodyVariable,
787799
}
788800
}
789801

790-
// Build parameter mappings from WITH clause
791-
var paramMappings []*microflows.RestParameterMapping
792-
var queryParamMappings []*microflows.RestQueryParameterMapping
793-
for _, p := range s.Parameters {
794-
// Determine if path or query param by convention:
795-
// the executor can't distinguish at this level, so we emit both
796-
// and let the BSON field names sort it out. For now, emit as
797-
// query parameter mappings (most common use case).
798-
queryParamMappings = append(queryParamMappings, &microflows.RestQueryParameterMapping{
799-
Parameter: operationQN + "." + p.Name,
800-
Value: p.Expression,
801-
Included: "Yes",
802-
})
803-
}
802+
// Build parameter mappings, routing to ParameterMappings (path) or
803+
// QueryParameterMappings (query) based on the operation definition.
804+
paramMappings, queryParamMappings := buildRestParameterMappings(s.Parameters, opDef, operationQN)
804805

805806
// RestOperationCallAction does not support custom error handling (CE6035).
806807
// ON ERROR clauses in the MDL are silently ignored for this action type.
@@ -831,6 +832,84 @@ func (fb *flowBuilder) addSendRestRequestAction(s *ast.SendRestRequestStmt) mode
831832
return activity.ID
832833
}
833834

835+
// lookupRestOperation finds a specific operation in a consumed REST service list.
836+
func lookupRestOperation(services []*model.ConsumedRestService, serviceName, opName string) *model.RestClientOperation {
837+
for _, svc := range services {
838+
if svc.Name != serviceName {
839+
continue
840+
}
841+
for _, op := range svc.Operations {
842+
if op.Name == opName {
843+
return op
844+
}
845+
}
846+
}
847+
return nil
848+
}
849+
850+
// shouldSetBodyVariable returns true if a BodyVariable BSON field should be
851+
// emitted for a call to the given operation.
852+
// For JSON, TEMPLATE, and FILE body kinds, the body expression lives on the
853+
// operation definition and must not be overridden by a BodyVariable (CE7067).
854+
// For EXPORT_MAPPING, the caller provides an entity to export via BodyVariable.
855+
// When the operation definition is unknown (nil), we preserve old behaviour and
856+
// set BodyVariable so the caller's intent is not silently dropped.
857+
func shouldSetBodyVariable(op *model.RestClientOperation) bool {
858+
if op == nil {
859+
return true // unknown operation — preserve caller intent
860+
}
861+
switch op.BodyType {
862+
case "JSON", "TEMPLATE", "FILE":
863+
return false
864+
default:
865+
// EXPORT_MAPPING or empty (no body) — only set if EXPORT_MAPPING
866+
return op.BodyType == "EXPORT_MAPPING"
867+
}
868+
}
869+
870+
// buildRestParameterMappings splits parameter bindings from a SEND REST REQUEST
871+
// WITH clause into path parameter mappings and query parameter mappings,
872+
// using the operation definition to determine which is which.
873+
// When op is nil (operation not found), all parameters fall back to query
874+
// parameter mappings (preserves old behaviour).
875+
func buildRestParameterMappings(
876+
params []ast.SendRestParamDef,
877+
op *model.RestClientOperation,
878+
operationQN string,
879+
) ([]*microflows.RestParameterMapping, []*microflows.RestQueryParameterMapping) {
880+
if len(params) == 0 {
881+
return nil, nil
882+
}
883+
884+
// Build lookup sets from the operation definition.
885+
pathParamSet := map[string]bool{}
886+
if op != nil {
887+
for _, p := range op.Parameters {
888+
pathParamSet[p.Name] = true
889+
}
890+
}
891+
892+
var pathMappings []*microflows.RestParameterMapping
893+
var queryMappings []*microflows.RestQueryParameterMapping
894+
895+
for _, p := range params {
896+
if pathParamSet[p.Name] {
897+
pathMappings = append(pathMappings, &microflows.RestParameterMapping{
898+
Parameter: operationQN + "." + p.Name,
899+
Value: p.Expression,
900+
})
901+
} else {
902+
queryMappings = append(queryMappings, &microflows.RestQueryParameterMapping{
903+
Parameter: operationQN + "." + p.Name,
904+
Value: p.Expression,
905+
Included: "Yes",
906+
})
907+
}
908+
}
909+
910+
return pathMappings, queryMappings
911+
}
912+
834913
// addExecuteDatabaseQueryAction creates an EXECUTE DATABASE QUERY statement.
835914
func (fb *flowBuilder) addExecuteDatabaseQueryAction(s *ast.ExecuteDatabaseQueryStmt) model.ID {
836915
// DynamicQuery is a Mendix expression — string literals need single quotes

mdl/executor/cmd_microflows_builder_control.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ func (fb *flowBuilder) addLoopStatement(s *ast.LoopStmt) model.ID {
287287
measurer: fb.measurer, // Share measurer
288288
reader: fb.reader, // Share reader
289289
hierarchy: fb.hierarchy, // Share hierarchy
290+
restServices: fb.restServices, // Share REST services for parameter classification
290291
}
291292

292293
// Process loop body statements and connect them with flows
@@ -360,6 +361,7 @@ func (fb *flowBuilder) addWhileStatement(s *ast.WhileStmt) model.ID {
360361
measurer: fb.measurer,
361362
reader: fb.reader,
362363
hierarchy: fb.hierarchy,
364+
restServices: fb.restServices,
363365
}
364366

365367
var lastBodyID model.ID

mdl/executor/cmd_microflows_builder_flows.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func (fb *flowBuilder) addErrorHandlerFlow(sourceActivityID model.ID, sourceX in
6666
measurer: fb.measurer,
6767
reader: fb.reader,
6868
hierarchy: fb.hierarchy,
69+
restServices: fb.restServices,
6970
}
7071

7172
var lastErrID model.ID

0 commit comments

Comments
 (0)