Skip to content

Commit fb69f79

Browse files
authored
Merge pull request #378 from hjotha/submit/builder-rest-httpresponse-result-handling
fix: emit ResultHandlingHttpResponse for REST 'returns response'
2 parents ed4b670 + 9e39cad commit fb69f79

8 files changed

Lines changed: 262 additions & 3 deletions
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
-- ============================================================================
2+
-- Bug #375: REST HttpResponse result handling serialized as string
3+
-- ============================================================================
4+
--
5+
-- Symptom (before fix):
6+
-- When a Studio Pro REST call action bound the complete HTTP response
7+
-- to an output variable, the parser/AST captured this as
8+
-- `ResultHandlingHttpResponse` and the action-level
9+
-- `ResultHandlingType` was `HttpResponse`. The writer, however, did
10+
-- NOT have an explicit branch for `ResultHandlingHttpResponse` inside
11+
-- `serializeRestResultHandling`, so it fell through to the string
12+
-- default. The serialized result variable was emitted as
13+
-- `DataTypes$StringType`, not the expected
14+
-- `DataTypes$ObjectType` bound to `System.HttpResponse`.
15+
-- Studio Pro then surfaced a type mismatch on the bound output
16+
-- variable, and dependent activities that reached into the response
17+
-- (`$Response/Content`, `$Response/StatusCode`) failed validation
18+
-- with CE0117.
19+
--
20+
-- After fix (writer only):
21+
-- `serializeRestResultHandling` now has an explicit
22+
-- `ResultHandlingHttpResponse` branch that emits a `DataTypes$ObjectType`
23+
-- variable type bound to `System.HttpResponse`.
24+
--
25+
-- Scope note — known related gap:
26+
-- The MDL builder for `rest call ... returns response` still creates
27+
-- a `ResultHandlingString` (not `ResultHandlingHttpResponse`). See
28+
-- `mdl/executor/cmd_microflows_builder_calls.go` — the
29+
-- `case ast.RestResultResponse` arm carries the comment
30+
-- "we would need a different result type, but using String for now".
31+
-- That means newly authored MDL with `returns response` does NOT yet
32+
-- trigger the new writer branch. PR #376 fixes the symptom for
33+
-- existing Studio Pro models that already contain
34+
-- `ResultHandlingHttpResponse` in their BSON; the BUILDER side is
35+
-- tracked separately and verified by the SDK-level Go test
36+
-- `TestSerializeRestResultHandlingHttpResponseUsesObjectType`, which
37+
-- constructs the AST directly.
38+
--
39+
-- Usage (positive control):
40+
-- mxcli exec mdl-examples/bug-tests/375-rest-httpresponse-result-type.mdl -p app.mpr
41+
-- `mx check` against the resulting MPR must report 0 errors. The
42+
-- negative case (a Studio Pro REST call with HttpResponse binding)
43+
-- needs the existing BSON shape and is covered by the Go test above.
44+
-- ============================================================================
45+
46+
create module BugTest375;
47+
48+
-- Inline REST call returning a string. Once the builder learns to emit
49+
-- ResultHandlingHttpResponse for `returns response`, this script can be
50+
-- updated to use `returns response` and exercise the writer branch end
51+
-- to end.
52+
create microflow BugTest375.MF_RestCall ()
53+
returns boolean as $Ok
54+
begin
55+
declare $Ok boolean = false;
56+
57+
$Body = rest call get 'https://httpbin.org/get'
58+
header Accept = 'application/json'
59+
timeout 15
60+
returns string
61+
on error continue;
62+
63+
if $Body != '' then
64+
set $Ok = true;
65+
end if;
66+
67+
return $Ok;
68+
end;
69+
/
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
-- ============================================================================
2+
-- Bug #377: Builder emitted ResultHandlingString for `returns response`
3+
-- ============================================================================
4+
--
5+
-- Symptom (before fix):
6+
-- When MDL authored a REST call with `returns response` (intent: bind
7+
-- the full HttpResponse object to the output variable), the microflow
8+
-- builder constructed a `ResultHandlingString` instead of a
9+
-- `ResultHandlingHttpResponse`. The action-level `ResultHandlingType`
10+
-- then derived as `String`, and the writer serialized the result
11+
-- variable as `DataTypes$StringType`. Studio Pro treated the bound
12+
-- variable as a string, so dependent activities reaching into the
13+
-- response (`$Response/Content`, `$Response/StatusCode`) failed
14+
-- validation with CE0117.
15+
--
16+
-- The builder file `cmd_microflows_builder_calls.go` carried an
17+
-- explicit TODO comment for this case:
18+
-- "we would need a different result type, but using String for now".
19+
--
20+
-- After fix:
21+
-- The `ast.RestResultResponse` arm now constructs a
22+
-- `ResultHandlingHttpResponse{VariableName: s.OutputVariable}`. The
23+
-- writer's existing branch (added in PR #376) then emits a bound
24+
-- `Microflows$ResultHandling` whose `VariableType` is
25+
-- `DataTypes$ObjectType` with entity `System.HttpResponse`, and the
26+
-- action-level `ResultHandlingType` is correctly derived as
27+
-- `HttpResponse`.
28+
--
29+
-- Dependency:
30+
-- This bug-test relies on the writer fix from PR #376 being present
31+
-- in the same branch — without it, the action-level type is still
32+
-- `HttpResponse` but the nested ResultHandling falls through to the
33+
-- string default. This branch is stacked on PR #376 specifically so
34+
-- the round-trip can be verified end to end.
35+
--
36+
-- Usage:
37+
-- mxcli exec mdl-examples/bug-tests/377-rest-returns-response-builder.mdl -p app.mpr
38+
-- mxcli -p app.mpr -c "describe microflow BugTest377.MF_GetResponse"
39+
-- `mx check` against the resulting MPR must report 0 errors and the
40+
-- `$Response/Content` / `$Response/StatusCode` references must
41+
-- resolve in Studio Pro.
42+
-- ============================================================================
43+
44+
create module BugTest377;
45+
46+
-- Inline REST call binding the full HttpResponse to the output variable.
47+
-- The downstream `if` and `log` reach into the response, so the bound
48+
-- variable's type must resolve to `System.HttpResponse` for `mx check`
49+
-- to pass.
50+
create microflow BugTest377.MF_GetResponse ()
51+
returns boolean as $Ok
52+
begin
53+
declare $Ok boolean = false;
54+
55+
$Response = rest call get 'https://httpbin.org/get'
56+
header Accept = 'application/json'
57+
timeout 15
58+
returns response
59+
on error continue;
60+
61+
if $Response != empty and $Response/StatusCode = 200 then
62+
set $Ok = true;
63+
end if;
64+
65+
return $Ok;
66+
end;
67+
/

