From 65b52863cb748f96b9b5b81260216a57fb8da904 Mon Sep 17 00:00:00 2001 From: Vojtech Vitek Date: Sat, 4 May 2024 11:14:47 +0200 Subject: [PATCH] Update sessions client --- sessions/proto/sessions.gen.go | 1367 ++++++++++++-------------------- 1 file changed, 514 insertions(+), 853 deletions(-) diff --git a/sessions/proto/sessions.gen.go b/sessions/proto/sessions.gen.go index 8baf084f..aae0ddae 100644 --- a/sessions/proto/sessions.gen.go +++ b/sessions/proto/sessions.gen.go @@ -1,6 +1,6 @@ -// sessions v0.0.1 b96502864e03f0bea75f41cafaf2178946a9e3b7 +// sessions v0.0.1 d81ea64cbe41c1ab8b0107bce2f118817b34ebc0 // -- -// Code generated by webrpc-gen@v0.10.x-dev with golang generator. DO NOT EDIT. +// Code generated by webrpc-gen@v0.18.6 with golang generator. DO NOT EDIT. // // webrpc-gen -schema=sessions.ridl -target=golang -pkg=proto -server -client -out=./sessions.gen.go package proto @@ -12,10 +12,10 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" "net/url" "strings" + "time" "github.com/0xsequence/go-sequence/lib/prototyp" ) @@ -32,11 +32,11 @@ func WebRPCSchemaVersion() string { // Schema hash generated from your RIDL schema func WebRPCSchemaHash() string { - return "b96502864e03f0bea75f41cafaf2178946a9e3b7" + return "d81ea64cbe41c1ab8b0107bce2f118817b34ebc0" } // -// Types +// Common types // type SignatureType int32 @@ -63,23 +63,51 @@ func (x SignatureType) String() string { return SignatureType_name[int32(x)] } -func (x SignatureType) MarshalJSON() ([]byte, error) { - buf := bytes.NewBufferString(`"`) - buf.WriteString(SignatureType_name[int32(x)]) - buf.WriteString(`"`) - return buf.Bytes(), nil +func (x SignatureType) MarshalText() ([]byte, error) { + return []byte(SignatureType_name[int32(x)]), nil } -func (x *SignatureType) UnmarshalJSON(b []byte) error { - var j string - err := json.Unmarshal(b, &j) - if err != nil { - return err - } - *x = SignatureType(SignatureType_value[j]) +func (x *SignatureType) UnmarshalText(b []byte) error { + *x = SignatureType(SignatureType_value[string(b)]) return nil } +func (x *SignatureType) Is(values ...SignatureType) bool { + if x == nil { + return false + } + for _, v := range values { + if *x == v { + return true + } + } + return false +} + +type RuntimeStatus struct { + Healthy bool `json:"healthy"` + Started time.Time `json:"started"` + Uptime uint64 `json:"uptime"` + Version string `json:"version"` + Branch string `json:"branch"` + Commit string `json:"commit"` + Wallets map[string]uint64 `json:"wallets"` + Configs map[string]uint64 `json:"configs"` + ConfigTrees uint64 `json:"configTrees"` + Migrations map[string]uint64 `json:"migrations"` + Signatures uint64 `json:"signatures"` + Digests uint64 `json:"digests"` + Recorder *RecorderStatus `json:"recorder"` +} + +type RecorderStatus struct { + Requests uint64 `json:"requests"` + Buffer uint64 `json:"buffer"` + LastFlush *time.Time `json:"lastFlush"` + SecondsSinceLastFlush *uint64 `json:"secondsSinceLastFlush"` + Endpoints map[string]uint64 `json:"endpoints"` +} + type Context struct { Version int `json:"version"` Factory prototyp.Hash `json:"factory"` @@ -118,20 +146,6 @@ type TransactionBundle struct { Signature prototyp.Hash `json:"signature"` } -type Sessions interface { - Ping(ctx context.Context) error - Config(ctx context.Context, imageHash string) (int, interface{}, error) - Wallets(ctx context.Context, signer string) (map[string]*Signature, error) - DeployHash(ctx context.Context, wallet string) (string, *Context, error) - ConfigUpdates(ctx context.Context, wallet string, fromImageHash string, allUpdates *bool) ([]*ConfigUpdate, error) - Migrations(ctx context.Context, wallet string, fromVersion int, fromImageHash string, chainID *string) (map[string]map[int]map[string]*TransactionBundle, error) - SaveConfig(ctx context.Context, version int, config interface{}) error - SaveWallet(ctx context.Context, version int, deployConfig interface{}) error - SaveSignature(ctx context.Context, wallet string, digest string, chainID string, signature string, toConfig *interface{}) error - SaveSignerSignatures(ctx context.Context, wallet string, digest string, chainID string, signatures []string, toConfig *interface{}) error - SaveMigration(ctx context.Context, wallet string, fromVersion int, toVersion int, toConfig interface{}, executor string, transactions []*Transaction, nonce string, signature string, chainID *string) error -} - var WebRPCServices = map[string][]string{ "Sessions": { "Ping", @@ -148,6 +162,42 @@ var WebRPCServices = map[string][]string{ }, } +// +// Server types +// + +type Sessions interface { + Ping(ctx context.Context) error + Config(ctx context.Context, imageHash string) (int, interface{}, error) + Wallets(ctx context.Context, signer string) (map[string]*Signature, error) + DeployHash(ctx context.Context, wallet string) (string, *Context, error) + ConfigUpdates(ctx context.Context, wallet string, fromImageHash string, allUpdates *bool) ([]*ConfigUpdate, error) + Migrations(ctx context.Context, wallet string, fromVersion int, fromImageHash string, chainID *string) (map[string]map[int]map[string]*TransactionBundle, error) + SaveConfig(ctx context.Context, version int, config interface{}) error + SaveWallet(ctx context.Context, version int, deployConfig interface{}) error + SaveSignature(ctx context.Context, wallet string, digest string, chainID string, signature string, toConfig *interface{}) error + SaveSignerSignatures(ctx context.Context, wallet string, digest string, chainID string, signatures []string, toConfig *interface{}) error + SaveMigration(ctx context.Context, wallet string, fromVersion int, toVersion int, toConfig interface{}, executor string, transactions []*Transaction, nonce string, signature string, chainID *string) error +} + +// +// Client types +// + +type SessionsClient interface { + Ping(ctx context.Context) error + Config(ctx context.Context, imageHash string) (int, interface{}, error) + Wallets(ctx context.Context, signer string) (map[string]*Signature, error) + DeployHash(ctx context.Context, wallet string) (string, *Context, error) + ConfigUpdates(ctx context.Context, wallet string, fromImageHash string, allUpdates *bool) ([]*ConfigUpdate, error) + Migrations(ctx context.Context, wallet string, fromVersion int, fromImageHash string, chainID *string) (map[string]map[int]map[string]*TransactionBundle, error) + SaveConfig(ctx context.Context, version int, config interface{}) error + SaveWallet(ctx context.Context, version int, deployConfig interface{}) error + SaveSignature(ctx context.Context, wallet string, digest string, chainID string, signature string, toConfig *interface{}) error + SaveSignerSignatures(ctx context.Context, wallet string, digest string, chainID string, signatures []string, toConfig *interface{}) error + SaveMigration(ctx context.Context, wallet string, fromVersion int, toVersion int, toConfig interface{}, executor string, transactions []*Transaction, nonce string, signature string, chainID *string) error +} + // // Server // @@ -158,101 +208,92 @@ type WebRPCServer interface { type sessionsServer struct { Sessions + OnError func(r *http.Request, rpcErr *WebRPCError) } -func NewSessionsServer(svc Sessions) WebRPCServer { +func NewSessionsServer(svc Sessions) *sessionsServer { return &sessionsServer{ Sessions: svc, } } func (s *sessionsServer) 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, "Sessions") - if r.Method != "POST" { - err := Errorf(ErrBadRoute, "unsupported method %q (only POST is allowed)", r.Method) - RespondWithError(w, err) - return - } - + var handler func(ctx context.Context, w http.ResponseWriter, r *http.Request) switch r.URL.Path { case "/rpc/Sessions/Ping": - s.servePing(ctx, w, r) - return + handler = s.servePingJSON case "/rpc/Sessions/Config": - s.serveConfig(ctx, w, r) - return + handler = s.serveConfigJSON case "/rpc/Sessions/Wallets": - s.serveWallets(ctx, w, r) - return + handler = s.serveWalletsJSON case "/rpc/Sessions/DeployHash": - s.serveDeployHash(ctx, w, r) - return + handler = s.serveDeployHashJSON case "/rpc/Sessions/ConfigUpdates": - s.serveConfigUpdates(ctx, w, r) - return + handler = s.serveConfigUpdatesJSON case "/rpc/Sessions/Migrations": - s.serveMigrations(ctx, w, r) - return + handler = s.serveMigrationsJSON case "/rpc/Sessions/SaveConfig": - s.serveSaveConfig(ctx, w, r) - return + handler = s.serveSaveConfigJSON case "/rpc/Sessions/SaveWallet": - s.serveSaveWallet(ctx, w, r) - return + handler = s.serveSaveWalletJSON case "/rpc/Sessions/SaveSignature": - s.serveSaveSignature(ctx, w, r) - return + handler = s.serveSaveSignatureJSON case "/rpc/Sessions/SaveSignerSignatures": - s.serveSaveSignerSignatures(ctx, w, r) - return + handler = s.serveSaveSignerSignaturesJSON case "/rpc/Sessions/SaveMigration": - s.serveSaveMigration(ctx, w, r) - return + handler = s.serveSaveMigrationJSON default: - err := Errorf(ErrBadRoute, "no handler for path %q", r.URL.Path) - RespondWithError(w, err) + err := ErrWebrpcBadRoute.WithCause(fmt.Errorf("no handler for path %q", r.URL.Path)) + s.sendErrorJSON(w, r, err) + return + } + + 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 *sessionsServer) servePing(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.servePingJSON(ctx, w, r) + handler(ctx, w, r) default: - err := Errorf(ErrBadRoute, "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 *sessionsServer) 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, ErrorInternal("internal service panic")) - panic(rr) - } - }() - err = s.Sessions.Ping(ctx) - }() - + // Call service method implementation. + err := s.Sessions.Ping(ctx) if err != nil { - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } @@ -261,70 +302,42 @@ func (s *sessionsServer) servePingJSON(ctx context.Context, w http.ResponseWrite w.Write([]byte("{}")) } -func (s *sessionsServer) serveConfig(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.serveConfigJSON(ctx, w, r) - default: - err := Errorf(ErrBadRoute, "unexpected Content-Type: %q", r.Header.Get("Content-Type")) - RespondWithError(w, err) - } -} - func (s *sessionsServer) serveConfigJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error ctx = context.WithValue(ctx, MethodNameCtxKey, "Config") - reqContent := struct { - Arg0 string `json:"imageHash"` - }{} - reqBody, err := ioutil.ReadAll(r.Body) + reqBody, err := io.ReadAll(r.Body) if err != nil { - err = WrapError(ErrInternal, err, "failed to read request data") - 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) + reqPayload := struct { + Arg0 string `json:"imageHash"` + }{} + 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 implementation. + ret0, ret1, err := s.Sessions.Config(ctx, reqPayload.Arg0) if err != nil { - err = WrapError(ErrInvalidArgument, err, "failed to unmarshal request data") - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } - // Call service method - var ret0 int - var ret1 interface{} - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorInternal("internal service panic")) - panic(rr) - } - }() - ret0, ret1, err = s.Sessions.Config(ctx, reqContent.Arg0) - }() - respContent := struct { + respPayload := struct { Ret0 int `json:"version"` Ret1 interface{} `json:"config"` }{ret0, ret1} - - if err != nil { - RespondWithError(w, err) - return - } - respBody, err := json.Marshal(respContent) + respBody, err := json.Marshal(respPayload) if err != nil { - err = WrapError(ErrInternal, err, "failed to marshal json response") - RespondWithError(w, err) + s.sendErrorJSON(w, r, ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to marshal json response: %w", err))) return } @@ -333,68 +346,41 @@ func (s *sessionsServer) serveConfigJSON(ctx context.Context, w http.ResponseWri w.Write(respBody) } -func (s *sessionsServer) serveWallets(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.serveWalletsJSON(ctx, w, r) - default: - err := Errorf(ErrBadRoute, "unexpected Content-Type: %q", r.Header.Get("Content-Type")) - RespondWithError(w, err) - } -} - func (s *sessionsServer) serveWalletsJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error ctx = context.WithValue(ctx, MethodNameCtxKey, "Wallets") - reqContent := struct { - Arg0 string `json:"signer"` - }{} - reqBody, err := ioutil.ReadAll(r.Body) + reqBody, err := io.ReadAll(r.Body) if err != nil { - err = WrapError(ErrInternal, err, "failed to read request data") - 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 = WrapError(ErrInvalidArgument, err, "failed to unmarshal request data") - RespondWithError(w, err) + reqPayload := struct { + Arg0 string `json:"signer"` + }{} + 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 map[string]*Signature - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorInternal("internal service panic")) - panic(rr) - } - }() - ret0, err = s.Sessions.Wallets(ctx, reqContent.Arg0) - }() - respContent := struct { - Ret0 map[string]*Signature `json:"wallets"` - }{ret0} - + // Call service method implementation. + ret0, err := s.Sessions.Wallets(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 map[string]*Signature `json:"wallets"` + }{ret0} + respBody, err := json.Marshal(respPayload) if err != nil { - err = WrapError(ErrInternal, err, "failed to marshal json response") - RespondWithError(w, err) + s.sendErrorJSON(w, r, ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to marshal json response: %w", err))) return } @@ -403,70 +389,42 @@ func (s *sessionsServer) serveWalletsJSON(ctx context.Context, w http.ResponseWr w.Write(respBody) } -func (s *sessionsServer) serveDeployHash(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.serveDeployHashJSON(ctx, w, r) - default: - err := Errorf(ErrBadRoute, "unexpected Content-Type: %q", r.Header.Get("Content-Type")) - RespondWithError(w, err) - } -} - func (s *sessionsServer) serveDeployHashJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error ctx = context.WithValue(ctx, MethodNameCtxKey, "DeployHash") - reqContent := struct { - Arg0 string `json:"wallet"` - }{} - reqBody, err := ioutil.ReadAll(r.Body) + reqBody, err := io.ReadAll(r.Body) if err != nil { - err = WrapError(ErrInternal, err, "failed to read request data") - 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) + reqPayload := struct { + Arg0 string `json:"wallet"` + }{} + 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 implementation. + ret0, ret1, err := s.Sessions.DeployHash(ctx, reqPayload.Arg0) if err != nil { - err = WrapError(ErrInvalidArgument, err, "failed to unmarshal request data") - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } - // Call service method - var ret0 string - var ret1 *Context - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorInternal("internal service panic")) - panic(rr) - } - }() - ret0, ret1, err = s.Sessions.DeployHash(ctx, reqContent.Arg0) - }() - respContent := struct { + respPayload := struct { Ret0 string `json:"deployHash"` Ret1 *Context `json:"context"` }{ret0, ret1} - + respBody, err := json.Marshal(respPayload) if err != nil { - RespondWithError(w, err) - return - } - respBody, err := json.Marshal(respContent) - if err != nil { - err = WrapError(ErrInternal, err, "failed to marshal json response") - RespondWithError(w, err) + s.sendErrorJSON(w, r, ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to marshal json response: %w", err))) return } @@ -475,70 +433,43 @@ func (s *sessionsServer) serveDeployHashJSON(ctx context.Context, w http.Respons w.Write(respBody) } -func (s *sessionsServer) serveConfigUpdates(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 *sessionsServer) serveConfigUpdatesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { + ctx = context.WithValue(ctx, MethodNameCtxKey, "ConfigUpdates") - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveConfigUpdatesJSON(ctx, w, r) - default: - err := Errorf(ErrBadRoute, "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 *sessionsServer) serveConfigUpdatesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "ConfigUpdates") - reqContent := struct { + reqPayload := struct { Arg0 string `json:"wallet"` Arg1 string `json:"fromImageHash"` Arg2 *bool `json:"allUpdates"` }{} - - reqBody, err := ioutil.ReadAll(r.Body) - if err != nil { - err = WrapError(ErrInternal, err, "failed to read request data") - 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.Sessions.ConfigUpdates(ctx, reqPayload.Arg0, reqPayload.Arg1, reqPayload.Arg2) if err != nil { - err = WrapError(ErrInvalidArgument, err, "failed to unmarshal request data") - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } - // Call service method - var ret0 []*ConfigUpdate - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorInternal("internal service panic")) - panic(rr) - } - }() - ret0, err = s.Sessions.ConfigUpdates(ctx, reqContent.Arg0, reqContent.Arg1, reqContent.Arg2) - }() - respContent := struct { + respPayload := struct { Ret0 []*ConfigUpdate `json:"updates"` }{ret0} - + respBody, err := json.Marshal(respPayload) if err != nil { - RespondWithError(w, err) - return - } - respBody, err := json.Marshal(respContent) - if err != nil { - err = WrapError(ErrInternal, err, "failed to marshal json response") - RespondWithError(w, err) + s.sendErrorJSON(w, r, ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to marshal json response: %w", err))) return } @@ -547,71 +478,44 @@ func (s *sessionsServer) serveConfigUpdatesJSON(ctx context.Context, w http.Resp w.Write(respBody) } -func (s *sessionsServer) serveMigrations(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 *sessionsServer) serveMigrationsJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { + ctx = context.WithValue(ctx, MethodNameCtxKey, "Migrations") - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveMigrationsJSON(ctx, w, r) - default: - err := Errorf(ErrBadRoute, "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 *sessionsServer) serveMigrationsJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "Migrations") - reqContent := struct { + reqPayload := struct { Arg0 string `json:"wallet"` Arg1 int `json:"fromVersion"` Arg2 string `json:"fromImageHash"` Arg3 *string `json:"chainID"` }{} - - reqBody, err := ioutil.ReadAll(r.Body) - if err != nil { - err = WrapError(ErrInternal, err, "failed to read request data") - 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.Sessions.Migrations(ctx, reqPayload.Arg0, reqPayload.Arg1, reqPayload.Arg2, reqPayload.Arg3) if err != nil { - err = WrapError(ErrInvalidArgument, err, "failed to unmarshal request data") - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } - // Call service method - var ret0 map[string]map[int]map[string]*TransactionBundle - func() { - defer func() { - // In case of a panic, serve a 500 error and then panic. - if rr := recover(); rr != nil { - RespondWithError(w, ErrorInternal("internal service panic")) - panic(rr) - } - }() - ret0, err = s.Sessions.Migrations(ctx, reqContent.Arg0, reqContent.Arg1, reqContent.Arg2, reqContent.Arg3) - }() - respContent := struct { + respPayload := struct { Ret0 map[string]map[int]map[string]*TransactionBundle `json:"migrations"` }{ret0} - + respBody, err := json.Marshal(respPayload) if err != nil { - RespondWithError(w, err) - return - } - respBody, err := json.Marshal(respContent) - if err != nil { - err = WrapError(ErrInternal, err, "failed to marshal json response") - RespondWithError(w, err) + s.sendErrorJSON(w, r, ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to marshal json response: %w", err))) return } @@ -620,59 +524,33 @@ func (s *sessionsServer) serveMigrationsJSON(ctx context.Context, w http.Respons w.Write(respBody) } -func (s *sessionsServer) serveSaveConfig(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.serveSaveConfigJSON(ctx, w, r) - default: - err := Errorf(ErrBadRoute, "unexpected Content-Type: %q", r.Header.Get("Content-Type")) - RespondWithError(w, err) - } -} - func (s *sessionsServer) serveSaveConfigJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error ctx = context.WithValue(ctx, MethodNameCtxKey, "SaveConfig") - reqContent := struct { - Arg0 int `json:"version"` - Arg1 interface{} `json:"config"` - }{} - reqBody, err := ioutil.ReadAll(r.Body) + reqBody, err := io.ReadAll(r.Body) if err != nil { - err = WrapError(ErrInternal, err, "failed to read request data") - 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 = WrapError(ErrInvalidArgument, err, "failed to unmarshal request data") - RespondWithError(w, err) + reqPayload := struct { + Arg0 int `json:"version"` + Arg1 interface{} `json:"config"` + }{} + 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, ErrorInternal("internal service panic")) - panic(rr) - } - }() - err = s.Sessions.SaveConfig(ctx, reqContent.Arg0, reqContent.Arg1) - }() - + // Call service method implementation. + err = s.Sessions.SaveConfig(ctx, reqPayload.Arg0, reqPayload.Arg1) if err != nil { - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } @@ -681,59 +559,33 @@ func (s *sessionsServer) serveSaveConfigJSON(ctx context.Context, w http.Respons w.Write([]byte("{}")) } -func (s *sessionsServer) serveSaveWallet(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.serveSaveWalletJSON(ctx, w, r) - default: - err := Errorf(ErrBadRoute, "unexpected Content-Type: %q", r.Header.Get("Content-Type")) - RespondWithError(w, err) - } -} - func (s *sessionsServer) serveSaveWalletJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error ctx = context.WithValue(ctx, MethodNameCtxKey, "SaveWallet") - reqContent := struct { - Arg0 int `json:"version"` - Arg1 interface{} `json:"deployConfig"` - }{} - reqBody, err := ioutil.ReadAll(r.Body) + reqBody, err := io.ReadAll(r.Body) if err != nil { - err = WrapError(ErrInternal, err, "failed to read request data") - 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 = WrapError(ErrInvalidArgument, err, "failed to unmarshal request data") - RespondWithError(w, err) + reqPayload := struct { + Arg0 int `json:"version"` + Arg1 interface{} `json:"deployConfig"` + }{} + 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, ErrorInternal("internal service panic")) - panic(rr) - } - }() - err = s.Sessions.SaveWallet(ctx, reqContent.Arg0, reqContent.Arg1) - }() - + // Call service method implementation. + err = s.Sessions.SaveWallet(ctx, reqPayload.Arg0, reqPayload.Arg1) if err != nil { - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } @@ -742,62 +594,36 @@ func (s *sessionsServer) serveSaveWalletJSON(ctx context.Context, w http.Respons w.Write([]byte("{}")) } -func (s *sessionsServer) serveSaveSignature(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 *sessionsServer) serveSaveSignatureJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { + ctx = context.WithValue(ctx, MethodNameCtxKey, "SaveSignature") - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveSaveSignatureJSON(ctx, w, r) - default: - err := Errorf(ErrBadRoute, "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 *sessionsServer) serveSaveSignatureJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "SaveSignature") - reqContent := struct { + reqPayload := struct { Arg0 string `json:"wallet"` Arg1 string `json:"digest"` Arg2 string `json:"chainID"` Arg3 string `json:"signature"` Arg4 *interface{} `json:"toConfig"` }{} - - reqBody, err := ioutil.ReadAll(r.Body) - if err != nil { - err = WrapError(ErrInternal, err, "failed to read request data") - RespondWithError(w, err) - return - } - defer r.Body.Close() - - err = json.Unmarshal(reqBody, &reqContent) - if err != nil { - err = WrapError(ErrInvalidArgument, err, "failed to unmarshal request data") - 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 } - // 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, ErrorInternal("internal service panic")) - panic(rr) - } - }() - err = s.Sessions.SaveSignature(ctx, reqContent.Arg0, reqContent.Arg1, reqContent.Arg2, reqContent.Arg3, reqContent.Arg4) - }() - + // Call service method implementation. + err = s.Sessions.SaveSignature(ctx, reqPayload.Arg0, reqPayload.Arg1, reqPayload.Arg2, reqPayload.Arg3, reqPayload.Arg4) if err != nil { - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } @@ -806,62 +632,36 @@ func (s *sessionsServer) serveSaveSignatureJSON(ctx context.Context, w http.Resp w.Write([]byte("{}")) } -func (s *sessionsServer) serveSaveSignerSignatures(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 *sessionsServer) serveSaveSignerSignaturesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { + ctx = context.WithValue(ctx, MethodNameCtxKey, "SaveSignerSignatures") - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveSaveSignerSignaturesJSON(ctx, w, r) - default: - err := Errorf(ErrBadRoute, "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 *sessionsServer) serveSaveSignerSignaturesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "SaveSignerSignatures") - reqContent := struct { + reqPayload := struct { Arg0 string `json:"wallet"` Arg1 string `json:"digest"` Arg2 string `json:"chainID"` Arg3 []string `json:"signatures"` Arg4 *interface{} `json:"toConfig"` }{} - - reqBody, err := ioutil.ReadAll(r.Body) - if err != nil { - err = WrapError(ErrInternal, err, "failed to read request data") - 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) - if err != nil { - err = WrapError(ErrInvalidArgument, err, "failed to unmarshal request data") - RespondWithError(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, ErrorInternal("internal service panic")) - panic(rr) - } - }() - err = s.Sessions.SaveSignerSignatures(ctx, reqContent.Arg0, reqContent.Arg1, reqContent.Arg2, reqContent.Arg3, reqContent.Arg4) - }() + // Call service method implementation. + err = s.Sessions.SaveSignerSignatures(ctx, reqPayload.Arg0, reqPayload.Arg1, reqPayload.Arg2, reqPayload.Arg3, reqPayload.Arg4) if err != nil { - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) return } @@ -870,26 +670,17 @@ func (s *sessionsServer) serveSaveSignerSignaturesJSON(ctx context.Context, w ht w.Write([]byte("{}")) } -func (s *sessionsServer) serveSaveMigration(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 *sessionsServer) serveSaveMigrationJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { + ctx = context.WithValue(ctx, MethodNameCtxKey, "SaveMigration") - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveSaveMigrationJSON(ctx, w, r) - default: - err := Errorf(ErrBadRoute, "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 *sessionsServer) serveSaveMigrationJSON(ctx context.Context, w http.ResponseWriter, r *http.Request) { - var err error - ctx = context.WithValue(ctx, MethodNameCtxKey, "SaveMigration") - reqContent := struct { + reqPayload := struct { Arg0 string `json:"wallet"` Arg1 int `json:"fromVersion"` Arg2 int `json:"toVersion"` @@ -900,56 +691,49 @@ func (s *sessionsServer) serveSaveMigrationJSON(ctx context.Context, w http.Resp Arg7 string `json:"signature"` Arg8 *string `json:"chainID"` }{} - - reqBody, err := ioutil.ReadAll(r.Body) - if err != nil { - err = WrapError(ErrInternal, err, "failed to read request data") - 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. + err = s.Sessions.SaveMigration(ctx, reqPayload.Arg0, reqPayload.Arg1, reqPayload.Arg2, reqPayload.Arg3, reqPayload.Arg4, reqPayload.Arg5, reqPayload.Arg6, reqPayload.Arg7, reqPayload.Arg8) if err != nil { - err = WrapError(ErrInvalidArgument, err, "failed to unmarshal request data") - RespondWithError(w, err) + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) 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, ErrorInternal("internal service panic")) - panic(rr) - } - }() - err = s.Sessions.SaveMigration(ctx, reqContent.Arg0, reqContent.Arg1, reqContent.Arg2, reqContent.Arg3, reqContent.Arg4, reqContent.Arg5, reqContent.Arg6, reqContent.Arg7, reqContent.Arg8) - }() + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte("{}")) +} - if err != nil { - RespondWithError(w, err) - return +func (s *sessionsServer) 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(http.StatusOK) - w.Write([]byte("{}")) + w.WriteHeader(rpcErr.HTTPStatus) + + respBody, _ := json.Marshal(rpcErr) + w.Write(respBody) } func RespondWithError(w http.ResponseWriter, err error) { - rpcErr, ok := err.(Error) + rpcErr, ok := err.(WebRPCError) if !ok { - rpcErr = WrapError(ErrInternal, err, "webrpc error") + rpcErr = ErrWebrpcEndpoint.WithCause(err) } - statusCode := HTTPStatusFromErrorCode(rpcErr.Code()) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(statusCode) + w.WriteHeader(rpcErr.HTTPStatus) - respBody, _ := json.Marshal(rpcErr.Payload()) + respBody, _ := json.Marshal(rpcErr) w.Write(respBody) } @@ -964,7 +748,7 @@ type sessionsClient struct { urls [11]string } -func NewSessionsClient(addr string, client HTTPClient) Sessions { +func NewSessionsClient(addr string, client HTTPClient) SessionsClient { prefix := urlBase(addr) + SessionsPathPrefix urls := [11]string{ prefix + "Ping", @@ -987,7 +771,14 @@ func NewSessionsClient(addr string, client HTTPClient) Sessions { func (c *sessionsClient) Ping(ctx context.Context) error { - err := doJSONRequest(ctx, c.client, c.urls[0], nil, nil) + resp, err := doHTTPRequest(ctx, c.client, c.urls[0], nil, nil) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to close response body: %w", cerr)) + } + } + return err } @@ -1000,7 +791,14 @@ func (c *sessionsClient) Config(ctx context.Context, imageHash string) (int, int Ret1 interface{} `json:"config"` }{} - err := doJSONRequest(ctx, c.client, c.urls[1], in, &out) + resp, err := doHTTPRequest(ctx, c.client, c.urls[1], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to close response body: %w", cerr)) + } + } + return out.Ret0, out.Ret1, err } @@ -1012,7 +810,14 @@ func (c *sessionsClient) Wallets(ctx context.Context, signer string) (map[string Ret0 map[string]*Signature `json:"wallets"` }{} - err := doJSONRequest(ctx, c.client, c.urls[2], in, &out) + resp, err := doHTTPRequest(ctx, c.client, c.urls[2], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to close response body: %w", cerr)) + } + } + return out.Ret0, err } @@ -1025,7 +830,14 @@ func (c *sessionsClient) DeployHash(ctx context.Context, wallet string) (string, Ret1 *Context `json:"context"` }{} - err := doJSONRequest(ctx, c.client, c.urls[3], in, &out) + resp, err := doHTTPRequest(ctx, c.client, c.urls[3], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to close response body: %w", cerr)) + } + } + return out.Ret0, out.Ret1, err } @@ -1039,7 +851,14 @@ func (c *sessionsClient) ConfigUpdates(ctx context.Context, wallet string, fromI Ret0 []*ConfigUpdate `json:"updates"` }{} - err := doJSONRequest(ctx, c.client, c.urls[4], in, &out) + resp, err := doHTTPRequest(ctx, c.client, c.urls[4], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to close response body: %w", cerr)) + } + } + return out.Ret0, err } @@ -1054,7 +873,14 @@ func (c *sessionsClient) Migrations(ctx context.Context, wallet string, fromVers Ret0 map[string]map[int]map[string]*TransactionBundle `json:"migrations"` }{} - err := doJSONRequest(ctx, c.client, c.urls[5], in, &out) + resp, err := doHTTPRequest(ctx, c.client, c.urls[5], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to close response body: %w", cerr)) + } + } + return out.Ret0, err } @@ -1064,7 +890,14 @@ func (c *sessionsClient) SaveConfig(ctx context.Context, version int, config int Arg1 interface{} `json:"config"` }{version, config} - err := doJSONRequest(ctx, c.client, c.urls[6], in, nil) + resp, err := doHTTPRequest(ctx, c.client, c.urls[6], in, nil) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to close response body: %w", cerr)) + } + } + return err } @@ -1074,7 +907,14 @@ func (c *sessionsClient) SaveWallet(ctx context.Context, version int, deployConf Arg1 interface{} `json:"deployConfig"` }{version, deployConfig} - err := doJSONRequest(ctx, c.client, c.urls[7], in, nil) + resp, err := doHTTPRequest(ctx, c.client, c.urls[7], in, nil) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to close response body: %w", cerr)) + } + } + return err } @@ -1087,7 +927,14 @@ func (c *sessionsClient) SaveSignature(ctx context.Context, wallet string, diges Arg4 *interface{} `json:"toConfig"` }{wallet, digest, chainID, signature, toConfig} - err := doJSONRequest(ctx, c.client, c.urls[8], in, nil) + resp, err := doHTTPRequest(ctx, c.client, c.urls[8], in, nil) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to close response body: %w", cerr)) + } + } + return err } @@ -1100,7 +947,14 @@ func (c *sessionsClient) SaveSignerSignatures(ctx context.Context, wallet string Arg4 *interface{} `json:"toConfig"` }{wallet, digest, chainID, signatures, toConfig} - err := doJSONRequest(ctx, c.client, c.urls[9], in, nil) + resp, err := doHTTPRequest(ctx, c.client, c.urls[9], in, nil) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to close response body: %w", cerr)) + } + } + return err } @@ -1117,7 +971,14 @@ func (c *sessionsClient) SaveMigration(ctx context.Context, wallet string, fromV Arg8 *string `json:"chainID"` }{wallet, fromVersion, toVersion, toConfig, executor, transactions, nonce, signature, chainID} - err := doJSONRequest(ctx, c.client, c.urls[10], in, nil) + resp, err := doHTTPRequest(ctx, c.client, c.urls[10], in, nil) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to close response body: %w", cerr)) + } + } + return err } @@ -1145,7 +1006,7 @@ func urlBase(addr string) string { // newRequest makes an http.Request from a client, adding common headers. func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType string) (*http.Request, error) { - req, err := http.NewRequest("POST", url, reqBody) + req, err := http.NewRequestWithContext(ctx, "POST", url, reqBody) if err != nil { return nil, err } @@ -1161,85 +1022,55 @@ func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType return req, nil } -// doJSONRequest is common code to make a request to the remote service. -func doJSONRequest(ctx context.Context, client HTTPClient, url string, in, out interface{}) error { +// doHTTPRequest is common code to make a request to the remote service. +func doHTTPRequest(ctx context.Context, client HTTPClient, url string, in, out interface{}) (*http.Response, error) { reqBody, err := json.Marshal(in) if err != nil { - return clientError("failed to marshal json request", err) + return nil, ErrWebrpcRequestFailed.WithCause(fmt.Errorf("failed to marshal JSON body: %w", err)) } if err = ctx.Err(); err != nil { - return clientError("aborted because context was done", err) + return nil, 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 clientError("could not build request", err) + return nil, ErrWebrpcRequestFailed.WithCause(fmt.Errorf("could not build request: %w", err)) } + resp, err := client.Do(req) if err != nil { - return clientError("request failed", err) + return nil, ErrWebrpcRequestFailed.WithCause(err) } - defer func() { - cerr := resp.Body.Close() - if err == nil && cerr != nil { - err = clientError("failed to close response body", cerr) + if resp.StatusCode != 200 { + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to read server error response body: %w", err)) } - }() - if err = ctx.Err(); err != nil { - return clientError("aborted because context was done", err) - } - - if resp.StatusCode != 200 { - return errorFromResponse(resp) + var rpcErr WebRPCError + if err := json.Unmarshal(respBody, &rpcErr); err != nil { + return nil, ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to unmarshal server error: %w", err)) + } + if rpcErr.Cause != "" { + rpcErr.cause = errors.New(rpcErr.Cause) + } + return nil, rpcErr } if out != nil { - respBody, err := ioutil.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) if err != nil { - return clientError("failed to read response body", err) + return nil, ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to read response body: %w", err)) } err = json.Unmarshal(respBody, &out) if err != nil { - return clientError("failed to unmarshal json response body", err) - } - if err = ctx.Err(); err != nil { - return clientError("aborted because context was done", err) + return nil, ErrWebrpcBadResponse.WithCause(fmt.Errorf("failed to unmarshal JSON response body: %w", err)) } } - return nil -} - -// errorFromResponse builds a webrpc Error from a non-200 HTTP response. -func errorFromResponse(resp *http.Response) Error { - respBody, err := ioutil.ReadAll(resp.Body) - if err != nil { - return clientError("failed to read server error response body", err) - } - - var respErr ErrorPayload - if err := json.Unmarshal(respBody, &respErr); err != nil { - return clientError("failed unmarshal error response", err) - } - - errCode := ErrorCode(respErr.Code) - - if HTTPStatusFromErrorCode(errCode) == 0 { - return ErrorInternal("invalid code returned from server error response: %s", respErr.Code) - } - - return &rpcErr{ - code: errCode, - msg: respErr.Msg, - cause: errors.New(respErr.Cause), - } -} - -func clientError(desc string, err error) Error { - return WrapError(ErrInternal, err, desc) + return resp, nil } func WithHTTPRequestHeaders(ctx context.Context, h http.Header) (context.Context, error) { @@ -1272,287 +1103,117 @@ func HTTPRequestHeaders(ctx context.Context) (http.Header, bool) { // Helpers // -type ErrorPayload struct { - Status int `json:"status"` - Code string `json:"code"` - Cause string `json:"cause,omitempty"` - Msg string `json:"msg"` - Error string `json:"error"` +type contextKey struct { + name string } -type Error interface { - // Code is of the valid error codes - Code() ErrorCode - - // Msg returns a human-readable, unstructured messages describing the error - Msg() string - - // Cause is reason for the error - Cause() error +func (k *contextKey) String() string { + return "webrpc context value " + k.name +} - // Error returns a string of the form "webrpc error : " - Error() string +var ( + HTTPClientRequestHeadersCtxKey = &contextKey{"HTTPClientRequestHeaders"} + HTTPResponseWriterCtxKey = &contextKey{"HTTPResponseWriter"} - // Error response payload - Payload() ErrorPayload -} + HTTPRequestCtxKey = &contextKey{"HTTPRequest"} -func Errorf(code ErrorCode, msgf string, args ...interface{}) Error { - msg := fmt.Sprintf(msgf, args...) - if IsValidErrorCode(code) { - return &rpcErr{code: code, msg: msg} - } - return &rpcErr{code: ErrInternal, msg: "invalid error type " + string(code)} -} + ServiceNameCtxKey = &contextKey{"ServiceName"} -func WrapError(code ErrorCode, cause error, format string, args ...interface{}) Error { - msg := fmt.Sprintf(format, args...) - if IsValidErrorCode(code) { - return &rpcErr{code: code, msg: msg, cause: cause} - } - return &rpcErr{code: ErrInternal, msg: "invalid error type " + string(code), cause: cause} -} + MethodNameCtxKey = &contextKey{"MethodName"} +) -func Failf(format string, args ...interface{}) Error { - return Errorf(ErrFail, format, args...) +func ServiceNameFromContext(ctx context.Context) string { + service, _ := ctx.Value(ServiceNameCtxKey).(string) + return service } -func WrapFailf(cause error, format string, args ...interface{}) Error { - return WrapError(ErrFail, cause, format, args...) +func MethodNameFromContext(ctx context.Context) string { + method, _ := ctx.Value(MethodNameCtxKey).(string) + return method } -func ErrorNotFound(format string, args ...interface{}) Error { - return Errorf(ErrNotFound, format, args...) +func RequestFromContext(ctx context.Context) *http.Request { + r, _ := ctx.Value(HTTPRequestCtxKey).(*http.Request) + return r } - -func ErrorInvalidArgument(argument string, validationMsg string) Error { - return Errorf(ErrInvalidArgument, argument+" "+validationMsg) +func ResponseWriterFromContext(ctx context.Context) http.ResponseWriter { + w, _ := ctx.Value(HTTPResponseWriterCtxKey).(http.ResponseWriter) + return w } -func ErrorRequiredArgument(argument string) Error { - return ErrorInvalidArgument(argument, "is required") -} +// +// Errors +// -func ErrorInternal(format string, args ...interface{}) Error { - return Errorf(ErrInternal, format, args...) +type WebRPCError struct { + Name string `json:"error"` + Code int `json:"code"` + Message string `json:"msg"` + Cause string `json:"cause,omitempty"` + HTTPStatus int `json:"status"` + cause error } -type ErrorCode string - -const ( - // Unknown error. For example when handling errors raised by APIs that do not - // return enough error information. - ErrUnknown ErrorCode = "unknown" - - // Fail error. General failure error type. - ErrFail ErrorCode = "fail" - - // Canceled indicates the operation was cancelled (typically by the caller). - ErrCanceled ErrorCode = "canceled" - - // InvalidArgument indicates client specified an invalid argument. It - // indicates arguments that are problematic regardless of the state of the - // system (i.e. a malformed file name, required argument, number out of range, - // etc.). - ErrInvalidArgument ErrorCode = "invalid argument" - - // DeadlineExceeded means operation expired before completion. For operations - // that change the state of the system, this error may be returned even if the - // operation has completed successfully (timeout). - ErrDeadlineExceeded ErrorCode = "deadline exceeded" - - // NotFound means some requested entity was not found. - ErrNotFound ErrorCode = "not found" - - // BadRoute means that the requested URL path wasn't routable to a webrpc - // service and method. This is returned by the generated server, and usually - // shouldn't be returned by applications. Instead, applications should use - // NotFound or Unimplemented. - ErrBadRoute ErrorCode = "bad route" - - // AlreadyExists means an attempt to create an entity failed because one - // already exists. - ErrAlreadyExists ErrorCode = "already exists" - - // PermissionDenied indicates the caller does not have permission to execute - // the specified operation. It must not be used if the caller cannot be - // identified (Unauthenticated). - ErrPermissionDenied ErrorCode = "permission denied" - - // Unauthenticated indicates the request does not have valid authentication - // credentials for the operation. - ErrUnauthenticated ErrorCode = "unauthenticated" - - // ResourceExhausted indicates some resource has been exhausted, perhaps a - // per-user quota, or perhaps the entire file system is out of space. - ErrResourceExhausted ErrorCode = "resource exhausted" - - // FailedPrecondition indicates operation was rejected because the system is - // not in a state required for the operation's execution. For example, doing - // an rmdir operation on a directory that is non-empty, or on a non-directory - // object, or when having conflicting read-modify-write on the same resource. - ErrFailedPrecondition ErrorCode = "failed precondition" - - // Aborted indicates the operation was aborted, typically due to a concurrency - // issue like sequencer check failures, transaction aborts, etc. - ErrAborted ErrorCode = "aborted" - - // OutOfRange means operation was attempted past the valid range. For example, - // seeking or reading past end of a paginated collection. - // - // Unlike InvalidArgument, this error indicates a problem that may be fixed if - // the system state changes (i.e. adding more items to the collection). - // - // There is a fair bit of overlap between FailedPrecondition and OutOfRange. - // We recommend using OutOfRange (the more specific error) when it applies so - // that callers who are iterating through a space can easily look for an - // OutOfRange error to detect when they are done. - ErrOutOfRange ErrorCode = "out of range" - - // Unimplemented indicates operation is not implemented or not - // supported/enabled in this service. - ErrUnimplemented ErrorCode = "unimplemented" - - // Internal errors. When some invariants expected by the underlying system - // have been broken. In other words, something bad happened in the library or - // backend service. Do not confuse with HTTP Internal Server Error; an - // Internal error could also happen on the client code, i.e. when parsing a - // server response. - ErrInternal ErrorCode = "internal" - - // Unavailable indicates the service is currently unavailable. This is a most - // likely a transient condition and may be corrected by retrying with a - // backoff. - ErrUnavailable ErrorCode = "unavailable" - - // DataLoss indicates unrecoverable data loss or corruption. - ErrDataLoss ErrorCode = "data loss" - - // ErrNone is the zero-value, is considered an empty error and should not be - // used. - ErrNone ErrorCode = "" -) +var _ error = WebRPCError{} -func HTTPStatusFromErrorCode(code ErrorCode) int { - switch code { - case ErrCanceled: - return 408 // RequestTimeout - case ErrUnknown: - return 400 // Bad Request - case ErrFail: - return 422 // Unprocessable Entity - case ErrInvalidArgument: - return 400 // BadRequest - case ErrDeadlineExceeded: - return 408 // RequestTimeout - case ErrNotFound: - return 404 // Not Found - case ErrBadRoute: - return 404 // Not Found - case ErrAlreadyExists: - return 409 // Conflict - case ErrPermissionDenied: - return 403 // Forbidden - case ErrUnauthenticated: - return 401 // Unauthorized - case ErrResourceExhausted: - return 403 // Forbidden - case ErrFailedPrecondition: - return 412 // Precondition Failed - case ErrAborted: - return 409 // Conflict - case ErrOutOfRange: - return 400 // Bad Request - case ErrUnimplemented: - return 501 // Not Implemented - case ErrInternal: - return 500 // Internal Server Error - case ErrUnavailable: - return 503 // Service Unavailable - case ErrDataLoss: - return 500 // Internal Server Error - case ErrNone: - return 200 // OK - default: - return 0 // Invalid! +func (e WebRPCError) Error() string { + if e.cause != nil { + return fmt.Sprintf("%s %d: %s: %v", e.Name, e.Code, e.Message, e.cause) } + return fmt.Sprintf("%s %d: %s", e.Name, e.Code, e.Message) } -func IsErrorCode(err error, code ErrorCode) bool { - if rpcErr, ok := err.(Error); ok { - if rpcErr.Code() == code { - return true - } +func (e WebRPCError) Is(target error) bool { + if target == nil { + return false } - return false -} - -func IsValidErrorCode(code ErrorCode) bool { - return HTTPStatusFromErrorCode(code) != 0 -} - -type rpcErr struct { - code ErrorCode - msg string - cause error -} - -func (e *rpcErr) Code() ErrorCode { - return e.code -} - -func (e *rpcErr) Msg() string { - return e.msg + if rpcErr, ok := target.(WebRPCError); ok { + return rpcErr.Code == e.Code + } + return errors.Is(e.cause, target) } -func (e *rpcErr) Cause() error { +func (e WebRPCError) Unwrap() error { return e.cause } -func (e *rpcErr) Error() string { - if e.cause != nil && e.cause.Error() != "" { - if e.msg != "" { - return fmt.Sprintf("webrpc %s error: %s -- %s", e.code, e.cause.Error(), e.msg) - } else { - return fmt.Sprintf("webrpc %s error: %s", e.code, e.cause.Error()) - } - } else { - return fmt.Sprintf("webrpc %s error: %s", e.code, e.msg) - } -} - -func (e *rpcErr) Payload() ErrorPayload { - statusCode := HTTPStatusFromErrorCode(e.Code()) - errPayload := ErrorPayload{ - Status: statusCode, - Code: string(e.Code()), - Msg: e.Msg(), - Error: e.Error(), - } - if e.Cause() != nil { - errPayload.Cause = e.Cause().Error() - } - return errPayload +func (e WebRPCError) WithCause(cause error) WebRPCError { + err := e + err.cause = cause + err.Cause = cause.Error() + return err } -type contextKey struct { - name string +func (e WebRPCError) WithCausef(format string, args ...interface{}) WebRPCError { + cause := fmt.Errorf(format, args...) + err := e + err.cause = cause + err.Cause = cause.Error() + return err } -func (k *contextKey) String() string { - return "webrpc context value " + k.name +// Deprecated: Use .WithCause() method on WebRPCError. +func ErrorWithCause(rpcErr WebRPCError, cause error) WebRPCError { + return rpcErr.WithCause(cause) } +// Webrpc errors var ( - // For Client - HTTPClientRequestHeadersCtxKey = &contextKey{"HTTPClientRequestHeaders"} - - // For Server - HTTPResponseWriterCtxKey = &contextKey{"HTTPResponseWriter"} - - HTTPRequestCtxKey = &contextKey{"HTTPRequest"} - - ServiceNameCtxKey = &contextKey{"ServiceName"} + ErrWebrpcEndpoint = WebRPCError{Code: 0, Name: "WebrpcEndpoint", Message: "endpoint error", HTTPStatus: 400} + ErrWebrpcRequestFailed = WebRPCError{Code: -1, Name: "WebrpcRequestFailed", Message: "request failed", HTTPStatus: 400} + ErrWebrpcBadRoute = WebRPCError{Code: -2, Name: "WebrpcBadRoute", Message: "bad route", HTTPStatus: 404} + ErrWebrpcBadMethod = WebRPCError{Code: -3, Name: "WebrpcBadMethod", Message: "bad method", HTTPStatus: 405} + ErrWebrpcBadRequest = WebRPCError{Code: -4, Name: "WebrpcBadRequest", Message: "bad request", HTTPStatus: 400} + ErrWebrpcBadResponse = WebRPCError{Code: -5, Name: "WebrpcBadResponse", Message: "bad response", HTTPStatus: 500} + ErrWebrpcServerPanic = WebRPCError{Code: -6, Name: "WebrpcServerPanic", Message: "server panic", HTTPStatus: 500} + ErrWebrpcInternalError = WebRPCError{Code: -7, Name: "WebrpcInternalError", Message: "internal error", HTTPStatus: 500} + ErrWebrpcClientDisconnected = WebRPCError{Code: -8, Name: "WebrpcClientDisconnected", Message: "client disconnected", HTTPStatus: 400} + ErrWebrpcStreamLost = WebRPCError{Code: -9, Name: "WebrpcStreamLost", Message: "stream lost", HTTPStatus: 400} + ErrWebrpcStreamFinished = WebRPCError{Code: -10, Name: "WebrpcStreamFinished", Message: "stream finished", HTTPStatus: 200} +) - MethodNameCtxKey = &contextKey{"MethodName"} +// Schema errors +var ( + ErrInvalidArgument = WebRPCError{Code: 1, Name: "InvalidArgument", Message: "invalid argument", HTTPStatus: 400} + ErrNotFound = WebRPCError{Code: 2, Name: "NotFound", Message: "not found", HTTPStatus: 404} )