diff --git a/internal/bridge/user_events.go b/internal/bridge/user_events.go index 6d53f0b7..47215021 100644 --- a/internal/bridge/user_events.go +++ b/internal/bridge/user_events.go @@ -19,10 +19,10 @@ package bridge import ( "context" - "errors" "fmt" "github.com/ProtonMail/gluon/reporter" + "github.com/ProtonMail/proton-bridge/v3/internal" "github.com/ProtonMail/proton-bridge/v3/internal/events" "github.com/ProtonMail/proton-bridge/v3/internal/safe" "github.com/ProtonMail/proton-bridge/v3/internal/user" @@ -177,7 +177,7 @@ func (bridge *Bridge) handleUserDeauth(ctx context.Context, user *user.User) { func (bridge *Bridge) handleUserBadEvent(ctx context.Context, user *user.User, err error) { safe.Lock(func() { if rerr := bridge.reporter.ReportMessageWithContext("Failed to handle event", reporter.Context{ - "error_type": fmt.Sprintf("%T", getUnwrappedError(err)), + "error_type": fmt.Sprintf("%T", internal.ErrCause(err)), "error": err, }); rerr != nil { logrus.WithError(rerr).Error("Failed to report failed event handling") @@ -189,22 +189,9 @@ func (bridge *Bridge) handleUserBadEvent(ctx context.Context, user *user.User, e func (bridge *Bridge) handleUncategorizedErrorEvent(event events.UncategorizedEventError) { if rerr := bridge.reporter.ReportMessageWithContext("Failed to handle due to uncategorized error", reporter.Context{ - "error_type": fmt.Sprintf("%T", getUnwrappedError(event.Error)), + "error_type": fmt.Sprintf("%T", internal.ErrCause(event.Error)), "error": event.Error, }); rerr != nil { logrus.WithError(rerr).Error("Failed to report failed event handling") } } - -func getUnwrappedError(err error) error { - for { - unwrapped := errors.Unwrap(err) - if unwrapped == nil { - break - } - - err = unwrapped - } - - return err -} diff --git a/internal/errors.go b/internal/errors.go new file mode 100644 index 00000000..f8e839a6 --- /dev/null +++ b/internal/errors.go @@ -0,0 +1,31 @@ +// Copyright (c) 2023 Proton AG +// +// This file is part of Proton Mail Bridge. +// +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . + +package internal + +import "errors" + +// ErrCause returns the cause of the error, the inner-most error in the wrapped chain. +func ErrCause(err error) error { + cause := err + + for errors.Unwrap(cause) != nil { + cause = errors.Unwrap(cause) + } + + return cause +} diff --git a/internal/user/user.go b/internal/user/user.go index e3e31539..64d0fdd0 100644 --- a/internal/user/user.go +++ b/internal/user/user.go @@ -34,6 +34,7 @@ import ( "github.com/ProtonMail/gluon/queue" "github.com/ProtonMail/gluon/reporter" "github.com/ProtonMail/go-proton-api" + "github.com/ProtonMail/proton-bridge/v3/internal" "github.com/ProtonMail/proton-bridge/v3/internal/async" "github.com/ProtonMail/proton-bridge/v3/internal/events" "github.com/ProtonMail/proton-bridge/v3/internal/logging" @@ -632,36 +633,7 @@ func (user *User) doEventPoll(ctx context.Context) error { event, err := user.client.GetEvent(ctx, user.vault.EventID()) if err != nil { - if netErr := new(proton.NetError); errors.As(err, &netErr) { - return fmt.Errorf("failed to get event due to network issue: %w", err) - } - - // Catch all for uncategorized net errors that may slip through. - if netErr := new(net.OpError); errors.As(err, &netErr) { - user.eventCh.Enqueue(events.UncategorizedEventError{ - UserID: user.ID(), - Error: err, - }) - - return fmt.Errorf("failed to get event due to network issues (uncategorized): %w", err) - } - - // In case a json decode error slips through. - if jsonErr := new(json.UnmarshalTypeError); errors.As(err, &jsonErr) { - user.eventCh.Enqueue(events.UncategorizedEventError{ - UserID: user.ID(), - Error: err, - }) - - return fmt.Errorf("failed to get event due to JSON issue: %w", err) - } - - // If the error is a server-side issue, return error to retry later. - if apiErr := new(proton.APIError); errors.As(err, &apiErr) && apiErr.Status >= 500 { - return fmt.Errorf("failed to get event due to server error: %w", err) - } - - return fmt.Errorf("failed to get event: %w", err) + return fmt.Errorf("failed to get event (caused by %T): %w", internal.ErrCause(err), err) } // If the event ID hasn't changed, there are no new events. @@ -677,6 +649,11 @@ func (user *User) doEventPoll(ctx context.Context) error { // Handle the event. if err := user.handleAPIEvent(ctx, event); err != nil { + // If the error is a context cancellation, return error to retry later. + if errors.Is(err, context.Canceled) { + return fmt.Errorf("failed to handle event due to context cancellation: %w", err) + } + // If the error is a network error, return error to retry later. if netErr := new(proton.NetError); errors.As(err, &netErr) { return fmt.Errorf("failed to handle event due to network issue: %w", err)