Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for int, float, and bool on ingress #905

Merged
merged 1 commit into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
)
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the tests!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me too!

@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
)
}
}