Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/skills/fix-issue/findings/mdl-executor.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -713,3 +713,4 @@
{"area": "mdl/executor", "date": "2026-09-25", "symptom": "`textbox t (Attribute: FullName)` at the top of a page (CREATE PAGE/SNIPPET, a plain container, or ALTER PAGE … INSERT at page level) passed plain `mxcli check`, `exec --no-check`/ALTER reported success, and `bson dump` showed `AttributeRef: null` — mxbuild 11.13.0: CE0544 \"This widget can only function inside a data context\" + CE7005 (textbox/textarea/datepicker/checkbox/radiobuttons/dropdown), CE0402 (dynamictext Attribute:), CE0642 (combobox). Qualified `Mod.Ent.Attr` there is stored and fails CE0544/CE2421/CE1365/CE7247 \"Move this widget into a data container\" + CE7006. `Attribute: $P/Attr` / `$currentObject/Attr` dropped even INSIDE a data view.", "cause": "resolveAttributePath returns the bare name when entityContext is \"\", and attributeRefToGen (and widgetobj setAttributeRefField) write nil for any path with < 2 dots, so the binding vanished between builder and writer; refuseBareAttributeRefs never sees it because no Attribute string is emitted. The only refusal (validatePageContextTree) runs in the --references phase for CREATE PAGE/SNIPPET, so plain check, --no-check and ALTER were unguarded. `$x/Attr` parses via the generic property rule as an *ast.DataSourceV3, so GetAttribute() returns \"\" and every builder skipped it.", "file": "mdl/executor/cmd_pages_input_binding_context.go (inputBindingProblem, checkInputBinding, validateInputBindingContext = MDL-WIDGET34), wired in cmd_pages_builder_v3_widgets.go (6 input builders + buildDynamicTextV3), widget_engine.go (primary Attribute mapping), validate_widgets.go (validateWidgetTreeIn); tests cmd_pages_input_binding_context_test.go; bug-tests input-binding-without-context{,.fail}.mdl", "insight": "Reuse the MDL-PAGEARG01 three-state context (pageArgContext known/present) rather than entityContext==\"\" as the 'outside a data container' signal: entityContext is also empty INSIDE a container whose flow cannot be resolved (excluded ShareFeedback_Logo), where DESCRIBE writes qualified names that must keep building — refusing qualified-on-empty-entity would have broken that round trip. So known-absent context refuses bare AND qualified; unknown context (ALTER) refuses only the bare name the writer provably nulls. Two existing unit tests (OnChangeSurvivesBuilder, DynamicTextV3_AttributeBinds) built inputs with NO entity and passed — the second asserted a bare `Title` AttributeRef counted as 'bound', i.e. it pinned the bug: when a fixture has no entity context, ask what the writer does with its output. The `$P/Attr` drop was found only by dumping the control page, not from the report — print the AST value type with a probe test before assuming a spelling reaches the builder. Evidence: 22 mxbuild errors before on the probe matrix; after, every case refused with nothing written, controls (dataview/listview/gallery/datagrid/snippet dataview/ALTER into dataview) 0 errors, 17/17 stock pages + 4/4 snippets describe→exec round trip.", "refs": ["MDL-WIDGET34"], "ce": ["CE0544", "CE7005", "CE0402", "CE0642", "CE2421", "CE1365", "CE7247", "CE7006"]}
{"area":"mdl/executor","date":"2026-09-25","symptom":"`alter page FeedbackModule.ShareFeedback_Logo { insert after textBox1 { image zzImg (ImageType: imageUrl, ImageUrl: '{1}', ImageUrlParams: [{1} = ImageB64]) } }` passed `check --references`; exec wrote a bare AttributeRef and `mx check` could not LOAD the project (ArgumentNullException setting 'Attribute'). Same at page top level outside any data container; a text box's `Attribute:` there is silently dropped (CE7005).","cause":"ALTER's entity context comes from the STORED document (nearest enclosing data source, or a flow source's return type via resolveDataSourceFlowEntity). With the flow missing (Feedback v4.0.2 ships no DS_FeedbackForm) or no container at all, entityContext is \"\" and resolveAttributePath returns the bare name. CREATE PAGE refused this at check time (relaxExcludedWidgetRefs/unscopedBindings, #678); ALTER's check never opened the document, so nothing could know the scope.","file":"mdl/executor/validate_alter_unscoped.go, mdl/executor/cmd_alter_page.go (alterEntityContext), mdl/executor/validate.go (bindingsWithoutScope)","insight":"For ALTER, scope is a property of the stored document, not the statement: a check-time question about it must open the document (OpenPageForMutation, never Save; validate_alter_set.go already does this) and ask through the SAME function exec uses, so the INSERT/REPLACE entity resolution was lifted into alterEntityContext rather than restated, and the binding walk lifted out of unscopedBindings (bindingsWithoutScope) rather than copied. Controls that keep it from blocking working scripts: skip a target the stored doc lacks (added earlier in the script), a flow the script declares with an entity return (sc.flowParams), DataGrid2 column and list-view-template paths, and documents the script creates. Reproduce with a Studio Pro-authored page whose flow is genuinely absent.","refs":["#678","#685"]}
{"area": "mdl/executor", "date": "2026-09-25", "symptom": "`describe page` on a File Uploader (files mode) emits `DataSource: association …`, and exec of that output fails: \"widget `upFiles` (fileuploader) exposes 2 datasources, so a generic `datasource:` clause is ambiguous — name the one you mean: associatedFiles, associatedImages\"", "cause": "DESCRIBE chose generic vs named by counting CONFIGURED datasources (namedCustomWidgetDataSources drops unset ones, so files mode = 1), while the builder's refuseAmbiguousGenericDataSource counts DECLARED datasource mappings (a generated def maps every top-level datasource = 2). Two sides of one round trip deciding the same question from different evidence.", "file": "mdl/executor/cmd_pages_describe_parse.go", "insight": "When describe and build each decide 'is this ambiguous?', they must count the same set. Fix read the DECLARED count from the stored schema (PropertyTypes with ValueType.Type=DataSource, excluding IsLinked) and excluded widgets with an embedded .def.json — those are hand-written and pick one datasource mapping per mode, so a database-mode ComboBox (two declared) must keep the generic clause. The #956 bug-test script itself authored the refused generic clause on a File Uploader: a bug-test that only runs `mxcli check` without a project can't see an exec-time refusal, so grep bug-tests for the old spelling whenever a builder starts refusing one.", "refs": ["mendixlabs/mxcli#1199", "mendixlabs/mxcli#956", "mendixlabs/mxcli#1109"], "rules": []}
{"area": "mdl/executor", "date": "2026-09-26", "symptom": "`create published rest service` with `GET 'status' microflow M.GetStatus` where the microflow takes `$orderNumber: String` passed `check`, exec said created, and mx check failed with CE0350 (parameter not an operation parameter); a `{id}` path bound to `$id: Integer` failed CE6539; an object or FileDocument parameter failed CE0350. A generated app parsed `HttpRequest/Uri` by hand to get a query parameter.", "cause": "publishedRestOperationToGen built Rest$RestOperationParameter only from the path's {name} placeholders, always as DataTypes$StringType, and never read the operation's microflow. Studio Pro derives every operation parameter from the microflow: path name -> Path, object/list -> Body, System.HttpRequest/HttpResponse -> none, anything else -> Query.", "file": "mdl/executor/cmd_published_rest.go", "insight": "When MDL has no syntax for part of a document, check whether Studio Pro derives that part from something MDL does write (here: the microflow's parameters). Deriving it at write time needs no new syntax and keeps describe -> exec a fixed point. mx check on a one-operation repro names the gap in seconds; `mxcli check` cannot see it without the microflow.", "refs": ["mendixlabs/mxcli#1206"]}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Fixed

