template-monorepo/internal/model/notification/provider/sms/chain.go

79 lines
1.6 KiB
Go
Raw Normal View History

package sms
import (
"context"
"fmt"
"sort"
"time"
)
const defaultSendTimeout = 10 * time.Second
// Chain tries senders in ascending Sort order until one succeeds.
type Chain struct {
senders []Sender
timeout time.Duration
}
type ChainOption func(*Chain)
func WithTimeout(d time.Duration) ChainOption {
return func(c *Chain) {
c.timeout = d
}
}
func NewChain(senders ...Sender) *Chain {
c := &Chain{
senders: append([]Sender(nil), senders...),
timeout: defaultSendTimeout,
}
c.sortSenders()
return c
}
func NewChainWithOptions(senders []Sender, opts ...ChainOption) *Chain {
c := &Chain{
senders: append([]Sender(nil), senders...),
timeout: defaultSendTimeout,
}
for _, opt := range opts {
opt(c)
}
c.sortSenders()
return c
}
func (c *Chain) sortSenders() {
sort.Slice(c.senders, func(i, j int) bool {
return c.senders[i].Sort() < c.senders[j].Sort()
})
}
// Send returns the winning provider name and provider-assigned message id.
func (c *Chain) Send(ctx context.Context, msg *Message) (providerName, messageID string, err error) {
if len(c.senders) == 0 {
return "", "", fmt.Errorf("sms: no senders configured")
}
if msg == nil {
return "", "", fmt.Errorf("sms: message is nil")
}
sendCtx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
var lastErr error
for _, sender := range c.senders {
id, sendErr := sender.Send(sendCtx, msg)
if sendErr == nil {
return sender.Name(), id, nil
}
lastErr = sendErr
}
if lastErr == nil {
lastErr = fmt.Errorf("sms: all providers failed")
}
return "", "", fmt.Errorf("sms: all providers failed: %w", lastErr)
}