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: HttpRequest path and query mapping #2422

Merged
merged 1 commit into from
Aug 19, 2024
Merged

Conversation

stuartwdouglas
Copy link
Collaborator

@stuartwdouglas stuartwdouglas commented Aug 19, 2024

fixes: #2230

@stuartwdouglas stuartwdouglas added the run-all A PR with this label will run the full set of CI jobs in the PR rather than in the merge queue label Aug 19, 2024
@stuartwdouglas stuartwdouglas requested review from a team and gak and removed request for a team August 19, 2024 00:35
@ftl-robot ftl-robot mentioned this pull request Aug 19, 2024
@stuartwdouglas stuartwdouglas force-pushed the stuartwdouglas/2230 branch 9 times, most recently from 1fbd7de to 668d0b0 Compare August 19, 2024 05:25
Copy link
Collaborator

@alecthomas alecthomas left a comment

Choose a reason for hiding this comment

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

Awesome!

@@ -17,10 +17,10 @@ type Response struct {
}

//ftl:ingress http GET /test
func Get(ctx context.Context, req builtin.HttpRequest[External]) (builtin.HttpResponse[Response, string], error) {
func Get(ctx context.Context, req builtin.HttpRequest[ftl.Unit, ftl.Unit, External]) (builtin.HttpResponse[Response, string], error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe later we should code-gen some type aliases to eradicate some of the boilerplate, it's pretty verbose.

Something like

type HttpRequestBody[T any] HttpRequest[Unit, Unit, T]

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I had the same thought, we just need to figure out which ones we want to provide and what to name them.

bodyField = field
break
}
}

if bodyField == nil {
return nil, fmt.Errorf("verb %s must have a 'body' field", ref.Name)
return nil, fmt.Errorf("verb %s must have a '%s' field", ref.Name, name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

tip: use %q for quoted strings rather than '%s'

@@ -118,7 +118,7 @@ func TestHttpIngress(t *testing.T) {
assert.Equal(t, []string{"application/json; charset=utf-8"}, resp.Headers["Content-Type"])
assert.Equal(t, in.JsonData(t, []in.Obj{{"item": "a"}, {"item": "b"}}), resp.BodyBytes)
}),
in.HttpCall(http.MethodGet, "/typeenum", nil, in.JsonData(t, in.Obj{"name": "A", "value": "hello"}), func(t testing.TB, resp *in.HTTPResponse) {
in.HttpCall(http.MethodPost, "/typeenum", nil, in.JsonData(t, in.Obj{"name": "A", "value": "hello"}), func(t testing.TB, resp *in.HTTPResponse) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why the method changes?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah because of the request body in the test.

Path: "/multiMapQuery",
Query: map[string][]string{"alias": []string{"value"}},
},
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a test for mapping query and path parameters onto structs?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, there were existing tests that were mapping them to a body struct, these have been changed so now tests are mapping them onto the path or query param struct with a Unit body.

backend/schema/validate.go Outdated Show resolved Hide resolved
case *String, *Data, *Unit, *Float, *Int, *Bool, *Map: // Valid HTTP param payload types.
case *TypeAlias:
// allow aliases of external types
if len(t.Metadata) > 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should check that the metadata is of the expected type, MetadataTypeMap. There's a helper function (that I cannot for the life of me find at the moment) that can extract a concrete type from a list of sum types if it exists.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was part of the original validation logic, I have updated it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Aaah, thanks @stuartwdouglas

switch t := n.(type) {
case *Bytes, *String, *Data, *Unit, *Float, *Int, *Bool, *Map, *Array: // Valid HTTP response payload types.
case *TypeAlias:
// allow aliases of external types
if len(t.Metadata) > 0 {
return nil
}
return validatePayloadType(t.Type, r, v, reqOrResp)
return validateBodyPayloadType(t.Type, r, v, reqOrResp)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here.

case *Data, *Unit, *Map: // Valid HTTP query payload types.
case *TypeAlias:
// allow aliases of external types
if len(t.Metadata) > 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here.

backend/schema/validate_test.go Outdated Show resolved Hide resolved
```go

//ftl:ingress GET /http/users/{userId}/posts
func Get(ctx context.Context, req builtin.HttpRequest[ftl.Unit, int64, GetRequestQueryParams]) (builtin.HttpResponse[GetResponse, ErrorResponse], error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Noice!

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 👍

return float, nil
case *schema.Bool:
// TODO: is anything else considered truthy?
return val == "true", nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we use an LLM here to decide?

queryParamSymbol Symbol
}

func validateIngressResponse(scopes Scopes, module *Module, n *Verb, reqOrResp string, r Type) (merr []error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could reqOrResp be a bool or enum?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's mostly used for constructing error messages, it was part of the existing code.

@stuartwdouglas stuartwdouglas added this pull request to the merge queue Aug 19, 2024
Merged via the queue into main with commit 826dd3f Aug 19, 2024
74 checks passed
@stuartwdouglas stuartwdouglas deleted the stuartwdouglas/2230 branch August 19, 2024 07:30
@wesbillman
Copy link
Collaborator

This is awesome! Thanks @stuartwdouglas!

return builtin.HttpResponse[ftl.Unit, string]{Body: ftl.Some(ftl.Unit{})}, nil
}

//ftl:ingress http GET /string
func String(ctx context.Context, req builtin.HttpRequest[string]) (builtin.HttpResponse[string, string], error) {
//ftl:ingress http POST /string
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did these change to POST?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

They have a body, and GET requests with a body don't make sense.

safeer pushed a commit that referenced this pull request Aug 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
run-all A PR with this label will run the full set of CI jobs in the PR rather than in the merge queue
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Extend builtin.HttpRequest to have separate type parameters for body, path parameters, headers, and query?
4 participants