Skip to content

Commit 2816e7d

Browse files
akokhode-mxclaude
committed
feat: add Import Mapping, Export Mapping, and SEND REST REQUEST support
Clean rework of PR #84 (call-rest branch): drops duplicate JSON Structure code already on main, removes formatting noise (~30 files), and changes mapping syntax from -> to AS for consistency with existing database client mappings and design guidelines. New features: - CREATE/DROP/SHOW/DESCRIBE IMPORT MAPPING with AS syntax - CREATE/DROP/SHOW/DESCRIBE EXPORT MAPPING with AS syntax - SEND REST REQUEST microflow activity - Lint rule mpr008_overlapping_activities - Import/export mapping examples in REST client doctype tests - Skill doc for end-to-end REST call from JSON workflow Co-Authored-By: Dennis Kho <dennis.kho@mendix.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 453d748 commit 2816e7d

42 files changed

Lines changed: 17951 additions & 11734 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/mendix/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ External system integration:
4141
|-------|---------|----------|
4242
| [database-connections.md](database-connections.md) | Mendix Database Connector | Connecting to Oracle, PostgreSQL, etc. via JDBC |
4343
| [demo-data.md](demo-data.md) | Demo data & IMPORT | Seeding data, `IMPORT FROM` bulk import from external DB |
44-
| [rest-client.md](rest-client.md) | REST API consumption | Calling external REST APIs |
44+
| [rest-client.md](rest-client.md) | REST API consumption | Calling external REST APIs via consumed REST client documents |
45+
| [rest-call-from-json.md](rest-call-from-json.md) | REST CALL end-to-end | JSON Structure → Entities → Import Mapping → REST CALL microflow |
4546
| [java-actions.md](java-actions.md) | Custom Java actions | Extending with Java code |
4647

