Skip to content

Commit c44fe45

Browse files
committed
fix: address code review issues from issue #19 implementation
- Wire inferPropertyKind into newUnknownObject via FieldKinds field on UnknownElement — eliminates dead code, surfaces property kind hints - inferPropertyKind: implement key-based shortcuts ($ID/$ContainerID → "id", $Type → "type-discriminator"); use extractBsonArrayWithMarker to classify array fields as collection:by-name/part-secondary/part-primary - extractBsonArrayWithMarker: handle marker=1 (by-name arrays) alongside 2/3 - UnknownElement: add FieldKinds map[string]string field for diagnostic output - writer_microflow.go: add *model.UnknownElement case in serializeMicroflowObject to write RawFields back as-is, completing P0 round-trip fidelity - parseMicroflowObject: return nil for empty $Type (symmetric with workflow parser) - Fix init() comment: clarify actual reason (initialization order) vs incorrect "recursion" claim
1 parent 50cf3ef commit c44fe45

5 files changed

Lines changed: 59 additions & 10 deletions

File tree

model/types.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -718,12 +718,18 @@ type DistributionSettings struct {
718718
// UnknownElement is a generic fallback for BSON elements with unrecognized $Type values.
719719
// It preserves all raw BSON fields so developers can diagnose unimplemented types
720720
// without silent data loss.
721+
//
722+
// FieldKinds maps each raw field name to its inferred Mendix property kind
723+
// (e.g. "primitive", "part", "by-name-reference", "collection:part-primary").
724+
// This guides implementors in writing a proper parser without inspecting the
725+
// mendixmodelsdk JS source manually.
721726
type UnknownElement struct {
722727
BaseElement
723-
Position Point `json:"position,omitempty"`
724-
Name string `json:"name,omitempty"`
725-
Caption string `json:"caption,omitempty"`
726-
RawFields map[string]any `json:"-"`
728+
Position Point `json:"position,omitempty"`
729+
Name string `json:"name,omitempty"`
730+
Caption string `json:"caption,omitempty"`
731+
RawFields map[string]any `json:"-"`
732+
FieldKinds map[string]string `json:"-"`
727733
}
728734

729735
// GetPosition returns the element's position (satisfies microflows.MicroflowObject).

sdk/mpr/parser.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,21 +174,31 @@ func extractBsonArrayWithMarker(v any) BsonArrayInfo {
174174
}
175175

176176
if len(slice) > 0 {
177-
if marker, ok := slice[0].(int32); ok && (marker == 2 || marker == 3) {
177+
if marker, ok := slice[0].(int32); ok && (marker == 1 || marker == 2 || marker == 3) {
178178
return BsonArrayInfo{Marker: marker, Items: slice[1:]}
179179
}
180180
}
181181
return BsonArrayInfo{Items: slice}
182182
}
183183

184-
// inferPropertyKind determines whether a BSON field value represents a primitive,
185-
// a by-name reference, or a part (embedded object). This is used by the generic
186-
// unknown-element parser to preserve raw fields with semantic context.
184+
// inferPropertyKind determines the Mendix property kind of a BSON field from its key
185+
// and value shape. Returns one of: "id", "type-discriminator", "by-name-reference",
186+
// "primitive", "part", "collection:by-name" (marker=1), "collection:part-secondary"
187+
// (marker=2), "collection:part-primary" (marker=3), "collection".
188+
// Used by UnknownElement to surface diagnostic info when an unimplemented $Type is encountered.
187189
func inferPropertyKind(key string, v any) string {
188190
if v == nil {
189191
return "primitive"
190192
}
191193

194+
// Key-based shortcuts take priority over value shape.
195+
switch key {
196+
case "$ID", "$ContainerID":
197+
return "id"
198+
case "$Type":
199+
return "type-discriminator"
200+
}
201+
192202
switch val := v.(type) {
193203
case map[string]any:
194204
if _, hasType := val["$Type"]; hasType {
@@ -219,10 +229,19 @@ func inferPropertyKind(key string, v any) string {
219229
return "primitive"
220230

221231
case primitive.A, []any:
232+
info := extractBsonArrayWithMarker(v)
233+
switch info.Marker {
234+
case 1:
235+
return "collection:by-name"
236+
case 2:
237+
return "collection:part-secondary"
238+
case 3:
239+
return "collection:part-primary"
240+
}
222241
return "collection"
223242

224243
case string:
225-
// Heuristic: qualified names like "Module.Entity" are likely by-name references
244+
// Heuristic: qualified names like "Module.Entity" are likely by-name references.
226245
if strings.Contains(val, ".") && !strings.Contains(val, " ") && !strings.Contains(val, "/") {
227246
return "by-name-reference"
228247
}

sdk/mpr/parser_microflow.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,9 @@ func parseMicroflowObjectCollection(raw map[string]any) *microflows.MicroflowObj
247247

248248
// microflowObjectParsers maps Mendix $Type strings to their parser functions.
249249
// Adding support for a new type requires only one new entry here.
250-
// Initialized in init() to avoid initialization cycle (parseLoopedActivity → parseMicroflowObjectCollection → parseMicroflowObject).
250+
// Declared as a nil var and populated in init() so that the map literal can
251+
// reference parseLoopedActivity, which itself calls parseMicroflowObjectCollection,
252+
// keeping the package-level initialization order unambiguous.
251253
var microflowObjectParsers map[string]func(map[string]any) microflows.MicroflowObject
252254

253255
func init() {
@@ -267,8 +269,12 @@ func init() {
267269
}
268270

269271
// parseMicroflowObject parses a single microflow object based on its $Type.
272+
// Returns nil for elements with an empty $Type (corrupt or placeholder records).
270273
func parseMicroflowObject(raw map[string]any) microflows.MicroflowObject {
271274
typeName, _ := raw["$Type"].(string)
275+
if typeName == "" {
276+
return nil
277+
}
272278
if fn, ok := microflowObjectParsers[typeName]; ok {
273279
return fn(raw)
274280
}

sdk/mpr/parser_unknown.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import "github.com/mendixlabs/mxcli/model"
66

77
// newUnknownObject creates an UnknownElement that preserves raw BSON fields
88
// for unrecognized $Type values, preventing silent data loss.
9+
// FieldKinds is populated by inferPropertyKind so callers can see the inferred
10+
// Mendix property kind for each field without inspecting the SDK JS source.
911
func newUnknownObject(typeName string, raw map[string]any) *model.UnknownElement {
1012
id := ""
1113
if raw != nil {
@@ -19,6 +21,10 @@ func newUnknownObject(typeName string, raw map[string]any) *model.UnknownElement
1921
elem.Position = parsePoint(raw["RelativeMiddlePoint"])
2022
elem.Name = extractString(raw["Name"])
2123
elem.Caption = extractString(raw["Caption"])
24+
elem.FieldKinds = make(map[string]string, len(raw))
25+
for k, v := range raw {
26+
elem.FieldKinds[k] = inferPropertyKind(k, v)
27+
}
2228
}
2329
return elem
2430
}

sdk/mpr/writer_microflow.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,18 @@ func serializeMicroflowObject(obj microflows.MicroflowObject) bson.D {
502502
{Key: "Size", Value: sizeToString(o.Size)},
503503
}
504504

505+
case *model.UnknownElement:
506+
// Write-through: serialize RawFields back as-is so unknown activities
507+
// are not silently dropped when the MPR is saved.
508+
if o.RawFields == nil {
509+
return nil
510+
}
511+
doc := make(bson.D, 0, len(o.RawFields))
512+
for k, v := range o.RawFields {
513+
doc = append(doc, bson.E{Key: k, Value: v})
514+
}
515+
return doc
516+
505517
default:
506518
return nil
507519
}

0 commit comments

Comments
 (0)