From 2f9f09137c923e1559fbffb2d79b9a3e81302a5a Mon Sep 17 00:00:00 2001 From: Lucas Fleischer Date: Wed, 12 Aug 2026 21:32:10 -0300 Subject: [PATCH 1/2] chore: update error handling for permission denied cases and bump crec-api-go dependency to v0.8.0-rc2 --- apierror/apierror.go | 7 +++++++ apierror/apierror_test.go | 14 +++++++++++++ channels/channels.go | 25 +++++++++++++++++++++++ crec.go | 8 ++++++++ events/events.go | 14 +++++++++++++ go.mod | 2 +- go.sum | 4 ++-- queries/queries.go | 15 ++++++++++++++ transact/transact.go | 25 +++++++++++++++++++++++ wallets/wallets.go | 25 +++++++++++++++++++++++ watchers/watchers.go | 42 +++++++++++++++++++++++++++++++++++++++ 11 files changed, 178 insertions(+), 3 deletions(-) diff --git a/apierror/apierror.go b/apierror/apierror.go index 90fbaa6f..266aa295 100644 --- a/apierror/apierror.go +++ b/apierror/apierror.go @@ -14,6 +14,11 @@ import ( // ApplicationError of type ORGANIZATION_NOT_FOUND). var ErrOrganizationNotFound = errors.New("organization not found") +// ErrPermissionDenied is returned when the CREC API reports that the +// authenticated principal lacks the required permission for the operation +// (HTTP 403 with an ApplicationError of type PERMISSION_DENIED). +var ErrPermissionDenied = errors.New("permission denied") + // Canonical not-found sentinels for HTTP 404 responses. The API disambiguates // which resource was missing via ApplicationError.code. Packages that assign // these variables (rather than defining their own) share the same sentinel @@ -51,6 +56,8 @@ func FromApplicationError(appErr *apiClient.ApplicationError) error { switch appErr.Type { case apiClient.ORGANIZATIONNOTFOUND: return ErrOrganizationNotFound + case apiClient.PERMISSIONDENIED: + return ErrPermissionDenied default: return nil } diff --git a/apierror/apierror_test.go b/apierror/apierror_test.go index 2881b5a3..ac57c80b 100644 --- a/apierror/apierror_test.go +++ b/apierror/apierror_test.go @@ -27,6 +27,11 @@ func TestApierror_FromApplicationError(t *testing.T) { appErr: &apiClient.ApplicationError{Type: apiClient.ORGANIZATIONNOTFOUND, Message: "organization not found"}, wantErr: apierror.ErrOrganizationNotFound, }, + { + name: "permission denied maps to sentinel", + appErr: &apiClient.ApplicationError{Type: apiClient.PERMISSIONDENIED, Message: "principal lacks permission crec:wallet:create"}, + wantErr: apierror.ErrPermissionDenied, + }, { name: "unknown future type degrades to nil", appErr: &apiClient.ApplicationError{Type: "SOME_FUTURE_TYPE", Message: "new"}, @@ -208,6 +213,15 @@ func TestApierror_Wrap(t *testing.T) { assert.NotErrorIs(t, err, apierror.ErrUnexpectedStatusCode) }) + t.Run("permission denied wraps opErr", func(t *testing.T) { + appErr := &apiClient.ApplicationError{Type: apiClient.PERMISSIONDENIED, Message: "principal lacks permission crec:wallet:create"} + err := apierror.Wrap(appErr, opErr, http.StatusForbidden) + + assert.ErrorIs(t, err, opErr) + assert.ErrorIs(t, err, apierror.ErrPermissionDenied) + assert.NotErrorIs(t, err, apierror.ErrUnexpectedStatusCode) + }) + t.Run("unmapped type falls back to unexpected-status error", func(t *testing.T) { appErr := &apiClient.ApplicationError{Type: "SOME_FUTURE_TYPE", Message: "new"} err := apierror.Wrap(appErr, opErr, http.StatusUnauthorized) diff --git a/channels/channels.go b/channels/channels.go index edcc3a76..19f1c63d 100644 --- a/channels/channels.go +++ b/channels/channels.go @@ -137,6 +137,11 @@ func (c *Client) Create(ctx context.Context, input CreateInput) (*apiClient.Chan "channel_id", resp.JSON201.ChannelId.String(), "name", resp.JSON201.Name) return resp.JSON201, nil + case http.StatusForbidden: + c.logger.Error("Permission denied when creating channel", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, apierror.Wrap(resp.JSON403, ErrCreateChannel, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when creating channel", "status_code", resp.StatusCode(), @@ -186,6 +191,11 @@ func (c *Client) Get(ctx context.Context, channelID uuid.UUID) (*apiClient.Chann "code", apierror.NotFoundCode(resp.JSON404), ) return nil, fmt.Errorf("%w: channel ID %s", ErrChannelNotFound, channelID.String()) + case http.StatusForbidden: + c.logger.Error("Permission denied when getting channel", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, apierror.Wrap(resp.JSON403, ErrGetChannel, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when getting channel", "status_code", resp.StatusCode(), @@ -247,6 +257,11 @@ func (c *Client) List(ctx context.Context, input ListInput) ([]apiClient.Channel "count", len(resp.JSON200.Data), "has_more", resp.JSON200.HasMore) return resp.JSON200.Data, resp.JSON200.HasMore, nil + case http.StatusForbidden: + c.logger.Error("Permission denied when listing channels", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, false, apierror.Wrap(resp.JSON403, ErrListChannels, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when listing channels", "status_code", resp.StatusCode(), @@ -314,6 +329,11 @@ func (c *Client) Update(ctx context.Context, channelID uuid.UUID, input UpdateIn "code", apierror.NotFoundCode(resp.JSON404), ) return nil, fmt.Errorf("%w: channel ID %s", ErrChannelNotFound, channelID.String()) + case http.StatusForbidden: + c.logger.Error("Permission denied when updating channel", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, apierror.Wrap(resp.JSON403, ErrUpdateChannel, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when updating channel", "status_code", resp.StatusCode(), @@ -368,6 +388,11 @@ func (c *Client) Archive(ctx context.Context, channelID uuid.UUID) (*apiClient.C "code", apierror.NotFoundCode(resp.JSON404), ) return nil, fmt.Errorf("%w: channel ID %s", ErrChannelNotFound, channelID.String()) + case http.StatusForbidden: + c.logger.Error("Permission denied when archiving channel", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, apierror.Wrap(resp.JSON403, ErrArchiveChannel, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when archiving channel", "status_code", resp.StatusCode(), diff --git a/crec.go b/crec.go index 9730df39..8b18a99e 100644 --- a/crec.go +++ b/crec.go @@ -270,6 +270,14 @@ func (c *Client) ListNetworks(ctx context.Context) ([]apiClient.Network, bool, e return nil, false, ErrListNetworks } return resp.JSON200.Data, resp.JSON200.HasMore, nil + case http.StatusForbidden: + c.logger.Error("Permission denied when listing networks", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + if mapped := apierror.FromApplicationError(resp.JSON403); mapped != nil { + return nil, false, fmt.Errorf("%w: %w", ErrListNetworks, mapped) + } + return nil, false, fmt.Errorf("%w (status code %d)", ErrListNetworks, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when listing networks", "status_code", resp.StatusCode(), diff --git a/events/events.go b/events/events.go index 83ee7730..391f0022 100644 --- a/events/events.go +++ b/events/events.go @@ -288,6 +288,13 @@ func (c *Client) Poll( return nil, false, apierror.WrapChannelNotFound( resp.JSON404, ErrPollEvents, "channel ID "+channelID.String(), ) + case http.StatusForbidden: + c.logger.Error( + "Failed to get events - permission denied", + "status_code", resp.StatusCode(), + "body", string(resp.Body), + ) + return nil, false, apierror.Wrap(resp.JSON403, ErrPollEvents, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error( "Failed to get events - unauthorized", @@ -370,6 +377,13 @@ func (c *Client) SearchEvents( return nil, false, fmt.Errorf( "%w: %w: %s (status code %d)", ErrSearchEvents, ErrBadRequest, errorMsg, resp.StatusCode(), ) + case http.StatusForbidden: + c.logger.Error( + "Failed to search events - permission denied", + "status_code", resp.StatusCode(), + "body", string(resp.Body), + ) + return nil, false, apierror.Wrap(resp.JSON403, ErrSearchEvents, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error( "Failed to search events - unauthorized", diff --git a/go.mod b/go.mod index 71bba730..99ec0afc 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/oapi-codegen/runtime v1.1.2 github.com/smartcontractkit/chain-selectors v1.0.89 github.com/smartcontractkit/chainlink-common v0.10.0 - github.com/smartcontractkit/crec-api-go v0.8.0-rc1 + github.com/smartcontractkit/crec-api-go v0.8.0-rc2 github.com/stretchr/testify v1.11.1 github.com/testcontainers/testcontainers-go v0.38.0 github.com/testcontainers/testcontainers-go/modules/vault v0.38.0 diff --git a/go.sum b/go.sum index cbb9bb52..2338709a 100644 --- a/go.sum +++ b/go.sum @@ -435,8 +435,8 @@ github.com/smartcontractkit/chainlink-common v0.10.0 h1:d90b9UPJecrIryzhl43F1oQw github.com/smartcontractkit/chainlink-common v0.10.0/go.mod h1:13YN2kb3Vqpw2S7d4IwhX/578WPGC0JHN5JrOnAEsOc= github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260210221717-2546aed27ebe h1:Vc4zoSc/j6/FdCQ7vcyHTTB7kzHI2f+lHCHqFuiCcJQ= github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260210221717-2546aed27ebe/go.mod h1:Jqt53s27Tr0jDl8mdBXg1xhu6F8Fci8JOuq43tgHOM8= -github.com/smartcontractkit/crec-api-go v0.8.0-rc1 h1:qEhP1q+fBdoIOrZdRuqw3aUaS7np7sSO2gVYdMA4s/M= -github.com/smartcontractkit/crec-api-go v0.8.0-rc1/go.mod h1:y91qqcZFtWiKLFu66c/dmBp10bTzcpGcb4XFR7eLknk= +github.com/smartcontractkit/crec-api-go v0.8.0-rc2 h1:YuhcGkl8/1umaFiw718ahIb1+eJds4QFdWbrUd7472o= +github.com/smartcontractkit/crec-api-go v0.8.0-rc2/go.mod h1:y91qqcZFtWiKLFu66c/dmBp10bTzcpGcb4XFR7eLknk= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d h1:LokA9PoCNb8mm8mDT52c3RECPMRsGz1eCQORq+J3n74= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= diff --git a/queries/queries.go b/queries/queries.go index ddc7ce11..d248cd1e 100644 --- a/queries/queries.go +++ b/queries/queries.go @@ -303,6 +303,11 @@ func (c *Client) Create(ctx context.Context, input CreateInput) (*apiClient.Quer return nil, fmt.Errorf("%w: %w", ErrCreateQuery, ErrIdempotencyConflict) case http.StatusTooManyRequests: return nil, fmt.Errorf("%w: %w", ErrCreateQuery, ErrRateLimitExceeded) + case http.StatusForbidden: + c.logger.Error("Permission denied when creating query", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, apierror.Wrap(resp.JSON403, ErrCreateQuery, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when creating query", "status_code", resp.StatusCode(), @@ -369,6 +374,11 @@ func (c *Client) Get(ctx context.Context, channelID uuid.UUID, queryID uuid.UUID ErrGetQuery, fmt.Sprintf("channel ID %s, query ID %s", channelID.String(), queryID.String()), ) + case http.StatusForbidden: + c.logger.Error("Permission denied when getting query", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, apierror.Wrap(resp.JSON403, ErrGetQuery, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when getting query", "status_code", resp.StatusCode(), @@ -426,6 +436,11 @@ func (c *Client) List(ctx context.Context, input ListInput) ([]apiClient.Query, return nil, false, apierror.WrapChannelNotFound( resp.JSON404, ErrListQueries, "channel ID "+input.ChannelID.String(), ) + case http.StatusForbidden: + c.logger.Error("Permission denied when listing queries", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, false, apierror.Wrap(resp.JSON403, ErrListQueries, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when listing queries", "status_code", resp.StatusCode(), diff --git a/transact/transact.go b/transact/transact.go index 10111f75..f97c6bfe 100644 --- a/transact/transact.go +++ b/transact/transact.go @@ -258,6 +258,11 @@ func (c *Client) postCreateOperation( "code", apierror.NotFoundCode(resp.JSON404), ) return nil, apierror.WrapNotFound(resp.JSON404, ErrCreateOperation, detail) + case http.StatusForbidden: + c.logger.Error("Permission denied when creating operation", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, apierror.Wrap(resp.JSON403, ErrCreateOperation, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when creating operation", "status_code", resp.StatusCode(), @@ -541,6 +546,11 @@ func (c *Client) GetOperation(ctx context.Context, channelID uuid.UUID, operatio ErrGetOperation, fmt.Sprintf("channel ID %s, operation ID %s", channelID.String(), operationID.String()), ) + case http.StatusForbidden: + c.logger.Error("Permission denied when getting operation", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, apierror.Wrap(resp.JSON403, ErrGetOperation, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when getting operation", "status_code", resp.StatusCode(), @@ -628,6 +638,11 @@ func (c *Client) ListOperations(ctx context.Context, input ListOperationsInput) return nil, false, apierror.WrapChannelNotFound( resp.JSON404, ErrListOperations, "channel ID "+input.ChannelID.String(), ) + case http.StatusForbidden: + c.logger.Error("Permission denied when listing operations", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, false, apierror.Wrap(resp.JSON403, ErrListOperations, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when listing operations", "status_code", resp.StatusCode(), @@ -758,6 +773,11 @@ func (c *Client) SendSignedDraftOperation( return nil, ErrDraftNotFound case http.StatusConflict: return nil, ErrDraftNotFinalizable + case http.StatusForbidden: + c.logger.Error("Permission denied when sending operation", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, apierror.Wrap(resp.JSON403, ErrSendOperation, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when sending operation", "status_code", resp.StatusCode(), @@ -827,6 +847,11 @@ func (c *Client) CancelDraftOperation(ctx context.Context, channelID uuid.UUID, return ErrDraftNotFound case http.StatusConflict: return ErrDraftNotCancellable + case http.StatusForbidden: + c.logger.Error("Permission denied when cancelling operation", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return apierror.Wrap(resp.JSON403, ErrSendOperation, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when cancelling operation", "status_code", resp.StatusCode(), diff --git a/wallets/wallets.go b/wallets/wallets.go index 055d858b..83207eeb 100644 --- a/wallets/wallets.go +++ b/wallets/wallets.go @@ -194,6 +194,11 @@ func (c *Client) Create(ctx context.Context, input CreateInput) (*apiClient.Wall "address", resp.JSON201.Address, "chain_selector", resp.JSON201.ChainSelector) return resp.JSON201, nil + case http.StatusForbidden: + c.logger.Error("Permission denied when creating wallet", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, apierror.Wrap(resp.JSON403, ErrCreateWallet, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when creating wallet", "status_code", resp.StatusCode(), @@ -248,6 +253,11 @@ func (c *Client) Get(ctx context.Context, walletID uuid.UUID) (*apiClient.Wallet "code", apierror.NotFoundCode(resp.JSON404), ) return nil, fmt.Errorf("%w: wallet ID %s", ErrWalletNotFound, walletID.String()) + case http.StatusForbidden: + c.logger.Error("Permission denied when getting wallet", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, apierror.Wrap(resp.JSON403, ErrGetWallet, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when getting wallet", "status_code", resp.StatusCode(), @@ -344,6 +354,11 @@ func (c *Client) List(ctx context.Context, input ListInput) ([]apiClient.Wallet, "count", len(resp.JSON200.Data), "has_more", resp.JSON200.HasMore) return resp.JSON200.Data, resp.JSON200.HasMore, nil + case http.StatusForbidden: + c.logger.Error("Permission denied when listing wallets", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return nil, false, apierror.Wrap(resp.JSON403, ErrListWallets, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when listing wallets", "status_code", resp.StatusCode(), @@ -411,6 +426,11 @@ func (c *Client) Update(ctx context.Context, walletID uuid.UUID, input UpdateInp "code", apierror.NotFoundCode(resp.JSON404), ) return fmt.Errorf("%w: wallet ID %s", ErrWalletNotFound, walletID.String()) + case http.StatusForbidden: + c.logger.Error("Permission denied when updating wallet", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return apierror.Wrap(resp.JSON403, ErrUpdateWallet, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when updating wallet", "status_code", resp.StatusCode(), @@ -465,6 +485,11 @@ func (c *Client) Archive(ctx context.Context, walletID uuid.UUID) error { "code", apierror.NotFoundCode(resp.JSON404), ) return fmt.Errorf("%w: wallet ID %s", ErrWalletNotFound, walletID.String()) + case http.StatusForbidden: + c.logger.Error("Permission denied when archiving wallet", + "status_code", resp.StatusCode(), + "body", string(resp.Body)) + return apierror.Wrap(resp.JSON403, ErrArchiveWallet, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error("Unauthorized when archiving wallet", "status_code", resp.StatusCode(), diff --git a/watchers/watchers.go b/watchers/watchers.go index 28e1ba31..c9e2e2ed 100644 --- a/watchers/watchers.go +++ b/watchers/watchers.go @@ -270,6 +270,13 @@ func (c *Client) CreateWithService( } c.logger.Info("Watcher created successfully", "watcher_id", resp.JSON201.WatcherId.String()) return resp.JSON201, nil + case http.StatusForbidden: + c.logger.Error( + "Failed to create watcher with service - permission denied", + "status_code", resp.StatusCode(), + "body", string(resp.Body), + ) + return nil, apierror.Wrap(resp.JSON403, ErrCreateWatcherService, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error( "Failed to create watcher with service - unauthorized", @@ -400,6 +407,13 @@ func (c *Client) CreateWithABI(ctx context.Context, channelID uuid.UUID, input C } c.logger.Info("Watcher created successfully", "watcher_id", resp.JSON201.WatcherId.String()) return resp.JSON201, nil + case http.StatusForbidden: + c.logger.Error( + "Failed to create watcher with ABI - permission denied", + "status_code", resp.StatusCode(), + "body", string(resp.Body), + ) + return nil, apierror.Wrap(resp.JSON403, ErrCreateWatcherABI, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error( "Failed to create watcher with ABI - unauthorized", @@ -465,6 +479,13 @@ func (c *Client) List(ctx context.Context, channelID uuid.UUID, filters ListFilt return nil, apierror.WrapChannelNotFound( resp.JSON404, ErrListWatchers, "channel ID "+channelID.String(), ) + case http.StatusForbidden: + c.logger.Error( + "Failed to list watchers - permission denied", + "status_code", resp.StatusCode(), + "body", string(resp.Body), + ) + return nil, apierror.Wrap(resp.JSON403, ErrListWatchers, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error( "Failed to list watchers - unauthorized", @@ -525,6 +546,13 @@ func (c *Client) Get(ctx context.Context, channelID uuid.UUID, watcherID uuid.UU ErrGetWatcher, fmt.Sprintf("channel ID %s, watcher ID %s", channelID.String(), watcherID.String()), ) + case http.StatusForbidden: + c.logger.Error( + "Failed to get watcher - permission denied", + "status_code", resp.StatusCode(), + "body", string(resp.Body), + ) + return nil, apierror.Wrap(resp.JSON403, ErrGetWatcher, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error( "Failed to get watcher - unauthorized", @@ -596,6 +624,13 @@ func (c *Client) Update( ErrUpdateWatcher, fmt.Sprintf("channel ID %s, watcher ID %s", channelID.String(), watcherID.String()), ) + case http.StatusForbidden: + c.logger.Error( + "Failed to update watcher - permission denied", + "status_code", resp.StatusCode(), + "body", string(resp.Body), + ) + return nil, apierror.Wrap(resp.JSON403, ErrUpdateWatcher, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error( "Failed to update watcher - unauthorized", @@ -757,6 +792,13 @@ func (c *Client) Archive(ctx context.Context, channelID uuid.UUID, watcherID uui ErrArchiveWatcher, fmt.Sprintf("channel ID %s, watcher ID %s", channelID.String(), watcherID.String()), ) + case http.StatusForbidden: + c.logger.Error( + "Failed to archive watcher - permission denied", + "status_code", resp.StatusCode(), + "body", string(resp.Body), + ) + return nil, apierror.Wrap(resp.JSON403, ErrArchiveWatcher, resp.StatusCode()) case http.StatusUnauthorized: c.logger.Error( "Failed to archive watcher - unauthorized", From 4b966cf8622cc530a7e976c7a5da0c2a8ff7f730 Mon Sep 17 00:00:00 2001 From: Lucas Fleischer Date: Thu, 13 Aug 2026 11:06:35 -0300 Subject: [PATCH 2/2] chore: refactor error handling across SDK endpoints to use HandleErrorStatus helper --- apierror/apierror.go | 32 ++++++++++ apierror/apierror_test.go | 45 +++++++++++++ channels/channels.go | 75 ++-------------------- crec.go | 21 +----- events/events.go | 46 +------------- queries/queries.go | 45 +------------ transact/transact.go | 75 ++-------------------- wallets/wallets.go | 75 ++-------------------- watchers/watchers.go | 130 ++------------------------------------ 9 files changed, 104 insertions(+), 440 deletions(-) diff --git a/apierror/apierror.go b/apierror/apierror.go index 266aa295..ba66c959 100644 --- a/apierror/apierror.go +++ b/apierror/apierror.go @@ -5,6 +5,8 @@ package apierror import ( "errors" "fmt" + "log/slog" + "net/http" apiClient "github.com/smartcontractkit/crec-api-go/client" ) @@ -74,6 +76,36 @@ func Wrap(appErr *apiClient.ApplicationError, opErr error, statusCode int) error return fmt.Errorf("%w: %w (status code %d)", opErr, ErrUnexpectedStatusCode, statusCode) } +// HandleErrorStatus handles the common HTTP error cases shared across all SDK endpoints. +// Endpoints handle their success and specific cases (404, 409, 429, etc.) in +// their own switch and delegate the remaining cases to this helper via default. +func HandleErrorStatus( + statusCode int, + json401, json403 *apiClient.ApplicationError, + opErr error, + opDesc string, + body []byte, + logger *slog.Logger, +) error { + switch statusCode { + case http.StatusForbidden: + logger.Error("Permission denied when "+opDesc, + "status_code", statusCode, + "body", string(body)) + return Wrap(json403, opErr, statusCode) + case http.StatusUnauthorized: + logger.Error("Unauthorized when "+opDesc, + "status_code", statusCode, + "body", string(body)) + return Wrap(json401, opErr, statusCode) + default: + logger.Error("Unexpected status code when "+opDesc, + "status_code", statusCode, + "body", string(body)) + return fmt.Errorf("%w: %w (status code %d)", opErr, ErrUnexpectedStatusCode, statusCode) + } +} + // NotFound maps a 404 ApplicationError to its canonical not-found sentinel based // on ApplicationError.code, or returns nil when the code is missing or // unrecognized (forward-compatible for codes added after this SDK release). diff --git a/apierror/apierror_test.go b/apierror/apierror_test.go index ac57c80b..b048d44c 100644 --- a/apierror/apierror_test.go +++ b/apierror/apierror_test.go @@ -2,6 +2,7 @@ package apierror_test import ( "errors" + "log/slog" "net/http" "testing" @@ -238,3 +239,47 @@ func TestApierror_Wrap(t *testing.T) { assert.ErrorIs(t, err, apierror.ErrUnexpectedStatusCode) }) } + +func TestApierror_HandleErrorStatus(t *testing.T) { + opErr := errors.New("failed to create wallet") + logger := slog.New(slog.DiscardHandler) + + t.Run("403 with PERMISSION_DENIED wraps with ErrPermissionDenied", func(t *testing.T) { + json403 := &apiClient.ApplicationError{Type: apiClient.PERMISSIONDENIED, Message: "principal lacks permission crec:wallet:create"} + err := apierror.HandleErrorStatus(http.StatusForbidden, nil, json403, opErr, "creating wallet", []byte("body"), logger) + + assert.ErrorIs(t, err, opErr) + assert.ErrorIs(t, err, apierror.ErrPermissionDenied) + assert.NotErrorIs(t, err, apierror.ErrUnexpectedStatusCode) + }) + + t.Run("401 with ORGANIZATION_NOT_FOUND wraps with ErrOrganizationNotFound", func(t *testing.T) { + json401 := &apiClient.ApplicationError{Type: apiClient.ORGANIZATIONNOTFOUND, Message: "organization not found"} + err := apierror.HandleErrorStatus(http.StatusUnauthorized, json401, nil, opErr, "creating wallet", []byte("body"), logger) + + assert.ErrorIs(t, err, opErr) + assert.ErrorIs(t, err, apierror.ErrOrganizationNotFound) + assert.NotErrorIs(t, err, apierror.ErrUnexpectedStatusCode) + }) + + t.Run("401 with unmapped type falls back to ErrUnexpectedStatusCode", func(t *testing.T) { + json401 := &apiClient.ApplicationError{Type: "SOME_FUTURE_TYPE", Message: "new"} + err := apierror.HandleErrorStatus(http.StatusUnauthorized, json401, nil, opErr, "creating wallet", []byte("body"), logger) + + assert.ErrorIs(t, err, opErr) + assert.ErrorIs(t, err, apierror.ErrUnexpectedStatusCode) + }) + + t.Run("500 falls back to ErrUnexpectedStatusCode", func(t *testing.T) { + err := apierror.HandleErrorStatus(http.StatusInternalServerError, nil, nil, opErr, "creating wallet", []byte("body"), logger) + + assert.ErrorIs(t, err, opErr) + assert.ErrorIs(t, err, apierror.ErrUnexpectedStatusCode) + }) + + t.Run("nil json401 and json403 do not panic", func(t *testing.T) { + err := apierror.HandleErrorStatus(http.StatusUnauthorized, nil, nil, opErr, "creating wallet", nil, logger) + assert.ErrorIs(t, err, opErr) + assert.ErrorIs(t, err, apierror.ErrUnexpectedStatusCode) + }) +} diff --git a/channels/channels.go b/channels/channels.go index 19f1c63d..f2ae459b 100644 --- a/channels/channels.go +++ b/channels/channels.go @@ -137,21 +137,8 @@ func (c *Client) Create(ctx context.Context, input CreateInput) (*apiClient.Chan "channel_id", resp.JSON201.ChannelId.String(), "name", resp.JSON201.Name) return resp.JSON201, nil - case http.StatusForbidden: - c.logger.Error("Permission denied when creating channel", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON403, ErrCreateChannel, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when creating channel", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON401, ErrCreateChannel, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when creating channel", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrCreateChannel, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrCreateChannel, "creating channel", resp.Body, c.logger) } } @@ -191,21 +178,8 @@ func (c *Client) Get(ctx context.Context, channelID uuid.UUID) (*apiClient.Chann "code", apierror.NotFoundCode(resp.JSON404), ) return nil, fmt.Errorf("%w: channel ID %s", ErrChannelNotFound, channelID.String()) - case http.StatusForbidden: - c.logger.Error("Permission denied when getting channel", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON403, ErrGetChannel, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when getting channel", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON401, ErrGetChannel, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when getting channel", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrGetChannel, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrGetChannel, "getting channel", resp.Body, c.logger) } } @@ -257,21 +231,8 @@ func (c *Client) List(ctx context.Context, input ListInput) ([]apiClient.Channel "count", len(resp.JSON200.Data), "has_more", resp.JSON200.HasMore) return resp.JSON200.Data, resp.JSON200.HasMore, nil - case http.StatusForbidden: - c.logger.Error("Permission denied when listing channels", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, false, apierror.Wrap(resp.JSON403, ErrListChannels, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when listing channels", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, false, apierror.Wrap(resp.JSON401, ErrListChannels, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when listing channels", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, false, fmt.Errorf("%w: %w (status code %d)", ErrListChannels, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, false, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrListChannels, "listing channels", resp.Body, c.logger) } } @@ -329,21 +290,8 @@ func (c *Client) Update(ctx context.Context, channelID uuid.UUID, input UpdateIn "code", apierror.NotFoundCode(resp.JSON404), ) return nil, fmt.Errorf("%w: channel ID %s", ErrChannelNotFound, channelID.String()) - case http.StatusForbidden: - c.logger.Error("Permission denied when updating channel", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON403, ErrUpdateChannel, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when updating channel", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON401, ErrUpdateChannel, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when updating channel", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrUpdateChannel, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrUpdateChannel, "updating channel", resp.Body, c.logger) } } @@ -388,20 +336,7 @@ func (c *Client) Archive(ctx context.Context, channelID uuid.UUID) (*apiClient.C "code", apierror.NotFoundCode(resp.JSON404), ) return nil, fmt.Errorf("%w: channel ID %s", ErrChannelNotFound, channelID.String()) - case http.StatusForbidden: - c.logger.Error("Permission denied when archiving channel", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON403, ErrArchiveChannel, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when archiving channel", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON401, ErrArchiveChannel, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when archiving channel", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrArchiveChannel, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrArchiveChannel, "archiving channel", resp.Body, c.logger) } } diff --git a/crec.go b/crec.go index 8b18a99e..5f579490 100644 --- a/crec.go +++ b/crec.go @@ -270,26 +270,7 @@ func (c *Client) ListNetworks(ctx context.Context) ([]apiClient.Network, bool, e return nil, false, ErrListNetworks } return resp.JSON200.Data, resp.JSON200.HasMore, nil - case http.StatusForbidden: - c.logger.Error("Permission denied when listing networks", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - if mapped := apierror.FromApplicationError(resp.JSON403); mapped != nil { - return nil, false, fmt.Errorf("%w: %w", ErrListNetworks, mapped) - } - return nil, false, fmt.Errorf("%w (status code %d)", ErrListNetworks, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when listing networks", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - if mapped := apierror.FromApplicationError(resp.JSON401); mapped != nil { - return nil, false, fmt.Errorf("%w: %w", ErrListNetworks, mapped) - } - return nil, false, fmt.Errorf("%w (status code %d)", ErrListNetworks, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when listing networks", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, false, fmt.Errorf("%w (status code %d)", ErrListNetworks, resp.StatusCode()) + return nil, false, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrListNetworks, "listing networks", resp.Body, c.logger) } } diff --git a/events/events.go b/events/events.go index 391f0022..93ee4d36 100644 --- a/events/events.go +++ b/events/events.go @@ -288,29 +288,8 @@ func (c *Client) Poll( return nil, false, apierror.WrapChannelNotFound( resp.JSON404, ErrPollEvents, "channel ID "+channelID.String(), ) - case http.StatusForbidden: - c.logger.Error( - "Failed to get events - permission denied", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, false, apierror.Wrap(resp.JSON403, ErrPollEvents, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error( - "Failed to get events - unauthorized", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, false, apierror.Wrap(resp.JSON401, ErrPollEvents, resp.StatusCode()) default: - c.logger.Error( - "Failed to get events - unexpected status code", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, false, fmt.Errorf( - "%w: %w (status code %d)", ErrPollEvents, apierror.ErrUnexpectedStatusCode, resp.StatusCode(), - ) + return nil, false, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrPollEvents, "polling events", resp.Body, c.logger) } } @@ -377,29 +356,8 @@ func (c *Client) SearchEvents( return nil, false, fmt.Errorf( "%w: %w: %s (status code %d)", ErrSearchEvents, ErrBadRequest, errorMsg, resp.StatusCode(), ) - case http.StatusForbidden: - c.logger.Error( - "Failed to search events - permission denied", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, false, apierror.Wrap(resp.JSON403, ErrSearchEvents, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error( - "Failed to search events - unauthorized", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, false, apierror.Wrap(resp.JSON401, ErrSearchEvents, resp.StatusCode()) default: - c.logger.Error( - "Failed to search events - unexpected status code", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, false, fmt.Errorf( - "%w: %w (status code %d)", ErrSearchEvents, apierror.ErrUnexpectedStatusCode, resp.StatusCode(), - ) + return nil, false, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrSearchEvents, "searching events", resp.Body, c.logger) } } diff --git a/queries/queries.go b/queries/queries.go index d248cd1e..7c71b1b6 100644 --- a/queries/queries.go +++ b/queries/queries.go @@ -303,21 +303,8 @@ func (c *Client) Create(ctx context.Context, input CreateInput) (*apiClient.Quer return nil, fmt.Errorf("%w: %w", ErrCreateQuery, ErrIdempotencyConflict) case http.StatusTooManyRequests: return nil, fmt.Errorf("%w: %w", ErrCreateQuery, ErrRateLimitExceeded) - case http.StatusForbidden: - c.logger.Error("Permission denied when creating query", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON403, ErrCreateQuery, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when creating query", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON401, ErrCreateQuery, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when creating query", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrCreateQuery, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrCreateQuery, "creating query", resp.Body, c.logger) } } @@ -374,21 +361,8 @@ func (c *Client) Get(ctx context.Context, channelID uuid.UUID, queryID uuid.UUID ErrGetQuery, fmt.Sprintf("channel ID %s, query ID %s", channelID.String(), queryID.String()), ) - case http.StatusForbidden: - c.logger.Error("Permission denied when getting query", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON403, ErrGetQuery, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when getting query", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON401, ErrGetQuery, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when getting query", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrGetQuery, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrGetQuery, "getting query", resp.Body, c.logger) } } @@ -436,21 +410,8 @@ func (c *Client) List(ctx context.Context, input ListInput) ([]apiClient.Query, return nil, false, apierror.WrapChannelNotFound( resp.JSON404, ErrListQueries, "channel ID "+input.ChannelID.String(), ) - case http.StatusForbidden: - c.logger.Error("Permission denied when listing queries", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, false, apierror.Wrap(resp.JSON403, ErrListQueries, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when listing queries", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, false, apierror.Wrap(resp.JSON401, ErrListQueries, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when listing queries", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, false, fmt.Errorf("%w: %w (status code %d)", ErrListQueries, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, false, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrListQueries, "listing queries", resp.Body, c.logger) } } diff --git a/transact/transact.go b/transact/transact.go index f97c6bfe..af1daebd 100644 --- a/transact/transact.go +++ b/transact/transact.go @@ -258,21 +258,8 @@ func (c *Client) postCreateOperation( "code", apierror.NotFoundCode(resp.JSON404), ) return nil, apierror.WrapNotFound(resp.JSON404, ErrCreateOperation, detail) - case http.StatusForbidden: - c.logger.Error("Permission denied when creating operation", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON403, ErrCreateOperation, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when creating operation", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON401, ErrCreateOperation, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when creating operation", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrCreateOperation, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrCreateOperation, "creating operation", resp.Body, c.logger) } } @@ -546,21 +533,8 @@ func (c *Client) GetOperation(ctx context.Context, channelID uuid.UUID, operatio ErrGetOperation, fmt.Sprintf("channel ID %s, operation ID %s", channelID.String(), operationID.String()), ) - case http.StatusForbidden: - c.logger.Error("Permission denied when getting operation", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON403, ErrGetOperation, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when getting operation", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON401, ErrGetOperation, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when getting operation", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrGetOperation, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrGetOperation, "getting operation", resp.Body, c.logger) } } @@ -638,21 +612,8 @@ func (c *Client) ListOperations(ctx context.Context, input ListOperationsInput) return nil, false, apierror.WrapChannelNotFound( resp.JSON404, ErrListOperations, "channel ID "+input.ChannelID.String(), ) - case http.StatusForbidden: - c.logger.Error("Permission denied when listing operations", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, false, apierror.Wrap(resp.JSON403, ErrListOperations, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when listing operations", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, false, apierror.Wrap(resp.JSON401, ErrListOperations, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when listing operations", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, false, fmt.Errorf("%w: %w (status code %d)", ErrListOperations, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, false, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrListOperations, "listing operations", resp.Body, c.logger) } } @@ -773,21 +734,8 @@ func (c *Client) SendSignedDraftOperation( return nil, ErrDraftNotFound case http.StatusConflict: return nil, ErrDraftNotFinalizable - case http.StatusForbidden: - c.logger.Error("Permission denied when sending operation", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON403, ErrSendOperation, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when sending operation", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON401, ErrSendOperation, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when sending operation", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrSendOperation, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrSendOperation, "sending operation", resp.Body, c.logger) } } @@ -847,20 +795,7 @@ func (c *Client) CancelDraftOperation(ctx context.Context, channelID uuid.UUID, return ErrDraftNotFound case http.StatusConflict: return ErrDraftNotCancellable - case http.StatusForbidden: - c.logger.Error("Permission denied when cancelling operation", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return apierror.Wrap(resp.JSON403, ErrSendOperation, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when cancelling operation", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return apierror.Wrap(resp.JSON401, ErrSendOperation, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when cancelling operation", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return fmt.Errorf("%w: %w (status code %d)", ErrSendOperation, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrSendOperation, "cancelling operation", resp.Body, c.logger) } } diff --git a/wallets/wallets.go b/wallets/wallets.go index 83207eeb..b3b1d2a7 100644 --- a/wallets/wallets.go +++ b/wallets/wallets.go @@ -194,21 +194,8 @@ func (c *Client) Create(ctx context.Context, input CreateInput) (*apiClient.Wall "address", resp.JSON201.Address, "chain_selector", resp.JSON201.ChainSelector) return resp.JSON201, nil - case http.StatusForbidden: - c.logger.Error("Permission denied when creating wallet", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON403, ErrCreateWallet, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when creating wallet", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON401, ErrCreateWallet, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when creating wallet", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrCreateWallet, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrCreateWallet, "creating wallet", resp.Body, c.logger) } } @@ -253,21 +240,8 @@ func (c *Client) Get(ctx context.Context, walletID uuid.UUID) (*apiClient.Wallet "code", apierror.NotFoundCode(resp.JSON404), ) return nil, fmt.Errorf("%w: wallet ID %s", ErrWalletNotFound, walletID.String()) - case http.StatusForbidden: - c.logger.Error("Permission denied when getting wallet", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON403, ErrGetWallet, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when getting wallet", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, apierror.Wrap(resp.JSON401, ErrGetWallet, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when getting wallet", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrGetWallet, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrGetWallet, "getting wallet", resp.Body, c.logger) } } @@ -354,21 +328,8 @@ func (c *Client) List(ctx context.Context, input ListInput) ([]apiClient.Wallet, "count", len(resp.JSON200.Data), "has_more", resp.JSON200.HasMore) return resp.JSON200.Data, resp.JSON200.HasMore, nil - case http.StatusForbidden: - c.logger.Error("Permission denied when listing wallets", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, false, apierror.Wrap(resp.JSON403, ErrListWallets, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when listing wallets", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, false, apierror.Wrap(resp.JSON401, ErrListWallets, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when listing wallets", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return nil, false, fmt.Errorf("%w: %w (status code %d)", ErrListWallets, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, false, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrListWallets, "listing wallets", resp.Body, c.logger) } } @@ -426,21 +387,8 @@ func (c *Client) Update(ctx context.Context, walletID uuid.UUID, input UpdateInp "code", apierror.NotFoundCode(resp.JSON404), ) return fmt.Errorf("%w: wallet ID %s", ErrWalletNotFound, walletID.String()) - case http.StatusForbidden: - c.logger.Error("Permission denied when updating wallet", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return apierror.Wrap(resp.JSON403, ErrUpdateWallet, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when updating wallet", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return apierror.Wrap(resp.JSON401, ErrUpdateWallet, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when updating wallet", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return fmt.Errorf("%w: %w (status code %d)", ErrUpdateWallet, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrUpdateWallet, "updating wallet", resp.Body, c.logger) } } @@ -485,20 +433,7 @@ func (c *Client) Archive(ctx context.Context, walletID uuid.UUID) error { "code", apierror.NotFoundCode(resp.JSON404), ) return fmt.Errorf("%w: wallet ID %s", ErrWalletNotFound, walletID.String()) - case http.StatusForbidden: - c.logger.Error("Permission denied when archiving wallet", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return apierror.Wrap(resp.JSON403, ErrArchiveWallet, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error("Unauthorized when archiving wallet", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return apierror.Wrap(resp.JSON401, ErrArchiveWallet, resp.StatusCode()) default: - c.logger.Error("Unexpected status code when archiving wallet", - "status_code", resp.StatusCode(), - "body", string(resp.Body)) - return fmt.Errorf("%w: %w (status code %d)", ErrArchiveWallet, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrArchiveWallet, "archiving wallet", resp.Body, c.logger) } } diff --git a/watchers/watchers.go b/watchers/watchers.go index c9e2e2ed..0df2c989 100644 --- a/watchers/watchers.go +++ b/watchers/watchers.go @@ -270,29 +270,8 @@ func (c *Client) CreateWithService( } c.logger.Info("Watcher created successfully", "watcher_id", resp.JSON201.WatcherId.String()) return resp.JSON201, nil - case http.StatusForbidden: - c.logger.Error( - "Failed to create watcher with service - permission denied", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, apierror.Wrap(resp.JSON403, ErrCreateWatcherService, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error( - "Failed to create watcher with service - unauthorized", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, apierror.Wrap(resp.JSON401, ErrCreateWatcherService, resp.StatusCode()) default: - c.logger.Error( - "Failed to create watcher with service - unexpected status code", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, fmt.Errorf( - "%w: %w (status code %d)", ErrCreateWatcherService, apierror.ErrUnexpectedStatusCode, resp.StatusCode(), - ) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrCreateWatcherService, "creating watcher with service", resp.Body, c.logger) } } @@ -407,29 +386,8 @@ func (c *Client) CreateWithABI(ctx context.Context, channelID uuid.UUID, input C } c.logger.Info("Watcher created successfully", "watcher_id", resp.JSON201.WatcherId.String()) return resp.JSON201, nil - case http.StatusForbidden: - c.logger.Error( - "Failed to create watcher with ABI - permission denied", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, apierror.Wrap(resp.JSON403, ErrCreateWatcherABI, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error( - "Failed to create watcher with ABI - unauthorized", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, apierror.Wrap(resp.JSON401, ErrCreateWatcherABI, resp.StatusCode()) default: - c.logger.Error( - "Failed to create watcher with ABI - unexpected status code", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, fmt.Errorf( - "%w: %w (status code %d)", ErrCreateWatcherABI, apierror.ErrUnexpectedStatusCode, resp.StatusCode(), - ) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrCreateWatcherABI, "creating watcher with ABI", resp.Body, c.logger) } } @@ -479,27 +437,8 @@ func (c *Client) List(ctx context.Context, channelID uuid.UUID, filters ListFilt return nil, apierror.WrapChannelNotFound( resp.JSON404, ErrListWatchers, "channel ID "+channelID.String(), ) - case http.StatusForbidden: - c.logger.Error( - "Failed to list watchers - permission denied", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, apierror.Wrap(resp.JSON403, ErrListWatchers, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error( - "Failed to list watchers - unauthorized", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, apierror.Wrap(resp.JSON401, ErrListWatchers, resp.StatusCode()) default: - c.logger.Error( - "Failed to list watchers - unexpected status code", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrListWatchers, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrListWatchers, "listing watchers", resp.Body, c.logger) } } @@ -546,27 +485,8 @@ func (c *Client) Get(ctx context.Context, channelID uuid.UUID, watcherID uuid.UU ErrGetWatcher, fmt.Sprintf("channel ID %s, watcher ID %s", channelID.String(), watcherID.String()), ) - case http.StatusForbidden: - c.logger.Error( - "Failed to get watcher - permission denied", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, apierror.Wrap(resp.JSON403, ErrGetWatcher, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error( - "Failed to get watcher - unauthorized", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, apierror.Wrap(resp.JSON401, ErrGetWatcher, resp.StatusCode()) default: - c.logger.Error( - "Failed to get watcher - unexpected status code", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrGetWatcher, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrGetWatcher, "getting watcher", resp.Body, c.logger) } } @@ -624,27 +544,8 @@ func (c *Client) Update( ErrUpdateWatcher, fmt.Sprintf("channel ID %s, watcher ID %s", channelID.String(), watcherID.String()), ) - case http.StatusForbidden: - c.logger.Error( - "Failed to update watcher - permission denied", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, apierror.Wrap(resp.JSON403, ErrUpdateWatcher, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error( - "Failed to update watcher - unauthorized", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, apierror.Wrap(resp.JSON401, ErrUpdateWatcher, resp.StatusCode()) default: - c.logger.Error( - "Failed to update watcher - unexpected status code", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrUpdateWatcher, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrUpdateWatcher, "updating watcher", resp.Body, c.logger) } } @@ -792,27 +693,8 @@ func (c *Client) Archive(ctx context.Context, channelID uuid.UUID, watcherID uui ErrArchiveWatcher, fmt.Sprintf("channel ID %s, watcher ID %s", channelID.String(), watcherID.String()), ) - case http.StatusForbidden: - c.logger.Error( - "Failed to archive watcher - permission denied", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, apierror.Wrap(resp.JSON403, ErrArchiveWatcher, resp.StatusCode()) - case http.StatusUnauthorized: - c.logger.Error( - "Failed to archive watcher - unauthorized", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, apierror.Wrap(resp.JSON401, ErrArchiveWatcher, resp.StatusCode()) default: - c.logger.Error( - "Failed to archive watcher - unexpected status code", - "status_code", resp.StatusCode(), - "body", string(resp.Body), - ) - return nil, fmt.Errorf("%w: %w (status code %d)", ErrArchiveWatcher, apierror.ErrUnexpectedStatusCode, resp.StatusCode()) + return nil, apierror.HandleErrorStatus(resp.StatusCode(), resp.JSON401, resp.JSON403, ErrArchiveWatcher, "archiving watcher", resp.Body, c.logger) } }