Skip to content

Commit efa3acf

Browse files
committed
draft: stage remaining microflow roundtrip fixes
Symptom: the focused upstream PR set does not yet include every local roundtrip-validity fix found during long-running audits of large legacy Mendix projects. Root cause: several remaining fixes are intertwined across microflow control-flow reconstruction, handler routing, split/case preservation, validation, and writer/parser compatibility. They need further extraction before they are suitable for review as individual upstream PRs. Fix: stage the remaining all-fixes tree as one draft branch so the outstanding local work is visible upstream while smaller PRs continue to be split out from it. This draft is not intended to merge as-is. Tests: make build, make test, and make lint-go pass on the staged tree.
1 parent d871691 commit efa3acf

90 files changed

Lines changed: 33509 additions & 15584 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/commands/mxcli-dev/review.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ proactively. Add a row after every review that surfaces something new.
3232
| 7 | Skill/doc table references a function that doesn't exist (e.g. `formatActionStatement()` vs `formatAction()`) | Docs quality | Grep function names before writing: `grep -r "func formatA" mdl/executor/` |
3333
| 8 | "Always X" rule is too absolute for trivial edge cases (e.g. "always write failing test first" for one-char typos) | Docs quality | Soften to "prefer X" or add an exception clause; include the reasoning so readers can judge edge cases |
3434
| 9 | Doc comment promises a fallback/feature that doesn't exist in the code (e.g., "raw-map fallback in the client" when no such fallback was implemented) | Docs quality | Grep for function/type names referenced in doc comments to confirm they exist before committing |
35+
| 10 | New MDL keyword or statement added without a parser example in `mdl-examples/` | Syntax feature | Add a minimal `mdl-examples/doctype-tests/*.mdl` fixture and include the statement in the quick reference before review |
36+
| 11 | Commit titled `fix:` actually introduces new MDL syntax | Scope & atomicity | Split the syntax addition into a `feat:` or proposal-backed PR; keep bug fixes separate from language design |
37+
| 12 | Round-trip bug patched on only one half of parse/write | DESCRIBE/EXEC symmetry | Fix both parser/defaulting and writer serialization, then add tests for missing/default fields and explicit fields |
3538

3639
---
3740

.claude/skills/mendix/write-microflows.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,7 @@ commit $Product;
559559

