Files
galaxy-game/authsession/internal/ports/mail_sender.go
T
2026-04-17 18:39:16 +02:00

103 lines
3.4 KiB
Go

package ports
import (
"context"
"errors"
"fmt"
"strings"
"galaxy/authsession/internal/domain/common"
)
// MailSender delivers the public login code or intentionally suppresses
// outward delivery while keeping the auth flow success-shaped.
type MailSender interface {
// SendLoginCode attempts delivery for one generated login code. Explicit
// delivery failure is reported through error, while sent vs suppressed is
// returned in the result.
SendLoginCode(ctx context.Context, input SendLoginCodeInput) (SendLoginCodeResult, error)
}
// SendLoginCodeInput describes one mail-delivery request generated by the auth
// flow.
type SendLoginCodeInput struct {
// Email identifies the normalized target e-mail address.
Email common.Email
// IdempotencyKey stores the raw challenge_id value sent to Mail Service as
// the required Idempotency-Key header.
IdempotencyKey string
// Code stores the cleartext login code that should be delivered to Email.
Code string
// Locale stores the canonical BCP 47 language tag that selects the auth
// mail template locale.
Locale string
}
// Validate reports whether SendLoginCodeInput contains a complete delivery
// request.
func (i SendLoginCodeInput) Validate() error {
if err := i.Email.Validate(); err != nil {
return fmt.Errorf("send login code input email: %w", err)
}
switch {
case strings.TrimSpace(i.IdempotencyKey) == "":
return errors.New("send login code input idempotency key must not be empty")
case strings.TrimSpace(i.IdempotencyKey) != i.IdempotencyKey:
return errors.New("send login code input idempotency key must not contain surrounding whitespace")
case strings.TrimSpace(i.Code) == "":
return errors.New("send login code input code must not be empty")
case strings.TrimSpace(i.Code) != i.Code:
return errors.New("send login code input code must not contain surrounding whitespace")
case strings.TrimSpace(i.Locale) == "":
return errors.New("send login code input locale must not be empty")
case strings.TrimSpace(i.Locale) != i.Locale:
return errors.New("send login code input locale must not contain surrounding whitespace")
default:
return nil
}
}
// SendLoginCodeOutcome identifies the coarse mail-delivery outcome reported
// back to the auth flow.
type SendLoginCodeOutcome string
const (
// SendLoginCodeOutcomeSent reports that delivery was attempted and accepted.
SendLoginCodeOutcomeSent SendLoginCodeOutcome = "sent"
// SendLoginCodeOutcomeSuppressed reports that outward behavior remains
// success-shaped while actual delivery is intentionally skipped.
SendLoginCodeOutcomeSuppressed SendLoginCodeOutcome = "suppressed"
)
// IsKnown reports whether SendLoginCodeOutcome is supported by the current
// mail-sender contract.
func (o SendLoginCodeOutcome) IsKnown() bool {
switch o {
case SendLoginCodeOutcomeSent, SendLoginCodeOutcomeSuppressed:
return true
default:
return false
}
}
// SendLoginCodeResult describes the stable outcome returned by MailSender for
// one delivery request.
type SendLoginCodeResult struct {
// Outcome reports whether delivery was sent or intentionally suppressed.
Outcome SendLoginCodeOutcome
}
// Validate reports whether SendLoginCodeResult satisfies the mail-sender
// contract invariants.
func (r SendLoginCodeResult) Validate() error {
if !r.Outcome.IsKnown() {
return fmt.Errorf("send login code result outcome %q is unsupported", r.Outcome)
}
return nil
}