mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-12 16:02:44 +00:00
63 lines
2.2 KiB
Go
63 lines
2.2 KiB
Go
package msgraph
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/warmbly/warmbly/internal/errx"
|
|
)
|
|
|
|
// graphErrorEnvelope is Graph's standard error shape.
|
|
type graphErrorEnvelope struct {
|
|
Error struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
} `json:"error"`
|
|
}
|
|
|
|
// HandleError maps a non-2xx Graph response to a MailError. It classifies by
|
|
// HTTP status so the send/sync retry loop can tell transient (retryable) from
|
|
// critical (needs re-auth / stop) failures:
|
|
// - 401 -> authentication failed (token expired/revoked, re-consent)
|
|
// - 403 -> authorization failed (missing scope / mailbox disabled)
|
|
// - 404 -> resource not found (a folder the tenant does not have, a message
|
|
// already gone); its own code so a caller can skip what is absent without
|
|
// also skipping on a 503
|
|
// - 429 -> sending too fast (throttled; Retry-After honored by the caller loop)
|
|
// - 5xx / other -> server unreachable (retry)
|
|
func HandleError(resp *http.Response) *errx.MailError {
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<16))
|
|
var env graphErrorEnvelope
|
|
_ = json.Unmarshal(body, &env)
|
|
|
|
now := time.Now().UTC()
|
|
failure := errx.SendFailure{Provider: "microsoft", Protocol: "http", Status: resp.StatusCode,
|
|
Cause: errx.SafeProviderCode(env.Error.Code), Scope: "mailbox", ObservedAt: now,
|
|
Disposition: errx.SendRetry, RetryAt: errx.RetryAfterAt(resp.Header.Get("Retry-After"), now)}
|
|
base := errx.ErrMailServerUnreachable
|
|
switch resp.StatusCode {
|
|
case http.StatusUnauthorized:
|
|
base, failure.Disposition = errx.ErrMailAuthenticationFailed, errx.SendAuth
|
|
case http.StatusForbidden:
|
|
base, failure.Disposition = errx.ErrMailAuthorizationFailed, errx.SendAuth
|
|
case http.StatusNotFound:
|
|
base, failure.Disposition = errx.ErrMailResourceNotFound, errx.SendPermanent
|
|
case http.StatusTooManyRequests:
|
|
base, failure.Disposition = errx.ErrMailSendingTooFast, errx.SendThrottle
|
|
default:
|
|
if resp.StatusCode >= 400 && resp.StatusCode < 500 {
|
|
failure.Disposition = errx.SendPermanent
|
|
}
|
|
}
|
|
return errx.WithSendFailure(base, failure)
|
|
}
|
|
|
|
func retryAfter(value string, now time.Time) time.Duration {
|
|
if at := errx.RetryAfterAt(value, now); at != nil {
|
|
return at.Sub(now)
|
|
}
|
|
return 0
|
|
}
|