Skip to content

Fix: persist and read back consumed REST operation mappings (#843) - #99

Merged
ako merged 2 commits into
mainfrom
claude/fix-843-rest-response-mapping
Aug 6, 2026
Merged

Fix: persist and read back consumed REST operation mappings (#843)#99
ako merged 2 commits into
mainfrom
claude/fix-843-rest-response-mapping

Conversation

@ako

@ako ako commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Fixes mendixlabs#843.

Symptom

create rest client reports success. describe rest client then omits Query:, Parameters: and Headers: entirely, and always prints Response: none. mx check reports 0 errors, so nothing anywhere complains.

Dumping the stored BSON for the reporter's script shows the split:

"QueryParameters": [2, {"$Type": "Rest$QueryParameter", "Name": "page"}, ...]   ← stored fine
"Headers":         [2, {"$Type": "Rest$HeaderWithValueTemplate", ...}]          ← stored fine
"ResponseHandling": {"$Type": "Rest$NoResponseHandling"}                        ← mapping gone

Three defects behind one symptom

1. Write path — a case-sensitive comparison whose false branch is silent

model.RestClientOperation documents BodyType/ResponseType as upper-case tokens, and every consumer compares against that spelling — both serializers and the REST-call microflow builder. The MDL executor stored the visitor's lower-case source text:

op.ResponseType = "mapping"                    // mdl/executor/cmd_rest_clients.go
...
if op.ResponseType == "MAPPING" && ... {       // mdl/backend/modelsdk/consumed_rest_write.go
    addPart(g, "ResponseHandling", restImplicitMappingResponseToGen(...))
} else {
    addPart(g, "ResponseHandling", restResponseHandlingToGen(...))   // ← always taken
}

The mapping was dropped on every response mapping authored in MDL, not just the reporter's syntax — a fully correct Response: mapping Mod.Entity { Attr = field } was lost too. Nothing errored because the else-branch is a legitimate outcome for an operation with no mapping, so a producer/consumer mismatch got laundered into a plausible-looking model.

Fixed by normalizing with strings.ToUpper at the one place the AST becomes the semantic model, and making the two serializer comparisons EqualFold so the landmine isn't left armed for the next producer. (The OpenAPI import path already emits the documented upper-case form, which is why it was unaffected.)

2. Read path — one type assertion for two different gen types

restOperationFromGen populated only Name/HttpMethod/Path/Timeout, and type-asserted *genRest.RestParameter for both parameter lists. The writer emits Rest$OperationParameter and Rest$QueryParameter — two different types — so both assertions fell to ok=false and skipped every item. Headers and ResponseHandling weren't read at all.

Now reads parameters (with DataType), query parameters, headers, the response handler and the body, including the ImportMappings/ExportMappings element trees. This also repairs shouldSetBodyVariable, which could never observe EXPORT_MAPPING because BodyType was never populated.

3. The reporter's actual syntax is not expressible in Mendix

Response: mapping ZZB."IMM_R10" names an import mapping document. The clause expects a target entity plus a { ... } body — Mendix stores the mapping inline on the operation and has nowhere to put a document reference. Rest$RestOperationResponseHandling has exactly two implementations:

// generated/metamodel/types.go:738
// RestOperationResponseHandling is implemented by: ImplicitMappingResponseHandling, NoResponseHandling

So it parsed, named a document where an entity belongs, contributed no field mappings, and was written as "no response handling" — in silence. Now refused at exec time and at mxcli check time (MDL-REST01, no project needed):

Error: operation "SearchRoutes": Response: mapping ZZB.IMM_R10 has no mapping body.
  A consumed REST operation cannot reference an import/export mapping document;
  Mendix stores the mapping inline, so the fields must be listed here.
  Name the target entity and its fields:
    Response: mapping Module.Entity { Attribute = jsonField, ... }

One deliberate non-fix

Rest$QueryParameter stores no DataType — Mendix does not model a type for query parameters, so the one written in MDL is dropped at write time and there is nothing to read back. The grammar still requires $name: Type, so describe emitted $page: , which does not re-parse. It now emits String. Reconstructing the authored type would be inventing a value the model does not hold — the mistake fixed in mendixlabs#840.

Validation

Mendix 11.13.0, fresh projects via mxcli new:

before after
reporter's script silent success, mapping lost fails loudly with the syntax to use
corrected script → ResponseHandling Rest$NoResponseHandling Rest$ImplicitMappingResponseHandling + full ImportMappings$ObjectMappingElement tree
describe rest client Response: none, no Query/Parameters/Headers all clauses present
mx check 0 errors 0 errors
describe → exec → describe n/a byte-identical (fixed point)
  • Tests written first; all three fixes reverted independently to confirm each is the cause of its test's failure (Parameters = 0, want 1 / lowercase "mapping" lost the response mapping / ResponseType = "json", want "JSON").
  • go test ./..., make check-mdl green.
  • The pre-existing round-trip test built an operation with a query parameter but only asserted the operation count — which is why this shipped. The new test asserts the parameter survives.

Changes

File Change
mdl/executor/cmd_rest_clients.go normalize type tokens; checkInlineMappingBody; restParamTypeOrDefault
mdl/backend/modelsdk/integration_read.go correct type assertions; read headers, response, body, mapping trees
mdl/backend/modelsdk/consumed_rest_write.go EqualFold on the two silent-drop comparisons
modelsdk/mpr/serialize_web_services.go same, other engine
mdl/executor/validate_rest_mapping.go new — MDL-REST01 check-time rule
cmd/mxcli/cmd_check.go wire the rule into the no-project pass
cmd/mxcli/syntax/features_integration.go Query:/Timeout: in help; entity-not-document note
.claude/skills/mendix/rest-client.md document the entity-plus-body rule and the reuse alternative
.claude/skills/fix-issue.md symptom row
mdl-examples/bug-tests/843-*.mdl positive repro + .fail.mdl negative test

🤖 Generated with Claude Code

https://claude.ai/code/session_01LUToAkUx54bNkNjsBpufRH


Generated by Claude Code

claude and others added 2 commits August 6, 2026 08:21
…bs#843)

A `create rest client` operation reported success, but `describe rest
client` omitted Query/Parameters/Headers and always printed
`Response: none`. Dumping the stored BSON showed the query parameters and
headers written correctly while ResponseHandling was
Rest$NoResponseHandling — the response mapping was gone. `mx check`
reported 0 errors, so nothing anywhere complained.

Three independent defects behind the one symptom.

Write path. model.RestClientOperation documents BodyType/ResponseType as
upper-case tokens, and every consumer compares against that spelling: both
serializers and the REST-call microflow builder. The MDL executor stored
the visitor's lower-case source text, so `op.ResponseType == "MAPPING"`
never matched and the mapping fell through to the else-branch — which
legitimately writes "no response handling". Nothing errored because that
is a real outcome for an operation without a mapping; the mismatch was
laundered into a plausible model. Normalized with strings.ToUpper at the
one place the AST becomes the semantic model, and made the two serializer
comparisons EqualFold so the same landmine is not left armed for the next
producer (the OpenAPI import path already emits the upper-case form).

Read path. restOperationFromGen populated only Name/HttpMethod/Path/
Timeout and type-asserted Rest$RestParameter for both parameter lists,
while the writer emits Rest$OperationParameter and Rest$QueryParameter —
two different gen types, so both assertions failed to ok=false and
skipped every item. Headers and ResponseHandling were not read at all.
Now reads parameters (with DataType), query parameters, headers, the
response handler and the body, including the Import/ExportMappings
element trees. This also repairs shouldSetBodyVariable, which could never
see EXPORT_MAPPING because BodyType was never populated.

Unsupported reference syntax. `Response: mapping Mod.IMM_X` — what the
reporter wrote — names an import mapping *document*. The clause expects a
target entity plus a `{ ... }` body, and Mendix has nowhere to store a
document reference: Rest$RestOperationResponseHandling has exactly two
implementations, inline-mapping and none. It parsed, contributed no
entries, and was written as "none". Now refused at exec time and at
`mxcli check` time (MDL-REST01, no project required), with the inline
form spelled out in the message.

Rest$QueryParameter stores no DataType, so the MDL type is decorative and
dropped at write time. describe now re-emits query parameters as String
rather than an empty type, which did not re-parse.

Verified on Mendix 11.13.0: reporter's script now fails loudly instead of
silently; the corrected script stores
Rest$ImplicitMappingResponseHandling with a full
ImportMappings$ObjectMappingElement tree, mx check reports 0 errors, and
describe -> exec -> describe is byte-identical. Each of the three fixes
was reverted independently to confirm it is the cause of its test's
failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUToAkUx54bNkNjsBpufRH
@ako
ako merged commit 8ea774d into main Aug 6, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

create rest client operation: Response: mapping clause is silently discarded at write time (not just missing from read-back)

2 participants