fix(mappings): import-mapping Range authoring (#881) and JSON member-name resolution (#882) - #140
Merged
Conversation
Mendix stores the import activity's Range on the ImportMappingCall in two
variants:
Microflows$ConstantRange{SingleObject} All / First
Microflows$CustomRange{LimitExpression, OffsetExpression} Custom
mxcli only ever wrote the first and read only SingleObject, so "Custom" was
not merely undescribed but unrepresentable: a bounded import became unbounded
the moment anything rewrote the activity. DESCRIBE emitted no range at all,
so all three settings round-tripped identically and describe -> edit -> exec
silently changed the activity's meaning.
Adds the trailing clause:
import from mapping M.IMM($src) [all | first | limit <e> [offset <e>]]
Omitting it keeps the pre-existing inference from the mapping's root shape,
so scripts that predate the syntax write exactly what they always did.
DESCRIBE always emits one of the three, because silence re-enters that
inference and an object-rooted mapping set to All -- Studio Pro's own default,
shipped in the blank app -- comes back as First.
The Range and the result variable's cardinality are separate axes. Folding
them writes a ListType against an object-rooted mapping, which mxbuild rejects
with CE0243; Mendix's own FeedbackModule.SUB_Feedback_PostToAppInsights pairs
ConstantRange{SingleObject:false} with an ObjectType variable. The stored
VariableType is the authority on cardinality, the range is its own flag
(RangeSingleObject, nil = follow the inference), and only `first` pins both.
The ImportMappingCall is built at three sites -- the import statement, REST
result handling, and the legacy writer -- so the range selection is one shared
helper per engine; fixing two of the three let a limit reach the model while a
ConstantRange was still written. Both engines are fixed: they share the
semantic model, and a fix in one is invisible to a user on the other.
Verified end-to-end on mxbuild 11.6.6 (mx check: 0 errors on both engines,
all three ranges re-describing verbatim). `offset` is rejected by Mendix with
CE6100 unless the mapping's root is a list, while `limit` is accepted either
way; that is documented rather than validated, because mxcli cannot currently
author a list-rooted import mapping to test the positive case against.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8
…g paths A JsonStructures$JsonElement carries TWO names, and for any lowercase-initial key they differ: Path "(Object)|uuid" the raw JSON key — what the RUNTIME resolves by ExposedName "Uuid" Mendix's derived name — what Studio Pro DISPLAYS Mendix derives the exposed name by capitalising the initial, and by suffixing "Item" on an array's item object. Both are Mendix's own: the blank app's Studio-Pro-authored FeedbackModule.JSON_AppInsightsResponse stores ExposedName "Uuid" against Path "(Object)|uuid", and its IMM_PostResponse binds JsonPath "(Object)|uuid". So the capitalisation DESCRIBE shows is faithful rendering. The defect is that mxcli's DESCRIBE emits the exposed name while its builder resolved only raw keys, so mxcli's own output did not round-trip. Re-executing a DESCRIBE fabricated a path from the exposed name: (Object)|total -> (Object)|Total (Object)|entityInstances|__Value|(Object) -> (Object)|EntityInstances|__ValueItem The array's "|(Object)" item marker vanished entirely and MaxOccurs went to 0. `mxcli check` passed; mxbuild reported CE5015. Export mappings had the identical bug. Members now resolve by raw key or exposed name — including an array addressed by its item's exposed name, which resolves to the array so the "|(Object)" step is still taken. A member matching neither is REFUSED, listing the spellings that would have worked, instead of being written with an invented path: that path passed `mxcli check` and surfaced only later, which is the worst failure mode because the tool that wrote it reported success. Separately, both engines wrote IsDefaultType on every ValueMappingElement. The generated metamodel declares it on Import/ExportObjectMappingElement and on neither value type, and Studio Pro's own mappings carry it on the object element alone. Per the overlay-writes rule in CLAUDE.md, a property the type does not own is the shape mxbuild tolerates and Studio Pro refuses to open, so a green build is not evidence — it is dropped from the value writers in both engines. Verified on mxbuild 11.6.6, both engines: the raw-key and exposed-name spellings now produce byte-identical stored paths, and `mx check` reports 0 errors where it previously reported CE5015. Each fix was reverted in turn to confirm the symptom returns. Refs upstream mendixlabs#882. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8
…ing elements
A JSON structure element's OriginalValue is the SAMPLE parsed out of the snippet
("42", "\"Widget\""). It describes the structure, not the mapping, and Studio Pro
leaves it empty on every mapping element.
Measured across the two Studio-Pro-authored mappings a blank app ships —
FeedbackModule.IMM_PostResponse and EMM_PostFeedback, ~15 value elements between
them — all write OriginalValue "", while their JSON structures carry 17 non-empty
samples. mxcli cloned the sample in, so an mxcli-written mapping differed from a
Studio-Pro-written one over the same structure by the snippet's example data,
which is the first thing a side-by-side comparison of the two shows.
Refs upstream mendixlabs#882.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8
The refusal added for mendixlabs#882 fired whenever a member failed to resolve, including when there was nothing to resolve against. `create import mapping X { ... }` with no `with json structure` clause is legal MDL, and an XML-schema or message-definition mapping resolves no JSON elements either — so every schema-less mapping was rejected: "id" is not a member of the JSON structure at (Object), which has no members there That broke eight round-trip integration tests, which unit tests did not cover because they all build against a populated index. The refusal now applies only where a schema exists to contradict the name. With no schema loaded, the authored name is taken at face value and becomes both the exposed name and the path segment — the pre-mendixlabs#882 behaviour, unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two related mapping fixes. Both were reported as one thing and turned out to be another; both are verified against mxbuild with the reported symptom reproduced and then removed.
Part 1 — mendixlabs#881: author and round-trip the import-mapping Range
Mendix stores the Range on the
ImportMappingCallin two variants:mxcli only ever wrote the first, and read only
SingleObject. So "Custom" was unrepresentable, not merely undescribed — a bounded import became unbounded the moment anything rewrote the activity — andDESCRIBEemitted no range at all, so all three settings round-tripped identically and describe → edit → exec silently changed the activity's meaning.Syntax
Omitting the clause keeps the pre-existing inference from the mapping's root shape, so scripts that predate the syntax write exactly what they always did.
DESCRIBEalways emits one of the three, never nothing: silence re-enters that inference, and an object-rooted mapping set to All — Studio Pro's own default, shipped in the blank app — comes back as First.limit/offsetmirror theretrieveclause rather than inventing a second spelling.firstis a separate word on purpose:limit 1is a list of one,firstbinds a single object, so they cannot share syntax.The trap in the fix
The Range and the result variable's cardinality are separate axes. The first pass folded them, and every
allwrote aListTypeagainst an object-rooted mapping — which mxbuild rejects with CE0243 ("the mapping used to return 'List of X' but now returns 'X'").Mendix's own
FeedbackModule.SUB_Feedback_PostToAppInsightssettles it:ConstantRange{SingleObject:false}bound to an ObjectType variable, because its mapping returns one object. So the storedVariableTypeis the authority on cardinality, the range is its own flag (RangeSingleObject *bool, nil = follow the inference), and onlyfirstpins both.Second trap: the
ImportMappingCallis built at three sites —importXmlActionToGen, the RESTResultHandlingMappingcase, and the legacy writer. Patching two let a limit reach the model while aConstantRangewas still written. Each engine now has one shared range helper.Known limitation, documented rather than validated
Mendix rejects
offsetwith CE6100 ("This entity does not support offset") unless the mapping's root is a list;limitalone is accepted either way. I could not add a checker, because mxcli cannot currently author a list-rooted import mapping — array-root mappings emit CE5015, a separate defect — so there is no positive case to test a rule against. The constraint is documented in the skill, the quick reference, themicroflow.mappingsyntax topic, and the example file instead.Part 2 — mendixlabs#882: resolve JSON members by either name, stop inventing paths
The reported cause is Mendix's own convention
A
JsonStructures$JsonElementcarries two names, and for any lowercase-initial key they differ:Both are Mendix's. A blank app's Studio-Pro-authored
FeedbackModule.JSON_AppInsightsResponsestoresExposedName "Uuid"againstPath "(Object)|uuid", and itsIMM_PostResponsebindsJsonPath "(Object)|uuid". So the capitalisationDESCRIBEshows (total→Total,__Value→__ValueItem) is faithful rendering, not corruption. Running the reporter's repro as written confirms it: the stored paths come out byte-identical to the structure's.The actual defect
DESCRIBEemits the exposed name; the builder resolved only raw keys. mxcli's own output therefore did not round-trip — re-executing a DESCRIBE fabricated a path:The array's
|(Object)item marker vanished entirely andMaxOccurswent to 0.mxcli checkpassed; mxbuild reported CE5015. Export mappings had the identical bug.Members now resolve by raw key or exposed name — including an array addressed by its item's exposed name, which resolves to the array so the
|(Object)step is still taken. A member matching neither is refused, listing the spellings that would have worked:An invented path passed
mxcli checkand surfaced only later — the worst failure mode, because the tool that wrote it reported success.Two divergences from what Studio Pro writes
IsDefaultTypeon value elements.generated/metamodeldeclares it onImport/ExportObjectMappingElementand on neither value type, and Studio Pro's own mappings carry it on the object element alone. Per the overlay-writes rule inCLAUDE.md, a property the type does not own is the shape mxbuild tolerates and Studio Pro refuses to open — so a green build is not evidence. Dropped from the value writers in both engines.OriginalValuecarrying the snippet's sample. It is the sample value parsed out of the JSON structure's snippet ("42","\"Widget\"") and belongs to the structure. Measured across both Studio-Pro-authored mappings a blank app ships (IMM_PostResponseandEMM_PostFeedback, ~15 value elements): all"", while their structures carry 17 non-empty samples. No longer cloned.What this does not fix
The reporter's runtime error —
key not found: Path(QName(None,),None,)atMappingCache.storeValueMappingElement— is not reproduced. Their standalone repro, executed by mxcli and run against a real Mendix runtime, imports cleanly on both 11.6.6 and 11.13.0, bracketing their 11.12.1. Theirmx checkis also clean, which rules out the fabricated-path defect above. Something their real endpoint carries and the repro does not is still unaccounted for.Verification
Against mxbuild, on both engines (
modelsdkandlegacy):mx check, all fixturesEvery fix was reverted or stubbed in turn to confirm the reported symptom returns: re-conflating the Range axes reproduces CE0243; stubbing the
$Typedispatch drops the limit; stubbing the exposed-name lookup reproduces the fabricated path; re-addingIsDefaultTypefails its test.One methodology note worth recording: an early runtime run appeared to reproduce a mapping failure, and the control — Studio Pro's own mapping in the same harness — failed identically, locating the fault in the test's missing module-role grants rather than in anything mxcli wrote. Without that control this PR would have claimed a false reproduction.
Changes
MDLMicroflow.g4(importMappingRange),MDLLexer.g4(FIRST),MDLSettings.g4(keyword list)mdl/ast/ast_microflow.go,mdl/visitor/visitor_import_export_mapping.gosdk/microflows/microflows_actions.go(RangeSingleObject,LimitExpression,OffsetExpression,RangeSingleObjectOf)cmd_microflows_builder_calls.go,cmd_microflows_format_action.gocmd_import_mappings.go(jsonSchemaIndex,resolve,memberNames),cmd_export_mappings.gomicroflow_write.go,microflow_read_actions.go,mapping_write.gosdk/mpr/writer_microflow_actions.go,parser_microflow_actions.go,writer_import_mapping.go,writer_export_mapping.goMDL_QUICK_REFERENCE.md,json-structures-and-mappings.mdskill,syntax/features_microflow.go(newmicroflow.mappingtopic),fix-issue.mdsymptom rowscmd_microflows_import_range_test.go,microflow_import_range_test.go,parser_microflow_import_range_test.go,cmd_mappings_member_resolution_test.go,mapping_isdefaulttype_test.gomdl-examples/bug-tests/881-import-mapping-range.mdl,882-mapping-member-names.mdlOne drive-by in the legacy parser: it read
VariableTypeoff theImportMappingCallrather than theResultHandling, so the result entity came back empty for everything mxcli or Studio Pro writes.