go-mail/senderror.go
Winni Neessen 78df991399
Proposal change for #90
Did a complete overhaul of the senderror.go.

- the list of `errors.New()` has been replaced with constant itoa error reasons as `SendErrReason`. Instead, the `Error()` method now reports the corresponding error message based on the reason.
- The `SendError` received a `Is()` method so that we can use `errors.Is()` for very specific error checking. I.e. we can check for `&SendErrors{Reason: ErrSMTPMailFrom, isTemp: true}`. This provides much more flexibility in the error checking capabilities
- A `isTemp` field has been added to the `SendError` type, indicating whether the received error is temporary and can be retried or not. Accordingly, the `*Msg` now has a `SendErrorIsTemp()` method indicating the same. The decision is based on the first 3 characters returned from the SMTP server. If the error code is within the 4xx range, the error is seen as temporary
- A test for the SendError type has been added
2023-01-01 14:20:13 +01:00

142 lines
3.6 KiB
Go

// SPDX-FileCopyrightText: 2022 Winni Neessen <winni@neessen.dev>
//
// SPDX-License-Identifier: MIT
package mail
import (
"fmt"
"strconv"
"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
)
// SendError is an error wrapper for delivery errors of the Msg
type SendError struct {
Reason SendErrReason
isTemp bool
errlist []error
rcpt []string
}
// SendErrReason represents a comparable reason on why the delivery failed
type SendErrReason int
// Error implements the error interface for the SendError type
func (e *SendError) Error() string {
if e.Reason > 9 {
return "client_send: unknown error"
}
var em strings.Builder
_, _ = fmt.Fprintf(&em, "client_send: %s", e.Reason)
if len(e.errlist) > 0 {
em.WriteRune(':')
for i := range e.errlist {
em.WriteRune(' ')
em.WriteString(e.errlist[i].Error())
if i != len(e.errlist)-1 {
em.WriteString(", ")
}
}
}
if len(e.rcpt) > 0 {
em.WriteString(", affected recipient(s): ")
for i := range e.rcpt {
em.WriteString(e.rcpt[i])
if i != len(e.rcpt)-1 {
em.WriteString(", ")
}
}
}
return em.String()
}
// Is implements the errors.Is functionality and compares the SendErrReason
func (e *SendError) Is(et error) bool {
t, ok := et.(*SendError)
if !ok {
return false
}
return e.Reason == t.Reason && e.isTemp == t.isTemp
}
// String implements the Stringer interface for 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()
}
return "unknown reason"
}
// isTempError checks the given SMTP error and returns true if the given error is of temporary nature
// and should be retried
func isTempError(e error) bool {
ec, err := strconv.Atoi(e.Error()[:3])
if err != nil {
return false
}
if ec >= 400 && ec <= 500 {
return true
}
return false
}