mdl/executor/cmd_microflows_builder_calls.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -729,10 +729,14 @@ func (fb *flowBuilder) addRestCallAction(s *ast.RestCallStmt) model.ID {
729729
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
730730
}
731731
case ast.RestResultResponse:
732-
resultHandling = &microflows.ResultHandlingString{
733-
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
732+
// Bind the full HTTP response object to the output variable. The writer
733+
// emits the matching `DataTypes$ObjectType` bound to System.HttpResponse;
734+
// the action-level `ResultHandlingType` is derived as "HttpResponse" from
735+
// this concrete type.
736+
resultHandling = &microflows.ResultHandlingHttpResponse{
737+
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
738+
VariableName: s.OutputVariable,
734739
}
735-
// Note: For HttpResponse, we would need a different result type, but using String for now
736740
case ast.RestResultMapping:
737741
mappingQN := s.Result.MappingName.Module + "." + s.Result.MappingName.Name
738742
entityQN := s.Result.ResultEntity.Module + "." + s.Result.ResultEntity.Name
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package executor
4+
5+
import (
6+
"testing"
7+
8+
"github.com/mendixlabs/mxcli/mdl/ast"
9+
"github.com/mendixlabs/mxcli/sdk/microflows"
10+
)
11+
12+
// TestAddRestCallAction_ReturnsResponseUsesHttpResponseHandling pins the
13+
// builder behavior for issue #377: when MDL authors `rest call ... returns
14+
// response`, the builder must construct a ResultHandlingHttpResponse so the
15+
// writer's matching branch (PR #376) emits a DataTypes$ObjectType bound to
16+
// System.HttpResponse, instead of falling back to a string variable.
17+
func TestAddRestCallAction_ReturnsResponseUsesHttpResponseHandling(t *testing.T) {
18+
fb := &flowBuilder{
19+
posX: 100,
20+
posY: 100,
21+
spacing: HorizontalSpacing,
22+
varTypes: map[string]string{},
23+
declaredVars: map[string]string{},
24+
measurer: &layoutMeasurer{},
25+
}
26+
27+
stmt := &ast.RestCallStmt{
28+
OutputVariable: "Response",
29+
Method: ast.HttpMethodGet,
30+
URL: &ast.LiteralExpr{Kind: ast.LiteralString, Value: "https://example.com"},
31+
Result: ast.RestResult{Type: ast.RestResultResponse},
32+
}
33+
fb.addRestCallAction(stmt)
34+
35+
if len(fb.objects) == 0 {
36+
t.Fatalf("expected one activity, got %d", len(fb.objects))
37+
}
38+
39+
activity, ok := fb.objects[0].(*microflows.ActionActivity)
40+
if !ok {
41+
t.Fatalf("first object is %T, want *microflows.ActionActivity", fb.objects[0])
42+
}
43+
action, ok := activity.Action.(*microflows.RestCallAction)
44+
if !ok {
45+
t.Fatalf("activity.Action is %T, want *microflows.RestCallAction", activity.Action)
46+
}
47+
48+
httpResponse, ok := action.ResultHandling.(*microflows.ResultHandlingHttpResponse)
49+
if !ok {
50+
t.Fatalf("ResultHandling is %T, want *microflows.ResultHandlingHttpResponse", action.ResultHandling)
51+
}
52+
if httpResponse.VariableName != "Response" {
53+
t.Errorf("VariableName = %q, want %q", httpResponse.VariableName, "Response")
54+
}
55+
}

mdl/executor/cmd_microflows_format_action.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,8 @@ func formatRestCallAction(ctx *ExecContext, a *microflows.RestCallAction) string
937937
case *microflows.ResultHandlingString:
938938
sb.WriteString("String")
939939
_ = rh // used for type assertion only
940+
case *microflows.ResultHandlingHttpResponse:
941+
sb.WriteString("response")
940942
case *microflows.ResultHandlingMapping:
941943
sb.WriteString("mapping ")
942944
sb.WriteString(string(rh.MappingID))

mdl/executor/cmd_microflows_format_restcall_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ func TestFormatRestCallAction_GET(t *testing.T) {
3131
assertContains(t, got, "returns String")
3232
}
3333

34+
func TestFormatRestCallAction_HttpResponse(t *testing.T) {
35+
e := newTestExecutor()
36+
action := &microflows.RestCallAction{
37+
HttpConfiguration: &microflows.HttpConfiguration{
38+
HttpMethod: microflows.HttpMethodGet,
39+
LocationTemplate: "https://api.example.com/orders",
40+
},
41+
ResultHandling: &microflows.ResultHandlingHttpResponse{VariableName: "Response"},
42+
}
43+
got := e.formatRestCallAction(action)
44+
assertContains(t, got, "$Response = ")
45+
assertContains(t, got, "returns response")
46+
}
47+
3448
func TestFormatRestCallAction_POST_CustomBody(t *testing.T) {
3549
e := newTestExecutor()
3650
action := &microflows.RestCallAction{

sdk/mpr/writer_microflow_actions.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,20 @@ func serializeRestResultHandling(rh microflows.ResultHandling, outputVar string)
813813
)
814814
return doc
815815

816+
case *microflows.ResultHandlingHttpResponse:
817+
return bson.D{
818+
{Key: "$ID", Value: idToBsonBinary(string(h.ID))},
819+
{Key: "$Type", Value: "Microflows$ResultHandling"},
820+
{Key: "Bind", Value: outputVar != ""},
821+
{Key: "ImportMappingCall", Value: nil},
822+
{Key: "ResultVariableName", Value: outputVar},
823+
{Key: "VariableType", Value: bson.D{
824+
{Key: "$ID", Value: idToBsonBinary(GenerateID())},
825+
{Key: "$Type", Value: "DataTypes$ObjectType"},
826+
{Key: "Entity", Value: "System.HttpResponse"},
827+
}},
828+
}
829+
816830
case *microflows.ResultHandlingNone:
817831
return bson.D{
818832
{Key: "$ID", Value: idToBsonBinary(string(h.ID))},
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package mpr
4+
5+
import (
6+
"testing"
7+
8+
"github.com/mendixlabs/mxcli/model"
9+
"github.com/mendixlabs/mxcli/sdk/microflows"
10+
"go.mongodb.org/mongo-driver/bson"
11+
)
12+
13+
func TestSerializeRestResultHandlingHttpResponseUsesObjectType(t *testing.T) {
14+
handling := &microflows.ResultHandlingHttpResponse{
15+
BaseElement: model.BaseElement{ID: "result-1"},
16+
VariableName: "HttpResponse",
17+
}
18+
19+
doc := serializeRestResultHandling(handling, "HttpResponse")
20+
21+
if got := getBSONField(doc, "ResultVariableName"); got != "HttpResponse" {
22+
t.Fatalf("ResultVariableName = %#v, want HttpResponse", got)
23+
}
24+
variableType, ok := getBSONField(doc, "VariableType").(bson.D)
25+
if !ok {
26+
t.Fatalf("VariableType is %T, want bson.D", getBSONField(doc, "VariableType"))
27+
}
28+
if got := getBSONField(variableType, "$Type"); got != "DataTypes$ObjectType" {
29+
t.Fatalf("VariableType.$Type = %#v, want DataTypes$ObjectType", got)
30+
}
31+
if got := getBSONField(variableType, "Entity"); got != "System.HttpResponse" {
32+
t.Fatalf("VariableType.Entity = %#v, want System.HttpResponse", got)
33+
}
34+
}

0 commit comments

Comments
 (0)