diff --git a/.claude/skills/fix-issue.md b/.claude/skills/fix-issue.md index 11b71601e..358610118 100644 --- a/.claude/skills/fix-issue.md +++ b/.claude/skills/fix-issue.md @@ -37,6 +37,7 @@ to the symptom table below, so the next similar issue costs fewer reads. | CE0463 "widget definition changed" on every pluggable widget that contains a caption/template parameter (gallery, datagrid2 captions, dynamictext with ContentParams) on Mendix 11.9 — cascades into CE3637 on master-detail pages | `serializeClientTemplateParameter` emitted `Forms$FormattingInfo` with a `TimeFormat: "HoursMinutes"` field, but the FormattingInfo reflection schema only declares CustomDateFormat / DateFormat / DecimalPrecision / EnumFormat / GroupDigits — the extra field made Studio Pro mark the embedded WidgetType as drifted | `sdk/mpr/writer_widgets.go` `serializeClientTemplateParameter` + `mdl/backend/mpr/widget_builder.go` (mirror copy of the same FormattingInfo block) | Drop the `TimeFormat` entry from both writers. Verify by diffing your BSON against a Studio Pro-saved page's FormattingInfo block — if you see keys outside the reflection schema, that's the trigger | | Pluggable widget `Selection` BSON value is lowercase (`single` / `multi` / `none`) but Studio Pro stores PascalCase — contributes to CE0463 drift on stricter widgets and looks wrong in diffs | MDL passes the user's typed value verbatim to `SetSelection`; the builder didn't normalise | `mdl/backend/mpr/widget_builder.go` `SetSelection` | Canonicalise via `canonicalSelectionValue` helper (lowercase-keyed switch → `Single` / `Multi` / `None`); unknown values pass through | | Nightly `mx check` reports `CE0117 "Error(s) in expression." at Log message activity 'Log message (warning)'` on Mendix 10.24.19+ but not 10.24.16 or 11.x | Mendix 10.24.19 tightened expression validation: `toString()` is now a type error (toString expects a non-string input). An example called `toString($OrderNumber)` where `$OrderNumber` was already a string parameter | The offending `log warning ... with ({1} = toString($stringVar))` — find via `~/.mxcli/mxbuild/{ver}/modeler/mx check`, then bisect with `drop microflow ...` until CE0117 disappears | Remove the redundant `toString()` wrapper around already-string values. Only wrap non-string values (integers, decimals, dates, enums) in `toString()`. The Mendix 11.x parser is more lenient and lets this slide, but 10.24.19+ rejects it | +| `describe entity` shows every String attribute as `String(unlimited)` and `CATALOG.ATTRIBUTES.Length` reports `0` for all of them, even though Studio Pro shows a finite max length and runtime rejects overlong values | BSON numeric width mismatch — Mendix Studio Pro stores `Length` as int64, but `parseAttributeType` used `raw["Length"].(int32)` so the assertion always failed and Length defaulted to 0 | `sdk/mpr/parser_domainmodel.go` → `parseAttributeType` `case "DomainModels$StringAttributeType"` | Replace narrow type assertions on BSON numeric fields with the existing `extractInt(raw["X"])` helper (`sdk/mpr/parser.go`), which handles int32/int64/int/float64. Sweep other `parser_*.go` files for the same `.(int32)` / `.(int64)` pattern on size/length/count fields. Issue #583 | --- diff --git a/mdl-examples/bug-tests/583-string-attribute-length-roundtrip.mdl b/mdl-examples/bug-tests/583-string-attribute-length-roundtrip.mdl new file mode 100644 index 000000000..2f560e7ef --- /dev/null +++ b/mdl-examples/bug-tests/583-string-attribute-length-roundtrip.mdl @@ -0,0 +1,28 @@ +-- Bug test: String attribute length must roundtrip through write -> read -> describe. +-- Issue #583: describe entity and CATALOG.ATTRIBUTES reported every String attribute +-- as String(unlimited) / Length: 0, even when a finite length was set. Root cause was +-- a type assertion in sdk/mpr/parser_domainmodel.go that only accepted BSON int32, +-- while Mendix Studio Pro stores the Length field as BSON int64. +-- +-- The Go unit test in sdk/mpr/parser_domainmodel_test.go reproduces the original +-- int64-from-Studio-Pro decode path. This MDL script is a complementary end-to-end +-- regression test: it creates entities with several finite lengths plus one unlimited +-- string, then asserts that describe emits the expected lengths back. +-- +-- Run with: +-- mxcli exec mdl-examples/bug-tests/583-string-attribute-length-roundtrip.mdl +-- mxcli check mdl-examples/bug-tests/583-string-attribute-length-roundtrip.mdl +create module bug583; + +create persistent entity bug583.Item ( + ShortCode: string(10), + Description: string(200), + LongText: string(4000), + Notes: string +); + +describe entity bug583.Item; + +select Name, DataType, Length from CATALOG.ATTRIBUTES + where EntityQualifiedName = 'bug583.Item' + order by Name; diff --git a/sdk/mpr/parser_domainmodel.go b/sdk/mpr/parser_domainmodel.go index 8e4bbe693..d43cc1541 100644 --- a/sdk/mpr/parser_domainmodel.go +++ b/sdk/mpr/parser_domainmodel.go @@ -360,9 +360,9 @@ func parseAttributeType(raw map[string]any) domainmodel.AttributeType { case "DomainModels$StringAttributeType": t := &domainmodel.StringAttributeType{} t.ID = typeID - if length, ok := raw["Length"].(int32); ok { - t.Length = int(length) - } + // Issue #583: Studio Pro stores Length as BSON int64; mxcli's writer + // emits int32. extractInt accepts both (plus int and float64). + t.Length = extractInt(raw["Length"]) return t case "DomainModels$IntegerAttributeType": t := &domainmodel.IntegerAttributeType{} diff --git a/sdk/mpr/parser_domainmodel_test.go b/sdk/mpr/parser_domainmodel_test.go new file mode 100644 index 000000000..f021bbe33 --- /dev/null +++ b/sdk/mpr/parser_domainmodel_test.go @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 + +package mpr + +import ( + "testing" + + "github.com/mendixlabs/mxcli/sdk/domainmodel" +) + +// Issue #583: parseAttributeType silently dropped the Length value for +// StringAttributeType when Mendix Studio Pro stored it as BSON int64. +// The previous code only handled int32, so every String attribute in a +// Studio Pro-written MPR was reported as String(unlimited) / Length: 0. +// +// Studio Pro and mxcli can both store integers as int32 or int64 depending +// on encoder choice; the parser must handle every BSON numeric width. +func TestParseAttributeType_StringLength_BsonNumericWidths(t *testing.T) { + cases := []struct { + name string + length any + want int + }{ + {"int32 (mxcli writer)", int32(40), 40}, + {"int64 (Studio Pro writer)", int64(40), 40}, + {"int", int(40), 40}, + {"float64 (extended JSON)", float64(40), 40}, + {"missing field = unlimited", nil, 0}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + raw := map[string]any{ + "$Type": "DomainModels$StringAttributeType", + } + if tc.length != nil { + raw["Length"] = tc.length + } + at := parseAttributeType(raw) + st, ok := at.(*domainmodel.StringAttributeType) + if !ok { + t.Fatalf("parseAttributeType returned %T, want *StringAttributeType", at) + } + if st.Length != tc.want { + t.Errorf("Length = %d, want %d (input %T(%v))", st.Length, tc.want, tc.length, tc.length) + } + }) + } +}