64 lines
2.3 KiB
Go
64 lines
2.3 KiB
Go
package email
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"git.sendico.io/tech/sxchnge/notifications/model"
|
|
)
|
|
|
|
func (c *client) post(ctx context.Context, key string, payload any) Outcome {
|
|
body, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return Outcome{State: model.Failed, Code: "invalid_payload"}
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpoint, bytes.NewReader(body))
|
|
if err != nil {
|
|
return Outcome{State: model.Failed, Code: "invalid_provider_config"}
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+c.key)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
if key != "" {
|
|
req.Header.Set("Idempotency-Key", key)
|
|
}
|
|
resp, err := c.http.Do(req)
|
|
if err != nil {
|
|
return Outcome{State: model.Unknown, Code: "provider_outcome_unknown", ProviderMessage: safeErrorText(err.Error(), []string{c.key, key})}
|
|
}
|
|
defer resp.Body.Close()
|
|
// Never pass raw provider bodies through the outcome or log them directly.
|
|
details, readErr := io.ReadAll(io.LimitReader(resp.Body, maxErrorBody+1))
|
|
outcome := Outcome{HTTPStatus: resp.StatusCode}
|
|
switch {
|
|
case resp.StatusCode >= 200 && resp.StatusCode < 300:
|
|
outcome.State = model.Accepted
|
|
case resp.StatusCode == 429:
|
|
delay := time.Minute
|
|
if n, e := strconv.Atoi(resp.Header.Get("Retry-After")); e == nil && n > 0 && n <= 86400 {
|
|
delay = time.Duration(n) * time.Second
|
|
} else if date, e := http.ParseTime(resp.Header.Get("Retry-After")); e == nil && time.Until(date) > 0 {
|
|
delay = time.Until(date)
|
|
}
|
|
outcome.State, outcome.Code, outcome.RetryAfter = model.RetryWait, "provider_rate_limited", delay
|
|
case resp.StatusCode >= 500 || resp.StatusCode == 408:
|
|
outcome.State, outcome.Code = model.Unknown, "provider_outcome_unknown"
|
|
case resp.StatusCode == 401 || resp.StatusCode == 403:
|
|
outcome.State, outcome.Code = model.Failed, "provider_authentication_failed"
|
|
default:
|
|
outcome.State, outcome.Code = model.Failed, "provider_rejected"
|
|
}
|
|
if outcome.State != model.Accepted {
|
|
if readErr != nil || len(details) > maxErrorBody {
|
|
outcome.ProviderMessage = "Provider error details unavailable: incomplete or oversized response"
|
|
} else {
|
|
outcome.ProviderError, outcome.ProviderMessage = providerFailure(details, body, c.key, key)
|
|
}
|
|
}
|
|
return outcome
|
|
}
|