Skip to content

Commit

Permalink
feat: add support for int, float, and bool on ingress (#905)
Browse files Browse the repository at this point in the history
Some `go` examples:

```go
//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
}
```
  • Loading branch information
wesbillman authored Feb 8, 2024
1 parent 9788c7d commit 0013a43
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 12 deletions.
4 changes: 2 additions & 2 deletions backend/controller/ingress/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ func validateValue(fieldType schema.Type, path path, value any, sch *schema.Sche

case *schema.Int:
switch value := value.(type) {
case float64:
case int64, float64:
typeMatches = true
case string:
if _, err := strconv.ParseFloat(value, 64); err == nil {
if _, err := strconv.ParseInt(value, 10, 64); err == nil {
typeMatches = true
}
}
Expand Down
36 changes: 36 additions & 0 deletions backend/controller/ingress/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"net/http"
"net/url"
"strconv"
"strings"

"github.com/TBD54566975/ftl/backend/controller/dal"
Expand Down Expand Up @@ -101,6 +102,41 @@ func extractHTTPRequestBody(route *dal.IngressRoute, r *http.Request, dataRef *s
}
return string(bodyData), nil

case *schema.Int:
bodyData, err := readRequestBody(r)
if err != nil {
return nil, err
}

intVal, err := strconv.ParseInt(string(bodyData), 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse integer from request body: %w", err)
}
return intVal, nil

case *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 float 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

Expand Down
24 changes: 23 additions & 1 deletion backend/controller/ingress/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"strconv"

"github.com/TBD54566975/ftl/backend/schema"
)
Expand Down Expand Up @@ -47,10 +48,31 @@ func ResponseBodyForVerb(sch *schema.Schema, verb *schema.Verb, body []byte, hea
case *schema.String:
var responseString string
if err := json.Unmarshal(body, &responseString); err != nil {
return nil, fmt.Errorf("HTTP response body is not valid string: %w", err)
return nil, fmt.Errorf("HTTP response body is not a valid string: %w", err)
}
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 a valid int: %w", err)
}
return []byte(strconv.Itoa(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 a valid float: %w", err)
}
return []byte(strconv.FormatFloat(responseFloat, 'f', -1, 64)), nil

case *schema.Bool:
var responseBool bool
if err := json.Unmarshal(body, &responseBool); err != nil {
return nil, fmt.Errorf("HTTP response body is not a valid bool: %w", err)
}
return []byte(strconv.FormatBool(responseBool)), nil

case *schema.Unit:
return []byte{}, nil

Expand Down
16 changes: 16 additions & 0 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.56789"), 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.56789"), 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)
}),
}},
}
})
Expand Down
30 changes: 21 additions & 9 deletions integration/testdata/go/httpingress/echo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
30 changes: 30 additions & 0 deletions integration/testdata/kotlin/httpingress/Echo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,34 @@ class Echo {
body = req.body
)
}

@Verb
@HttpIngress(Method.GET, "/int")
fun int(context: Context, req: HttpRequest<Int>): HttpResponse<Int> {
return HttpResponse<Int>(
status = 200,
headers = mapOf("Int" to arrayListOf("Header from FTL")),
body = req.body
)
}

@Verb
@HttpIngress(Method.GET, "/float")
fun float(context: Context, req: HttpRequest<Double>): HttpResponse<Double> {
return HttpResponse<Double>(
status = 200,
headers = mapOf("Float" to arrayListOf("Header from FTL")),
body = req.body
)
}

@Verb
@HttpIngress(Method.GET, "/bool")
fun bool(context: Context, req: HttpRequest<Boolean>): HttpResponse<Boolean> {
return HttpResponse<Boolean>(
status = 200,
headers = mapOf("Bool" to arrayListOf("Header from FTL")),
body = req.body
)
}
}

0 comments on commit 0013a43

Please sign in to comment.