560560
**Rules:**
561561
- `@annotation` before an activity attaches the note to that activity
562+
- `@annotation` before activity-binding metadata such as `@position`, `@caption`, `@color`, `@excluded`, or `@anchor` stays free-floating when later metadata binds the following activity
562563
- `@annotation` at the end (no following activity) creates a free-floating note
563564
- Escape single quotes by doubling: `@annotation 'Don''t forget'`
564565
- `@position` always appears in DESCRIBE output; `@caption` only when custom; `@color` only when not Default
@@ -824,6 +825,60 @@ rest call delete 'https://api.example.com/items/{1}' with (
824825

825826
**REST CALL supports full error handling** (`on error continue`, `on error rollback`, custom error handlers).
826827

828+
## Legacy SOAP Web Service Calls
829+
830+
`call web service` preserves legacy Mendix SOAP activities. Prefer REST clients
831+
for new integrations; this syntax exists mainly so existing projects can round-trip
832+
without dropping SOAP actions.
833+
834+
```mdl
835+
-- Structured passthrough form using Mendix document IDs.
836+
$Root = call web service 'sample-service-id'
837+
operation 'FetchSampleItems'
838+
send mapping 'sample-send-mapping-id'
839+
receive mapping 'sample-receive-mapping-id'
840+
timeout 30
841+
on error rollback;
842+
843+
-- Raw escape hatch emitted by describe when the SOAP action has fields that
844+
-- are not expressible yet. The base64 payload is the authoritative BSON action.
845+
$Root = call web service raw 'AQID';
846+
```
847+
848+
**Design note:** service and mapping references are currently opaque Mendix IDs,
849+
not qualified names. Treat this as round-trip support, not a recommended authoring
850+
syntax for new SOAP actions.
851+
852+
## File Downloads
853+
854+
Use `download file` to stream a `System.FileDocument` from a microflow. Add
855+
`show in browser` when Studio Pro's action should open the file inline instead
856+
of forcing a download.
857+
858+
```mdl
859+
download file $GeneratedReport show in browser;
860+
download file $GeneratedExport;
861+
```
862+
863+
## Empty Java-Action Argument (`...`)
864+
865+
When `describe` round-trips a Java-action call that has an unbound parameter
866+
in Studio Pro, it emits `...` as the argument value. This preserves the
867+
underlying empty `BasicCodeActionParameterValue.Argument` so that the next
868+
`describe → exec → describe` cycle stays symmetric.
869+
870+
```mdl
871+
$Total = call java action SampleModule.Recalculate(
872+
CompanyId = ...,
873+
RecalculateAll = true,
874+
ItemList = ...
875+
);
876+
```
877+
878+
`...` is a *round-trip-only* placeholder. New scripts should bind every
879+
parameter to a real expression; reach for `...` only when you're regenerating
880+
MDL from an existing project that already had unbound parameters.
881+
827882
## Error Handling
828883

829884
MDL supports error handling for activities that may fail (microflow calls, commits, external service calls, etc.).

cmd/mxcli/lsp_completions_gen.go

Lines changed: 8 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/01-project/MDL_QUICK_REFERENCE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ authentication basic, session
215215
| List declaration | `declare $list list of Module.Entity = empty;` | |
216216
| Assignment | `set $Var = expression;` | Variable must be declared first |
217217
| Create object | `$Var = create Module.Entity (attr = value);` | |
218+
| Duplicate implicit output | `$Var`, `$Var_2`, `$Var_3` | Describe may alias same-position duplicate outputs for round-trip preservation |
218219
| Change object | `change $entity (attr = value);` | |
219220
| Commit | `commit $entity [with events] [refresh];` | |
220221
| Delete | `delete $entity;` | |
@@ -223,14 +224,18 @@ authentication basic, session
223224
| Retrieve (Assoc) | `retrieve $list from $Parent/Module.AssocName;` | Retrieve by association |
224225
| Call microflow | `$Result = call microflow Module.Name (Param = $value);` | |
225226
| Call nanoflow | `$Result = call nanoflow Module.Name (Param = $value);` | |
227+
| Call web service | `$Result = call web service 'Module.Service' operation 'OperationName';` | Legacy SOAP; unresolved dangling refs fall back to raw IDs |
228+
| Call web service raw | `$Result = call web service raw 'base64-bson';` | Escape hatch for byte-for-byte legacy SOAP round-trip |
226229
| Show page | `show page Module.PageName ($Param = $value);` | Also accepts `(Param: $value)` |
227230
| Close page | `close page;` | |
231+
| Download file | `download file $FileDocument [show in browser];` | Streams a `System.FileDocument` |
228232
| Validation | `validation feedback $entity/attribute message 'message';` | Requires attribute path + MESSAGE |
229233
| Log | `log info\|warning\|error [node 'name'] 'message';` | |
230234
| Position | `@position(x, y)` | Canvas position (before activity) |
231235
| Caption | `@caption 'text'` | Custom caption (before activity) |
232236
| Color | `@color Green` | Background color (before activity) |
233237
| Annotation | `@annotation 'text'` | Visual note attached to next activity |
238+
| Free annotation | `@annotation 'text'` before `@position(...)` | Free-floating visual note preserved by order |
234239
| IF | `if condition then ... [else ...] end if;` | |
235240
| LOOP | `loop $item in $list begin ... end loop;` | FOR EACH over list |
236241
| WHILE | `while condition begin ... end while;` | Condition-based loop |
@@ -775,6 +780,7 @@ Module.OrderResponse_CustomerInfo/Module.CustomerInfo as customer {
775780
| Create exposed action | `... exposed as 'caption' in 'Category' as $$ ... $$;` | Toolbox-visible in Studio Pro |
776781
| Drop Java action | `drop java action Module.Name;` | Delete a Java action |
777782
| Call from microflow | `$Result = call java action Module.Name(Param = value);` | Inside BEGIN...END |
783+
| Empty argument | `call java action Module.Name(Param = ...);` | `...` placeholder for an unbound code-action parameter (round-trip only) |
778784

779785
**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 <pEntity>` (type parameter declaration), bare `pEntity` (type parameter reference).
780786

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Ellipsis Placeholder Expression
2+
3+
Status: Draft
4+
5+
## Summary
6+
7+
Add a single-token expression `...` that represents an unbound /
8+
intentionally-empty argument value in microflow call statements.
9+
10+
```mdl
11+
$Total = call java action SampleModule.Recalculate(
12+
CompanyId = ...,
13+
RecalculateAll = true,
14+
ItemList = ...
15+
);
16+
```
17+
18+
`...` produces a parameter binding with an empty `Argument` string in the
19+
serialized BSON (`Microflows$BasicCodeActionParameterValue.Argument = ""`).
20+
Re-executing a script that contains `...` reproduces the same empty
21+
binding byte-for-byte, so describe → exec → describe stays symmetric for
22+
existing Studio Pro projects that have unbound code-action parameters.
23+
24+
## Motivation
25+
26+
Studio Pro's Java-action call dialog allows a developer to leave individual
27+
parameters empty — for example, when a Java action declares a parameter that
28+
the calling microflow does not yet have a meaningful value for, or when an
29+
external mapping is expected to fill the slot at runtime. The on-disk
30+
representation is a `Microflows$JavaActionParameterMapping` whose `Value` is a
31+
`BasicCodeActionParameterValue` with `Argument: ""`.
32+
33+
Before `...` existed, the describer had two options for these empty bindings:
34+
35+
1. Emit `''` (empty string literal). On re-exec, the visitor would round-trip
36+
to a non-empty single-quote literal whose `Argument` was `''`, not `""`,
37+
and Studio Pro would render the parameter as the literal string `''`.
38+
2. Drop the parameter entirely. Studio Pro would then add a back a
39+
placeholder mapping with a generated value, breaking the round-trip.
40+
41+
Both lose information. `...` lets the describer round-trip the empty binding
42+
without inventing a fake value.
43+
44+
## Syntax
45+
46+
```antlr
47+
atomicExpression
48+
: literal
49+
| ELLIPSIS
50+
| ...
51+
;
52+
```
53+
54+
Where `ELLIPSIS` is the lexer token `'...'`. The token is reserved for this
55+
single use; it is not valid in arithmetic / boolean / comparison
56+
expressions.
57+
58+
## Semantics
59+
60+
- `...` is recognised by the builder via `isPlaceholderExpression` in
61+
`mdl/executor/cmd_microflows_builder_calls.go`.
62+
- Inside a Java-action `callArgument`, `...` produces a
63+
`BasicCodeActionParameterValue` with `Argument: ""`.
64+
- Outside of `callArgument` lists, `...` parses but the builder rejects it
65+
(it never resolves to a runtime value). Future statements may extend the
66+
set of contexts that accept `...` — see Open Questions.
67+
68+
## Examples
69+
70+
```mdl
71+
-- Java action call with two unbound and one bound argument
72+
$Total = call java action SampleModule.Recalculate(
73+
CompanyId = ...,
74+
RecalculateAll = true,
75+
ItemList = ...
76+
);
77+
```
78+
79+
The Mendix BSON for the unbound arguments is:
80+
81+
```
82+
JavaActionParameterMapping {
83+
Parameter: 'SampleModule.Recalculate.CompanyId',
84+
Value: BasicCodeActionParameterValue { Argument: '' }
85+
}
86+
```
87+
88+
## Tests And Examples
89+
90+
- Builder coverage: `TestBuildJavaAction_PlaceholderArgumentPreservesEmptyBasicValue`
91+
in `mdl/executor/cmd_microflows_builder_java_action_test.go`.
92+
- Visitor coverage: `atomicExpression`'s `ELLIPSIS` arm produces
93+
`ast.SourceExpr{Source: "..."}` (see
94+
`mdl/visitor/visitor_microflow_expression.go`).
95+
- Example script: `mdl-examples/doctype-tests/ellipsis_placeholder.test.mdl`.
96+
97+
## Open Questions
98+
99+
- Should `...` be allowed as an argument to `call microflow` and
100+
`call nanoflow` calls as well? Today only Java actions consume the
101+
`BasicCodeActionParameterValue` form, so there is no symmetric BSON
102+
representation, but a future proposal could extend this.
103+
- Should we explicitly document `...` as round-trip-only and warn the linter
104+
when an authored microflow uses `...` outside of a known describe-emitted
105+
context? This would prevent users from authoring scripts that produce
106+
Studio Pro warnings ("unbound parameter") on import.
107+
- Should the surface syntax be `...` or a clearer keyword like
108+
`unspecified` / `default` / `unbound`? `...` was chosen because it is
109+
visually distinct, short, and matches existing "this is intentionally
110+
blank" conventions in other tools (Python's `Ellipsis`, TypeScript's
111+
`never` placeholder, etc.).
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Microflow Call Web Service Statement
2+
3+
Status: Draft
4+
5+
## Summary
6+
7+
Add MDL support for legacy Mendix SOAP `Microflows$CallWebServiceAction`.
8+
9+
```mdl
10+
$Root = call web service 'SampleSOAP.OrderService'
11+
operation 'FetchSampleItems'
12+
send mapping 'SampleSOAP.OrderRequest'
13+
receive mapping 'SampleSOAP.OrderResponse'
14+
timeout 30;
15+
16+
$Root = call web service 'dangling-service-id'
17+
operation 'FetchSampleItems'
18+
send mapping 'dangling-send-mapping-id'
19+
receive mapping 'dangling-receive-mapping-id';
20+
21+
$Root = call web service raw 'AQID';
22+
```
23+
24+
This proposal is primarily about safe round-trip preservation of existing SOAP
25+
actions. New integrations should prefer consumed REST services or inline REST
26+
calls.
27+
28+
## Motivation
29+
30+
Legacy projects can contain SOAP web service calls. Without an MDL
31+
representation, describe output either drops the activity or emits an
32+
unsupported-action comment that cannot be re-executed into the same model.
33+
34+
The immediate goal is therefore fidelity:
35+
36+
- Parse existing `CallWebServiceAction` BSON.
37+
- Emit an MDL statement that can be executed back into the MPR.
38+
- Preserve unsupported or version-specific BSON fields when the structured
39+
fields are incomplete.
40+
41+
## Syntax
42+
43+
```antlr
44+
callWebServiceStatement
45+
: (VARIABLE EQUALS)? CALL WEB SERVICE
46+
(RAW STRING_LITERAL
47+
| STRING_LITERAL
48+
(OPERATION STRING_LITERAL)?
49+
(SEND MAPPING STRING_LITERAL)?
50+
(RECEIVE MAPPING STRING_LITERAL)?
51+
(TIMEOUT expression)?)
52+
onErrorClause?
53+
SEMICOLON
54+
;
55+
```
56+
57+
## Design Notes
58+
59+
The structured form prefers stable qualified names for the imported web service
60+
and mapping references. During `describe`, mxcli resolves known
61+
`WebServices$ImportedWebService`, `ExportMappings$ExportMapping`, and
62+
`ImportMappings$ImportMapping` IDs through the backend and emits
63+
`Module.DocumentName`.
64+
65+
If a reference is dangling or the backend cannot resolve it, mxcli deliberately
66+
falls back to the raw ID string so unsupported legacy projects still round-trip.
67+
68+
The `raw` form is an explicit escape hatch. Its string is base64-encoded BSON
69+
for the complete action payload and is authoritative when re-executed. It exists
70+
so unsupported SOAP fields can be preserved byte-for-byte until the structured
71+
syntax covers them.
72+
73+
## Tests And Examples
74+
75+
- Parser/visitor coverage: `TestCallWebServiceStatement` and
76+
`TestCallWebServiceRawStatement`.
77+
- Builder/writer coverage: `TestBuildFlowGraph_WebServiceCallCreatesRealAction`,
78+
`TestBuildFlowGraph_WebServiceCallPreservesRawBSON`, and MPR RawBSON tests.
79+
- Example script: `mdl-examples/doctype-tests/call_web_service.test.mdl`.
80+
81+
## Open Questions
82+
83+
- Should the raw payload eventually move to a generic
84+
`raw microflow action '...'` escape hatch instead of remaining under
85+
`call web service raw`?
86+
87+
## Resolved Questions
88+
89+
- Service and mapping references are emitted as `Module.Document` names when
90+
the backend can resolve them. Raw IDs remain the fallback for dangling
91+
references and incomplete project metadata.

0 commit comments

Comments
 (0)