fix: roundtrip REST mapping cardinality via as list of syntax#519
fix: roundtrip REST mapping cardinality via as list of syntax#519hjotha wants to merge 6 commits intomendixlabs:mainfrom
as list of syntax#519Conversation
AI Code ReviewWhat Looks GoodThe PR presents a well-focused bug fix that correctly addresses the issue of incorrect Key strengths:
The fix resolves the reported RecommendationApprove - This is a clean, focused bug fix with appropriate test coverage that resolves the issue without introducing regressions or violating any project guidelines. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
ReviewThe core fix is sound: falling back to ModerateM1 — Array fallback in
// sdk/mpr/parser_import_mapping.go
switch typeName {
case "ImportMappings$ObjectMappingElement": // → Kind: "Object"
return parseImportObjectMappingElement(raw)
case "ImportMappings$ValueMappingElement": // → Kind: "Value"
return parseImportValueMappingElement(raw)
default:
return nil // any Array element type falls here and is dropped
}
Fix options (pick one):
M2 — No test for Two new tests cover the REST call path but none exercise Minorm3 — Nil guard inconsistency The new fallback guards if len(im.Elements) > 0 && im.Elements[0].Entity != "" { // no nil guardLow risk in production (parser never appends nil), but inconsistent with the guard added two lines above. VerdictApprove after M1 and M2. M1 in particular could mislead a future contributor into believing array-rooted message-definition mappings work end-to-end when the parser silently drops them. |
AI Code ReviewWhat Looks Good:
Recommendation: Approve The PR is focused, correctly implements the fallback logic described in the issue, includes appropriate tests, and maintains backward compatibility. The fix addresses the root cause (missing JsonStructure for schema-backed mappings) while preserving existing behavior for JSON-backed mappings. All checklist items are satisfied for this type of bug fix. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
|
Thanks @ako — addressed in M1 — dead Array branch removed. You're right: M2 —
m3 — guard parity. Added the If we ever discover an |
Review (updated)All three items from the first round have been addressed or superseded. Two things left. ModerateM1 —
func extractInt(v any) int {
if v == nil { return 0 }
switch val := v.(type) {
case int32: return int(val)
case int64: return int(val)
case int: return val
case float64: return int(val)
}
return 0
}The new Minorm2 — The test comment says "an Array root on the mapping element must yield a list-typed result handling" but exercises m3 — Nil guard inconsistency (pre-existing, now more visible) What's improved vs round 1
Approve after the |
…bsent addRestCallAction and addImportFromMappingAction decide whether the mapping result is a single object or a list by looking up the JSON structure the import mapping references. For mappings backed by an XML schema or message definition the mapping has no JsonStructure, so the JSON-structure lookup short-circuits and SingleObject defaults to false (REST) or stays true (import-from-mapping). When the authored mapping is rooted on an Object, the resulting BSON ResultHandling type (ListType vs ObjectType) and Range.SingleObject flag mismatch the microflow's ObjectType return, surfacing as CE0117 "Error in expression" at the End event (and CE0019/CE0136/CE0243 cascades on any downstream retrieve over the misclassified variable). Fall back to the import mapping's own root element kind when JsonStructure is empty: ImportMappingElement.Kind is "Object", "Array", or "Value" — Studio Pro authors it the same way for both JSON-backed and schema/message-definition mappings, so a single source of truth at the mapping's first element correctly recovers the shape for both code paths. Two regression tests cover the new fallback (Object → SingleObject=true, Array → SingleObject=false) using synthetic Module.Mapping names; the existing JSON-structure-driven test still asserts the prior path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…llback When the import mapping is backed by an XML schema or message definition (no JsonStructure), the previous fallback infered single-vs- list purely from `im.Elements[0].Kind`. Studio Pro models a repeating Object element — `MaxOccurs > 1` or unbounded (`-1`) — as a list of that entity, distinct from a singleton. Treating the root as a single object for those mappings produces a non-list result variable, so any downstream aggregate / loop / list-operation activity that consumes it fails `mx check` with CE0013 "Input variable must be of type 'List'" or CE0100 "is of type X, but should be of type List". Augment the fallback to consult MaxOccurs alongside Kind: an Object root with MaxOccurs > 1 or -1 yields a list; a non-repeating Object root keeps the singleton behaviour, matching Studio Pro's authored BSON. The same MaxOccurs check is added to the symmetric addImportFromMappingAction path. Two new regression tests cover the unbounded (-1) and bounded (>1) repeat cases; the existing singleton test now sets `MaxOccurs: 1, MinOccurs: 1` to pin the unchanged path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The MPR reader was dropping the MinOccurs/MaxOccurs fields when parsing ImportMappings$ObjectMappingElement and ImportMappings$ValueMappingElement, returning 0 even when the BSON has the authored values. The downstream fallback that infers single-vs-list from the import mapping's root element kind cannot use MaxOccurs to detect repeating Object roots if the field is silently dropped. Mendix authors these fields as int64 (the existing JsonElement parser in parser_misc.go handles int32 only because JSON structure BSON uses int32 — Import-mapping BSON differs). Add a small bsonInt helper that accepts both int32 and int64 and use it to read MinOccurs / MaxOccurs on object and value mapping elements. With this in place, the single-vs-list fallback in addRestCallAction correctly classifies an Object root with MaxOccurs=-1 as a list, eliminating the resulting mx check CE0013 / CE0100 cascade. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tests
Address review feedback on the import-mapping single-vs-list fallback.
M1 — drop the `Kind == "Array"` branch from both fallback sites.
parseImportMappingElement only ever produces `Kind: "Object"` or
`Kind: "Value"` (any other `$Type` falls through to `default: return
nil` and the nil element is never appended). The Array branch was
unreachable from real MPR data, and the matching test asserted a state
the parser cannot produce. Repetition for the list-vs-singleton call
now comes purely from MaxOccurs ( -1 or > 1 ).
m3 — add the missing `im.Elements[0] != nil` guard before the
pre-existing entity lookup, matching the guard added two lines above.
M2 — two new tests for `addImportFromMappingAction` cover the
message-definition / XML-schema fallback path:
- `TestAddImportFromMappingFallsBackToImportMappingRootForListResult`
pins the Object root with MaxOccurs=-1 → list-typed result
- `TestAddImportFromMappingFallsBackToImportMappingRootForSingleObject`
pins the Object root with MaxOccurs=1 → singleton
The dead `TestAddRestCallAction_MappingFallsBackToArrayKindWhenJsonStructureMissing`
test is removed alongside the Array branch it exercised.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Studio Pro stores REST-call import-mapping cardinality (single object vs list) on the microflow's ImportMappingCall in BSON (Range.SingleObject + ForceSingleOccurrence), independent of the underlying import mapping. The same mapping element with MaxOccurs=-1 yields a single-Object result for one call site (PCD's REST_GetEnvironmentByUUID — ForceSingleOccurrence=true, Range.SingleObject=false) and a list result for another (MendixSSO's RetrieveUserRoles — both false). The MDL `returns mapping ... as Module.Entity` form was lossy: the describer dropped the call-site cardinality, and the builder was forced to guess from the import mapping shape alone. The MaxOccurs-based fallback added in 86ef6fc (now reverted) classified PCD's call as a list and tripped CE0117 at the End event, since the microflow returns a single Object. Add explicit MDL syntax to express the choice: returns mapping Module.IMM as Module.Entity // single object returns mapping Module.IMM as list of Module.Entity // list result Pipeline: - Grammar (MDLMicroflow.g4): new `RETURNS MAPPING qn AS LIST_OF qn` alternative ahead of the bare `AS qn` form. - AST (RestResult.IsList): records the cardinality. - Visitor: sets IsList when LIST_OF() is present in the parse tree. - Describer (formatRestCallAction): emits `as list of` when rh.SingleObject is false, `as` otherwise. - Builder (addRestCallAction): trusts s.Result.IsList to set both SingleObject and ForceSingleOccurrence (mirrored), so the writer reproduces the BSON layout Studio Pro emits. Tests: - Replace the MaxOccurs-based builder tests with explicit IsList assertions covering both `as Entity` (single, FSO=true) and `as list of Entity` (list, FSO=false). - Add format-side tests pinning that SingleObject=true emits `as` and SingleObject=false emits `as list of`. Verified against the Control Centre roundtrip audit on the seven microflows that exercise the touched paths (DataDogIntegration.CreateAppMetric, DataLake.SendMetaDataEventsInBatches, MendixSSO.RetrieveUserRoles, MxKafka.IVK_PublishMessage, PrivateCloudData.REST_GetEnvironmentByUUID, TokenUtilization.GetBearerTokenFromRequest, plus the pre-existing SBOMData cosmetic whitespace mismatch): six match with zero mx check errors; PCD now matches where it previously failed CE0117. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
c393dde to
c4febc1
Compare
as list of syntax
Address review M1 from PR mendixlabs#519: bsonInt in parser_import_mapping.go duplicates extractInt from parser.go in the same package. extractInt already handles int32, int64, int, and float64 (the BSON numeric shapes Mendix produces) and includes the nil guard bsonInt was missing. Replace both call sites and delete the helper. No behavior change — extractInt is a strict superset of bsonInt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks @ako — addressed in M1 — You're right, m2 — unreachable Array-kind test Already removed in the previous fixup m3 — nil guard inconsistency Fixed in The PR now also adds new MDL syntax |
Summary
Restore correct cardinality (single object vs list) for REST-call import mappings, especially those backed by XML schemas or message definitions. The fix has three layers: (1) make the JSON-structure lookup work through folder hierarchies, (2) fix the parser so
MinOccurs/MaxOccurssurvive the BSON read, and (3) propagate the call-site cardinality through MDL using newas list of Module.Entitysyntax instead of guessing from the import mapping shape.Why the call site needs explicit syntax
Studio Pro stores the cardinality on the microflow's
ImportMappingCall, not on the import mapping itself:Range.SingleObjectForceSingleOccurrencePrivateCloudData.REST_GetEnvironmentByUUIDfalsetrueMendixSSO.RetrieveUserRolesfalsefalseBoth bind the same shape of mapping element (
Kind: "Object",MaxOccurs: -1). The user's choice is at the call site. Earlier iterations of this PR tried to derive cardinality fromMaxOccursalone (-1⇒ list); that classified PCD as a list and producedCE0117 "Error(s) in expression."at the End event, where the microflow returns a singlePrivateCloudData.Environment.Fixes
GetJsonStructureByQualifiedNamematched the storedContainerIDdirectly against the module ID, so structures inside module subfolders (Module/Sub/Json) were reported as not found. Walk the container parent chain viabuildContainerModuleNameMap, the same helperGetImportMappingByQualifiedNamealready uses.JsonStructureis empty (XML schema / message definition), use the mapping's own root element kind as the cardinality hint when MDL omits the explicitas list ofform.MinOccurs/MaxOccurs— the MPR reader silently dropped these onImportMappings$ObjectMappingElement/ImportMappings$ValueMappingElementbecause Mendix authors them asint64while the existing JsonElement parser only handlesint32. Add a smallbsonInthelper that accepts both.as list of Module.Entitysyntax — new MDL form to mark a list result. Pipeline:MDLMicroflow.g4): addsRETURNS MAPPING qn AS LIST_OF qnahead of the bareAS qnform.RestResult.IsList): records the cardinality.IsListwhenLIST_OF()is present in the parse tree.formatRestCallAction): emitsas list of Module.Entitywhenrh.SingleObjectis false;as Module.Entityotherwise.addRestCallAction): trustss.Result.IsListto set bothSingleObjectandForceSingleOccurrence(mirrored), so the writer reproduces the BSON shape Studio Pro emits.MDL syntax
Reproduction
A REST call binding a single object via a list-typed mapping:
Before this PR:
mx checkreportsCE0117 "Error(s) in expression." at End eventbecause the result was forced to aListTypewhile the microflow returns a single Object. After this PR: 0 errors; the result is correctly typed and the BSON roundtrip preserves bothRange.SingleObjectandForceSingleOccurrence.Test plan
TestGetJsonStructureByQualifiedName_ResolvesThroughFolders— folder-nested JSON structure lookup.TestAddRestCallAction_MappingAsEntityProducesSingleObject—as Module.Entity(nolist of) ⇒SingleObject=true,ForceSingleOccurrence=true.TestAddRestCallAction_MappingAsListOfEntityProducesListResult—as list of Module.Entity⇒SingleObject=false,ForceSingleOccurrence=false.TestFormatRestCallAction_MappingSingleObject— describer emitsasforSingleObject=true.TestFormatRestCallAction_MappingListOf— describer emitsas list offorSingleObject=false.TestAddImportFromMappingFallsBackToImportMappingRootForListResult/…ForSingleObject— heuristic fallback forIMPORT FROM MAPPING(no MDL syntax change there).go test ./...passes.REST_GetEnvironmentByUUID, MendixSSORetrieveUserRoles, plus five others): six match with zeromx checkerrors; PCD now matches where it previously failedCE0117. The seventh (SBOMData.REST_GetSBOMByComponentId) was a pre-existing whitespace-only cosmetic mismatch unrelated to this change.🤖 Generated with Claude Code