4748
## Page Patterns
@@ -79,6 +80,8 @@ Load skills based on the task:
7980
| "Process list of items" | `patterns-data-processing.md` |
8081
| "Fix MDL error" | `cheatsheet-errors.md` |
8182
| "Import data from database" | `demo-data.md` |
83+
| "Call a REST API / integrate JSON endpoint" | `rest-call-from-json.md` |
84+
| "Create JSON structure / import mapping" | `rest-call-from-json.md` |
8285
| "Seed/populate test data" | `demo-data.md` |
8386
| "Update widget properties" | `bulk-widget-updates.md` |
8487
| "Change widgets in bulk" | `bulk-widget-updates.md` |
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
# REST Call from JSON Payload — End-to-End Skill
2+
3+
Use this skill to generate the full stack of Mendix integration artifacts from a JSON payload:
4+
JSON Structure → Non-persistent entities → Import Mapping → REST CALL microflow.
5+
6+
## Overview — Four Steps
7+
8+
1. **CREATE JSON STRUCTURE** — store the raw payload and derive the element tree
9+
2. **CREATE ENTITY** (non-persistent) — one per JSON object type, with attributes per JSON field
10+
3. **CREATE IMPORT MAPPING** — link JSON structure elements to entities and attributes
11+
4. **CREATE MICROFLOW** — inline REST CALL that invokes the import mapping
12+
13+
---
14+
15+
## Step 1 — JSON Structure
16+
17+
```sql
18+
CREATE JSON STRUCTURE Module.JSON_MyStructure
19+
SNIPPET '{"key": "value", "count": 1}';
20+
```
21+
22+
- The executor **formats** the snippet (pretty-print) then **refreshes** (derives element tree) automatically.
23+
- The snippet must be valid JSON; use single quotes around it in MDL.
24+
- Escape single quotes inside the snippet by doubling them: `''`.
25+
- The derived element tree must stay consistent with the snippet — the executor sorts JSON object keys alphabetically to match `json.MarshalIndent` output.
26+
27+
**Verify** after creation:
28+
```sql
29+
DESCRIBE JSON STRUCTURE Module.JSON_MyStructure;
30+
-- Should show: element tree under "-- Element tree:" comment
31+
```
32+
33+
---
34+
35+
## Step 2 — Non-Persistent Entities
36+
37+
Derive one entity per JSON object type. Name them after what they represent (not after JSON keys).
38+
39+
```sql
40+
CREATE ENTITY Module.MyRootObject (NON_PERSISTENT)
41+
stringField : String
42+
intField : Integer
43+
decimalField : Decimal
44+
boolField : Boolean DEFAULT false;
45+
46+
CREATE ENTITY Module.MyNestedObject (NON_PERSISTENT)
47+
name : String
48+
code : String;
49+
50+
CREATE ASSOCIATION Module.MyRootObject_MyNestedObject
51+
FROM Module.MyRootObject
52+
TO Module.MyNestedObject;
53+
```
54+
55+
**Rules:**
56+
- All string fields: bare `String` (no length — unlimited)
57+
- All number fields: `Integer`, `Decimal`, or `Long` — remove defaults for optional fields
58+
- Boolean fields **require** `DEFAULT true|false`
59+
- `NON_PERSISTENT` — these entities are not stored in the database
60+
- One association per parent→child relationship; name it `Parent_Child`
61+
62+
---
63+
64+
## Step 3 — Import Mapping
65+
66+
```sql
67+
CREATE IMPORT MAPPING Module.IMM_MyMapping
68+
FROM JSON STRUCTURE Module.JSON_MyStructure
69+
{
70+
"" AS Module.MyRootObject (Create) {
71+
nestedKey AS Module.MyNestedObject (Create) VIA Module.MyRootObject_MyNestedObject {
72+
name AS name (String)
73+
code AS code (String)
74+
}
75+
stringField AS stringField (String)
76+
intField AS intField (Integer)
77+
}
78+
};
79+
```
80+
81+
**Syntax rules:**
82+
- Root element uses `""` (empty string) as the JSON key — it maps the top-level object
83+
- Object mappings: `jsonKey AS Module.Entity (Create|Find|FindOrCreate)`
84+
- Value mappings: `jsonKey AS attributeName (String|Integer|Long|Decimal|Boolean|DateTime)`
85+
- `VIA Module.Association` — required when mapping a nested object reachable via an association
86+
- No semicolons between child elements inside `{}`
87+
88+
**Verify** after creation — check Schema elements are ticked in Studio Pro:
89+
- Open the import mapping in Studio Pro
90+
- All JSON structure elements should appear ticked in the Schema elements panel
91+
- If not ticked: JsonPath mismatch between import mapping and JSON structure elements
92+
93+
---
94+
95+
## Step 4 — REST CALL Microflow
96+
97+
Place the microflow in the `[Pages]/Operations/` folder or `Private/` depending on whether it is public.
98+
99+
```sql
100+
CREATE MICROFLOW Module.GET_MyData ()
101+
BEGIN
102+
@position(-5, 200)
103+
DECLARE $baseUrl String = 'https://api.example.com';
104+
@position(185, 200)
105+
DECLARE $endpoint String = $baseUrl + '/path';
106+
@position(375, 200)
107+
$Result = REST CALL GET '{1}' WITH ({1} = $endpoint)
108+
HEADER 'Accept' = 'application/json'
109+
TIMEOUT 300
110+
RETURNS MAPPING Module.IMM_MyMapping AS Module.MyRootObject ON ERROR ROLLBACK;
111+
@position(565, 200)
112+
LOG INFO NODE 'Integration' 'Retrieved result' WITH ();
113+
END;
114+
/
115+
```
116+
117+
**Key points:**
118+
- `@position` annotations control the canvas layout — StartEvent is auto-placed 150px to the left of the first annotated activity
119+
- The output variable name is **automatically derived** from the entity name in `AS Module.MyEntity` — do NOT hardcode it on the left side; the executor overrides it
120+
- Single vs list result is **automatically detected**: if the JSON structure's root element is an Object, the variable type is `ObjectType` (single); if Array, `ListType` (list)
121+
- `ON ERROR ROLLBACK` — standard error handling for integration calls
122+
123+
**For list responses** (JSON root is an array):
124+
```sql
125+
$Results = REST CALL GET '{1}' WITH ({1} = $endpoint)
126+
HEADER 'Accept' = 'application/json'
127+
TIMEOUT 300
128+
RETURNS MAPPING Module.IMM_MyMapping AS Module.MyItem ON ERROR ROLLBACK;
129+
@position(565, 200)
130+
$Count = COUNT($MyItem);
131+
```
132+
133+
---
134+
135+
## Complete Example — Bible Verse API
136+
137+
```sql
138+
-- Step 1: JSON Structure
139+
CREATE JSON STRUCTURE Integrations.JSON_BibleVerse
140+
SNIPPET '{"translation":{"identifier":"web","name":"World English Bible","language":"English","language_code":"eng","license":"Public Domain"},"random_verse":{"book_id":"1SA","book":"1 Samuel","chapter":17,"verse":49,"text":"David put his hand in his bag, took a stone, and slung it."}}';
141+
142+
-- Step 2: Entities
143+
CREATE ENTITY Integrations.BibleApiResponse (NON_PERSISTENT);
144+
145+
CREATE ENTITY Integrations.BibleTranslation (NON_PERSISTENT)
146+
identifier : String
147+
name : String
148+
language : String
149+
language_code : String
150+
license : String;
151+
152+
CREATE ENTITY Integrations.BibleVerse (NON_PERSISTENT)
153+
book_id : String
154+
book : String
155+
chapter : Integer
156+
verse : Integer
157+
text : String;
158+
159+
CREATE ASSOCIATION Integrations.BibleApiResponse_BibleTranslation
160+
FROM Integrations.BibleApiResponse
161+
TO Integrations.BibleTranslation;
162+
163+
CREATE ASSOCIATION Integrations.BibleApiResponse_BibleVerse
164+
FROM Integrations.BibleApiResponse
165+
TO Integrations.BibleVerse;
166+
167+
-- Step 3: Import Mapping
168+
CREATE IMPORT MAPPING Integrations.IMM_BibleVerse
169+
FROM JSON STRUCTURE Integrations.JSON_BibleVerse
170+
{
171+
"" AS Integrations.BibleApiResponse (Create) {
172+
translation AS Integrations.BibleTranslation (Create) VIA Integrations.BibleApiResponse_BibleTranslation {
173+
identifier AS identifier (String)
174+
language AS language (String)
175+
language_code AS language_code (String)
176+
license AS license (String)
177+
name AS name (String)
178+
}
179+
random_verse AS Integrations.BibleVerse (Create) VIA Integrations.BibleApiResponse_BibleVerse {
180+
book AS book (String)
181+
book_id AS book_id (String)
182+
chapter AS chapter (Integer)
183+
text AS text (String)
184+
verse AS verse (Integer)
185+
}
186+
}
187+
};
188+
189+
-- Step 4: Microflow
190+
CREATE MICROFLOW Integrations.GET_BibleVerse_Random ()
191+
BEGIN
192+
@position(-5, 200)
193+
DECLARE $baseUrl String = 'https://bible-api.com';
194+
@position(185, 200)
195+
DECLARE $endpoint String = $baseUrl + '/data/web/random';
196+
@position(375, 200)
197+
$Result = REST CALL GET '{1}' WITH ({1} = $endpoint)
198+
HEADER 'Accept' = 'application/json'
199+
TIMEOUT 300
200+
RETURNS MAPPING Integrations.IMM_BibleVerse AS Integrations.BibleApiResponse ON ERROR ROLLBACK;
201+
@position(565, 200)
202+
LOG INFO NODE 'Integration' 'Retrieved Bible verse' WITH ();
203+
END;
204+
/
205+
```
206+
207+
---
208+
209+
## Gotchas and Common Errors
210+
211+
| Symptom | Cause | Fix |
212+
|---------|-------|-----|
213+
| Studio Pro "not consistent with snippet" | JSON element tree keys not in alphabetical order | Executor sorts keys; re-derive from snippet |
214+
| Schema elements not ticked in import mapping | JsonPath mismatch | Named object elements use `(Object)\|key`, NOT `(Object)\|key\|(Object)` |
215+
| Import mapping not linked in REST call | Wrong BSON field name | Use `ReturnValueMapping`, not `Mapping` |
216+
| Studio Pro shows "List of X" but mapping returns single X | `ForceSingleOccurrence` not set | Executor auto-detects from JSON structure root element type |
217+
| StartEvent behind first activities | Default posX=200 vs @position(-5,...) | Fixed: executor pre-scans for first @position and shifts StartEvent left |
218+
| `TypeCacheUnknownTypeException` | Wrong BSON `$Type` names | `ImportMappings$ObjectMappingElement` / `ImportMappings$ValueMappingElement` (no `Import` prefix) |
219+
| Attribute not found in Studio Pro | Attribute not fully qualified | Must be `Module.Entity.AttributeName` in the BSON |
220+
221+
---
222+
223+
## Naming Conventions (MES)
224+
225+
| Artifact | Pattern | Example |
226+
|----------|---------|---------|
227+
| JSON Structure | `JSON_<ApiName>` | `JSON_BibleVerse` |
228+
| Import Mapping | `IMM_<ApiName>` | `IMM_BibleVerse` |
229+
| Root entity | Describes the API response | `BibleApiResponse` |
230+
| Nested entities | Describes the domain concept | `BibleVerse`, `BibleTranslation` |
231+
| Microflow | `METHOD_Resource_Operation` | `GET_BibleVerse_Random` |
232+
| Folder | `Private/` for mappings/structures, `Operations/` for public microflows ||

cmd/mxcli/cmd_lint.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ Examples:
141141
lint.AddRule(rules.NewWeakPasswordPolicyRule())
142142
lint.AddRule(rules.NewDemoUsersActiveRule())
143143

144+
// MPR008 - requires BSON inspection
145+
lint.AddRule(rules.NewOverlappingActivitiesRule())
146+
144147
// Convention rules (CONV011-CONV014) - require BSON inspection
145148
lint.AddRule(rules.NewNoCommitInLoopRule())
146149
lint.AddRule(rules.NewExclusiveSplitCaptionRule())

cmd/mxcli/cmd_report.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ Examples:
101101
lint.AddRule(rules.NewWeakPasswordPolicyRule())
102102
lint.AddRule(rules.NewDemoUsersActiveRule())
103103

104+
// MPR008 - requires BSON inspection
105+
lint.AddRule(rules.NewOverlappingActivitiesRule())
106+
104107
// Convention rules (CONV011-CONV014)
105108
lint.AddRule(rules.NewNoCommitInLoopRule())
106109
lint.AddRule(rules.NewExclusiveSplitCaptionRule())

cmd/mxcli/lsp_completions_gen.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)