- **A published REST operation now gets the parameters Studio Pro gives it** (mendixlabs/mxcli#1206) — `create published rest service` wrote only the path's `{name}` placeholders, each as a String, so any other microflow parameter failed `mx check` with CE0350 and an `{id}` bound to an Integer failed CE6539. MDL has no syntax for operation parameters, so a query parameter or an upload could not be built at all; a generated app parsed `HttpRequest/Uri` by hand instead. `create` and `alter` now derive them from the microflow as Studio Pro does: a parameter named in the path is a path parameter with the microflow parameter's type, an object or a list is the body, `System.HttpRequest` / `HttpResponse` are left out, and anything else is a query parameter. Measured on Mendix 11.12.1: the repro in `mdl-examples/bug-tests/published-rest-operation-parameters.mdl` went from 4 errors to 0, `GET …/status?orderNumber=ORD%2F2026%2F0012&count=3` answers `ORD/2026/0012:3` at runtime, and re-executing `describe` output leaves the service unchanged. A service written before its microflow still gets only its path parameters, and `exec` now says so.
- **Keyword operators were fused in some stored expressions** — a page action's microflow argument `Flag: $a and $b` was stored as `$aand$b`, `if $x then 'a' else 'b'` as `if$xthen'a'else'b'`; the same in `contentparams` values, `send rest request … with (…)` parameters and a dynamic `execute database query`. Those four places took the expression's text without its whitespace; literals, `+` and `$currentObject` were unaffected, which is why it went unnoticed, and `check --references` passed. Measured by decoding the stored units on a Mendix 11.14.0 project. The expression is now stored as written, with MDL comments removed, as the microflow expression sites already did.
- **An input bound to an attribute outside any data container was written with no binding** — `textbox t (Attribute: FullName)` at the top of a page (or of a snippet, or inside a plain container) has no entity to qualify the name with, and the writer stored it as `AttributeRef: null`. Plain `check` passed, `exec --no-check` and `alter page … insert` at page level said success, and mxbuild 11.13.0 failed the page with CE0544 "This widget can only function inside a data context" + CE7005 (text box, text area, date picker, check box, radio buttons, drop-down), CE0402 (dynamic text `Attribute:`) or CE0642 (combo box). A qualified attribute there is stored and fails the same way (CE0544 / CE2421 / CE1365 / CE7247 "Move this widget into a data container"). `Attribute: $P/Name` and `Attribute: $currentObject/Name` never parsed as an attribute path and were dropped even inside a data view. `check` now reports all three as **MDL-WIDGET34** (no project needed), and the page builder refuses them with the widget named, so nothing is written; `alter page` refuses a bare name it has no entity for. Place the widget in a data view, list view, gallery or data grid and bind the attribute by name.
- **An expression property written in brackets was silently dropped** (mendixlabs/mxcli#750) — `dynamicclasses: [ if $currentObject/Featured then 'a' else 'b' ]`, the spelling #750 proposes, parsed as a list that no writer reads: `check` was clean, `exec` said `Created page`, and the widget was stored with no dynamic class. `alter page … set DynamicClasses = [ … ]` said `Altered page` and changed nothing, and a column's `DynamicCellClass` stored the list's text — tokens fused, `[if$x/Ythen'a'else'b']` — as its expression. Measured on a copy of a Mendix 11.14.0 project with the pre-fix binary. `mxcli check` now reports **MDL-WIDGET32** for `DynamicClasses` and `DynamicCellClass` written as a list (no project needed), and ALTER refuses it, so `check -p` reports that too. Write the expression quoted.
Expand Down
3 changes: 2 additions & 1 deletion cmd/mxcli/syntax/features_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,10 @@ func init() {
Keywords: []string{
"create published rest", "publish rest", "rest resource",
"rest operation", "microflow", "path parameter",
"query parameter", "body parameter", "operation parameter",
"grant access", "revoke access",
},
Syntax: "CREATE [OR MODIFY] PUBLISHED REST SERVICE Module.Name (\n Path: 'rest/api/v1',\n Version: '1.0.0',\n ServiceName: 'My API'\n)\n{\n RESOURCE 'name' {\n GET '' MICROFLOW Module.GetAll;\n GET '{id}' MICROFLOW Module.GetById;\n POST '' MICROFLOW Module.Create;\n }\n};\n\nALTER PUBLISHED REST SERVICE Module.Name SET Version = '2.0.0';\nALTER PUBLISHED REST SERVICE Module.Name ADD RESOURCE 'items' { ... };\nALTER PUBLISHED REST SERVICE Module.Name DROP RESOURCE 'legacy';\nDROP PUBLISHED REST SERVICE Module.Name;",
Syntax: "CREATE [OR MODIFY] PUBLISHED REST SERVICE Module.Name (\n Path: 'rest/api/v1',\n Version: '1.0.0',\n ServiceName: 'My API'\n)\n{\n RESOURCE 'name' {\n GET '' MICROFLOW Module.GetAll;\n GET '{id}' MICROFLOW Module.GetById;\n POST '' MICROFLOW Module.Create;\n }\n};\n\n-- Operation parameters come from the microflow's parameters, as in Studio Pro:\n-- a parameter named in the path ({id}) is a path parameter, an object or a list\n-- is the body, System.HttpRequest / HttpResponse are the request and response,\n-- and any other parameter is a query parameter: $orderNumber: String behind\n-- GET 'status' answers /status?orderNumber=... Create the microflow first.\n\nALTER PUBLISHED REST SERVICE Module.Name SET Version = '2.0.0';\nALTER PUBLISHED REST SERVICE Module.Name ADD RESOURCE 'items' { ... };\nALTER PUBLISHED REST SERVICE Module.Name DROP RESOURCE 'legacy';\nDROP PUBLISHED REST SERVICE Module.Name;",
Example: "CREATE PUBLISHED REST SERVICE Module.OrderAPI (\n Path: 'rest/orders/v1',\n Version: '1.0.0',\n ServiceName: 'Order API'\n)\n{\n RESOURCE 'orders' {\n GET '' MICROFLOW Module.GetAllOrders;\n GET '{id}' MICROFLOW Module.GetOrderById;\n POST '' MICROFLOW Module.CreateOrder;\n DELETE '{id}' MICROFLOW Module.DeleteOrder;\n }\n};\n\nGRANT ACCESS ON PUBLISHED REST SERVICE Module.OrderAPI\n TO Module.User, Module.Admin;",
SeeAlso: []string{"rest", "rest.consumed"},
})
Expand Down
17 changes: 16 additions & 1 deletion docs-site/src/examples/rest-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,22 @@ CREATE PUBLISHED REST SERVICE Module.OrderAPI (
};
```

**Operation paths:** Use empty string `''` for the root, `'{paramName}'` for path parameters. Do NOT start or end with `/`. Path parameters must match a microflow parameter name exactly (case-sensitive) — e.g., `'{id}'` requires the microflow to declare `$id: String`.
**Operation paths:** Use empty string `''` for the root, `'{paramName}'` for path parameters. Do NOT start or end with `/`. Path parameters must match a microflow parameter name exactly (case-sensitive) — e.g., `'{id}'` requires the microflow to declare `$id`, of any primitive type.

**Operation parameters** come from the microflow's parameters, the way Studio Pro derives them: a parameter named in the path is a path parameter, an object or a list is the body, `System.HttpRequest` and `System.HttpResponse` give the microflow the request and the response, and any other parameter is a query parameter. Create the microflow before the service; a service written first gets only its path parameters, and says so.

```sql
-- GET rest/orders/v1/orders/status?orderNumber=ORD%2F2026%2F0012
create microflow Module.PRS_GetOrderStatus ($orderNumber: String)
returns String as $Status
begin
retrieve $Order from Module.Order where [OrderNumber = $orderNumber] limit 1;
declare $Status String = getCaption($Order/Status);
return $Status;
end;
/
-- in the service: GET 'status' MICROFLOW Module.PRS_GetOrderStatus;
```

### Multiple Resources

Expand Down
60 changes: 60 additions & 0 deletions mdl-examples/bug-tests/published-rest-operation-parameters.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
-- ============================================================================
-- mendixlabs/mxcli#1206 — a published REST operation gets its parameters from its microflow
-- ============================================================================
--
-- Studio Pro derives an operation's parameters from its microflow: a parameter
-- named in the path is a path parameter, an object or a list is the body,
-- System.HttpRequest / HttpResponse are the request and the response, and any
-- other parameter is a query parameter. mxcli wrote only the path placeholders,
-- each as a String, so mx check failed this service with CE0350 (orderNumber,
-- count, verbose, file) and CE6539 (the Integer id).
--
-- Verified on Mendix 11.12.1: 4 errors with v0.24.0, 0 with the fix, and
-- GET rest/orders/v1/orders/status?orderNumber=ORD%2F2026%2F0012&count=3
-- answers ORD/2026/0012:3 at runtime.
-- ============================================================================

create module BugRestParams;

create persistent entity BugRestParams.Upload extends System.FileDocument (
Label: String(100)
);

create microflow BugRestParams.GetStatus ($orderNumber: String, $count: Integer, $httpRequest: System.HttpRequest)
returns String as $Result
begin
declare $Result String = $orderNumber + ':' + toString($count);
return $Result;
end;
/

create microflow BugRestParams.GetById ($id: Integer, $verbose: Boolean)
returns String as $Result
begin
declare $Result String = toString($id);
return $Result;
end;
/

create microflow BugRestParams.PutFile ($file: BugRestParams.Upload)
returns String as $Result
begin
declare $Result String = 'ok';
return $Result;
end;
/

create published rest service BugRestParams.PRS_Orders (
Path: 'rest/orders/v1',
Version: '1.0.0',
ServiceName: 'Orders'
)
{
resource 'orders' {
GET 'status' microflow BugRestParams.GetStatus;
GET 'items/{id}' microflow BugRestParams.GetById;
POST 'upload' microflow BugRestParams.PutFile;
}
};
/

Loading
Loading