diff --git a/backend/controller/ingress/request.go b/backend/controller/ingress/request.go index 090fc5f776..71ad5eaadc 100644 --- a/backend/controller/ingress/request.go +++ b/backend/controller/ingress/request.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "net/url" + "strconv" "strings" "github.com/TBD54566975/ftl/backend/controller/dal" @@ -101,6 +102,29 @@ func extractHTTPRequestBody(route *dal.IngressRoute, r *http.Request, dataRef *s } return string(bodyData), nil + case *schema.Int, *schema.Float: + bodyData, err := readRequestBody(r) + if err != nil { + return nil, err + } + + floatVal, err := strconv.ParseFloat(string(bodyData), 64) + if err != nil { + return nil, fmt.Errorf("failed to parse integer from request body: %w", err) + } + return floatVal, nil + + case *schema.Bool: + bodyData, err := readRequestBody(r) + if err != nil { + return nil, err + } + boolVal, err := strconv.ParseBool(string(bodyData)) + if err != nil { + return nil, fmt.Errorf("failed to parse boolean from request body: %w", err) + } + return boolVal, nil + case *schema.Unit: return map[string]any{}, nil diff --git a/backend/controller/ingress/response.go b/backend/controller/ingress/response.go index dcf0f2b5f6..6c45e1f1c6 100644 --- a/backend/controller/ingress/response.go +++ b/backend/controller/ingress/response.go @@ -51,6 +51,27 @@ func ResponseBodyForVerb(sch *schema.Schema, verb *schema.Verb, body []byte, hea } return []byte(responseString), nil + case *schema.Int: + var responseInt int + if err := json.Unmarshal(body, &responseInt); err != nil { + return nil, fmt.Errorf("HTTP response body is not valid int: %w", err) + } + return []byte(fmt.Sprintf("%d", responseInt)), nil + + case *schema.Float: + var responseFloat float64 + if err := json.Unmarshal(body, &responseFloat); err != nil { + return nil, fmt.Errorf("HTTP response body is not valid float: %w", err) + } + return []byte(fmt.Sprintf("%f", responseFloat)), nil + + case *schema.Bool: + var responseBool bool + if err := json.Unmarshal(body, &responseBool); err != nil { + return nil, fmt.Errorf("HTTP response body is not valid bool: %w", err) + } + return []byte(fmt.Sprintf("%t", responseBool)), nil + case *schema.Unit: return []byte{}, nil diff --git a/integration/integration_test.go b/integration/integration_test.go index f6b6cb949d..ced671b6d0 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -127,6 +127,22 @@ func TestHttpIngress(t *testing.T) { assert.Equal(t, []string{"text/plain; charset=utf-8"}, resp.headers["Content-Type"]) assert.Equal(t, []byte("Hello, World!"), resp.bodyBytes) }), + + httpCall(rd, http.MethodGet, "/int", []byte("1234"), func(t testing.TB, resp *httpResponse) { + assert.Equal(t, 200, resp.status) + assert.Equal(t, []string{"text/plain; charset=utf-8"}, resp.headers["Content-Type"]) + assert.Equal(t, []byte("1234"), resp.bodyBytes) + }), + httpCall(rd, http.MethodGet, "/float", []byte("1234.567890"), func(t testing.TB, resp *httpResponse) { + assert.Equal(t, 200, resp.status) + assert.Equal(t, []string{"text/plain; charset=utf-8"}, resp.headers["Content-Type"]) + assert.Equal(t, []byte("1234.567890"), resp.bodyBytes) + }), + httpCall(rd, http.MethodGet, "/bool", []byte("true"), func(t testing.TB, resp *httpResponse) { + assert.Equal(t, 200, resp.status) + assert.Equal(t, []string{"text/plain; charset=utf-8"}, resp.headers["Content-Type"]) + assert.Equal(t, []byte("true"), resp.bodyBytes) + }), }}, } }) @@ -409,6 +425,8 @@ func httpCall(rd runtimeData, method string, path string, body []byte, onRespons bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err) + fmt.Printf("bodyBytes: %s\n", string(bodyBytes)) + var resBody map[string]any // ignore the error here since some responses are just `[]byte`. _ = json.Unmarshal(bodyBytes, &resBody) diff --git a/integration/testdata/go/httpingress/echo.go b/integration/testdata/go/httpingress/echo.go index ec20cd8997..c3e1f71000 100644 --- a/integration/testdata/go/httpingress/echo.go +++ b/integration/testdata/go/httpingress/echo.go @@ -103,23 +103,35 @@ func Html(ctx context.Context, req builtin.HttpRequest[HtmlRequest]) (builtin.Ht //ftl:verb //ftl:ingress http POST /bytes func Bytes(ctx context.Context, req builtin.HttpRequest[[]byte]) (builtin.HttpResponse[[]byte], error) { - return builtin.HttpResponse[[]byte]{ - Body: req.Body, - }, nil + return builtin.HttpResponse[[]byte]{Body: req.Body}, nil } //ftl:verb //ftl:ingress http GET /empty func Empty(ctx context.Context, req builtin.HttpRequest[ftl.Unit]) (builtin.HttpResponse[ftl.Unit], error) { - return builtin.HttpResponse[ftl.Unit]{ - Body: ftl.Unit{}, - }, nil + return builtin.HttpResponse[ftl.Unit]{Body: ftl.Unit{}}, nil } //ftl:verb //ftl:ingress http GET /string func String(ctx context.Context, req builtin.HttpRequest[string]) (builtin.HttpResponse[string], error) { - return builtin.HttpResponse[string]{ - Body: req.Body, - }, nil + return builtin.HttpResponse[string]{Body: req.Body}, nil +} + +//ftl:verb +//ftl:ingress http GET /int +func Int(ctx context.Context, req builtin.HttpRequest[int]) (builtin.HttpResponse[int], error) { + return builtin.HttpResponse[int]{Body: req.Body}, nil +} + +//ftl:verb +//ftl:ingress http GET /float +func Float(ctx context.Context, req builtin.HttpRequest[float64]) (builtin.HttpResponse[float64], error) { + return builtin.HttpResponse[float64]{Body: req.Body}, nil +} + +//ftl:verb +//ftl:ingress http GET /bool +func Bool(ctx context.Context, req builtin.HttpRequest[bool]) (builtin.HttpResponse[bool], error) { + return builtin.HttpResponse[bool]{Body: req.Body}, nil } diff --git a/integration/testdata/kotlin/httpingress/Echo.kt b/integration/testdata/kotlin/httpingress/Echo.kt index 379df6870f..0eb3c65cbd 100644 --- a/integration/testdata/kotlin/httpingress/Echo.kt +++ b/integration/testdata/kotlin/httpingress/Echo.kt @@ -127,4 +127,34 @@ class Echo { body = req.body ) } + + @Verb + @HttpIngress(Method.GET, "/int") + fun int(context: Context, req: HttpRequest): HttpResponse { + return HttpResponse( + status = 200, + headers = mapOf("Int" to arrayListOf("Header from FTL")), + body = req.body + ) + } + + @Verb + @HttpIngress(Method.GET, "/float") + fun float(context: Context, req: HttpRequest): HttpResponse { + return HttpResponse( + status = 200, + headers = mapOf("Float" to arrayListOf("Header from FTL")), + body = req.body + ) + } + + @Verb + @HttpIngress(Method.GET, "/bool") + fun bool(context: Context, req: HttpRequest): HttpResponse { + return HttpResponse( + status = 200, + headers = mapOf("Bool" to arrayListOf("Header from FTL")), + body = req.body + ) + } }