From cfe1a463a905b214c5060d53a9fbafea48ac6b30 Mon Sep 17 00:00:00 2001 From: ekerfelt Date: Mon, 30 May 2022 20:47:58 +0200 Subject: [PATCH] compiler/internal/codegen: Handle nil pointer responses (#254) This PR adds logic to handle nil pointers when serializing the response in the generated request handlers. --- cli/daemon/run/run_test.go | 8 + cli/daemon/run/testdata/echo/echo/echo.go | 6 + cli/daemon/run/testdata/echo_client/client.ts | 9 + .../run/testdata/echo_client/client/client.go | 14 + compiler/internal/codegen/codegen_main.go | 83 ++-- .../TestCodeGenMain__token_auth.golden | 25 +- .../testdata/TestCodeGenMain__variants.golden | 401 ++++++++++++++++-- .../internal/codegen/testdata/variants.txt | 21 + 8 files changed, 478 insertions(+), 89 deletions(-) diff --git a/cli/daemon/run/run_test.go b/cli/daemon/run/run_test.go index ddb3601493..2490ca07af 100644 --- a/cli/daemon/run/run_test.go +++ b/cli/daemon/run/run_test.go @@ -271,6 +271,14 @@ func TestEndToEndWithApp(t *testing.T) { c.Assert(w2.Body.Bytes(), qt.DeepEquals, w2.Body.Bytes()) } + // Call an endpoint without request parameters, returning nil + { + w := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/echo.NilResponse", nil) + run.ServeHTTP(w, req) + c.Assert(w.Code, qt.Equals, 200) + } + // Call an endpoint without request parameters and response value { w := httptest.NewRecorder() diff --git a/cli/daemon/run/testdata/echo/echo/echo.go b/cli/daemon/run/testdata/echo/echo/echo.go index 96f851074d..cd53057e4d 100644 --- a/cli/daemon/run/testdata/echo/echo/echo.go +++ b/cli/daemon/run/testdata/echo/echo/echo.go @@ -116,6 +116,12 @@ func Noop(ctx context.Context) error { return nil } +// NilResponse returns a nil response and nil error +//encore:api public method=GET,POST +func NilResponse(ctx context.Context) (*BasicData, error) { + return nil, nil +} + // MuteEcho absorbs a request //encore:api public method=GET func MuteEcho(ctx context.Context, params Data[string, string]) error { diff --git a/cli/daemon/run/testdata/echo_client/client.ts b/cli/daemon/run/testdata/echo_client/client.ts index f2235e9138..654597cbc3 100644 --- a/cli/daemon/run/testdata/echo_client/client.ts +++ b/cli/daemon/run/testdata/echo_client/client.ts @@ -234,6 +234,15 @@ export namespace echo { await this.baseClient.callAPI("GET", `/echo.MuteEcho`, undefined, {query}) } + /** + * NilResponse returns a nil response and nil error + */ + public async NilResponse(): Promise { + // Now make the actual call to the API + const resp = await this.baseClient.callAPI("POST", `/echo.NilResponse`) + return await resp.json() as BasicData + } + /** * NonBasicEcho echoes back the request data. */ diff --git a/cli/daemon/run/testdata/echo_client/client/client.go b/cli/daemon/run/testdata/echo_client/client/client.go index 29c6aa64fe..52b58f87e9 100644 --- a/cli/daemon/run/testdata/echo_client/client/client.go +++ b/cli/daemon/run/testdata/echo_client/client/client.go @@ -189,6 +189,9 @@ type EchoClient interface { // MuteEcho absorbs a request MuteEcho(ctx context.Context, params EchoData[string, string]) error + // NilResponse returns a nil response and nil error + NilResponse(ctx context.Context) (EchoBasicData, error) + // NonBasicEcho echoes back the request data. NonBasicEcho(ctx context.Context, pathString string, pathInt int, pathWild []string, params EchoNonBasicData) (EchoNonBasicData, error) @@ -307,6 +310,17 @@ func (c *echoClient) MuteEcho(ctx context.Context, params EchoData[string, strin return err } +// NilResponse returns a nil response and nil error +func (c *echoClient) NilResponse(ctx context.Context) (resp EchoBasicData, err error) { + // Now make the actual call to the API + _, err = callAPI(ctx, c.base, "POST", "/echo.NilResponse", nil, nil, &resp) + if err != nil { + return + } + + return +} + // NonBasicEcho echoes back the request data. func (c *echoClient) NonBasicEcho(ctx context.Context, pathString string, pathInt int, pathWild []string, params EchoNonBasicData) (resp EchoNonBasicData, err error) { // Convert our params into the objects we need for the request diff --git a/compiler/internal/codegen/codegen_main.go b/compiler/internal/codegen/codegen_main.go index ec15ee8ab6..7be0de160e 100644 --- a/compiler/internal/codegen/codegen_main.go +++ b/compiler/internal/codegen/codegen_main.go @@ -289,51 +289,68 @@ func (b *Builder) buildRPC(svc *est.Service, rpc *est.RPC) *Statement { func (b *Builder) encodeResponse(g *Group, rpc *est.RPC) { g.Comment("Serialize the response") - g.Var().Id("respData").Index().Byte() resp, err := encoding.DescribeResponse(b.res.Meta, rpc.Response.Type, nil) if err != nil { b.errors.Addf(rpc.Func.Pos(), "failed to describe response: %v", err.Error()) } if len(resp.BodyParameters) > 0 { - g.Line().Comment("Encode JSON body") - g.List(Id("respData"), Err()).Op("=").Qual("encore.dev/runtime/serde", "SerializeJSONFunc").Call(Id("json"), Func().Params(Id("ser").Op("*").Qual("encore.dev/runtime/serde", "JSONSerializer")).BlockFunc( - func(g *Group) { - for _, f := range resp.BodyParameters { - g.Add(Id("ser").Dot("WriteField").Call(Lit(f.Name), Id("resp").Dot(f.SrcName), Lit(f.OmitEmpty))) - } - })) - g.If(Err().Op("!=").Nil()).Block( - Id("marshalErr").Op(":=").Add(wrapErrCode(Err(), "Internal", "failed to marshal response")), - Qual("encore.dev/runtime", "FinishRequest").Call(Nil(), Id("marshalErr")), - Qual("encore.dev/beta/errs", "HTTPError").Call(Id("w"), Id("marshalErr")), - Return(), - ) + g.Id("respData").Op(":=").Index().Byte().Parens(Lit("null\n")) + } else { + g.Id("respData").Op(":=").Index().Byte().Values(LitRune('\n')) } - if len(resp.HeaderParameters) > 0 { - headerEncoder := b.marshaller.NewPossibleInstance("headerEncoder") - g.Line().Comment("Encode headers") - headerEncoder.Add(Id("headers").Op(":=").Map(String()).Index().String().ValuesFunc( - func(g *Group) { - for _, f := range resp.HeaderParameters { - headerSlice, err := headerEncoder.ToStringSlice(f.Type, Id("resp").Dot(f.SrcName)) - if err != nil { - b.errors.Addf(rpc.Func.Pos(), "failed to generate haader serializers: %v", err.Error()) + g.Var().Id("headers").Map(String()).Index().String() + } + + responseEncoder := CustomFunc(Options{Separator: "\n"}, func(g *Group) { + if len(resp.BodyParameters) > 0 { + g.Comment("Encode JSON body") + g.List(Id("respData"), Err()).Op("=").Qual("encore.dev/runtime/serde", "SerializeJSONFunc").Call(Id("json"), Func().Params(Id("ser").Op("*").Qual("encore.dev/runtime/serde", "JSONSerializer")).BlockFunc( + func(g *Group) { + for _, f := range resp.BodyParameters { + g.Add(Id("ser").Dot("WriteField").Call(Lit(f.Name), Id("resp").Dot(f.SrcName), Lit(f.OmitEmpty))) } - g.Add(Lit(f.Name).Op(":").Add(headerSlice)) - } - })) - g.Add(headerEncoder.Finalize( - Id("headerErr").Op(":=").Add(wrapErrCode(Id("headerEncoder").Dot("LastError"), "Internal", "failed to marshal headers")), - Qual("encore.dev/runtime", "FinishRequest").Call(Nil(), Id("headerErr")), - Qual("encore.dev/beta/errs", "HTTPError").Call(Id("w"), Id("headerErr")), - Return(), - )...) + })) + g.If(Err().Op("!=").Nil()).Block( + Id("marshalErr").Op(":=").Add(wrapErrCode(Err(), "Internal", "failed to marshal response")), + Qual("encore.dev/runtime", "FinishRequest").Call(Nil(), Id("marshalErr")), + Qual("encore.dev/beta/errs", "HTTPError").Call(Id("w"), Id("marshalErr")), + Return(), + ) + g.Id("respData").Op("=").Append(Id("respData"), LitRune('\n')) + } + + if len(resp.HeaderParameters) > 0 { + headerEncoder := b.marshaller.NewPossibleInstance("headerEncoder") + g.Line().Comment("Encode headers") + headerEncoder.Add(Id("headers").Op("=").Map(String()).Index().String().ValuesFunc( + func(g *Group) { + for _, f := range resp.HeaderParameters { + headerSlice, err := headerEncoder.ToStringSlice(f.Type, Id("resp").Dot(f.SrcName)) + if err != nil { + b.errors.Addf(rpc.Func.Pos(), "failed to generate haader serializers: %v", err.Error()) + } + g.Add(Lit(f.Name).Op(":").Add(headerSlice)) + } + })) + g.Add(headerEncoder.Finalize( + Id("headerErr").Op(":=").Add(wrapErrCode(Id("headerEncoder").Dot("LastError"), "Internal", "failed to marshal headers")), + Qual("encore.dev/runtime", "FinishRequest").Call(Nil(), Id("headerErr")), + Qual("encore.dev/beta/errs", "HTTPError").Call(Id("w"), Id("headerErr")), + Return(), + )...) + } + }) + + // If response is a ptr we need to check it's not nil + if rpc.Response.IsPtr { + g.If(Id("resp").Op("!=").Nil()).Block(responseEncoder) + } else { + g.Add(responseEncoder) } g.Line().Comment("Record tracing data") - g.Id("respData").Op("=").Append(Id("respData"), LitRune('\n')) g.Id("output").Op(":=").Index().Index().Byte().Values(Id("respData")) g.Qual("encore.dev/runtime", "FinishRequest").Call(Id("output"), Nil()) diff --git a/compiler/internal/codegen/testdata/TestCodeGenMain__token_auth.golden b/compiler/internal/codegen/testdata/TestCodeGenMain__token_auth.golden index e057d6d253..8d01552e2d 100644 --- a/compiler/internal/codegen/testdata/TestCodeGenMain__token_auth.golden +++ b/compiler/internal/codegen/testdata/TestCodeGenMain__token_auth.golden @@ -123,21 +123,22 @@ func __encore_svc_Eight(w http.ResponseWriter, req *http.Request, ps httprouter. } // Serialize the response - var respData []byte - - // Encode JSON body - respData, err = serde.SerializeJSONFunc(json, func(ser *serde.JSONSerializer) { - ser.WriteField("Message", resp.Message, false) - }) - if err != nil { - marshalErr := errs.WrapCode(err, errs.Internal, "failed to marshal response") - runtime.FinishRequest(nil, marshalErr) - errs.HTTPError(w, marshalErr) - return + respData := []byte("null\n") + if resp != nil { + // Encode JSON body + respData, err = serde.SerializeJSONFunc(json, func(ser *serde.JSONSerializer) { + ser.WriteField("Message", resp.Message, false) + }) + if err != nil { + marshalErr := errs.WrapCode(err, errs.Internal, "failed to marshal response") + runtime.FinishRequest(nil, marshalErr) + errs.HTTPError(w, marshalErr) + return + } + respData = append(respData, '\n') } // Record tracing data - respData = append(respData, '\n') output := [][]byte{respData} runtime.FinishRequest(output, nil) diff --git a/compiler/internal/codegen/testdata/TestCodeGenMain__variants.golden b/compiler/internal/codegen/testdata/TestCodeGenMain__variants.golden index 87597d9d85..c81ad013de 100644 --- a/compiler/internal/codegen/testdata/TestCodeGenMain__variants.golden +++ b/compiler/internal/codegen/testdata/TestCodeGenMain__variants.golden @@ -48,7 +48,7 @@ func __encore_svc_CronOne(w http.ResponseWriter, req *http.Request, ps httproute err = runtime.BeginRequest(ctx, runtime.RequestData{ AuthData: authData, Endpoint: "CronOne", - EndpointExprIdx: 14, + EndpointExprIdx: 16, Inputs: nil, Path: req.URL.Path, Service: "svc", @@ -108,7 +108,7 @@ func __encore_svc_Eight(w http.ResponseWriter, req *http.Request, ps httprouter. err = runtime.BeginRequest(ctx, runtime.RequestData{ AuthData: authData, Endpoint: "Eight", - EndpointExprIdx: 15, + EndpointExprIdx: 17, Inputs: inputs, Path: req.URL.Path, PathSegments: ps, @@ -145,21 +145,22 @@ func __encore_svc_Eight(w http.ResponseWriter, req *http.Request, ps httprouter. } // Serialize the response - var respData []byte - - // Encode JSON body - respData, err = serde.SerializeJSONFunc(json, func(ser *serde.JSONSerializer) { - ser.WriteField("Message", resp.Message, false) - }) - if err != nil { - marshalErr := errs.WrapCode(err, errs.Internal, "failed to marshal response") - runtime.FinishRequest(nil, marshalErr) - errs.HTTPError(w, marshalErr) - return + respData := []byte("null\n") + if resp != nil { + // Encode JSON body + respData, err = serde.SerializeJSONFunc(json, func(ser *serde.JSONSerializer) { + ser.WriteField("Message", resp.Message, false) + }) + if err != nil { + marshalErr := errs.WrapCode(err, errs.Internal, "failed to marshal response") + runtime.FinishRequest(nil, marshalErr) + errs.HTTPError(w, marshalErr) + return + } + respData = append(respData, '\n') } // Record tracing data - respData = append(respData, '\n') output := [][]byte{respData} runtime.FinishRequest(output, nil) @@ -226,7 +227,7 @@ func __encore_svc_Five(w http.ResponseWriter, req *http.Request, ps httprouter.P err = runtime.BeginRequest(ctx, runtime.RequestData{ AuthData: authData, Endpoint: "Five", - EndpointExprIdx: 16, + EndpointExprIdx: 18, Inputs: inputs, Path: req.URL.Path, PathSegments: ps, @@ -294,7 +295,7 @@ func __encore_svc_Four(w http.ResponseWriter, req *http.Request, ps httprouter.P err = runtime.BeginRequest(ctx, runtime.RequestData{ AuthData: authData, Endpoint: "Four", - EndpointExprIdx: 17, + EndpointExprIdx: 19, Inputs: inputs, Path: req.URL.Path, PathSegments: ps, @@ -335,6 +336,103 @@ func __encore_svc_Four(w http.ResponseWriter, req *http.Request, ps httprouter.P w.WriteHeader(200) } +func __encore_svc_Nine(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { + ctx := req.Context() + runtime.BeginOperation() + defer runtime.FinishOperation() + + var err error + dec := &marshaller{} + // Decode request + if value, err := url.PathUnescape(ps[0].Value); err == nil { + ps[0].Value = value + } + p0 := dec.ToString("bar", ps[0].Value, true) + if value, err := url.PathUnescape(ps[1].Value); err == nil { + ps[1].Value = value + } + p1 := dec.ToString("baz", ps[1].Value, true) + inputs, _ := runtime.SerializeInputs(p0, p1) + + uid, authData, proceed := __encore_authenticate(w, req, false, "svc", "Nine") + if !proceed { + return + } + + err = runtime.BeginRequest(ctx, runtime.RequestData{ + AuthData: authData, + Endpoint: "Nine", + EndpointExprIdx: 20, + Inputs: inputs, + Path: req.URL.Path, + PathSegments: ps, + Service: "svc", + Type: runtime.RPCCall, + UID: uid, + }) + if err != nil { + errs.HTTPError(w, errs.B().Code(errs.Internal).Msg("internal error").Err()) + return + } + if dec.LastError != nil { + err := dec.LastError + runtime.FinishRequest(nil, err) + errs.HTTPError(w, err) + return + } + + // Call the endpoint + defer func() { + // Catch handler panic + if e := recover(); e != nil { + err := errs.B().Code(errs.Internal).Msgf("panic handling request: %v", e).Err() + runtime.FinishRequest(nil, err) + errs.HTTPError(w, err) + } + }() + resp, respErr := svc.Nine(req.Context(), p0, p1) + if respErr != nil { + respErr = errs.Convert(respErr) + runtime.FinishRequest(nil, respErr) + errs.HTTPError(w, respErr) + return + } + + // Serialize the response + respData := []byte("null\n") + var headers map[string][]string + if resp != nil { + // Encode JSON body + respData, err = serde.SerializeJSONFunc(json, func(ser *serde.JSONSerializer) { + ser.WriteField("Message", resp.Message, false) + }) + if err != nil { + marshalErr := errs.WrapCode(err, errs.Internal, "failed to marshal response") + runtime.FinishRequest(nil, marshalErr) + errs.HTTPError(w, marshalErr) + return + } + respData = append(respData, '\n') + + // Encode headers + headers = map[string][]string{"x-header": {resp.Header}} + } + + // Record tracing data + output := [][]byte{respData} + runtime.FinishRequest(output, nil) + + // Write response + for k, vs := range headers { + for _, v := range vs { + w.Header().Add(k, v) + } + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + w.Write(respData) +} + func __encore_svc_One(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { ctx := req.Context() runtime.BeginOperation() @@ -349,7 +447,7 @@ func __encore_svc_One(w http.ResponseWriter, req *http.Request, ps httprouter.Pa err = runtime.BeginRequest(ctx, runtime.RequestData{ AuthData: authData, Endpoint: "One", - EndpointExprIdx: 18, + EndpointExprIdx: 21, Inputs: nil, Path: req.URL.Path, Service: "svc", @@ -425,7 +523,7 @@ func __encore_svc_Query(w http.ResponseWriter, req *http.Request, ps httprouter. err = runtime.BeginRequest(ctx, runtime.RequestData{ AuthData: authData, Endpoint: "Query", - EndpointExprIdx: 19, + EndpointExprIdx: 22, Inputs: inputs, Path: req.URL.Path, PathSegments: ps, @@ -491,7 +589,7 @@ func __encore_svc_Seven(w http.ResponseWriter, req *http.Request, ps httprouter. err = runtime.BeginRequest(ctx, runtime.RequestData{ AuthData: authData, Endpoint: "Seven", - EndpointExprIdx: 20, + EndpointExprIdx: 23, Inputs: nil, Path: req.URL.Path, PathSegments: ps, @@ -582,7 +680,7 @@ func __encore_svc_Six(w http.ResponseWriter, req *http.Request, ps httprouter.Pa err = runtime.BeginRequest(ctx, runtime.RequestData{ AuthData: authData, Endpoint: "Six", - EndpointExprIdx: 21, + EndpointExprIdx: 24, Inputs: inputs, Path: req.URL.Path, PathSegments: ps, @@ -623,6 +721,73 @@ func __encore_svc_Six(w http.ResponseWriter, req *http.Request, ps httprouter.Pa w.WriteHeader(200) } +func __encore_svc_Ten(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { + ctx := req.Context() + runtime.BeginOperation() + defer runtime.FinishOperation() + + var err error + uid, authData, proceed := __encore_authenticate(w, req, false, "svc", "Ten") + if !proceed { + return + } + + err = runtime.BeginRequest(ctx, runtime.RequestData{ + AuthData: authData, + Endpoint: "Ten", + EndpointExprIdx: 25, + Inputs: nil, + Path: req.URL.Path, + Service: "svc", + Type: runtime.RPCCall, + UID: uid, + }) + if err != nil { + errs.HTTPError(w, errs.B().Code(errs.Internal).Msg("internal error").Err()) + return + } + + // Call the endpoint + defer func() { + // Catch handler panic + if e := recover(); e != nil { + err := errs.B().Code(errs.Internal).Msgf("panic handling request: %v", e).Err() + runtime.FinishRequest(nil, err) + errs.HTTPError(w, err) + } + }() + resp, respErr := svc.Ten(req.Context()) + if respErr != nil { + respErr = errs.Convert(respErr) + runtime.FinishRequest(nil, respErr) + errs.HTTPError(w, respErr) + return + } + + // Serialize the response + respData := []byte{'\n'} + var headers map[string][]string + if resp != nil { + + // Encode headers + headers = map[string][]string{"x-header": {resp.Header}} + } + + // Record tracing data + output := [][]byte{respData} + runtime.FinishRequest(output, nil) + + // Write response + for k, vs := range headers { + for _, v := range vs { + w.Header().Add(k, v) + } + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + w.Write(respData) +} + func __encore_svc_Three(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { ctx := req.Context() runtime.BeginOperation() @@ -645,7 +810,7 @@ func __encore_svc_Three(w http.ResponseWriter, req *http.Request, ps httprouter. err = runtime.BeginRequest(ctx, runtime.RequestData{ AuthData: authData, Endpoint: "Three", - EndpointExprIdx: 22, + EndpointExprIdx: 26, Inputs: inputs, Path: req.URL.Path, PathSegments: ps, @@ -733,7 +898,7 @@ func __encore_svc_Two(w http.ResponseWriter, req *http.Request, ps httprouter.Pa err = runtime.BeginRequest(ctx, runtime.RequestData{ AuthData: authData, Endpoint: "Two", - EndpointExprIdx: 23, + EndpointExprIdx: 27, Inputs: inputs, Path: req.URL.Path, PathSegments: ps, @@ -806,6 +971,13 @@ func loadConfig() (*config.Config, error) { Name: "Four", Path: "/four/*baz", Raw: false, + }, { + Access: config.Public, + Handler: __encore_svc_Nine, + Methods: []string{"GET", "POST"}, + Name: "Nine", + Path: "/nine/:bar/:baz", + Raw: false, }, { Access: config.Public, Handler: __encore_svc_One, @@ -834,6 +1006,13 @@ func loadConfig() (*config.Config, error) { Name: "Six", Path: "/six/:id/*key", Raw: false, + }, { + Access: config.Public, + Handler: __encore_svc_Ten, + Methods: []string{"GET", "POST"}, + Name: "Ten", + Path: "/ten", + Raw: false, }, { Access: config.Public, Handler: __encore_svc_Three, @@ -944,7 +1123,7 @@ func __encore_validateToken(ctx context.Context, param *svc.AuthParams) (uid aut if err != nil { return "", nil, err } - call, err := runtime.BeginAuth(24, paramStr) + call, err := runtime.BeginAuth(28, paramStr) if err != nil { return "", nil, err } @@ -953,7 +1132,7 @@ func __encore_validateToken(ctx context.Context, param *svc.AuthParams) (uid aut defer close(done) authErr = call.BeginReq(ctx, runtime.RequestData{ Endpoint: "AuthHandler", - EndpointExprIdx: 24, + EndpointExprIdx: 28, Inputs: [][]byte{[]byte(paramStr)}, Service: "svc", Type: runtime.AuthHandler, @@ -1109,7 +1288,7 @@ func __encore_svc_CronOne(ctx context.Context) (err error) { var inputs [][]byte call, err := runtime.BeginCall(runtime.CallParams{ Endpoint: "CronOne", - EndpointExprIdx: 14, + EndpointExprIdx: 16, Service: "svc", }) if err != nil { @@ -1126,7 +1305,7 @@ func __encore_svc_CronOne(ctx context.Context) (err error) { defer close(done) err := call.BeginReq(ctx, runtime.RequestData{ Endpoint: "CronOne", - EndpointExprIdx: 14, + EndpointExprIdx: 16, Inputs: inputs, Path: "/cron", PathSegments: nil, @@ -1166,7 +1345,7 @@ func __encore_svc_Eight(ctx context.Context, p0 string, p1 string) (resp *Respon } call, err := runtime.BeginCall(runtime.CallParams{ Endpoint: "Eight", - EndpointExprIdx: 15, + EndpointExprIdx: 17, Service: "svc", }) if err != nil { @@ -1183,7 +1362,7 @@ func __encore_svc_Eight(ctx context.Context, p0 string, p1 string) (resp *Respon defer close(done) err := call.BeginReq(ctx, runtime.RequestData{ Endpoint: "Eight", - EndpointExprIdx: 15, + EndpointExprIdx: 17, Inputs: inputs, Path: fmt.Sprintf("/eight/%s/%s", url.PathEscape(p0), url.PathEscape(p1)), PathSegments: httprouter.Params{httprouter.Param{ @@ -1242,7 +1421,7 @@ func __encore_svc_Five(ctx context.Context, p0 uuid.UUID, p1 uint, p2 *FooParams } call, err := runtime.BeginCall(runtime.CallParams{ Endpoint: "Five", - EndpointExprIdx: 16, + EndpointExprIdx: 18, Service: "svc", }) if err != nil { @@ -1261,7 +1440,7 @@ func __encore_svc_Five(ctx context.Context, p0 uuid.UUID, p1 uint, p2 *FooParams p1Str := fmt.Sprint(p1) err := call.BeginReq(ctx, runtime.RequestData{ Endpoint: "Five", - EndpointExprIdx: 16, + EndpointExprIdx: 18, Inputs: inputs, Path: fmt.Sprintf("/five/%s/%s", url.PathEscape(p0Str), url.PathEscape(p1Str)), PathSegments: httprouter.Params{httprouter.Param{ @@ -1317,7 +1496,7 @@ func __encore_svc_Four(ctx context.Context, p0 string) (err error) { } call, err := runtime.BeginCall(runtime.CallParams{ Endpoint: "Four", - EndpointExprIdx: 17, + EndpointExprIdx: 19, Service: "svc", }) if err != nil { @@ -1334,7 +1513,7 @@ func __encore_svc_Four(ctx context.Context, p0 string) (err error) { defer close(done) err := call.BeginReq(ctx, runtime.RequestData{ Endpoint: "Four", - EndpointExprIdx: 17, + EndpointExprIdx: 19, Inputs: inputs, Path: fmt.Sprintf("/four/%s", url.PathEscape(p0)), PathSegments: httprouter.Params{httprouter.Param{ @@ -1378,11 +1557,87 @@ func __encore_svc_Four(ctx context.Context, p0 string) (err error) { return response.err } +func __encore_svc_Nine(ctx context.Context, p0 string, p1 string) (resp *ComplexResponse, err error) { + inputs, err := runtime.SerializeInputs(p0, p1) + if err != nil { + return + } + call, err := runtime.BeginCall(runtime.CallParams{ + Endpoint: "Nine", + EndpointExprIdx: 20, + Service: "svc", + }) + if err != nil { + return + } + + // Run the request in a different goroutine + var response struct { + data [][]byte + err error + } + done := make(chan struct{}) + go func() { + defer close(done) + err := call.BeginReq(ctx, runtime.RequestData{ + Endpoint: "Nine", + EndpointExprIdx: 20, + Inputs: inputs, + Path: fmt.Sprintf("/nine/%s/%s", url.PathEscape(p0), url.PathEscape(p1)), + PathSegments: httprouter.Params{httprouter.Param{ + Key: "bar", + Value: p0, + }, httprouter.Param{ + Key: "baz", + Value: p1, + }}, + RequireAuth: false, + Service: "svc", + Type: runtime.RPCCall, + }) + if err != nil { + response.err = err + return + } + defer func() { + if err2 := recover(); err2 != nil { + response.err = errs.B().Code(errs.Internal).Msgf("panic handling request: %v", err2).Err() + call.FinishReq(nil, response.err) + } + }() + + var ( + r0 string + r1 string + ) + if response.err = runtime.CopyInputs(inputs, []interface{}{&r0, &r1}); response.err != nil { + call.FinishReq(nil, response.err) + return + } + + rpcResp, rpcErr := Nine(ctx, r0, r1) + response.data, _ = runtime.SerializeInputs(rpcResp) + if rpcErr != nil { + call.FinishReq(nil, rpcErr) + response.err = errs.RoundTrip(rpcErr) + } else { + call.FinishReq(response.data, nil) + } + }() + <-done + + call.Finish(response.err) + if response.data != nil { + _ = runtime.CopyInputs(response.data, []interface{}{&resp}) + } + return resp, response.err +} + func __encore_svc_One(ctx context.Context) (err error) { var inputs [][]byte call, err := runtime.BeginCall(runtime.CallParams{ Endpoint: "One", - EndpointExprIdx: 18, + EndpointExprIdx: 21, Service: "svc", }) if err != nil { @@ -1399,7 +1654,7 @@ func __encore_svc_One(ctx context.Context) (err error) { defer close(done) err := call.BeginReq(ctx, runtime.RequestData{ Endpoint: "One", - EndpointExprIdx: 18, + EndpointExprIdx: 21, Inputs: inputs, Path: "/svc.One", PathSegments: nil, @@ -1439,7 +1694,7 @@ func __encore_svc_Query(ctx context.Context, p0 *QueryParams) (err error) { } call, err := runtime.BeginCall(runtime.CallParams{ Endpoint: "Query", - EndpointExprIdx: 19, + EndpointExprIdx: 22, Service: "svc", }) if err != nil { @@ -1456,7 +1711,7 @@ func __encore_svc_Query(ctx context.Context, p0 *QueryParams) (err error) { defer close(done) err := call.BeginReq(ctx, runtime.RequestData{ Endpoint: "Query", - EndpointExprIdx: 19, + EndpointExprIdx: 22, Inputs: inputs, Path: "/query", PathSegments: nil, @@ -1504,7 +1759,7 @@ func __encore_svc_Seven(ctx context.Context, p0 string, p1 string) (err error) { } call, err := runtime.BeginCall(runtime.CallParams{ Endpoint: "Seven", - EndpointExprIdx: 20, + EndpointExprIdx: 23, Service: "svc", }) if err != nil { @@ -1521,7 +1776,7 @@ func __encore_svc_Seven(ctx context.Context, p0 string, p1 string) (err error) { defer close(done) err := call.BeginReq(ctx, runtime.RequestData{ Endpoint: "Seven", - EndpointExprIdx: 20, + EndpointExprIdx: 23, Inputs: inputs, Path: fmt.Sprintf("/foo/%s/%s", url.PathEscape(p0), url.PathEscape(p1)), PathSegments: httprouter.Params{httprouter.Param{ @@ -1576,7 +1831,7 @@ func __encore_svc_Six(ctx context.Context, p0 uuid.UUID, p1 string, p2 *FooParam } call, err := runtime.BeginCall(runtime.CallParams{ Endpoint: "Six", - EndpointExprIdx: 21, + EndpointExprIdx: 24, Service: "svc", }) if err != nil { @@ -1594,7 +1849,7 @@ func __encore_svc_Six(ctx context.Context, p0 uuid.UUID, p1 string, p2 *FooParam p0Str := fmt.Sprint(p0) err := call.BeginReq(ctx, runtime.RequestData{ Endpoint: "Six", - EndpointExprIdx: 21, + EndpointExprIdx: 24, Inputs: inputs, Path: fmt.Sprintf("/six/%s/%s", url.PathEscape(p0Str), url.PathEscape(p1)), PathSegments: httprouter.Params{httprouter.Param{ @@ -1643,6 +1898,64 @@ func __encore_svc_Six(ctx context.Context, p0 uuid.UUID, p1 string, p2 *FooParam return response.err } +func __encore_svc_Ten(ctx context.Context) (resp *HeaderResponse, err error) { + var inputs [][]byte + call, err := runtime.BeginCall(runtime.CallParams{ + Endpoint: "Ten", + EndpointExprIdx: 25, + Service: "svc", + }) + if err != nil { + return + } + + // Run the request in a different goroutine + var response struct { + data [][]byte + err error + } + done := make(chan struct{}) + go func() { + defer close(done) + err := call.BeginReq(ctx, runtime.RequestData{ + Endpoint: "Ten", + EndpointExprIdx: 25, + Inputs: inputs, + Path: "/ten", + PathSegments: nil, + RequireAuth: false, + Service: "svc", + Type: runtime.RPCCall, + }) + if err != nil { + response.err = err + return + } + defer func() { + if err2 := recover(); err2 != nil { + response.err = errs.B().Code(errs.Internal).Msgf("panic handling request: %v", err2).Err() + call.FinishReq(nil, response.err) + } + }() + + rpcResp, rpcErr := Ten(ctx) + response.data, _ = runtime.SerializeInputs(rpcResp) + if rpcErr != nil { + call.FinishReq(nil, rpcErr) + response.err = errs.RoundTrip(rpcErr) + } else { + call.FinishReq(response.data, nil) + } + }() + <-done + + call.Finish(response.err) + if response.data != nil { + _ = runtime.CopyInputs(response.data, []interface{}{&resp}) + } + return resp, response.err +} + func __encore_svc_Three(ctx context.Context, p0 string) (err error) { inputs, err := runtime.SerializeInputs(p0) if err != nil { @@ -1650,7 +1963,7 @@ func __encore_svc_Three(ctx context.Context, p0 string) (err error) { } call, err := runtime.BeginCall(runtime.CallParams{ Endpoint: "Three", - EndpointExprIdx: 22, + EndpointExprIdx: 26, Service: "svc", }) if err != nil { @@ -1667,7 +1980,7 @@ func __encore_svc_Three(ctx context.Context, p0 string) (err error) { defer close(done) err := call.BeginReq(ctx, runtime.RequestData{ Endpoint: "Three", - EndpointExprIdx: 22, + EndpointExprIdx: 26, Inputs: inputs, Path: fmt.Sprintf("/three/%s", url.PathEscape(p0)), PathSegments: httprouter.Params{httprouter.Param{ @@ -1718,7 +2031,7 @@ func __encore_svc_Two(ctx context.Context, p0 *FooParams) (err error) { } call, err := runtime.BeginCall(runtime.CallParams{ Endpoint: "Two", - EndpointExprIdx: 23, + EndpointExprIdx: 27, Service: "svc", }) if err != nil { @@ -1735,7 +2048,7 @@ func __encore_svc_Two(ctx context.Context, p0 *FooParams) (err error) { defer close(done) err := call.BeginReq(ctx, runtime.RequestData{ Endpoint: "Two", - EndpointExprIdx: 23, + EndpointExprIdx: 27, Inputs: inputs, Path: "/svc.Two", PathSegments: nil, diff --git a/compiler/internal/codegen/testdata/variants.txt b/compiler/internal/codegen/testdata/variants.txt index 7365df132d..5ebb7535fc 100644 --- a/compiler/internal/codegen/testdata/variants.txt +++ b/compiler/internal/codegen/testdata/variants.txt @@ -75,6 +75,27 @@ func Eight(ctx context.Context, bar, baz string) (*Response, error) { return &Response{Message: bar}, nil } +type ComplexResponse struct { + Header string `header:"X-Header"` + Message string +} + +//encore:api public path=/nine/:bar/:baz +func Nine(ctx context.Context, bar, baz string) (*ComplexResponse, error) { + rlog.Info("nine", "bar", bar, "baz", baz) + return &Response{Message: bar, Header: baz}, nil +} + +type HeaderResponse struct { + Header string `header:"X-Header"` +} + +//encore:api public path=/ten +func Ten(ctx context.Context) (*HeaderResponse, error) { + rlog.Info("nine") + return &HeaderResponse{Header: "header"}, nil +} + //encore:api private path=/cron func CronOne(ctx context.Context) error { return nil