feat: mail service
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
// Package idempotency defines the deduplication record used by Mail Service
|
||||
// acceptance flows.
|
||||
package idempotency
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"galaxy/mail/internal/domain/common"
|
||||
"galaxy/mail/internal/domain/delivery"
|
||||
)
|
||||
|
||||
// Record stores the first accepted fingerprint bound to one `(source,
|
||||
// idempotency_key)` scope.
|
||||
type Record struct {
|
||||
// Source stores the frozen delivery source vocabulary value.
|
||||
Source delivery.Source
|
||||
|
||||
// IdempotencyKey stores the caller-owned deduplication key.
|
||||
IdempotencyKey common.IdempotencyKey
|
||||
|
||||
// DeliveryID stores the accepted delivery linked to the scope.
|
||||
DeliveryID common.DeliveryID
|
||||
|
||||
// RequestFingerprint stores the stable fingerprint of the first accepted
|
||||
// request.
|
||||
RequestFingerprint string
|
||||
|
||||
// CreatedAt stores when the deduplication record was created.
|
||||
CreatedAt time.Time
|
||||
|
||||
// ExpiresAt stores when the deduplication record becomes invalid.
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
// Validate reports whether Record satisfies the frozen Stage 2 structural
|
||||
// invariants.
|
||||
func (record Record) Validate() error {
|
||||
if !record.Source.IsKnown() {
|
||||
return fmt.Errorf("idempotency source %q is unsupported", record.Source)
|
||||
}
|
||||
if err := record.IdempotencyKey.Validate(); err != nil {
|
||||
return fmt.Errorf("idempotency key: %w", err)
|
||||
}
|
||||
if err := record.DeliveryID.Validate(); err != nil {
|
||||
return fmt.Errorf("idempotency delivery id: %w", err)
|
||||
}
|
||||
if err := validateToken("idempotency request fingerprint", record.RequestFingerprint); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := common.ValidateTimestamp("idempotency created at", record.CreatedAt); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := common.ValidateTimestamp("idempotency expires at", record.ExpiresAt); err != nil {
|
||||
return err
|
||||
}
|
||||
if !record.ExpiresAt.After(record.CreatedAt) {
|
||||
return fmt.Errorf("idempotency expires at must be after created at")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateToken(name string, value string) error {
|
||||
switch {
|
||||
case strings.TrimSpace(value) == "":
|
||||
return fmt.Errorf("%s must not be empty", name)
|
||||
case strings.TrimSpace(value) != value:
|
||||
return fmt.Errorf("%s must not contain surrounding whitespace", name)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user