go-mail/senderror.go
2024-11-07 14:59:06 +01:00

232 lines
7.1 KiB
Go

// SPDX-FileCopyrightText: 2022-2023 The go-mail Authors
//
// SPDX-License-Identifier: MIT
package mail
import (
"errors"
"strings"
)
// List of SendError reasons
const (
// ErrGetSender is returned if the Msg.GetSender method fails during a Client.Send
ErrGetSender SendErrReason = iota
// ErrGetRcpts is returned if the Msg.GetRecipients method fails during a Client.Send
ErrGetRcpts
// ErrSMTPMailFrom is returned if the Msg delivery failed when sending the MAIL FROM command
// to the sending SMTP server
ErrSMTPMailFrom
// ErrSMTPRcptTo is returned if the Msg delivery failed when sending the RCPT TO command
// to the sending SMTP server
ErrSMTPRcptTo
// ErrSMTPData is returned if the Msg delivery failed when sending the DATA command
// to the sending SMTP server
ErrSMTPData
// ErrSMTPDataClose is returned if the Msg delivery failed when trying to close the
// Client data writer
ErrSMTPDataClose
// ErrSMTPReset is returned if the Msg delivery failed when sending the RSET command
// to the sending SMTP server
ErrSMTPReset
// ErrWriteContent is returned if the Msg delivery failed when sending Msg content
// to the Client writer
ErrWriteContent
// ErrConnCheck is returned if the Msg delivery failed when checking if the SMTP
// server connection is still working
ErrConnCheck
// ErrNoUnencoded is returned if the Msg delivery failed when the Msg is configured for
// unencoded delivery but the server does not support this
ErrNoUnencoded
// ErrAmbiguous is a generalized delivery error for the SendError type that is
// returned if the exact reason for the delivery failure is ambiguous
ErrAmbiguous
)
// SendError is an error wrapper for delivery errors of the Msg.
//
// This struct represents an error that occurs during the delivery of a message. It holds
// details about the affected message, a list of errors, the recipient list, and whether
// the error is temporary or permanent. It also includes a reason code for the error.
type SendError struct {
affectedMsg *Msg
errlist []error
isTemp bool
rcpt []string
Reason SendErrReason
}
// SendErrReason represents a comparable reason on why the delivery failed
type SendErrReason int
// Error implements the error interface for the SendError type.
//
// This function returns a detailed error message string for the SendError, including the
// reason for failure, list of errors, affected recipients, and the message ID of the
// affected message (if available). If the reason is unknown (greater than 10), it returns
// "unknown reason". The error message is built dynamically based on the content of the
// error list, recipient list, and message ID.
//
// Returns:
// - A string representing the error message.
func (e *SendError) Error() string {
if e.Reason > ErrAmbiguous {
return "unknown reason"
}
var errMessage strings.Builder
errMessage.WriteString(e.Reason.String())
if len(e.errlist) > 0 {
errMessage.WriteRune(':')
for i := range e.errlist {
errMessage.WriteRune(' ')
errMessage.WriteString(e.errlist[i].Error())
if i != len(e.errlist)-1 {
errMessage.WriteString(",")
}
}
}
if len(e.rcpt) > 0 {
errMessage.WriteString(", affected recipient(s): ")
for i := range e.rcpt {
errMessage.WriteString(e.rcpt[i])
if i != len(e.rcpt)-1 {
errMessage.WriteString(", ")
}
}
}
if e.affectedMsg != nil && e.affectedMsg.GetMessageID() != "" {
errMessage.WriteString(", affected message ID: ")
errMessage.WriteString(e.affectedMsg.GetMessageID())
}
return errMessage.String()
}
// Is implements the errors.Is functionality and compares the SendErrReason.
//
// This function allows for comparison between two errors by checking if the provided
// error matches the SendError type and, if so, compares the SendErrReason and the
// temporary status (isTemp) of both errors.
//
// Parameters:
// - errType: The error to compare against the current SendError.
//
// Returns:
// - true if the errors have the same reason and temporary status, false otherwise.
func (e *SendError) Is(errType error) bool {
var t *SendError
if errors.As(errType, &t) && t != nil {
return e.Reason == t.Reason && e.isTemp == t.isTemp
}
return false
}
func (e *SendError) Unwrap() []error {
return e.errlist
}
// IsTemp returns true if the delivery error is of a temporary nature and can be retried.
//
// This function checks whether the SendError indicates a temporary error, which suggests
// that the delivery can be retried. If the SendError is nil, it returns false.
//
// Returns:
// - true if the error is temporary, false otherwise.
func (e *SendError) IsTemp() bool {
if e == nil {
return false
}
return e.isTemp
}
// MessageID returns the message ID of the affected Msg that caused the error.
//
// This function retrieves the message ID of the Msg associated with the SendError.
// If no message ID was set or if the SendError or Msg is nil, it returns an empty string.
//
// Returns:
// - The message ID as a string, or an empty string if no ID is available.
func (e *SendError) MessageID() string {
if e == nil || e.affectedMsg == nil {
return ""
}
return e.affectedMsg.GetMessageID()
}
// Msg returns the pointer to the affected message that caused the error.
//
// This function retrieves the Msg associated with the SendError. If the SendError or
// the affectedMsg is nil, it returns nil.
//
// Returns:
// - A pointer to the Msg that caused the error, or nil if not available.
func (e *SendError) Msg() *Msg {
if e == nil || e.affectedMsg == nil {
return nil
}
return e.affectedMsg
}
// String satisfies the fmt.Stringer interface for the SendErrReason type.
//
// This function converts the SendErrReason into a human-readable string representation based
// on the error type. If the error reason does not match any predefined case, it returns
// "unknown reason".
//
// Returns:
// - A string representation of the SendErrReason.
func (r SendErrReason) String() string {
switch r {
case ErrGetSender:
return "getting sender address"
case ErrGetRcpts:
return "getting recipient addresses"
case ErrSMTPMailFrom:
return "sending SMTP MAIL FROM command"
case ErrSMTPRcptTo:
return "sending SMTP RCPT TO command"
case ErrSMTPData:
return "sending SMTP DATA command"
case ErrSMTPDataClose:
return "closing SMTP DATA writer"
case ErrSMTPReset:
return "sending SMTP RESET command"
case ErrWriteContent:
return "sending message content"
case ErrConnCheck:
return "checking SMTP connection"
case ErrNoUnencoded:
return ErrServerNoUnencoded.Error()
case ErrAmbiguous:
return "ambiguous reason, check Msg.SendError for message specific reasons"
}
return "unknown reason"
}
// isTempError checks if the given SMTP error is of a temporary nature and should be retried.
//
// This function inspects the error message and returns true if the first character of the
// error message is '4', indicating a temporary SMTP error that can be retried.
//
// Parameters:
// - err: The error to check.
//
// Returns:
// - true if the error is temporary, false otherwise.
func isTempError(err error) bool {
return err.Error()[0] == '4'
}
var _ interface{ Unwrap() []error } = &SendError{}