From ce597c38b82827d0650f652d9851a44fbe7bc0c0 Mon Sep 17 00:00:00 2001 From: david-littlefarmer Date: Fri, 6 Oct 2023 12:01:18 +0200 Subject: [PATCH] petStore example fix --- _examples/petStore/go.mod | 2 +- _examples/petStore/go.sum | 2 + .../petStore/proto/client/petstore.gen.go | 68 +-- .../petStore/proto/client/petstore.gen.ts | 2 +- .../petStore/proto/petstore.debug.gen.txt | 65 ++- _examples/petStore/proto/petstore.gen.yaml | 2 +- _examples/petStore/proto/server.gen.go | 425 +++++++----------- 7 files changed, 272 insertions(+), 294 deletions(-) diff --git a/_examples/petStore/go.mod b/_examples/petStore/go.mod index fa0ae41..daeffbe 100644 --- a/_examples/petStore/go.mod +++ b/_examples/petStore/go.mod @@ -31,7 +31,7 @@ require ( github.com/sergi/go-diff v1.2.0 // indirect github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect github.com/src-d/gcfg v1.4.0 // indirect - github.com/webrpc/webrpc v0.12.1 // indirect + github.com/webrpc/webrpc v0.13.1 // indirect github.com/xanzy/ssh-agent v0.3.2 // indirect golang.org/x/crypto v0.11.0 // indirect golang.org/x/mod v0.12.0 // indirect diff --git a/_examples/petStore/go.sum b/_examples/petStore/go.sum index c2cdc9e..e68839c 100644 --- a/_examples/petStore/go.sum +++ b/_examples/petStore/go.sum @@ -94,6 +94,8 @@ github.com/webrpc/webrpc v0.12.1-0.20230801165705-bbb62bb52b8f h1:K5xCTeoVUTcl71 github.com/webrpc/webrpc v0.12.1-0.20230801165705-bbb62bb52b8f/go.mod h1:DwRj+2Vd0r4sanozfosHdv4RYi8kALVyVvpBPYqeGDQ= github.com/webrpc/webrpc v0.12.1 h1:L1PxRykmb6AyoOWjSsUjIhc3zwkAehx/DqGJ6nn0WUg= github.com/webrpc/webrpc v0.12.1/go.mod h1:DwRj+2Vd0r4sanozfosHdv4RYi8kALVyVvpBPYqeGDQ= +github.com/webrpc/webrpc v0.13.1 h1:Umb9q3tYUS2JbTS1dNxfgEtX+jn1qEMandj1RDipydY= +github.com/webrpc/webrpc v0.13.1/go.mod h1:DwRj+2Vd0r4sanozfosHdv4RYi8kALVyVvpBPYqeGDQ= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xanzy/ssh-agent v0.3.2 h1:eKj4SX2Fe7mui28ZgnFW5fmTz1EIr7ugo5s6wDxdHBM= github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= diff --git a/_examples/petStore/proto/client/petstore.gen.go b/_examples/petStore/proto/client/petstore.gen.go index a4d154b..ceb9dcf 100644 --- a/_examples/petStore/proto/client/petstore.gen.go +++ b/_examples/petStore/proto/client/petstore.gen.go @@ -1,11 +1,10 @@ // PetStore vTODO 204f6b26587305ef3a4c043b8636035ada3889ef // -- -// Code generated by webrpc-gen@v0.13.0-dev with golang generator. DO NOT EDIT. +// Code generated by webrpc-gen@v0.14.0-dev with golang generator. DO NOT EDIT. // // gospeak . package client - import ( "bytes" "context" @@ -13,12 +12,12 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" "net/url" "time" "github.com/google/uuid" + ) // WebRPC description and code-gen version @@ -147,7 +146,7 @@ func (c *petStoreClient) CreatePet(ctx context.Context, new *Pet) (*Pet, error) out := struct { Ret0 *Pet `json:"pet"` }{} - + err := doJSONRequest(ctx, c.client, c.urls[0], in, &out) return out.Ret0, err } @@ -156,7 +155,6 @@ func (c *petStoreClient) DeletePet(ctx context.Context, ID int64) error { in := struct { Arg0 int64 `json:"ID"` }{ID} - err := doJSONRequest(ctx, c.client, c.urls[1], in, nil) return err } @@ -168,7 +166,7 @@ func (c *petStoreClient) GetPet(ctx context.Context, ID int64) (*Pet, error) { out := struct { Ret0 *Pet `json:"pet"` }{} - + err := doJSONRequest(ctx, c.client, c.urls[2], in, &out) return out.Ret0, err } @@ -177,7 +175,7 @@ func (c *petStoreClient) ListPets(ctx context.Context) ([]*Pet, error) { out := struct { Ret0 []*Pet `json:"pets"` }{} - + err := doJSONRequest(ctx, c.client, c.urls[3], nil, &out) return out.Ret0, err } @@ -190,7 +188,7 @@ func (c *petStoreClient) UpdatePet(ctx context.Context, ID int64, update *Pet) ( out := struct { Ret0 *Pet `json:"pet"` }{} - + err := doJSONRequest(ctx, c.client, c.urls[4], in, &out) return out.Ret0, err } @@ -239,41 +237,41 @@ func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType func doJSONRequest(ctx context.Context, client HTTPClient, url string, in, out interface{}) error { reqBody, err := json.Marshal(in) if err != nil { - return ErrorWithCause(ErrWebrpcRequestFailed, fmt.Errorf("failed to marshal JSON body: %w", err)) + return ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to marshal JSON body: %w", err)) } if err = ctx.Err(); err != nil { - return ErrorWithCause(ErrWebrpcRequestFailed, fmt.Errorf("aborted because context was done: %w", err)) + return ErrWebrpcRequestFailed.WithCause(fmt.Errorf("aborted because context was done: %w", err)) } req, err := newRequest(ctx, url, bytes.NewBuffer(reqBody), "application/json") if err != nil { - return ErrorWithCause(ErrWebrpcRequestFailed, fmt.Errorf("could not build request: %w", err)) + return ErrWebrpcRequestFailed.WithCause(fmt.Errorf("could not build request: %w", err)) } resp, err := client.Do(req) if err != nil { - return ErrorWithCause(ErrWebrpcRequestFailed, err) + return ErrWebrpcRequestFailed.WithCause(err) } defer func() { cerr := resp.Body.Close() if err == nil && cerr != nil { - err = ErrorWithCause(ErrWebrpcRequestFailed, fmt.Errorf("failed to close response body: %w", cerr)) + err = ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to close response body: %w", cerr)) } }() if err = ctx.Err(); err != nil { - return ErrorWithCause(ErrWebrpcRequestFailed, fmt.Errorf("aborted because context was done: %w", err)) + return ErrWebrpcRequestFailed.WithCause(fmt.Errorf("aborted because context was done: %w", err)) } if resp.StatusCode != 200 { - respBody, err := ioutil.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) if err != nil { - return ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to read server error response body: %w", err)) + return ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to read server error response body: %w", err)) } var rpcErr WebRPCError if err := json.Unmarshal(respBody, &rpcErr); err != nil { - return ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to unmarshal server error: %w", err)) + return ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to unmarshal server error: %w", err)) } if rpcErr.Cause != "" { rpcErr.cause = errors.New(rpcErr.Cause) @@ -282,14 +280,14 @@ func doJSONRequest(ctx context.Context, client HTTPClient, url string, in, out i } if out != nil { - respBody, err := ioutil.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) if err != nil { - return ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to read response body: %w", err)) + return ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to read response body: %w", err)) } err = json.Unmarshal(respBody, &out) if err != nil { - return ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to unmarshal JSON response body: %w", err)) + return ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to unmarshal JSON response body: %w", err)) } } @@ -335,12 +333,7 @@ func (k *contextKey) String() string { } var ( - // For Client HTTPClientRequestHeadersCtxKey = &contextKey{"HTTPClientRequestHeaders"} - - // For Server - HTTPResponseWriterCtxKey = &contextKey{"HTTPResponseWriter"} - HTTPRequestCtxKey = &contextKey{"HTTPRequest"} ServiceNameCtxKey = &contextKey{"ServiceName"} @@ -348,6 +341,22 @@ var ( MethodNameCtxKey = &contextKey{"MethodName"} ) +func ServiceNameFromContext(ctx context.Context) string { + service, _ := ctx.Value(ServiceNameCtxKey).(string) + return service +} + +func MethodNameFromContext(ctx context.Context) string { + method, _ := ctx.Value(MethodNameCtxKey).(string) + return method +} + +func RequestFromContext(ctx context.Context) *http.Request { + r, _ := ctx.Value(HTTPRequestCtxKey).(*http.Request) + return r +} + + // // Errors // @@ -381,13 +390,18 @@ func (e WebRPCError) Unwrap() error { return e.cause } -func ErrorWithCause(rpcErr WebRPCError, cause error) WebRPCError { - err := rpcErr +func (e WebRPCError) WithCause(cause error) WebRPCError { + err := e err.cause = cause err.Cause = cause.Error() return err } +// Deprecated: Use .WithCause() method on WebRPCError. +func ErrorWithCause(rpcErr WebRPCError, cause error) WebRPCError { + return rpcErr.WithCause(cause) +} + // Webrpc errors var ( ErrWebrpcEndpoint = WebRPCError{Code: 0, Name: "WebrpcEndpoint", Message: "endpoint error", HTTPStatus: 400} diff --git a/_examples/petStore/proto/client/petstore.gen.ts b/_examples/petStore/proto/client/petstore.gen.ts index c5fab40..68922ef 100644 --- a/_examples/petStore/proto/client/petstore.gen.ts +++ b/_examples/petStore/proto/client/petstore.gen.ts @@ -1,7 +1,7 @@ /* eslint-disable */ // PetStore vTODO 204f6b26587305ef3a4c043b8636035ada3889ef // -- -// Code generated by webrpc-gen@v0.13.0-dev with typescript generator. DO NOT EDIT. +// Code generated by webrpc-gen@v0.14.0-dev with typescript generator. DO NOT EDIT. // // gospeak . diff --git a/_examples/petStore/proto/petstore.debug.gen.txt b/_examples/petStore/proto/petstore.debug.gen.txt index fe51e0a..67a10f6 100644 --- a/_examples/petStore/proto/petstore.debug.gen.txt +++ b/_examples/petStore/proto/petstore.debug.gen.txt @@ -1263,7 +1263,12 @@ }), Optional: (bool) false, InputArg: (bool) true, - OutputArg: (bool) false + OutputArg: (bool) false, + TypeExtra: (schema.TypeExtra) { + Optional: (bool) false, + Value: (string) "", + Meta: ([]schema.TypeFieldMeta) + } }) }, Outputs: ([]*schema.MethodArgument) (len=1 cap=2) { @@ -1842,7 +1847,12 @@ }), Optional: (bool) false, InputArg: (bool) false, - OutputArg: (bool) true + OutputArg: (bool) true, + TypeExtra: (schema.TypeExtra) { + Optional: (bool) false, + Value: (string) "", + Meta: ([]schema.TypeFieldMeta) + } }) }, Service: (*schema.Service)() @@ -1864,7 +1874,12 @@ }), Optional: (bool) false, InputArg: (bool) true, - OutputArg: (bool) false + OutputArg: (bool) false, + TypeExtra: (schema.TypeExtra) { + Optional: (bool) false, + Value: (string) "", + Meta: ([]schema.TypeFieldMeta) + } }) }, Outputs: ([]*schema.MethodArgument) (cap=1) { @@ -1888,7 +1903,12 @@ }), Optional: (bool) false, InputArg: (bool) true, - OutputArg: (bool) false + OutputArg: (bool) false, + TypeExtra: (schema.TypeExtra) { + Optional: (bool) false, + Value: (string) "", + Meta: ([]schema.TypeFieldMeta) + } }) }, Outputs: ([]*schema.MethodArgument) (len=1 cap=2) { @@ -2467,7 +2487,12 @@ }), Optional: (bool) false, InputArg: (bool) false, - OutputArg: (bool) true + OutputArg: (bool) true, + TypeExtra: (schema.TypeExtra) { + Optional: (bool) false, + Value: (string) "", + Meta: ([]schema.TypeFieldMeta) + } }) }, Service: (*schema.Service)() @@ -3063,7 +3088,12 @@ }), Optional: (bool) false, InputArg: (bool) false, - OutputArg: (bool) true + OutputArg: (bool) true, + TypeExtra: (schema.TypeExtra) { + Optional: (bool) false, + Value: (string) "", + Meta: ([]schema.TypeFieldMeta) + } }) }, Service: (*schema.Service)() @@ -3085,7 +3115,12 @@ }), Optional: (bool) false, InputArg: (bool) true, - OutputArg: (bool) false + OutputArg: (bool) false, + TypeExtra: (schema.TypeExtra) { + Optional: (bool) false, + Value: (string) "", + Meta: ([]schema.TypeFieldMeta) + } }), (*schema.MethodArgument)({ Name: (string) (len=6) "update", @@ -3662,7 +3697,12 @@ }), Optional: (bool) false, InputArg: (bool) true, - OutputArg: (bool) false + OutputArg: (bool) false, + TypeExtra: (schema.TypeExtra) { + Optional: (bool) false, + Value: (string) "", + Meta: ([]schema.TypeFieldMeta) + } }) }, Outputs: ([]*schema.MethodArgument) (len=1 cap=2) { @@ -4241,7 +4281,12 @@ }), Optional: (bool) false, InputArg: (bool) false, - OutputArg: (bool) true + OutputArg: (bool) true, + TypeExtra: (schema.TypeExtra) { + Optional: (bool) false, + Value: (string) "", + Meta: ([]schema.TypeFieldMeta) + } }) }, Service: (*schema.Service)() @@ -4253,7 +4298,7 @@ Deprecated_Messages: ([]interface {}) }), SchemaHash: (string) (len=40) "204f6b26587305ef3a4c043b8636035ada3889ef", - WebrpcGenVersion: (string) (len=11) "v0.13.0-dev", + WebrpcGenVersion: (string) (len=11) "v0.14.0-dev", WebrpcGenCommand: (string) (len=9) "gospeak .", WebrpcTarget: (string) (len=5) "debug", WebrpcErrors: ([]*schema.Error) (len=8 cap=8) { diff --git a/_examples/petStore/proto/petstore.gen.yaml b/_examples/petStore/proto/petstore.gen.yaml index 95e11ea..b1b7398 100644 --- a/_examples/petStore/proto/petstore.gen.yaml +++ b/_examples/petStore/proto/petstore.gen.yaml @@ -1,6 +1,6 @@ # PetStore vTODO 204f6b26587305ef3a4c043b8636035ada3889ef # -- -# Code generated by webrpc-gen@v0.13.0-dev with openapi generator; DO NOT EDIT +# Code generated by webrpc-gen@v0.14.0-dev with openapi generator; DO NOT EDIT # # gospeak . openapi: 3.0.0 diff --git a/_examples/petStore/proto/server.gen.go b/_examples/petStore/proto/server.gen.go index 6007e1c..8a35f9d 100644 --- a/_examples/petStore/proto/server.gen.go +++ b/_examples/petStore/proto/server.gen.go @@ -1,6 +1,6 @@ // PetStore vTODO 204f6b26587305ef3a4c043b8636035ada3889ef // -- -// Code generated by webrpc-gen@v0.13.0-dev with golang generator. DO NOT EDIT. +// Code generated by webrpc-gen@v0.14.0-dev with golang generator. DO NOT EDIT. // // gospeak . package proto @@ -10,7 +10,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "strings" ) @@ -81,111 +81,99 @@ type WebRPCServer interface { type petStoreServer struct { PetStore + OnError func(r *http.Request, rpcErr *WebRPCError) } -func NewPetStoreServer(svc PetStore) WebRPCServer { +func NewPetStoreServer(svc PetStore) *petStoreServer { return &petStoreServer{ PetStore: svc, } } func (s *petStoreServer) 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 { + s.sendErrorJSON(w, r, ErrWebrpcServerPanic.WithCause(fmt.Errorf("%v", rr))) + panic(rr) + } + }() + ctx := r.Context() ctx = context.WithValue(ctx, HTTPResponseWriterCtxKey, w) ctx = context.WithValue(ctx, HTTPRequestCtxKey, r) ctx = context.WithValue(ctx, ServiceNameCtxKey, "PetStore") - if r.Method != "POST" { - err := ErrorWithCause(ErrWebrpcBadMethod, fmt.Errorf("unsupported method %q (only POST is allowed)", r.Method)) - RespondWithError(w, err) + var handler func(ctx context.Context, w http.ResponseWriter, r *http.Request) + switch r.URL.Path { + case "/rpc/PetStore/CreatePet": handler = s.serveCreatePetJSON + case "/rpc/PetStore/DeletePet": handler = s.serveDeletePetJSON + case "/rpc/PetStore/GetPet": handler = s.serveGetPetJSON + case "/rpc/PetStore/ListPets": handler = s.serveListPetsJSON + case "/rpc/PetStore/UpdatePet": handler = s.serveUpdatePetJSON + default: + err := ErrWebrpcBadRoute.WithCause(fmt.Errorf("no handler for path %q", r.URL.Path)) + s.sendErrorJSON(w, r, err) return } - switch r.URL.Path { - case "/rpc/PetStore/CreatePet": - s.serveCreatePet(ctx, w, r) - return - case "/rpc/PetStore/DeletePet": - s.serveDeletePet(ctx, w, r) - return - case "/rpc/PetStore/GetPet": - s.serveGetPet(ctx, w, r) - return - case "/rpc/PetStore/ListPets": - s.serveListPets(ctx, w, r) - return - case "/rpc/PetStore/UpdatePet": - s.serveUpdatePet(ctx, w, r) - return - default: - err := ErrorWithCause(ErrWebrpcBadRoute, fmt.Errorf("no handler for path %q", r.URL.Path)) - RespondWithError(w, err) + if r.Method != "POST" { + w.Header().Add("Allow", "POST") // RFC 9110. + err := ErrWebrpcBadMethod.WithCause(fmt.Errorf("unsupported method %q (only POST is allowed)", r.Method)) + s.sendErrorJSON(w, r, err) return } -} -func (s *petStoreServer) serveCreatePet(ctx context.Context, w http.ResponseWriter, r *http.Request) { - header := r.Header.Get("Content-Type") - i := strings.Index(header, ";") - if i == -1 { - i = len(header) + contentType := r.Header.Get("Content-Type") + if i := strings.Index(contentType, ";"); i >= 0 { + contentType = contentType[:i] } + contentType = strings.TrimSpace(strings.ToLower(contentType)) - switch strings.TrimSpace(strings.ToLower(header[:i])) { + switch contentType { case "application/json": - s.serveCreatePetJSON(ctx, w, r) + handler(ctx, w, r) default: - err := ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("unexpected Content-Type: %q", r.Header.Get("Content-Type"))) - RespondWithError(w, err) + err := ErrWebrpcBadRequest.WithCause(fmt.Errorf("unexpected Content-Type: %q", r.Header.Get("Content-Type"))) + s.sendErrorJSON(w, r, err) } } func (s *petStoreServer) serveCreatePetJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error ctx = context.WithValue(ctx, MethodNameCtxKey, "CreatePet") - reqContent := struct { - Arg0 *Pet `json:"new"` - }{} - reqBody, err := ioutil.ReadAll(r.Body) + reqBody, err := io.ReadAll(r.Body) if err != nil { - err = ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to read request data: %w", err)) - RespondWithError(w, err) + s.sendErrorJSON(w, r, ErrWebrpcBadRequest.WithCause(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 *Pet `json:"new"` + }{} + if err := json.Unmarshal(reqBody, &reqPayload); err != nil { + s.sendErrorJSON(w, r, ErrWebrpcBadRequest.WithCause(fmt.Errorf("failed to unmarshal request data: %w", err))) return } - // Call service method - var ret0 *Pet - 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.PetStore.CreatePet(ctx, reqContent.Arg0) - }() - respContent := struct { - Ret0 *Pet `json:"pet"` - }{ret0} - + // Call service method implementation. + ret0, err := s.PetStore.CreatePet(ctx, reqPayload.Arg0) if err != nil { - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } - respBody, err := json.Marshal(respContent) + + respPayload := struct { + Ret0 *Pet `json:"pet"` + }{ret0} + respBody, err := json.Marshal(respPayload) if err != nil { - err = ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err)) - RespondWithError(w, err) + s.sendErrorJSON(w, r, ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to marshal json response: %w", err))) return } @@ -194,58 +182,32 @@ func (s *petStoreServer) serveCreatePetJSON(ctx context.Context, w http.Response w.Write(respBody) } -func (s *petStoreServer) serveDeletePet(ctx context.Context, w http.ResponseWriter, r *http.Request) { - header := r.Header.Get("Content-Type") - i := strings.Index(header, ";") - if i == -1 { - i = len(header) - } - - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveDeletePetJSON(ctx, w, r) - default: - err := ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("unexpected Content-Type: %q", r.Header.Get("Content-Type"))) - RespondWithError(w, err) - } -} - func (s *petStoreServer) serveDeletePetJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error ctx = context.WithValue(ctx, MethodNameCtxKey, "DeletePet") - reqContent := struct { - Arg0 int64 `json:"ID"` - }{} - reqBody, err := ioutil.ReadAll(r.Body) + reqBody, err := io.ReadAll(r.Body) if err != nil { - err = ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to read request data: %w", err)) - RespondWithError(w, err) + s.sendErrorJSON(w, r, ErrWebrpcBadRequest.WithCause(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 int64 `json:"ID"` + }{} + if err := json.Unmarshal(reqBody, &reqPayload); err != nil { + s.sendErrorJSON(w, r, ErrWebrpcBadRequest.WithCause(fmt.Errorf("failed to unmarshal request data: %w", err))) return } - // 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.PetStore.DeletePet(ctx, reqContent.Arg0) - }() - + // Call service method implementation. + err = s.PetStore.DeletePet(ctx, reqPayload.Arg0) if err != nil { - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } @@ -254,68 +216,41 @@ func (s *petStoreServer) serveDeletePetJSON(ctx context.Context, w http.Response w.Write([]byte("{}")) } -func (s *petStoreServer) serveGetPet(ctx context.Context, w http.ResponseWriter, r *http.Request) { - header := r.Header.Get("Content-Type") - i := strings.Index(header, ";") - if i == -1 { - i = len(header) - } - - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveGetPetJSON(ctx, w, r) - default: - err := ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("unexpected Content-Type: %q", r.Header.Get("Content-Type"))) - RespondWithError(w, err) - } -} - func (s *petStoreServer) serveGetPetJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error ctx = context.WithValue(ctx, MethodNameCtxKey, "GetPet") - reqContent := struct { - Arg0 int64 `json:"ID"` - }{} - reqBody, err := ioutil.ReadAll(r.Body) + reqBody, err := io.ReadAll(r.Body) if err != nil { - err = ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to read request data: %w", err)) - RespondWithError(w, err) + s.sendErrorJSON(w, r, ErrWebrpcBadRequest.WithCause(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 int64 `json:"ID"` + }{} + if err := json.Unmarshal(reqBody, &reqPayload); err != nil { + s.sendErrorJSON(w, r, ErrWebrpcBadRequest.WithCause(fmt.Errorf("failed to unmarshal request data: %w", err))) return } - // Call service method - var ret0 *Pet - 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.PetStore.GetPet(ctx, reqContent.Arg0) - }() - respContent := struct { - Ret0 *Pet `json:"pet"` - }{ret0} - + // Call service method implementation. + ret0, err := s.PetStore.GetPet(ctx, reqPayload.Arg0) if err != nil { - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } - respBody, err := json.Marshal(respContent) + + respPayload := struct { + Ret0 *Pet `json:"pet"` + }{ret0} + respBody, err := json.Marshal(respPayload) if err != nil { - err = ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err)) - RespondWithError(w, err) + s.sendErrorJSON(w, r, ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to marshal json response: %w", err))) return } @@ -324,50 +259,26 @@ func (s *petStoreServer) serveGetPetJSON(ctx context.Context, w http.ResponseWri w.Write(respBody) } -func (s *petStoreServer) serveListPets(ctx context.Context, w http.ResponseWriter, r *http.Request) { - header := r.Header.Get("Content-Type") - i := strings.Index(header, ";") - if i == -1 { - i = len(header) - } - - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveListPetsJSON(ctx, w, r) - default: - err := ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("unexpected Content-Type: %q", r.Header.Get("Content-Type"))) - RespondWithError(w, err) - } -} - func (s *petStoreServer) serveListPetsJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error ctx = context.WithValue(ctx, MethodNameCtxKey, "ListPets") - // Call service method - var ret0 []*Pet - 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.PetStore.ListPets(ctx) - }() - respContent := struct { - Ret0 []*Pet `json:"pets"` - }{ret0} - + // Call service method implementation. + ret0, err := s.PetStore.ListPets(ctx) if err != nil { - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } - respBody, err := json.Marshal(respContent) + + respPayload := struct { + Ret0 []*Pet `json:"pets"` + }{ret0} + respBody, err := json.Marshal(respPayload) if err != nil { - err = ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err)) - RespondWithError(w, err) + s.sendErrorJSON(w, r, ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to marshal json response: %w", err))) return } @@ -376,69 +287,42 @@ func (s *petStoreServer) serveListPetsJSON(ctx context.Context, w http.ResponseW w.Write(respBody) } -func (s *petStoreServer) serveUpdatePet(ctx context.Context, w http.ResponseWriter, r *http.Request) { - header := r.Header.Get("Content-Type") - i := strings.Index(header, ";") - if i == -1 { - i = len(header) - } +func (s *petStoreServer) serveUpdatePetJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { + ctx = context.WithValue(ctx, MethodNameCtxKey, "UpdatePet") - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveUpdatePetJSON(ctx, w, r) - default: - err := ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("unexpected Content-Type: %q", r.Header.Get("Content-Type"))) - RespondWithError(w, err) + reqBody, err := io.ReadAll(r.Body) + if err != nil { + s.sendErrorJSON(w, r, ErrWebrpcBadRequest.WithCause(fmt.Errorf("failed to read request data: %w", err))) + return } -} + defer r.Body.Close() -func (s *petStoreServer) serveUpdatePetJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "UpdatePet") - reqContent := struct { + reqPayload := struct { Arg0 int64 `json:"ID"` Arg1 *Pet `json:"update"` }{} - - reqBody, err := ioutil.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 { + s.sendErrorJSON(w, r, ErrWebrpcBadRequest.WithCause(fmt.Errorf("failed to unmarshal request data: %w", err))) return } - defer r.Body.Close() - err = json.Unmarshal(reqBody, &reqContent) + // Call service method implementation. + ret0, err := s.PetStore.UpdatePet(ctx, reqPayload.Arg0, reqPayload.Arg1) if err != nil { - err = ErrorWithCause(ErrWebrpcBadRequest, fmt.Errorf("failed to unmarshal request data: %w", err)) - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } - // Call service method - var ret0 *Pet - 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.PetStore.UpdatePet(ctx, reqContent.Arg0, reqContent.Arg1) - }() - respContent := struct { + respPayload := struct { Ret0 *Pet `json:"pet"` }{ret0} - + respBody, err := json.Marshal(respPayload) if err != nil { - RespondWithError(w, err) - return - } - respBody, err := json.Marshal(respContent) - if err != nil { - err = ErrorWithCause(ErrWebrpcBadResponse, fmt.Errorf("failed to marshal json response: %w", err)) - RespondWithError(w, err) + s.sendErrorJSON(w, r, ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to marshal json response: %w", err))) return } @@ -447,10 +331,22 @@ func (s *petStoreServer) serveUpdatePetJSON(ctx context.Context, w http.Response w.Write(respBody) } + +func (s *petStoreServer) sendErrorJSON(w http.ResponseWriter, r *http.Request, rpcErr WebRPCError) { + if s.OnError != nil { + s.OnError(r, &rpcErr) + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(rpcErr.HTTPStatus) + + respBody, _ := json.Marshal(rpcErr) + w.Write(respBody) +} func RespondWithError(w http.ResponseWriter, err error) { rpcErr, ok := err.(WebRPCError) if !ok { - rpcErr = ErrorWithCause(ErrWebrpcEndpoint, err) + rpcErr = ErrWebrpcEndpoint.WithCause(err) } w.Header().Set("Content-Type", "application/json") @@ -473,10 +369,6 @@ func (k *contextKey) String() string { } var ( - // For Client - HTTPClientRequestHeadersCtxKey = &contextKey{"HTTPClientRequestHeaders"} - - // For Server HTTPResponseWriterCtxKey = &contextKey{"HTTPResponseWriter"} HTTPRequestCtxKey = &contextKey{"HTTPRequest"} @@ -486,6 +378,26 @@ var ( MethodNameCtxKey = &contextKey{"MethodName"} ) +func ServiceNameFromContext(ctx context.Context) string { + service, _ := ctx.Value(ServiceNameCtxKey).(string) + return service +} + +func MethodNameFromContext(ctx context.Context) string { + method, _ := ctx.Value(MethodNameCtxKey).(string) + return method +} + +func RequestFromContext(ctx context.Context) *http.Request { + r, _ := ctx.Value(HTTPRequestCtxKey).(*http.Request) + return r +} +func ResponseWriterFromContext(ctx context.Context) http.ResponseWriter { + w, _ := ctx.Value(HTTPResponseWriterCtxKey).(http.ResponseWriter) + return w +} + + // // Errors // @@ -522,13 +434,18 @@ func (e WebRPCError) Unwrap() error { return e.cause } -func ErrorWithCause(rpcErr WebRPCError, cause error) WebRPCError { - err := rpcErr +func (e WebRPCError) WithCause(cause error) WebRPCError { + err := e err.cause = cause err.Cause = cause.Error() return err } +// Deprecated: Use .WithCause() method on WebRPCError. +func ErrorWithCause(rpcErr WebRPCError, cause error) WebRPCError { + return rpcErr.WithCause(cause) +} + // Webrpc errors var ( ErrWebrpcEndpoint = WebRPCError{Code: 0, Name: "WebrpcEndpoint", Message: "endpoint error", HTTPStatus: 400} @@ -545,42 +462,42 @@ var ( // Legacy errors // -// Deprecated: Use ErrorWithCause() instead. +// Deprecated: Use fmt.Errorf() or WebRPCError. func Errorf(err legacyError, format string, args ...interface{}) WebRPCError { - return ErrorWithCause(err.WebRPCError, fmt.Errorf(format, args...)) + return err.WebRPCError.WithCause(fmt.Errorf(format, args...)) } -// Deprecated: Use ErrorWithCause() instead. +// Deprecated: Use .WithCause() method on WebRPCError. func WrapError(err legacyError, cause error, format string, args ...interface{}) WebRPCError { - return ErrorWithCause(err.WebRPCError, fmt.Errorf("%v: %w", fmt.Errorf(format, args...), cause)) + return err.WebRPCError.WithCause(fmt.Errorf("%v: %w", fmt.Errorf(format, args...), cause)) } -// Deprecated: Use ErrorWithCause() instead. +// Deprecated: Use fmt.Errorf() or WebRPCError. func Failf(format string, args ...interface{}) WebRPCError { return Errorf(ErrFail, format, args...) } -// Deprecated: Use ErrorWithCause() instead. +// Deprecated: Use .WithCause() method on WebRPCError. func WrapFailf(cause error, format string, args ...interface{}) WebRPCError { return WrapError(ErrFail, cause, format, args...) } -// Deprecated: Use ErrorWithCause() instead. +// Deprecated: Use fmt.Errorf() or WebRPCError. func ErrorNotFound(format string, args ...interface{}) WebRPCError { return Errorf(ErrNotFound, format, args...) } -// Deprecated: Use ErrorWithCause() instead. +// Deprecated: Use fmt.Errorf() or WebRPCError. func ErrorInvalidArgument(argument string, validationMsg string) WebRPCError { return Errorf(ErrInvalidArgument, argument+" "+validationMsg) } -// Deprecated: Use ErrorWithCause() instead. +// Deprecated: Use fmt.Errorf() or WebRPCError. func ErrorRequiredArgument(argument string) WebRPCError { return ErrorInvalidArgument(argument, "is required") } -// Deprecated: Use ErrorWithCause() instead. +// Deprecated: Use fmt.Errorf() or WebRPCError. func ErrorInternal(format string, args ...interface{}) WebRPCError { return Errorf(ErrInternal, format, args...) }