// Package queue defines durable, at-least-once work delivery. Handlers must be // idempotent: a crash after a business commit but before ACK can cause redelivery. // This is a message queue, not a workflow engine or a distributed transaction. package queue import ( "context" "encoding/json" "errors" "fmt" "strings" ) var ErrFull = errors.New("queue: capacity reached") var ErrConflict = errors.New("queue: message ID already has different content") var ErrLeaseLost = errors.New("queue: delivery ownership changed") type Message struct { ID string `json:"id"` // Stable producer identity, saved before submitting. Name string `json:"name"` Payload json.RawMessage `json:"payload"` } type Receipt struct { ID string `json:"id"` BackendID string `json:"backendId"` } type Delivery struct { Message Attempt int64 `json:"attempt"` } type Handler func(context.Context, Delivery) error type Publisher interface { Publish(context.Context, Message) (Receipt, error) } type Consumer interface { Run(context.Context, Handler) error } // Reject marks a known permanent failure; it is retained for operator review. // Ordinary errors are retried up to the consumer's configured attempt limit. type Rejection struct{ Code string } func (e *Rejection) Error() string { return "queue: rejected: " + e.Code } func Reject(code string) error { return &Rejection{Code: code} } func Validate(ctx context.Context, m Message, maxBytes int) error { if ctx == nil { return errors.New("queue: context required") } if err := ctx.Err(); err != nil { return err } for _, v := range []string{m.ID, m.Name} { if v == "" || len(v) > 128 || strings.IndexFunc(v, func(r rune) bool { return r < 33 || r > 126 }) >= 0 { return errors.New("queue: ID and name must be 1..128 printable ASCII characters") } } if len(m.Payload) > maxBytes || !json.Valid(m.Payload) { return fmt.Errorf("queue: payload must be valid JSON within %d bytes", maxBytes) } return nil }