Skip to content

Commit 3ee46cd

Browse files
akoclaude
andcommitted
fix(odata-client): route Headers microflow to its own slot (#728, CE6808)
Follow-up to 045ee6f23. That fix mapped BOTH the "Configuration microflow" and "Headers microflow" MDL keywords to the configuration slot, so on 11.10+ a `HeadersMicroflow:` reference was written to ConfigurationEntityMicroflow — Studio Pro then demanded a System.ConsumedODataConfiguration return type and raised CE6808 against a microflow that (correctly) returns a list of System.HttpHeader. Mendix stores the two as distinct fields on 11.10+: ConfigurationEntity- Microflow vs HeaderListMicroflow (before 11.10 they shared ConfigurationMicroflow and were distinguished by return type). Track them as separate model fields: - model gains a HeadersMicroflow field + ODataHeadersMicroflowBSONKey (11.10+ → HeaderListMicroflow, else ConfigurationMicroflow). - AST/visitor route the `HeadersMicroflow` keyword to the new field; the executor, both writers, and both read paths carry it through; DESCRIBE emits the matching keyword. Verified end-to-end on the OData doctype example (11.12): HeadersConfigAPI now stores HeaderListMicroflow, DESCRIBE round-trips `HeadersMicroflow:`, and CE6808 is gone. Combined with the capability-default revert, the doctype script's OData errors drop from 36 to 9 (the remaining 9 are a separate published-service issue). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 26a4e9c commit 3ee46cd

12 files changed

Lines changed: 143 additions & 57 deletions

File tree

mdl-examples/bug-tests/728-odata-config-microflow-version-key.mdl

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,35 @@
3131

3232
create module BugTest728;
3333

34-
create microflow BugTest728.ConfigureRequest () returns void
34+
create microflow BugTest728.ConfigureRequest () returns System.ConsumedODataConfiguration
3535
begin
36+
$Config = create System.ConsumedODataConfiguration;
37+
return $Config;
38+
end;
39+
40+
create microflow BugTest728.SetHeaders () returns list of System.HttpHeader
41+
begin
42+
$Headers = create list of System.HttpHeader;
43+
return $Headers;
3644
end;
3745
/
3846

47+
-- Configuration microflow → BSON ConfigurationEntityMicroflow (11.10+).
3948
create odata client BugTest728.ConfigSvc (
4049
ODataVersion: OData4,
4150
MetadataUrl: 'https://example.com/odata/$metadata',
4251
Timeout: 300,
4352
ConfigurationMicroflow: microflow BugTest728.ConfigureRequest
4453
);
4554

55+
-- Headers microflow → BSON HeaderListMicroflow (11.10+), a DIFFERENT slot.
56+
-- Storing it in the configuration slot would trigger CE6808.
57+
create odata client BugTest728.HeadersSvc (
58+
ODataVersion: OData4,
59+
MetadataUrl: 'https://example.com/odata/$metadata',
60+
Timeout: 300,
61+
HeadersMicroflow: microflow BugTest728.SetHeaders
62+
);
63+
4664
describe odata client BugTest728.ConfigSvc;
65+
describe odata client BugTest728.HeadersSvc;

mdl/ast/ast_odata.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ type CreateODataClientStmt struct {
2626
HttpPassword string // Mendix expression for password
2727
ClientCertificate string
2828

29-
// Microflow reference. Both `ConfigurationMicroflow` and the legacy
30-
// `HeadersMicroflow` MDL keywords write to this same field; Studio
31-
// Pro distinguishes the two dropdown options by the microflow's
32-
// return type, not by BSON field.
33-
ConfigurationMicroflow string // microflow Module.ConfigureMF or Module.SetHeadersMF
29+
// Microflow references. `ConfigurationMicroflow` (returns
30+
// System.ConsumedODataConfiguration) and `HeadersMicroflow` (returns a list
31+
// of System.HttpHeader) are distinct storage slots on Mendix >= 11.10, so
32+
// they are tracked separately (writing one into the other's slot triggers
33+
// CE6808/CE6816).
34+
ConfigurationMicroflow string // microflow Module.ConfigureMF
35+
HeadersMicroflow string // microflow Module.SetHeadersMF
3436
ErrorHandlingMicroflow string // microflow Module.HandleErrorMF
3537

3638
// Proxy constant references

mdl/backend/modelsdk/integration_read.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,17 @@ func (b *Backend) ListConsumedODataServices() ([]*model.ConsumedODataService, er
4646
EndpointId: g.EndpointId(),
4747
CatalogUrl: g.CatalogUrl(),
4848
EnvironmentType: g.EnvironmentType(),
49-
// The config-microflow field was renamed across versions (issue #728);
50-
// coalesce every historical storage key so DESCRIBE round-trips a
51-
// service authored by any Mendix version.
49+
// The microflow storage fields were renamed/split across versions
50+
// (issue #728). Config slot: ConfigurationEntityMicroflow (11.10+) or
51+
// the pre-11.10 single slot (ConfigurationMicroflow / ancient
52+
// HeadersMicroflow). Headers slot is only distinct on 11.10+
53+
// (HeaderListMicroflow). Coalesce so DESCRIBE round-trips any version.
5254
ConfigurationMicroflow: firstNonEmpty(
53-
g.ConfigurationEntityMicroflowQualifiedName(), // >= 11.10
54-
g.ConfigurationMicroflowQualifiedName(), // 10.12 – 11.10
55-
g.HeadersMicroflowQualifiedName(), // < 10.12
55+
g.ConfigurationEntityMicroflowQualifiedName(), // >= 11.10 config
56+
g.ConfigurationMicroflowQualifiedName(), // 10.12 – 11.10 (single slot)
57+
g.HeadersMicroflowQualifiedName(), // < 10.12 (single slot)
5658
),
59+
HeadersMicroflow: g.HeaderListMicroflowQualifiedName(), // >= 11.10 headers
5760
ErrorHandlingMicroflow: g.ErrorHandlingMicroflowQualifiedName(),
5861
ProxyHost: g.ProxyHostQualifiedName(),
5962
ProxyPort: g.ProxyPortQualifiedName(),

mdl/backend/modelsdk/odata_write.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (b *Backend) CreateConsumedODataService(svc *model.ConsumedODataService) er
5252
svc.ID = model.ID(mmpr.GenerateID())
5353
}
5454
svc.TypeName = "Rest$ConsumedODataService"
55-
contents, err := (&codec.Encoder{}).Encode(consumedODataServiceToGen(svc, b.configMicroflowKey()))
55+
contents, err := b.encodeConsumedODataService(svc)
5656
if err != nil {
5757
return fmt.Errorf("CreateConsumedODataService: encode: %w", err)
5858
}
@@ -66,7 +66,7 @@ func (b *Backend) UpdateConsumedODataService(svc *model.ConsumedODataService) er
6666
if b.writer == nil {
6767
return fmt.Errorf("UpdateConsumedODataService: not connected for writing")
6868
}
69-
contents, err := (&codec.Encoder{}).Encode(consumedODataServiceToGen(svc, b.configMicroflowKey()))
69+
contents, err := b.encodeConsumedODataService(svc)
7070
if err != nil {
7171
return fmt.Errorf("UpdateConsumedODataService: encode: %w", err)
7272
}
@@ -80,17 +80,25 @@ func (b *Backend) DeleteConsumedODataService(id model.ID) error {
8080
return b.writer.DeleteUnit(string(id))
8181
}
8282

83-
// configMicroflowKey returns the version-appropriate BSON field name for the
84-
// consumed OData service's configuration microflow (issue #728). Defaults to the
85-
// pre-11.10 key when the project version is unknown.
86-
func (b *Backend) configMicroflowKey() string {
83+
// encodeConsumedODataService serializes the service with version-appropriate
84+
// microflow BSON keys.
85+
func (b *Backend) encodeConsumedODataService(svc *model.ConsumedODataService) ([]byte, error) {
86+
configKey, headersKey := b.microflowKeys()
87+
return (&codec.Encoder{}).Encode(consumedODataServiceToGen(svc, configKey, headersKey))
88+
}
89+
90+
// microflowKeys returns the version-appropriate BSON field names for the
91+
// consumed OData service's configuration and headers microflows (issue #728).
92+
// Defaults to the pre-11.10 keys when the project version is unknown.
93+
func (b *Backend) microflowKeys() (configKey, headersKey string) {
8794
if pv := b.ProjectVersion(); pv != nil {
88-
return model.ODataConfigMicroflowBSONKey(pv.MajorVersion, pv.MinorVersion)
95+
return model.ODataConfigMicroflowBSONKey(pv.MajorVersion, pv.MinorVersion),
96+
model.ODataHeadersMicroflowBSONKey(pv.MajorVersion, pv.MinorVersion)
8997
}
90-
return "ConfigurationMicroflow"
98+
return "ConfigurationMicroflow", "ConfigurationMicroflow"
9199
}
92100

93-
func consumedODataServiceToGen(svc *model.ConsumedODataService, configMicroflowKey string) element.Element {
101+
func consumedODataServiceToGen(svc *model.ConsumedODataService, configMicroflowKey, headersMicroflowKey string) element.Element {
94102
g := newElem("Rest$ConsumedODataService", string(svc.ID))
95103
addStr(g, "Name", svc.Name)
96104
addStr(g, "Documentation", svc.Documentation)
@@ -115,6 +123,7 @@ func consumedODataServiceToGen(svc *model.ConsumedODataService, configMicroflowK
115123
// Optional by-name / constant references — only emitted when set, matching the
116124
// legacy writer (Studio Pro omits empties).
117125
addStrIf(g, configMicroflowKey, svc.ConfigurationMicroflow)
126+
addStrIf(g, headersMicroflowKey, svc.HeadersMicroflow)
118127
addStrIf(g, "ErrorHandlingMicroflow", svc.ErrorHandlingMicroflow)
119128
addStrIf(g, "ProxyHost", svc.ProxyHost)
120129
addStrIf(g, "ProxyPort", svc.ProxyPort)

mdl/backend/modelsdk/odata_write_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func TestConsumedODataServiceToGen_ConfigMicroflowKey(t *testing.T) {
198198
{"ConfigurationEntityMicroflow", "ConfigurationEntityMicroflow", "ConfigurationMicroflow"},
199199
{"ConfigurationMicroflow", "ConfigurationMicroflow", "ConfigurationEntityMicroflow"},
200200
} {
201-
raw, err := (&codec.Encoder{}).Encode(consumedODataServiceToGen(svc, tc.key))
201+
raw, err := (&codec.Encoder{}).Encode(consumedODataServiceToGen(svc, tc.key, "HeaderListMicroflow"))
202202
if err != nil {
203203
t.Fatalf("encode(%s): %v", tc.key, err)
204204
}

mdl/executor/cmd_odata.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,15 @@ func outputConsumedODataServiceMDL(ctx *ExecContext, svc *model.ConsumedODataSer
174174
}
175175
}
176176

177-
// Microflow reference. Both the "Configuration microflow" and
178-
// "Headers microflow" Studio Pro dropdown options share a single
179-
// BSON field — DESCRIBE outputs `ConfigurationMicroflow` for both;
180-
// the source MDL keyword (`ConfigurationMicroflow` /
181-
// `HeadersMicroflow`) is purely a hint to the reader.
177+
// Microflow references. The "Configuration microflow" and "Headers
178+
// microflow" dropdown options are distinct storage slots (Mendix >= 11.10),
179+
// so DESCRIBE emits the matching MDL keyword for each.
182180
if svc.ConfigurationMicroflow != "" {
183181
props = append(props, fmt.Sprintf(" ConfigurationMicroflow: microflow %s", svc.ConfigurationMicroflow))
184182
}
183+
if svc.HeadersMicroflow != "" {
184+
props = append(props, fmt.Sprintf(" HeadersMicroflow: microflow %s", svc.HeadersMicroflow))
185+
}
185186
if svc.ErrorHandlingMicroflow != "" {
186187
props = append(props, fmt.Sprintf(" ErrorHandlingMicroflow: microflow %s", svc.ErrorHandlingMicroflow))
187188
}
@@ -953,6 +954,9 @@ func createODataClient(ctx *ExecContext, stmt *ast.CreateODataClientStmt) error
953954
if stmt.ConfigurationMicroflow != "" {
954955
svc.ConfigurationMicroflow = extractMicroflowRef(stmt.ConfigurationMicroflow)
955956
}
957+
if stmt.HeadersMicroflow != "" {
958+
svc.HeadersMicroflow = extractMicroflowRef(stmt.HeadersMicroflow)
959+
}
956960
if stmt.ErrorHandlingMicroflow != "" {
957961
svc.ErrorHandlingMicroflow = extractMicroflowRef(stmt.ErrorHandlingMicroflow)
958962
}
@@ -1040,6 +1044,7 @@ func createODataClient(ctx *ExecContext, stmt *ast.CreateODataClientStmt) error
10401044
ProxyType: stmt.ProxyType,
10411045
Description: stmt.Description,
10421046
ConfigurationMicroflow: extractMicroflowRef(stmt.ConfigurationMicroflow),
1047+
HeadersMicroflow: extractMicroflowRef(stmt.HeadersMicroflow),
10431048
ErrorHandlingMicroflow: extractMicroflowRef(stmt.ErrorHandlingMicroflow),
10441049
ProxyHost: stmt.ProxyHost,
10451050
ProxyPort: stmt.ProxyPort,
@@ -1189,9 +1194,10 @@ func alterODataClient(ctx *ExecContext, stmt *ast.AlterODataClientStmt) error {
11891194
svc.HttpConfiguration = &model.HttpConfiguration{}
11901195
}
11911196
svc.HttpConfiguration.ClientCertificate = strVal
1192-
case "configurationmicroflow", "headersmicroflow":
1193-
// Both MDL keywords write to the same BSON field.
1197+
case "configurationmicroflow":
11941198
svc.ConfigurationMicroflow = extractMicroflowRef(strVal)
1199+
case "headersmicroflow":
1200+
svc.HeadersMicroflow = extractMicroflowRef(strVal)
11951201
case "errorhandlingmicroflow":
11961202
svc.ErrorHandlingMicroflow = extractMicroflowRef(strVal)
11971203
case "proxyhost":

mdl/executor/cmd_odata_mock_test.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -665,11 +665,13 @@ func TestCreateODataClient_HeadersMicroflow(t *testing.T) {
665665
if captured == nil {
666666
t.Fatal("CreateConsumedODataService was not called")
667667
}
668-
// Both `HeadersMicroflow` and `ConfigurationMicroflow` MDL keywords
669-
// now map to the same model field (and the same Studio Pro BSON
670-
// field). Studio Pro picks the dropdown label by the microflow's
671-
// return type, not by which field carries the reference.
672-
if captured.ConfigurationMicroflow != "MyModule.SetHeaders" {
673-
t.Errorf("ConfigurationMicroflow = %q, want %q", captured.ConfigurationMicroflow, "MyModule.SetHeaders")
668+
// The `HeadersMicroflow` keyword maps to the distinct HeadersMicroflow
669+
// model field (Studio Pro's HeaderListMicroflow slot on 11.10+), NOT the
670+
// configuration slot — conflating them triggers CE6808 (issue #728).
671+
if captured.HeadersMicroflow != "MyModule.SetHeaders" {
672+
t.Errorf("HeadersMicroflow = %q, want %q", captured.HeadersMicroflow, "MyModule.SetHeaders")
673+
}
674+
if captured.ConfigurationMicroflow != "" {
675+
t.Errorf("ConfigurationMicroflow = %q, want empty (headers keyword must not populate the config slot)", captured.ConfigurationMicroflow)
674676
}
675677
}

mdl/visitor/visitor_odata.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,15 @@ func (b *Builder) ExitCreateODataClientStatement(ctx *parser.CreateODataClientSt
4949
stmt.HttpPassword = value
5050
case "clientcertificate":
5151
stmt.ClientCertificate = value
52-
case "configurationmicroflow", "headersmicroflow":
53-
// Both MDL property names map to the same BSON field
54-
// `ConfigurationMicroflow`. Studio Pro distinguishes the
55-
// "Configuration microflow" and "Headers microflow"
56-
// dropdown options by the microflow's return type:
57-
// - returns System.ConsumedODataConfiguration -> Config
58-
// - returns list of System.HttpHeader -> Headers
59-
// The `HeadersMicroflow` keyword stays for ergonomic MDL
60-
// (so the example MDL can self-document which signature
61-
// the referenced microflow is expected to have).
52+
case "configurationmicroflow":
53+
// "Configuration microflow" — returns System.ConsumedODataConfiguration.
6254
stmt.ConfigurationMicroflow = value
55+
case "headersmicroflow":
56+
// "Headers microflow" — returns list of System.HttpHeader. A distinct
57+
// storage slot from the configuration microflow on Mendix >= 11.10
58+
// (HeaderListMicroflow vs ConfigurationEntityMicroflow); conflating
59+
// them triggers CE6808.
60+
stmt.HeadersMicroflow = value
6361
case "errorhandlingmicroflow":
6462
stmt.ErrorHandlingMicroflow = value
6563
case "proxyhost":

model/odata_config_microflow_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,25 @@ func TestODataConfigMicroflowBSONKey(t *testing.T) {
2626
}
2727
}
2828
}
29+
30+
// TestODataHeadersMicroflowBSONKey guards the headers-microflow slot (issue
31+
// #728): distinct from the configuration slot on 11.10+ (HeaderListMicroflow);
32+
// before 11.10 both shared ConfigurationMicroflow. Writing a headers microflow
33+
// into the configuration slot triggers CE6808.
34+
func TestODataHeadersMicroflowBSONKey(t *testing.T) {
35+
cases := []struct {
36+
major, minor int
37+
want string
38+
}{
39+
{11, 9, "ConfigurationMicroflow"}, // pre-11.10 shared slot
40+
{11, 10, "HeaderListMicroflow"}, // split introduced
41+
{11, 11, "HeaderListMicroflow"}, // reporter's version
42+
{11, 12, "HeaderListMicroflow"}, // doctype project
43+
{12, 0, "HeaderListMicroflow"},
44+
}
45+
for _, c := range cases {
46+
if got := ODataHeadersMicroflowBSONKey(c.major, c.minor); got != c.want {
47+
t.Errorf("ODataHeadersMicroflowBSONKey(%d.%d) = %q, want %q", c.major, c.minor, got, c.want)
48+
}
49+
}
50+
}

model/types.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,12 @@ type ConsumedODataService struct {
327327
// only" (issue #728). The MDL `HeadersMicroflow` keyword is an alias that
328328
// writes to this same field.
329329
ConfigurationMicroflow string `json:"configurationMicroflow,omitempty"` // BSON: version-gated, see ODataConfigMicroflowBSONKey
330+
// HeadersMicroflow is the "Headers microflow" dropdown option (returns a
331+
// list of System.HttpHeader). It is a DIFFERENT storage slot from the
332+
// configuration microflow on Mendix >= 11.10 (see ODataHeadersMicroflowBSONKey):
333+
// mapping it to the configuration slot makes Studio Pro demand a
334+
// System.ConsumedODataConfiguration return type (CE6808).
335+
HeadersMicroflow string `json:"headersMicroflow,omitempty"` // BSON: version-gated, see ODataHeadersMicroflowBSONKey
330336
ErrorHandlingMicroflow string `json:"errorHandlingMicroflow,omitempty"` // BSON: ErrorHandlingMicroflow
331337

332338
// Proxy constant references (BY_NAME to Constants$Constant)
@@ -370,6 +376,19 @@ func ODataConfigMicroflowBSONKeys() []string {
370376
return []string{"ConfigurationEntityMicroflow", "ConfigurationMicroflow", "HeadersMicroflow"}
371377
}
372378

379+
// ODataHeadersMicroflowBSONKey returns the BSON storage field for a consumed
380+
// OData service's "Headers microflow" (returns list of System.HttpHeader). On
381+
// Mendix >= 11.10 this is a distinct slot, HeaderListMicroflow; before 11.10 the
382+
// configuration and headers microflows shared the single ConfigurationMicroflow
383+
// field (Studio Pro distinguished them by the microflow's return type). Writing
384+
// a headers microflow into the configuration slot triggers CE6808 (issue #728).
385+
func ODataHeadersMicroflowBSONKey(major, minor int) string {
386+
if major > 11 || (major == 11 && minor >= 10) {
387+
return "HeaderListMicroflow"
388+
}
389+
return "ConfigurationMicroflow"
390+
}
391+
373392
// HttpConfiguration represents the HTTP transport configuration (Microflows$HttpConfiguration).
374393
type HttpConfiguration struct {
375394
BaseElement

0 commit comments

Comments
 (0)