From 07c019e270ee88f9560088a64ffcd431f5b591d1 Mon Sep 17 00:00:00 2001 From: "Vojtech Vitek (golang.cz)" Date: Fri, 25 Aug 2023 14:05:17 +0200 Subject: [PATCH] Simplify server JSON handler code for RPC methods (#36) * Simplify server JSON handler code for RPC methods * Shorten generated code: Move defer-panic up to the .ServeHTTP() method No need to define defer-panic in JSON handler for each generated RPC method, since the generated Go methods are private and called only from within .ServerHTTP(). --- _examples/golang-basics/example.gen.go | 175 +++++++++---------------- _examples/golang-imports/api.gen.go | 88 +++++-------- server.go.tmpl | 65 ++++----- 3 files changed, 122 insertions(+), 206 deletions(-) diff --git a/_examples/golang-basics/example.gen.go b/_examples/golang-basics/example.gen.go index dd871ad..389dd8d 100644 --- a/_examples/golang-basics/example.gen.go +++ b/_examples/golang-basics/example.gen.go @@ -146,6 +146,14 @@ func NewExampleServiceServer(svc ExampleService) WebRPCServer { } func (s *exampleServiceServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + defer func() { + // In case of a panic, serve a HTTP 500 error and then panic. + if rr := recover(); rr != nil { + RespondWithError(w, ErrorWithCause(ErrWebrpcServerPanic, fmt.Errorf("%v", rr))) + panic(rr) + } + }() + ctx := r.Context() ctx = context.WithValue(ctx, HTTPResponseWriterCtxKey, w) ctx = context.WithValue(ctx, HTTPRequestCtxKey, r) @@ -192,21 +200,11 @@ func (s *exampleServiceServer) ServeHTTP(w http.ResponseWriter, r *http.Request) } func (s *exampleServiceServer) servePingJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "Ping") - // Call service method - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorWithCause(ErrWebrpcServerPanic, fmt.Errorf("%v", rr))) - panic(rr) - } - }() - err = s.ExampleService.Ping(ctx) - }() + ctx = context.WithValue(ctx, MethodNameCtxKey, "Ping") + // Call service method implementation. + err := s.ExampleService.Ping(ctx) if err != nil { RespondWithError(w, err) return @@ -218,33 +216,22 @@ func (s *exampleServiceServer) servePingJSON(ctx context.Context, w http.Respons } func (s *exampleServiceServer) serveStatusJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "Status") - // Call service method - var ret0 bool - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorWithCause(ErrWebrpcServerPanic, fmt.Errorf("%v", rr))) - panic(rr) - } - }() - ret0, err = s.ExampleService.Status(ctx) - }() - respContent := struct { - Ret0 bool `json:"status"` - }{ret0} + ctx = context.WithValue(ctx, MethodNameCtxKey, "Status") + // Call service method implementation. + ret0, err := s.ExampleService.Status(ctx) if err != nil { RespondWithError(w, err) return } - respBody, err := json.Marshal(initializeNilSlices(respContent)) + + respPayload := struct { + Ret0 bool `json:"status"` + }{ret0} + respBody, err := json.Marshal(initializeNilSlices(respPayload)) if err != nil { - err = ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err)) - RespondWithError(w, err) + RespondWithError(w, ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err))) return } @@ -254,33 +241,22 @@ func (s *exampleServiceServer) serveStatusJSON(ctx context.Context, w http.Respo } func (s *exampleServiceServer) serveVersionJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "Version") - // Call service method - var ret0 *Version - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorWithCause(ErrWebrpcServerPanic, fmt.Errorf("%v", rr))) - panic(rr) - } - }() - ret0, err = s.ExampleService.Version(ctx) - }() - respContent := struct { - Ret0 *Version `json:"version"` - }{ret0} + ctx = context.WithValue(ctx, MethodNameCtxKey, "Version") + // Call service method implementation. + ret0, err := s.ExampleService.Version(ctx) if err != nil { RespondWithError(w, err) return } - respBody, err := json.Marshal(initializeNilSlices(respContent)) + + respPayload := struct { + Ret0 *Version `json:"version"` + }{ret0} + respBody, err := json.Marshal(initializeNilSlices(respPayload)) if err != nil { - err = ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err)) - RespondWithError(w, err) + RespondWithError(w, ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err))) return } @@ -290,52 +266,38 @@ func (s *exampleServiceServer) serveVersionJSON(ctx context.Context, w http.Resp } func (s *exampleServiceServer) serveGetUserJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "GetUser") - reqContent := struct { - Arg0 map[string]string `json:"header"` - Arg1 uint64 `json:"userID"` - }{} reqBody, err := io.ReadAll(r.Body) if err != nil { - err = ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to read request data: %w", err)) - RespondWithError(w, err) + RespondWithError(w, ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to read request data: %w", err))) return } defer r.Body.Close() - err = json.Unmarshal(reqBody, &reqContent) - if err != nil { - err = ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to unmarshal request data: %w", err)) - RespondWithError(w, err) + reqPayload := struct { + Arg0 map[string]string `json:"header"` + Arg1 uint64 `json:"userID"` + }{} + if err := json.Unmarshal(reqBody, &reqPayload); err != nil { + RespondWithError(w, ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to unmarshal request data: %w", err))) return } - // Call service method - var ret0 *User - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorWithCause(ErrWebrpcServerPanic, fmt.Errorf("%v", rr))) - panic(rr) - } - }() - ret0, err = s.ExampleService.GetUser(ctx, reqContent.Arg0, reqContent.Arg1) - }() - respContent := struct { - Ret0 *User `json:"user"` - }{ret0} + ctx = context.WithValue(ctx, MethodNameCtxKey, "GetUser") + // Call service method implementation. + ret0, err := s.ExampleService.GetUser(ctx, reqPayload.Arg0, reqPayload.Arg1) if err != nil { RespondWithError(w, err) return } - respBody, err := json.Marshal(initializeNilSlices(respContent)) + + respPayload := struct { + Ret0 *User `json:"user"` + }{ret0} + respBody, err := json.Marshal(initializeNilSlices(respPayload)) if err != nil { - err = ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err)) - RespondWithError(w, err) + RespondWithError(w, ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err))) return } @@ -345,53 +307,38 @@ func (s *exampleServiceServer) serveGetUserJSON(ctx context.Context, w http.Resp } func (s *exampleServiceServer) serveFindUserJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "FindUser") - reqContent := struct { - Arg0 *SearchFilter `json:"s"` - }{} reqBody, err := io.ReadAll(r.Body) if err != nil { - err = ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to read request data: %w", err)) - RespondWithError(w, err) + RespondWithError(w, ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to read request data: %w", err))) return } defer r.Body.Close() - err = json.Unmarshal(reqBody, &reqContent) - if err != nil { - err = ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to unmarshal request data: %w", err)) - RespondWithError(w, err) + reqPayload := struct { + Arg0 *SearchFilter `json:"s"` + }{} + if err := json.Unmarshal(reqBody, &reqPayload); err != nil { + RespondWithError(w, ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to unmarshal request data: %w", err))) return } - // Call service method - var ret0 string - var ret1 *User - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorWithCause(ErrWebrpcServerPanic, fmt.Errorf("%v", rr))) - panic(rr) - } - }() - ret0, ret1, err = s.ExampleService.FindUser(ctx, reqContent.Arg0) - }() - respContent := struct { - Ret0 string `json:"name"` - Ret1 *User `json:"user"` - }{ret0, ret1} + ctx = context.WithValue(ctx, MethodNameCtxKey, "FindUser") + // Call service method implementation. + ret0, ret1, err := s.ExampleService.FindUser(ctx, reqPayload.Arg0) if err != nil { RespondWithError(w, err) return } - respBody, err := json.Marshal(initializeNilSlices(respContent)) + + respPayload := struct { + Ret0 string `json:"name"` + Ret1 *User `json:"user"` + }{ret0, ret1} + respBody, err := json.Marshal(initializeNilSlices(respPayload)) if err != nil { - err = ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err)) - RespondWithError(w, err) + RespondWithError(w, ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err))) return } diff --git a/_examples/golang-imports/api.gen.go b/_examples/golang-imports/api.gen.go index 9eb3a40..19b0c97 100644 --- a/_examples/golang-imports/api.gen.go +++ b/_examples/golang-imports/api.gen.go @@ -106,6 +106,14 @@ func NewExampleAPIServer(svc ExampleAPI) WebRPCServer { } func (s *exampleAPIServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + defer func() { + // In case of a panic, serve a HTTP 500 error and then panic. + if rr := recover(); rr != nil { + RespondWithError(w, ErrorWithCause(ErrWebrpcServerPanic, fmt.Errorf("%v", rr))) + panic(rr) + } + }() + ctx := r.Context() ctx = context.WithValue(ctx, HTTPResponseWriterCtxKey, w) ctx = context.WithValue(ctx, HTTPRequestCtxKey, r) @@ -145,21 +153,12 @@ func (s *exampleAPIServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func (s *exampleAPIServer) servePingJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "Ping") + - // Call service method - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorWithCause(ErrWebrpcServerPanic, fmt.Errorf("%v", rr))) - panic(rr) - } - }() - err = s.ExampleAPI.Ping(ctx) - }() + ctx = context.WithValue(ctx, MethodNameCtxKey, "Ping") + // Call service method implementation. + err := s.ExampleAPI.Ping(ctx) if err != nil { RespondWithError(w, err) return @@ -171,33 +170,23 @@ func (s *exampleAPIServer) servePingJSON(ctx context.Context, w http.ResponseWri } func (s *exampleAPIServer) serveStatusJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "Status") + - // Call service method - var ret0 bool - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorWithCause(ErrWebrpcServerPanic, fmt.Errorf("%v", rr))) - panic(rr) - } - }() - ret0, err = s.ExampleAPI.Status(ctx) - }() - respContent := struct { - Ret0 bool `json:"status"` - }{ret0} + ctx = context.WithValue(ctx, MethodNameCtxKey, "Status") + // Call service method implementation. + ret0, err := s.ExampleAPI.Status(ctx) if err != nil { RespondWithError(w, err) return } - respBody, err := json.Marshal(respContent) + + respPayload := struct { + Ret0 bool `json:"status"` + }{ret0} + respBody, err := json.Marshal(respPayload) if err != nil { - err = ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err)) - RespondWithError(w, err) + RespondWithError(w, ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err))) return } @@ -207,35 +196,24 @@ func (s *exampleAPIServer) serveStatusJSON(ctx context.Context, w http.ResponseW } func (s *exampleAPIServer) serveGetUsersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "GetUsers") + - // Call service method - var ret0 []*User - var ret1 *Location - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorWithCause(ErrWebrpcServerPanic, fmt.Errorf("%v", rr))) - panic(rr) - } - }() - ret0, ret1, err = s.ExampleAPI.GetUsers(ctx) - }() - respContent := struct { - Ret0 []*User `json:"users"` - Ret1 *Location `json:"location"` - }{ret0, ret1} + ctx = context.WithValue(ctx, MethodNameCtxKey, "GetUsers") + // Call service method implementation. + ret0, ret1, err := s.ExampleAPI.GetUsers(ctx) if err != nil { RespondWithError(w, err) return } - respBody, err := json.Marshal(respContent) + + respPayload := struct { + Ret0 []*User `json:"users"` + Ret1 *Location `json:"location"` + }{ret0, ret1} + respBody, err := json.Marshal(respPayload) if err != nil { - err = ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err)) - RespondWithError(w, err) + RespondWithError(w, ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err))) return } diff --git a/server.go.tmpl b/server.go.tmpl index 2e829e7..4f98d07 100644 --- a/server.go.tmpl +++ b/server.go.tmpl @@ -27,6 +27,14 @@ func New{{ .Name | firstLetterToUpper }}Server(svc {{$typePrefix}}{{.Name}}) Web } func (s *{{$serviceName}}) ServeHTTP(w http.ResponseWriter, r *http.Request) { + defer func() { + // In case of a panic, serve a HTTP 500 error and then panic. + if rr := recover(); rr != nil { + RespondWithError(w, ErrorWithCause(ErrWebrpcServerPanic, fmt.Errorf("%v", rr))) + panic(rr) + } + }() + ctx := r.Context() ctx = context.WithValue(ctx, HTTPResponseWriterCtxKey, w) ctx = context.WithValue(ctx, HTTPRequestCtxKey, r) @@ -66,68 +74,51 @@ func (s *{{$serviceName}}) ServeHTTP(w http.ResponseWriter, r *http.Request) { } {{range .Methods }} func (s *{{$serviceName}}) serve{{ .Name | firstLetterToUpper }}JSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "{{.Name}}") + {{ if .Inputs|len}} + reqBody, err := io.ReadAll(r.Body) + if err != nil { + RespondWithError(w, ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to read request data: %w", err))) + return + } + defer r.Body.Close() - {{- if .Inputs|len}} - reqContent := struct { + reqPayload := struct { {{- range $i, $input := .Inputs}} Arg{{$i}} {{template "field" dict "Name" $input.Name "Type" $input.Type "Optional" $input.Optional "TypeMap" $typeMap "TypePrefix" $typePrefix "TypeMeta" $input.Meta "JsonTags" true}} {{- end}} }{} - - reqBody, err := io.ReadAll(r.Body) - if err != nil { - err = ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to read request data: %w", err)) - RespondWithError(w, err) + if err := json.Unmarshal(reqBody, &reqPayload); err != nil { + RespondWithError(w, ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to unmarshal request data: %w", err))) return } - defer r.Body.Close() + {{- end}} - err = json.Unmarshal(reqBody, &reqContent) + ctx = context.WithValue(ctx, MethodNameCtxKey, "{{.Name}}") + + // Call service method implementation. + {{range $i, $output := .Outputs}}ret{{$i}}, {{end}}err := s.{{$name}}.{{.Name}}(ctx{{range $i, $_ := .Inputs}}, reqPayload.Arg{{$i}}{{end}}) if err != nil { - err = ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to unmarshal request data: %w", err)) RespondWithError(w, err) return } - {{- end}} - // Call service method - {{- range $i, $output := .Outputs}} - var ret{{$i}} {{template "field" dict "Name" $output.Name "Type" $output.Type "Optional" $output.Optional "TypeMap" $typeMap "TypePrefix" $typePrefix "TypeMeta" $output.Meta}} - {{- end}} - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorWithCause(ErrWebrpcServerPanic, fmt.Errorf("%v", rr))) - panic(rr) - } - }() - {{range $i, $output := .Outputs}}ret{{$i}}, {{end}}err = s.{{$name}}.{{.Name}}(ctx{{range $i, $_ := .Inputs}}, reqContent.Arg{{$i}}{{end}}) - }() {{- if .Outputs | len}} - respContent := struct { + + respPayload := struct { {{- range $i, $output := .Outputs}} Ret{{$i}} {{template "field" dict "Name" $output.Name "Type" $output.Type "Optional" $output.Optional "TypeMap" $typeMap "TypePrefix" $typePrefix "TypeMeta" $output.Meta "JsonTags" true}} {{- end}} }{ {{- range $i, $_ := .Outputs}}{{if gt $i 0}}, {{end}}ret{{$i}}{{end}}} {{- end}} - if err != nil { - RespondWithError(w, err) - return - } - {{- if .Outputs | len}} {{ if $opts.fixEmptyArrays -}} - respBody, err := json.Marshal(initializeNilSlices(respContent)) + respBody, err := json.Marshal(initializeNilSlices(respPayload)) {{ else -}} - respBody, err := json.Marshal(respContent) + respBody, err := json.Marshal(respPayload) {{ end -}} if err != nil { - err = ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err)) - RespondWithError(w, err) + RespondWithError(w, ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err))) return } {{- end}}