Skip to content

Commit 1e1c681

Browse files
committed
fix: prefer qualified refs for call web service
Symptom: review noted that structured `call web service` references were always emitted as string literals even when they were normal `Module.Document` references. Root cause: the grammar only accepted STRING_LITERAL references, so the formatter had to quote resolved SOAP services, operations, and mappings. Fix: add a webServiceReference rule that accepts qualifiedName for resolved references and STRING_LITERAL only as a dangling raw-ID fallback. Formatter now emits bare qualified refs when possible and quoted refs only when necessary. Docs, examples, proposal, and skill guidance were updated to match. Tests: regenerate the parser, run focused web-service visitor/formatter tests, then make build and make test.
1 parent 9defa89 commit 1e1c681

14 files changed

Lines changed: 10408 additions & 10160 deletions

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -736,19 +736,17 @@ for new integrations; this syntax exists mainly so existing projects can
736736
round-trip without dropping SOAP actions.
737737

738738
```mdl
739-
-- Structured form. SOAP references are quoted strings by design: DESCRIBE
740-
-- prefers Module.Document names when references are resolvable, but raw IDs
741-
-- and legacy document names must also round-trip.
742-
$Root = call web service 'SampleSOAP.OrderService'
743-
operation 'FetchSampleItems'
744-
send mapping 'SampleSOAP.OrderRequest'
745-
receive mapping 'SampleSOAP.OrderResponse'
739+
-- Structured form. Resolved SOAP references use normal qualified names.
740+
$Root = call web service SampleSOAP.OrderService
741+
operation FetchSampleItems
742+
send mapping SampleSOAP.OrderRequest
743+
receive mapping SampleSOAP.OrderResponse
746744
timeout 30
747745
on error rollback;
748746
749-
-- Raw IDs are accepted when old project references are dangling or unavailable.
747+
-- Quoted raw IDs are accepted when old project references are dangling or unavailable.
750748
$Root = call web service 'sample-service-id'
751-
operation 'FetchSampleItems'
749+
operation FetchSampleItems
752750
send mapping 'sample-send-mapping-id'
753751
receive mapping 'sample-receive-mapping-id';
754752

docs/01-project/MDL_QUICK_REFERENCE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ authentication basic, session
228228
| Call nanoflow | `$Result = call nanoflow Module.Name (Param = $value);` | |
229229
| Call JS action | `$Result = call javascript action Module.Name (Param = $value);` | JavaScript action (nanoflow/microflow) |
230230
| Call Java action | `$Result = call java action Module.Name (Param = $value);` | Java action (microflow only) |
231-
| Call web service | `$Result = call web service 'Module.Service' operation 'OperationName';` | Legacy SOAP; quoted refs preserve raw IDs and legacy names |
231+
| Call web service | `$Result = call web service Module.Service operation OperationName;` | Legacy SOAP; quoted refs are fallback for dangling raw IDs |
232232
| Call web service raw | `$Result = call web service raw 'base64-bson';` | Escape hatch for byte-for-byte legacy SOAP round-trip |
233233
| Show page | `show page Module.PageName ($Param = $value);` | Also accepts `(Param: $value)` |
234234
| Close page | `close page;` | |

docs/11-proposals/PROPOSAL_microflow_call_web_service_statement.md

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ Status: Draft
77
Add MDL support for legacy Mendix SOAP `Microflows$CallWebServiceAction`.
88

99
```mdl
10-
$Root = call web service 'SampleSOAP.OrderService'
11-
operation 'FetchSampleItems'
12-
send mapping 'SampleSOAP.OrderRequest'
13-
receive mapping 'SampleSOAP.OrderResponse'
10+
$Root = call web service SampleSOAP.OrderService
11+
operation FetchSampleItems
12+
send mapping SampleSOAP.OrderRequest
13+
receive mapping SampleSOAP.OrderResponse
1414
timeout 30;
1515
1616
$Root = call web service 'dangling-service-id'
17-
operation 'FetchSampleItems'
17+
operation FetchSampleItems
1818
send mapping 'dangling-send-mapping-id'
1919
receive mapping 'dangling-receive-mapping-id';
2020
@@ -44,13 +44,18 @@ The immediate goal is therefore fidelity:
4444
callWebServiceStatement
4545
: (VARIABLE EQUALS)? CALL WEB SERVICE
4646
(RAW STRING_LITERAL
47-
| STRING_LITERAL
48-
(OPERATION STRING_LITERAL)?
49-
(SEND MAPPING STRING_LITERAL)?
50-
(RECEIVE MAPPING STRING_LITERAL)?
47+
| webServiceReference
48+
(OPERATION webServiceReference)?
49+
(SEND MAPPING webServiceReference)?
50+
(RECEIVE MAPPING webServiceReference)?
5151
(TIMEOUT expression)?)
5252
onErrorClause?
5353
;
54+
55+
webServiceReference
56+
: qualifiedName
57+
| STRING_LITERAL
58+
;
5459
```
5560

5661
## Design Notes
@@ -62,7 +67,8 @@ and mapping references. During `describe`, mxcli resolves known
6267
`Module.DocumentName`.
6368

6469
If a reference is dangling or the backend cannot resolve it, mxcli deliberately
65-
falls back to the raw ID string so unsupported legacy projects still round-trip.
70+
falls back to a quoted raw ID string so unsupported legacy projects still
71+
round-trip without pretending the ID is a normal document name.
6672

6773
The `raw` form is an explicit escape hatch. Its string is base64-encoded BSON
6874
for the complete action payload and is authoritative when re-executed. It exists
@@ -80,13 +86,11 @@ syntax covers them.
8086
## Resolved Questions
8187

8288
- Service and mapping references are emitted as `Module.Document` names when
83-
the backend can resolve them. Raw IDs remain the fallback for dangling
84-
references and incomplete project metadata.
85-
- The structured syntax uses quoted strings for service and mapping references
86-
instead of `qualifiedName` tokens because legacy SOAP projects can contain
87-
raw IDs or document names that are not valid MDL identifiers. Resolved
88-
references should still be written as `Module.Document` text inside the
89-
string literal.
89+
the backend can resolve them. Raw IDs remain quoted fallback references for
90+
dangling references and incomplete project metadata.
91+
- Structured resolved references use `qualifiedName` tokens for consistency
92+
with other MDL document references. `STRING_LITERAL` is only the fallback for
93+
dangling raw IDs and names that cannot be emitted as bare identifiers.
9094

9195
## Open Questions
9296

mdl-examples/doctype-tests/call_web_service.mdl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ create entity SampleSOAP.OrderResponse (
88
create microflow SampleSOAP.ACT_FetchItems ()
99
returns SampleSOAP.OrderResponse as $Root
1010
begin
11-
$Root = call web service 'SampleSOAP.OrderService'
12-
operation 'FetchSampleItems'
13-
send mapping 'SampleSOAP.OrderRequest'
14-
receive mapping 'SampleSOAP.OrderResponse'
11+
$Root = call web service SampleSOAP.OrderService
12+
operation FetchSampleItems
13+
send mapping SampleSOAP.OrderRequest
14+
receive mapping SampleSOAP.OrderResponse
1515
timeout 30
1616
on error rollback;
1717

@@ -22,9 +22,9 @@ end;
2222
create microflow SampleSOAP.ACT_FetchItemsDanglingRefs ()
2323
returns SampleSOAP.OrderResponse as $Root
2424
begin
25-
-- Raw IDs are preserved when describe cannot resolve dangling legacy refs.
25+
-- Quoted raw IDs are preserved when describe cannot resolve dangling legacy refs.
2626
$Root = call web service 'sample-service-id'
27-
operation 'FetchSampleItems'
27+
operation FetchSampleItems
2828
send mapping 'sample-send-mapping-id'
2929
receive mapping 'sample-receive-mapping-id';
3030

mdl/executor/cmd_microflows_format_action.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -874,22 +874,59 @@ func formatWebServiceCallAction(ctx *ExecContext, a *microflows.WebServiceCallAc
874874
return prefix + "call web service raw " + mdlQuote(raw) + ";"
875875
}
876876

877-
parts := []string{prefix + "call web service " + mdlQuote(resolveWebServiceReference(ctx, a.ServiceID))}
877+
parts := []string{prefix + "call web service " + formatWebServiceReference(resolveWebServiceReference(ctx, a.ServiceID))}
878878
if a.OperationName != "" {
879-
parts = append(parts, "operation "+mdlQuote(a.OperationName))
879+
parts = append(parts, "operation "+formatWebServiceReference(a.OperationName))
880880
}
881881
if a.SendMappingID != "" {
882-
parts = append(parts, "send mapping "+mdlQuote(resolveWebServiceMappingReference(ctx, a.SendMappingID, true)))
882+
parts = append(parts, "send mapping "+formatWebServiceReference(resolveWebServiceMappingReference(ctx, a.SendMappingID, true)))
883883
}
884884
if a.ReceiveMappingID != "" {
885-
parts = append(parts, "receive mapping "+mdlQuote(resolveWebServiceMappingReference(ctx, a.ReceiveMappingID, false)))
885+
parts = append(parts, "receive mapping "+formatWebServiceReference(resolveWebServiceMappingReference(ctx, a.ReceiveMappingID, false)))
886886
}
887887
if a.TimeoutExpression != "" {
888888
parts = append(parts, "timeout "+strings.TrimRight(a.TimeoutExpression, " \t\n\r"))
889889
}
890890
return strings.Join(parts, "\n") + ";"
891891
}
892892

893+
func formatWebServiceReference(ref string) string {
894+
if isBareQualifiedReference(ref) {
895+
return ref
896+
}
897+
return mdlQuote(ref)
898+
}
899+
900+
func isBareQualifiedReference(ref string) bool {
901+
if ref == "" {
902+
return false
903+
}
904+
for _, part := range strings.Split(ref, ".") {
905+
if !isBareIdentifier(part) {
906+
return false
907+
}
908+
}
909+
return true
910+
}
911+
912+
func isBareIdentifier(part string) bool {
913+
if part == "" {
914+
return false
915+
}
916+
for i, r := range part {
917+
if i == 0 {
918+
if r != '_' && (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') {
919+
return false
920+
}
921+
continue
922+
}
923+
if r != '_' && (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') && (r < '0' || r > '9') {
924+
return false
925+
}
926+
}
927+
return true
928+
}
929+
893930
func resolveWebServiceReference(ctx *ExecContext, id model.ID) string {
894931
raw := string(id)
895932
if raw == "" || ctx == nil || ctx.Backend == nil {

mdl/executor/cmd_microflows_format_action_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,7 +1171,7 @@ func TestFormatAction_WebServiceCallResolvesKnownReferences(t *testing.T) {
11711171
UseReturnVariable: true,
11721172
}
11731173
got := formatAction(ctx, action, nil, nil)
1174-
want := "$Root = call web service 'SyntheticSOAP.OrderService'\noperation 'FetchOrders'\nsend mapping 'SyntheticSOAP.OrderRequest'\nreceive mapping 'SyntheticSOAP.OrderResponse';"
1174+
want := "$Root = call web service SyntheticSOAP.OrderService\noperation FetchOrders\nsend mapping SyntheticSOAP.OrderRequest\nreceive mapping SyntheticSOAP.OrderResponse;"
11751175
if got != want {
11761176
t.Errorf("got %q, want %q", got, want)
11771177
}
@@ -1200,7 +1200,7 @@ func TestFormatAction_WebServiceCallKeepsRawReferencesWhenUnknown(t *testing.T)
12001200
OutputVariable: "Root",
12011201
}
12021202
got := formatAction(ctx, action, nil, nil)
1203-
want := "$Root = call web service 'dangling-service-id'\noperation 'FetchOrders'\nsend mapping 'dangling-send-id'\nreceive mapping 'dangling-receive-id';"
1203+
want := "$Root = call web service 'dangling-service-id'\noperation FetchOrders\nsend mapping 'dangling-send-id'\nreceive mapping 'dangling-receive-id';"
12041204
if got != want {
12051205
t.Errorf("got %q, want %q", got, want)
12061206
}

mdl/grammar/MDLParser.g4

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,21 +1492,25 @@ callJavaScriptActionStatement
14921492

14931493
// Legacy SOAP call. The preferred structured form stores service and mapping
14941494
// references in STRING_LITERAL tokens rather than qualifiedName tokens because
1495-
// old projects can contain raw IDs or SOAP document names that are not valid
1496-
// MDL identifiers. DESCRIBE still prefers Module.Document text when references
1497-
// resolve; unresolved values pass through as strings. Raw BSON remains the
1498-
// escape hatch for unsupported SOAP payload details.
1495+
// Structured references prefer qualifiedName tokens. STRING_LITERAL remains as
1496+
// a fallback for dangling raw IDs that cannot be represented as identifiers.
1497+
// Raw BSON remains the escape hatch for unsupported SOAP payload details.
14991498
callWebServiceStatement
15001499
: (VARIABLE EQUALS)? CALL WEB SERVICE
15011500
(RAW STRING_LITERAL
1502-
| STRING_LITERAL
1503-
(OPERATION STRING_LITERAL)?
1504-
(SEND MAPPING STRING_LITERAL)?
1505-
(RECEIVE MAPPING STRING_LITERAL)?
1501+
| webServiceReference
1502+
(OPERATION webServiceReference)?
1503+
(SEND MAPPING webServiceReference)?
1504+
(RECEIVE MAPPING webServiceReference)?
15061505
(TIMEOUT expression)?)
15071506
onErrorClause?
15081507
;
15091508

1509+
webServiceReference
1510+
: qualifiedName
1511+
| STRING_LITERAL
1512+
;
1513+
15101514
// $Result = EXECUTE DATABASE QUERY Module.Connection.QueryName (param = 'value');
15111515
// $Result = EXECUTE DATABASE QUERY Module.Connection.QueryName DYNAMIC 'SELECT ...';
15121516
// $Result = EXECUTE DATABASE QUERY Module.Connection.QueryName CONNECTION (DBSource = $Url, DBUsername = $User, DBPassword = $Pass);

mdl/grammar/parser/MDLParser.interp

Lines changed: 2 additions & 1 deletion
Large diffs are not rendered by default.

mdl/grammar/parser/mdl_lexer.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)