75 lines
2.1 KiB
Go
75 lines
2.1 KiB
Go
// 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
|
|
}
|
|
}
|