feat: add JSON Structure, Import Mapping, Export Mapping, and SEND REST REQUEST#84
feat: add JSON Structure, Import Mapping, Export Mapping, and SEND REST REQUEST#84khode-mx wants to merge 6 commits intomendixlabs:mainfrom
Conversation
Implemented MDL for: * JSON Structure * Import Mapping * Call REST action
This reverts commit e9bf0df.
…ng, and Export Mapping Adds tests required by CONTRIBUTING.md for the new features on this branch: - sdk/mpr: BSON unit tests for writer_json_structure, writer_import_mapping, writer_export_mapping — covering $Type naming (critical BSON trap), required fields, default values, and DeriveJsonElementsFromSnippet/FormatJsonSnippet - mdl/executor: integration roundtrip tests for CREATE/DESCRIBE/DROP/SHOW of JSON structures, import mappings, and export mappings, plus mx check tests - roundtrip_helpers_test.go: extend assertRoundtrip switch to support the three new statement types so assertContains works for future tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AI Code ReviewLet me review this pull request against the checklist. SummaryThis PR implements full MDL support for building REST integrations end-to-end:
Let me go through the review checklist systematically. Overlap & duplication
MDL syntax designLet me check if the new MDL syntax follows the design guidelines: CREATE/DROP/SHOW/DESCRIBE pattern:
Qualified names:
Property format:
Colon vs AS usage:
Keywords as full English words:
Reads as English:
No keyword overloading:
Diff-friendly:
Consistency with existing MDL patterns:
Full-stack consistencyLet me verify if the new MDL features are wired through the full pipeline: Grammar:
AST:
Visitor:
Executor:
LSP:
DESCRIBE roundtrip:
VS Code extension:
Test coverage
Security & robustness
Scope & atomicity
Code quality
Specific checks from the diff:Looking at the actual code changes:
Potential Issues to Flag:
What Looks Good:
Recommendation:The PR appears to be well-executed and follows the project's conventions and requirements. The implementation covers the full pipeline from grammar to executor, includes proper documentation, and addresses critical BSON naming issues. I would recommend approving this PR, with the note that the visitor implementation (not visible in the diff) should be checked for correctness, but given the thoroughness of the rest of the implementation, it's likely correct. However, since I need to follow the format strictly and only list sections with findings, and most checks pass, I'll focus on what looks good and make the recommendation. Actually, let me check if there are any specific issues I should flag: Looking more carefully at the AST definitions: In ast_json_structure.go:
Actually, looking at the describeJsonStructure function: fmt.Fprintf(e.output, "CREATE JSON STRUCTURE %s.%s\n", moduleName, js.Name)
if js.JsonSnippet != "" {
fmt.Fprintf(e.output, " FROM '%s';\n", strings.ReplaceAll(js.JsonSnippet, "'", "''"))
} else {
fmt.Fprintln(e.output, " FROM '{}' -- no snippet stored;")
}It doesn't show the Folder. This could be an issue for roundtrip if Folder is set. Similarly, in the CREATE function: if s.Folder != "" {
folderID, err := e.resolveFolder(module.ID, s.Folder)
if err != nil {
return fmt.Errorf("failed to resolve folder %s: %w", s.Folder, err)
}
containerID = folderID
}So Folder is handled in CREATE but not shown in DESCRIBE. This would break the roundtrip. This is a moderate issue that should be fixed. Also, let me check the ImportMapping and ExportMapping DESCRIBE functions for similar issues. For ImportMapping DESCRIBE: fmt.Fprintf(e.output, "CREATE IMPORT MAPPING %s.%s\n", moduleName, im.Name)
if im.JsonStructure != "" {
fmt.Fprintf(e.output, " FROM JSON STRUCTURE %s\n", im.JsonStructure)
} else if im.XmlSchema != "" {
fmt.Fprintf(e.output, " FROM XML SCHEMA %s\n", im.XmlSchema)
}This looks complete - it shows the FROM clause. For ExportMapping DESCRIBE: fmt.Fprintf(e.output, "CREATE EXPORT MAPPING %s.%s\n", moduleName, em.Name)
if em.JsonStructure != "" {
fmt.Fprintf(e.output, " TO JSON STRUCTURE %s\n", em.JsonStructure)
} else if em.XmlSchema != "" {
fmt.Fprintf(e.output, " TO XML SCHEMA %s\n", em.XmlSchema)
}
if em.NullValueOption != "" && em.NullValueOption != "LeaveOutElement" {
fmt.Fprintf(e.output, " NULL VALUES %s\n", em.NullValueOption)
}This also looks complete. So the JSON Structure DESCRIBE is missing the Folder output. Let me also check if there are any other potential issues: In the skill document, it shows: But it doesn't show the FOLDER clause in the example, though it's mentioned in the text. The AST definition includes Folder, so the DESCRIBE should output it if present. This is a moderate issue affecting roundtrip completeness. Another potential issue: In the REST CALL microflow generation, the skill shows: But I don't see where the "WITH ({1} = $endpoint)" part is handled in the executor. Looking at cmd_microflows_builder_calls.go, I see it handles the REST CALL but need to check if it processes the WITH clause for parameters. Actually, looking more carefully at the diff in cmd_microflows_builder_calls.go: func (fb *flowBuilder) addRestCallAction(s *ast.RestCallStmt) model.ID {
// ...
case ast.RestResultMapping:
// ...This suggests the RestCallStmt has fields for Method, Url, etc., and the WITH clause would be part of the Url or parameters handling. Without seeing the full RestCallStmt definition, it's hard to say, but this is potentially an area to check. However, since the skill document shows it working, and the PR includes tests, it's likely implemented correctly. Let me also check for any syntax violations in the MDL examples: In the skill document: This looks correct:
Actually, wait - in the mapping: Here, This follows the pattern: The skill document explains this well. One more thing to check: In the ImportMappingElementDef in AST: type ImportMappingElementDef struct {
// JSON field name (or "root" for the root element)
JsonName string
// Object mapping fields (set when mapping to an entity)
Entity string // qualified entity name (e.g. "Module.Customer")
ObjectHandling string // "Create", "Find", "FindOrCreate", "Custom"
Association string // qualified association name for via clause
Children []*ImportMappingElementDef
// Value mapping fields (set when mapping to an attribute)
Attribute string // attribute name (unqualified, e.g. "Name")
DataType string // "String", "Integer", "Boolean", "Decimal", "DateTime"
IsKey bool
}And in the printing function: func printImportMappingElement(e *Executor, elem *model.ImportMappingElement, depth int) {
indent := strings.Repeat(" ", depth)
if elem.Kind == "Object" {
// ...
jsonKey := elem.ExposedName
if jsonKey == "" {
jsonKey = `""`
}
if len(elem.Children) > 0 {
fmt.Fprintf(e.output, "%s%s -> %s (%s)%s {\n", indent, jsonKey, entityOrName, handling, via)
// ...
} else {
fmt.Fprintf(e.output, "%s%s -> %s (%s)%s", indent, jsonKey, entityOrName, handling, via)
}
} else {
// Value mapping
// ...
fmt.Fprintf(e.output, "%s%s -> %s (%s%s)", indent, elem.ExposedName, attrName, dt, keyStr)
}
}This looks correct for generating MDL like: The root element uses Now, back to the Folder issue in JSON Structure DESCRIBE - that's a clear moderate issue. Let me also check check Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
ako
left a comment
There was a problem hiding this comment.
Review
Scope
This is a large PR (19K additions, 85 files) that bundles 6 independent features:
- JSON Structure (SHOW/DESCRIBE/CREATE/DROP)
- Import Mapping (SHOW/DESCRIBE/CREATE/DROP)
- Export Mapping (SHOW/DESCRIBE/CREATE/DROP)
- SEND REST REQUEST enhancements (auto-derive output variable, single/list detection)
- Lint rule
mpr008_overlapping_activities go fmtacross ~50 files
Per the repo's checklist, these should be separate PRs. At minimum: (a) JSON Structure, (b) Import/Export Mapping, (c) SEND REST REQUEST improvements, (d) lint rule, (e) formatting.
Overlap with PR #80
PR #80 (by @peterjumpnl) and this PR both implement JSON Structure SHOW/DESCRIBE/CREATE/DROP independently. Here's a detailed comparison:
Syntax
| Feature | PR #80 (peterjumpnl) | PR #84 (khode-mx) |
|---|---|---|
| CREATE syntax | CREATE JSON STRUCTURE M.N SNIPPET '...' |
CREATE JSON STRUCTURE M.N FROM '...' |
| Dollar quoting | ✅ SNIPPET $$...$$ |
❌ Only STRING_LITERAL |
| OR REPLACE | ✅ CREATE OR REPLACE |
❌ Not supported |
| Documentation | ✅ COMMENT 'doc' |
❌ Not supported |
| Custom name map | ✅ CUSTOM NAME MAP ('key' AS 'Name') |
❌ Not supported |
| FOLDER support | ❌ | ✅ FOLDER 'path' |
| DESCRIBE output | Re-executable CREATE OR REPLACE ... SNIPPET |
CREATE ... FROM '...' (not idempotent) |
Implementation quality
| Aspect | PR #80 | PR #84 |
|---|---|---|
| JSON key ordering | ✅ Preserves original key order via streaming json.Decoder |
❌ Uses map[string]any → sort.Strings(keys) (alphabetical, loses original order) |
| Reserved names | ✅ Handles Id/Type → _id/_type |
❌ Not handled |
| Primitive arrays | ✅ Wrapper element with Value child (matches Studio Pro) | ❌ Not handled |
| DateTime detection | ✅ Regex-based, normalizes to 7-digit fractional | ✅ time.Parse + reformat to local TZ |
| DateTime normalization | String manipulation (deterministic) | time.Parse + time.Local (non-deterministic — output depends on server timezone) |
| null handling | ✅ "Unknown" primitive type (matches Studio Pro) |
"String" primitive type (doesn't match Studio Pro) |
| Catalog integration | ✅ json_structures table |
❌ Not included |
| REPL autocomplete | ✅ | ❌ Not included |
| Tests | No Go tests (golden files only) | ✅ 6 test files, 45 tests including mx check |
| BSON serialization | Uses bson.D (ordered) |
Uses bson.M (unordered — BSON field order not guaranteed) |
Verdict on JSON Structure: PR #80 has better syntax design (SNIPPET keyword, dollar quoting, OR REPLACE, custom name map), better Studio Pro fidelity (key ordering, reserved names, primitive arrays, null handling), and better pipeline coverage (catalog, autocomplete). PR #84 has better test coverage and FOLDER support. Recommend merging PR #80's JSON Structure and adding PR #84's tests and FOLDER support on top.
Critical issue in PR #84's JSON Structure: bson.M vs bson.D
PR #84 serializes with bson.M (unordered map):
doc := bson.M{
"$ID": ...,
"$Type": "JsonStructures$JsonStructure",
"Name": js.Name,
...
}PR #80 uses bson.D (ordered document):
doc := bson.D{
{Key: "$ID", Value: ...},
{Key: "$Type", Value: "JsonStructures$JsonStructure"},
...
}bson.M produces non-deterministic field ordering in the serialized BSON. While Mendix may tolerate this for some document types, it risks issues with strict parsers and makes roundtrip testing unreliable.
Overlap with existing SEND REST REQUEST
The SEND REST REQUEST microflow action already exists in the codebase. PR #84 enhances it with:
- Auto-deriving the output variable name from the entity name
- Auto-detecting single vs list result by inspecting the JSON structure's root element type
These are useful improvements. However, the auto-derive logic overwrites s.OutputVariable unconditionally:
s.OutputVariable = s.Result.ResultEntity.NameThis means any explicitly set output variable in MDL would be silently overwritten. Should only set if s.OutputVariable == "".
Import/Export Mapping syntax
The mapping syntax uses -> arrows, which conflicts with the MDL design principle of "no symbols for domain operations":
root -> Module.Entity (Create) {
id -> Id (Integer, KEY);
name -> Name (String);
};
Per .claude/skills/design-mdl-syntax.md, MDL should use keyword-based constructs. Consider:
MAP root TO Module.Entity (Create) {
MAP id TO Id (Integer, KEY);
};
That said, the arrow syntax is arguably readable and maps clearly to the mental model. Worth discussing.
Other issues
-
go fmtacross 50 files — formatting changes are bundled with feature work, making the diff hard to review. Should be a separate commit/PR. -
Lint rule
mpr008_overlapping_activities— independent feature, should be its own PR. -
Model types in
model/types.go— both PRs defineJsonStructureandJsonElementtypes but in different locations (PR #80 insdk/mpr/reader_types.go, PR #84 inmodel/types.go). PR #84's approach of putting them inmodel/is architecturally cleaner since they're used across packages. -
Test plan has all boxes unchecked — none of the 9 verification items are checked.
Recommendation
- Split this PR into at least 4 PRs: JSON Structure, Import/Export Mapping, SEND REST REQUEST improvements, lint rule + formatting
- For JSON Structure: coordinate with PR #80 to avoid duplicate work. PR #80 has better syntax and Studio Pro fidelity; PR #84 has better tests. Merge PR #80 first, then add tests and FOLDER support on top.
- Fix
bson.M→bson.Dfor deterministic serialization - Fix the output variable overwrite in SEND REST REQUEST
- Discuss arrow syntax vs keyword syntax for mappings
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>
Summary
This PR implements full MDL support for building REST integrations end-to-end — from JSON contract to callable microflow action.
CREATE/DROP/SHOW/DESCRIBE JSON STRUCTURE: auto-derives the element tree from a JSON snippet, with Format andRefresh steps matching Studio Pro behaviour
CREATE/DROP/SHOW/DESCRIBE IMPORT MAPPING: maps JSON structure elements to Mendix entities with key-based matching,VIAassociations, and create/ignore/error handling strategiesCREATE/DROP/SHOW/DESCRIBE EXPORT MAPPING: maps Mendix entities back to JSON for outgoing REST calls, withconfigurable null value handling
mpr008_overlapping_activitiesto detect overlapping microflow activities.claude/skills/mendix/rest-call-from-json.mdcovering the full end-to-end workflowWhat changed
MDLLexer.g4,MDLParser.g4+ regenerated parser (make grammar)ast_json_structure.go,ast_query.govisitor_json_structure.go,visitor_query.go,visitor_entity.gocmd_json_structures.go,cmd_import_mappings.go,cmd_export_mappings.go,cmd_microflows_builder_calls.goparser_json_structure.go,parser_import_mapping.go,parser_export_mapping.go,reader_documents.gowriter_json_structure.go,writer_import_mapping.go,writer_export_mapping.go,writer_microflow_actions.gomodel/types.gogo fmtapplied across 50 files,go vetcleanTest plan
make buildpassesmake vetpassesmake fmtproduces no further changesCREATE JSON STRUCTURE→DESCRIBEroundtrip produces re-executable MDLCREATE IMPORT MAPPING→DESCRIBEroundtrip produces re-executable MDLCREATE EXPORT MAPPING→DESCRIBEroundtrip produces re-executable MDLSEND REST REQUESTactivity executes without errors in a microflowmake testunit tests pass (45 new tests insdk/mpr/andmdl/executor/)