Public source validation / validate (push) Failing after 3m8s
68 lines
2.1 KiB
Go
68 lines
2.1 KiB
Go
package notification
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
type Dispatcher struct {
|
|
Store Store
|
|
Channels map[string]ChannelSender
|
|
Limiter *RateLimiter
|
|
}
|
|
|
|
func (dispatcher Dispatcher) DispatchDue(ctx context.Context, now time.Time, limit int) (int, error) {
|
|
if dispatcher.Store == nil {
|
|
return 0, ErrUnavailable
|
|
}
|
|
items, err := dispatcher.Store.ClaimDue(ctx, now.UTC(), limit)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
delivered := 0
|
|
for _, item := range items {
|
|
sender, ok := dispatcher.Channels[item.ChannelID]
|
|
if !ok || sender == nil {
|
|
_, completeErr := dispatcher.Store.Complete(ctx, item.ID, item.Attempts, false, errors.New("notification channel sender unavailable"), now)
|
|
if completeErr != nil {
|
|
return delivered, completeErr
|
|
}
|
|
continue
|
|
}
|
|
delivery := Delivery{ID: item.ID, EventType: item.EventType, Subject: item.Subject, Body: item.Body, IdempotencyKey: item.IdempotencyKey, Attempt: item.Attempts}
|
|
sendErr := sender.Send(ctx, delivery)
|
|
if _, completeErr := dispatcher.Store.Complete(ctx, item.ID, item.Attempts, sendErr == nil, sendErr, now); completeErr != nil {
|
|
return delivered, completeErr
|
|
}
|
|
if sendErr == nil {
|
|
delivered++
|
|
}
|
|
}
|
|
return delivered, nil
|
|
}
|
|
|
|
// Test sends a bounded, in-memory test delivery. It deliberately does not
|
|
// enqueue or mutate an outbox record, and it uses the actor/channel pair as a
|
|
// rate-limit key so test actions cannot become a delivery amplification path.
|
|
func (dispatcher Dispatcher) Test(ctx context.Context, actor, channelID string, delivery Delivery, now time.Time) error {
|
|
if actor == "" || len(actor) > 160 || channelID == "" {
|
|
return fmt.Errorf("%w: test identity is invalid", ErrInvalid)
|
|
}
|
|
if dispatcher.Limiter == nil {
|
|
return ErrUnavailable
|
|
}
|
|
if !dispatcher.Limiter.Allow(actor+":"+channelID, now.UTC()) {
|
|
return fmt.Errorf("%w: notification test rate limit exceeded", ErrConflict)
|
|
}
|
|
sender, ok := dispatcher.Channels[channelID]
|
|
if !ok || sender == nil {
|
|
return ErrNotFound
|
|
}
|
|
if err := delivery.Validate(); err != nil {
|
|
return err
|
|
}
|
|
return sender.Send(ctx, delivery)
|
|
}
|