Skip to content

Commit 5d2d441

Browse files
akoclaude
andcommitted
fix: CREATE ENTITY rejects unknown attribute type names (#392)
The visitor parses any unrecognised type token as TypeEnumeration (the TypeEnumeration vs TypeEntity ambiguity documented in CLAUDE.md). convertDataType then blindly stored it as EnumerationAttributeType with an unresolvable ref, producing a corrupt model that Studio Pro flags. Added a pre-pass before the attribute-build loop: for every TypeEnumeration attribute, attempt to resolve the ref against known enumerations (findEnumeration) and known entities (findEntity). If neither resolves, return a validation error naming the attribute and the unrecognised type. Valid qualified enum and entity refs (e.g. MyModule.MyEnum) are still accepted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 40d1d83 commit 5d2d441

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

mdl/executor/cmd_entities.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,28 @@ func execCreateEntity(ctx *ExecContext, s *ast.CreateEntityStmt) error {
9898
}
9999
}
100100

101+
// Validate TypeEnumeration attribute refs before writing anything.
102+
// The visitor uses TypeEnumeration for both enum and entity type references
103+
// (TypeEnumeration vs TypeEntity ambiguity). Accept the ref when it resolves
104+
// to either a known enumeration or a known entity; reject unknown names fast
105+
// so typos don't silently produce corrupt models.
106+
for _, a := range s.Attributes {
107+
if a.Type.Kind != ast.TypeEnumeration || a.Type.EnumRef == nil {
108+
continue
109+
}
110+
refModule := a.Type.EnumRef.Module
111+
refName := a.Type.EnumRef.Name
112+
if findEnumeration(ctx, refModule, refName) != nil {
113+
continue
114+
}
115+
if _, err := findEntity(ctx, refModule, refName); err == nil {
116+
continue
117+
}
118+
return mdlerrors.NewValidationf(
119+
"attribute '%s': unknown type '%s' — not a primitive, enumeration, or entity",
120+
a.Name, a.Type.EnumRef.String())
121+
}
122+
101123
// Create attributes and build name-to-ID map for validation rules and indexes
102124
// Also detect pseudo-types (AutoOwner, AutoChangedBy, etc.) and set entity flags
103125
var storeOwner, storeChangedBy, storeCreatedDate, storeChangedDate bool

mdl/executor/cmd_entities_mock_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"testing"
88

9+
"github.com/mendixlabs/mxcli/mdl/ast"
910
"github.com/mendixlabs/mxcli/mdl/backend/mock"
1011
"github.com/mendixlabs/mxcli/model"
1112
"github.com/mendixlabs/mxcli/sdk/domainmodel"
@@ -82,6 +83,40 @@ func TestShowEntities_BackendError(t *testing.T) {
8283
assertError(t, listEntities(ctx, ""))
8384
}
8485

86+
// Issue #392 — CREATE ENTITY must reject attribute types that don't resolve
87+
// to a known primitive, enumeration, or entity.
88+
func TestCreateEntity_UnknownAttributeType_Issue392(t *testing.T) {
89+
mod := mkModule("M")
90+
dm := mkDomainModel(mod.ID)
91+
92+
mb := &mock.MockBackend{
93+
IsConnectedFunc: func() bool { return true },
94+
ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil },
95+
ListDomainModelsFunc: func() ([]*domainmodel.DomainModel, error) { return []*domainmodel.DomainModel{dm}, nil },
96+
GetDomainModelFunc: func(id model.ID) (*domainmodel.DomainModel, error) { return dm, nil },
97+
ListEnumerationsFunc: func() ([]*model.Enumeration, error) { return nil, nil },
98+
}
99+
h := mkHierarchy(mod)
100+
withContainer(h, dm.ID, mod.ID)
101+
102+
ctx, _ := newMockCtx(t, withBackend(mb), withHierarchy(h))
103+
err := execCreateEntity(ctx, &ast.CreateEntityStmt{
104+
Name: ast.QualifiedName{Module: "M", Name: "E"},
105+
Kind: ast.EntityPersistent,
106+
Attributes: []ast.Attribute{
107+
{
108+
Name: "Field1",
109+
Type: ast.DataType{
110+
Kind: ast.TypeEnumeration,
111+
EnumRef: &ast.QualifiedName{Name: "invalidtype"},
112+
},
113+
},
114+
},
115+
})
116+
assertError(t, err)
117+
assertContainsStr(t, err.Error(), "invalidtype")
118+
}
119+
85120
func TestShowEntities_JSON(t *testing.T) {
86121
mod := mkModule("App")
87122
ent := mkEntity(mod.ID, "Item")

0 commit comments

Comments
 (0)