feat: support legacy microflow call web service statement#334
feat: support legacy microflow call web service statement#334ako merged 10 commits intomendixlabs:mainfrom
Conversation
ako
left a comment
There was a problem hiding this comment.
PR #334 Review — feat: support legacy microflow call web service statement
Overview
Large but well-structured PR. Adds full MDL round-trip support for legacy SOAP CallWebServiceAction, using a dual-mode design: a structured form that resolves/emits qualified Module.Document names, and a raw base64 escape hatch for opaque byte-for-byte preservation of unsupported SOAP payloads.
Full-stack checklist
| Layer | Status |
|---|---|
| Grammar (tokens + rule) | WEB, RAW, RECEIVE added; rule in keyword list |
| Parser regenerated | Yes |
| AST node | CallWebServiceStmt with all fields |
| Visitor | Index-based STRING_LITERAL parsing; both structured and raw forms |
| Builder | addCallWebServiceAction + reference resolution helpers |
| Formatter | formatWebServiceCallAction; ID→QName resolution; error suffix via getActionErrorHandlingType |
| Parser (BSON) | parseMicroflowActionValue added to preserve primitive.D ordering; parseWebServiceCallActionFromD stores RawBSON |
| Writer (BSON) | serializeWebServiceCallAction; RawBSON takes priority |
| Tests | Visitor, builder, formatter, BSON parser/writer all covered |
Issues
1. Magic value ParameterMappings: bson.A{int32(2)} needs explanation or verification
In serializeWebServiceCallAction:
{Key: "ParameterMappings", Value: bson.A{int32(2)}},A single integer 2 in a BSON array is unusual. If this comes from actual Studio Pro-generated BSON, it needs a comment explaining what the value represents. If it is a placeholder, it may cause Studio Pro to reject the document. This should be verified against a real CallWebServiceAction exported from Studio Pro.
2. canonicalRawBSON sorts keys, breaking byte-for-byte identity in the MDL text
The function sorts BSON document keys alphabetically before encoding the base64 payload in the MDL text. This means describe → exec → describe produces a different base64 string than the original if the original had non-alphabetical key order. The write path is fine (RawBSON is preserved as-is when written to disk), but the MDL text diverges from the original, which contradicts the stated goal of "lossless round-trip". A comment explaining that canonical sorting is intentional for MDL text stability (not for disk output) would help future readers.
3. Visitor index-based STRING_LITERAL parsing is fragile
literals := callCtx.AllSTRING_LITERAL()
idx := 0
// ...advances idx conditionally for each clauseThis works because the grammar enforces clause ordering (OPERATION before SEND before RECEIVE), but a grammar ambiguity fix or reorder would silently break it. Consider extracting each literal by walking the specific grammar context token (e.g., using child context rather than positional index), or at minimum add a comment that the ordering depends on the grammar production order.
4. resolveWebServiceRefForWrite name fallback skips qualified-name check
if name == ref {
return string(unit.ID)
}This fallback (after the GetQualifiedName check) matches on bare Name without module qualification. If two modules have a web service with the same short name, the first match wins regardless of module. The fallback exists for when hierarchy is unavailable, but a comment acknowledging the ambiguity risk would help.
5. LSP completion label for RECEIVE is "REST keyword"
RECEIVE is added under the REST keyword group in lsp_completions_gen.go. It is also used for SOAP now. Not a functional issue but might mislead users browsing completions.
Nit
In addCallWebServiceAction, ErrorHandlingType is set on both the action and the activity.BaseActivity. This is consistent with the existing pattern for other actions (e.g., RestCallAction) but is worth noting as known redundancy.
Summary
The design is sound and the test coverage is thorough. The main concern before merging is the ParameterMappings: bson.A{int32(2)} value — this needs to be verified against actual Studio Pro-generated BSON and documented if it is correct. The canonical BSON sorting and index-based visitor are acceptable with added comments.
The legacy SOAP writer had a misleading comment claiming the leading `int32(2)` of an empty ParameterMappings array was a "declared count". It is actually the Mendix storage-list-type marker — the same constant used by every other empty list writer in this file (see PR mendixlabs#338 for showpage and PR mendixlabs#374 for change-action items). Addresses ako's review on PR mendixlabs#334. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Symptom: legacy SOAP CallWebServiceAction activities could not be represented as executable MDL, so describe/exec round-trips either dropped the action or had to keep it as an unsupported comment. Root cause: the microflow AST, grammar, visitor, formatter, graph builder, and MPR parser/writer had no structured representation for Microflows$CallWebServiceAction and no raw escape hatch for version-specific SOAP payload fields. Fix: add structured call web service syntax, raw base64 BSON preservation, qualified-name resolution for imported SOAP services and send/receive mappings during describe, writer support for structured and raw actions, docs, a doctype example, LSP completions, and microflow skill guidance. Tests: covered parser/visitor, graph builder, formatter reference resolution and raw fallback, MPR raw BSON preservation, and validated with make build, mxcli check for call_web_service.test.mdl, make lint-go, make test, and make test-integration.
…oject The unqualified `returns Object` in the original doctype test caused `entity '.Object' not found` when executed. Adds a synthetic SampleSOAP.OrderResponse entity and uses it as the return type so the script runs cleanly against a fresh Mendix project. Drops the `$Root =` assignment from the raw-base64 case since the encoded BSON has no OutputVariable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Symptom: review flagged several CALL WEB SERVICE implementation details whose correctness depended on non-obvious BSON and grammar constraints. Root cause: the code encoded those constraints directly without explaining why empty SOAP parameter mappings use the marker array, why raw BSON is canonicalized for MDL text, why string literals are consumed positionally, or why bare service name fallback is ambiguous. Fix: add focused comments at the writer, formatter, visitor, and builder sites that depend on those constraints. There is no behavior change. Tests: make build; focused web-service tests; mxcli check on the web-service doctype fixture; make test.
The legacy SOAP writer had a misleading comment claiming the leading `int32(2)` of an empty ParameterMappings array was a "declared count". It is actually the Mendix storage-list-type marker — the same constant used by every other empty list writer in this file (see PR mendixlabs#338 for showpage and PR mendixlabs#374 for change-action items). Addresses ako's review on PR mendixlabs#334. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
6ba80eb to
9095513
Compare
Symptom: the generated LSP completion entry for RECEIVE still described the keyword as REST-only after CALL WEB SERVICE started reusing it for legacy SOAP mapping clauses. Root cause: completion categories come from lexer sections, and RECEIVE lives in the REST keyword section even though the token is now shared across REST and SOAP service syntax. Fix: override the generated category for RECEIVE to "Service keyword" while leaving the rest of the REST section unchanged. Tests: make build Tests: make test Tests: make lint-go
|
Review follow-up status:
Local validation on the updated head: make build
make test
make lint-go |
Symptom: roundtripping a legacy SOAP call with simple request parameter mappings produced a model that mx check rejected because the reconstructed action dropped SOAP-specific request metadata. Root cause: parsed CallWebServiceAction values from map-backed BSON were treated as fully representable by the structured MDL syntax, even when they contained request/body/header fields that the syntax cannot author yet. Fix: keep qualified service references as authored and fall back to raw BSON when a parsed SOAP action contains unsupported fields, preserving the full action payload through exec. Tests: added parser coverage for unsupported SOAP request details, updated the builder expectation, ran make build, make test, and a targeted roundtrip with mx check.
ako
left a comment
There was a problem hiding this comment.
Review
Moderate: doctype example won't run in CI
mdl-examples/doctype-tests/call_web_service.test.mdl uses a .test.mdl suffix, which causes TestMxCheck_DoctypeScripts to skip it (the test suite only picks up plain .mdl files). The syntax won't be exercised by the automated doctype CI pass at all.
Fix: rename to call_web_service.mdl (or add a companion plain-.mdl file that just exercises the syntax without requiring a live project).
Relevant test filter (in roundtrip_*_test.go / TestMxCheck_DoctypeScripts):
// .test.mdl and .tests.mdl files are skipped — only plain .mdl gets CI coverageModerate: STRING_LITERAL (single-quoted) for Module.Document names is inconsistent with MDL conventions
Every other MDL command that references a document uses a bare qualifiedName token:
call microflow MyModule.MF_DoSomething(...)
show page MyModule.HomePage
describe page MyModule.HomePage
grant execute on microflow MyModule.MF_DoSomething to MyModule.User
This PR introduces a new pattern where Module.Document identifiers are embedded inside STRING_LITERAL tokens (single-quoted strings):
call web service 'SampleSOAP.OrderService'
operation 'SampleSOAP.Operation_SendOrder'
send mapping 'SampleSOAP.RequestMapping'
receive mapping 'SampleSOAP.ResponseMapping'
The DESCRIBE formatter calls mdlQuote(resolveWebServiceReference(...)), which always produces '...' single-quoted output. The skill and quick-reference docs faithfully reproduce this single-quote style.
Concerns:
- Inconsistency — readers and LLMs working from examples elsewhere in the docs will expect bare
Module.Documentidentifiers, not single-quoted strings. - Roundtrip brittleness —
mdlQuote()escapes apostrophes as''(Mendix expression style). A module or document name containing an apostrophe would serialize incorrectly. - Future friction — if the grammar is ever extended to accept
qualifiedNamehere (for consistency), the roundtrip format changes and existing scripts break.
Recommended fix (or at minimum, document the design choice explicitly):
Option A — change the grammar to accept qualifiedName for the structured form and keep STRING_LITERAL only for the raw legacy ID fallback:
callWebServiceStatement
: (VARIABLE EQUALS)? CALL WEB SERVICE
(RAW STRING_LITERAL
| qualifiedName -- service as Module.Document
(OPERATION qualifiedName)?
(SEND MAPPING qualifiedName)?
(RECEIVE MAPPING qualifiedName)?
(TIMEOUT expression)?)
onErrorClause?
;
Option B — if STRING_LITERAL is intentional (e.g. because service names can contain characters invalid in identifiers), add a comment in the grammar and a note in the skill explaining why this departs from the convention.
Positive notes
- The visitor's positional
STRING_LITERALwalk is correct — it uses keyword token presence (OPERATION(),SEND(),RECEIVE()) to advance the index, which safely handles optional clauses without ambiguity. resolveWebServiceReference/resolveWebServiceMappingReferencecorrectly look up module-qualified names from imported service metadata, with raw-ID fallback for unresolved cases.- The raw BSON base64 escape hatch for unsupported SOAP payload types is a pragmatic choice that avoids data loss on roundtrip.
Symptom: the call web service doctype example used a skipped .test.mdl suffix, and the grammar/docs did not explain why structured SOAP references are quoted strings. Root cause: legacy SOAP support intentionally accepts both Module.Document text and raw dangling IDs, but that design choice was only implicit in examples. Fix: rename the doctype example to a checked .mdl fixture and document the STRING_LITERAL reference choice in the grammar, skill, proposal, and quick reference. Tests: ran make build, mxcli check on the doctype fixture, and make test.
|
Review follow-up status:
Local validation on the updated branch: make build
./bin/mxcli check mdl-examples/doctype-tests/call_web_service.mdl
make testThe current |
ako
left a comment
There was a problem hiding this comment.
Follow-up on STRING_LITERAL justification
The updated remark says: "SOAP document names can contain text that is not a valid MDL identifier, so quoted references are the stable authoring/fallback form."
But qualifiedName already handles that case — identifierOrKeyword accepts QUOTED_IDENTIFIER (double-quoted "..." or backtick form):
qualifiedName
: identifierOrKeyword (DOT identifierOrKeyword)*
;
identifierOrKeyword
: IDENTIFIER
| QUOTED_IDENTIFIER // "double-quoted" or `backtick` — already in the grammar
| keyword
;So a service name with characters that aren't valid bare identifiers could be expressed as:
call web service "My Module"."My SOAP Service"
operation "My Module"."My_Operation"
The only case where STRING_LITERAL is genuinely necessary is a raw unresolved GUID/ID like {f3a7c8b2-...} — but that's already handled by the separate RAW STRING_LITERAL path.
For the structured (resolved) form, Mendix module and document names are always valid Mendix identifiers (alphanumeric + underscore), so they would work as bare qualifiedName without quoting. The "invalid identifier characters" argument doesn't apply to the structured path, only to the raw fallback that already has its own grammar alternative.
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.
# Conflicts: # mdl/grammar/parser/MDLParser.interp # mdl/grammar/parser/mdl_parser.go
Part of #332.
Summary
call web serviceMDL syntax for legacy SOAPMicroflows$CallWebServiceActionModule.Documentnames during describe, while keeping raw IDs as fallbackValidation
make build./bin/mxcli check mdl-examples/doctype-tests/call_web_service.test.mdlmake lint-gomake testmake test-integration