mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 13:50:49 +01:00
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
This commit is contained in:
parent
42a1fde51f
commit
78df991399
6 changed files with 189 additions and 49 deletions
|
@ -22,41 +22,42 @@ func (c *Client) Send(ml ...*Msg) error {
|
||||||
if m.encoding == NoEncoding {
|
if m.encoding == NoEncoding {
|
||||||
if ok, _ := c.sc.Extension("8BITMIME"); !ok {
|
if ok, _ := c.sc.Extension("8BITMIME"); !ok {
|
||||||
errs = append(errs, ErrServerNoUnencoded)
|
errs = append(errs, ErrServerNoUnencoded)
|
||||||
m.sendError = SendError{Err: ErrServerNoUnencoded}
|
m.sendError = &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f, err := m.GetSender(false)
|
f, err := m.GetSender(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
m.sendError = SendError{Err: ErrGetSender, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrGetSender, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rl, err := m.GetRecipients()
|
rl, err := m.GetRecipients()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.sendError = SendError{Err: ErrGetRcpts, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrGetRcpts, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.mail(f); err != nil {
|
if err := c.mail(f); err != nil {
|
||||||
errs = append(errs, fmt.Errorf("sending MAIL FROM command failed: %w", err))
|
errs = append(errs, fmt.Errorf("sending MAIL FROM command failed: %w", err))
|
||||||
m.sendError = SendError{Err: ErrSMTPMailFrom, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
if reserr := c.sc.Reset(); reserr != nil {
|
if reserr := c.sc.Reset(); reserr != nil {
|
||||||
errs = append(errs, reserr)
|
errs = append(errs, reserr)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
failed := false
|
failed := false
|
||||||
rse := SendError{}
|
rse := &SendError{}
|
||||||
rse.details = make([]error, 0)
|
rse.errlist = make([]error, 0)
|
||||||
rse.rcpt = make([]string, 0)
|
rse.rcpt = make([]string, 0)
|
||||||
for _, r := range rl {
|
for _, r := range rl {
|
||||||
if err := c.rcpt(r); err != nil {
|
if err := c.rcpt(r); err != nil {
|
||||||
errs = append(errs, fmt.Errorf("sending RCPT TO command failed: %w", err))
|
errs = append(errs, fmt.Errorf("sending RCPT TO command failed: %w", err))
|
||||||
rse.Err = ErrSMTPRcptTo
|
rse.Reason = ErrSMTPRcptTo
|
||||||
rse.details = append(rse.details, err)
|
rse.errlist = append(rse.errlist, err)
|
||||||
rse.rcpt = append(rse.rcpt, r)
|
rse.rcpt = append(rse.rcpt, r)
|
||||||
|
rse.isTemp = isTempError(err)
|
||||||
failed = true
|
failed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,30 +71,30 @@ func (c *Client) Send(ml ...*Msg) error {
|
||||||
w, err := c.sc.Data()
|
w, err := c.sc.Data()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, fmt.Errorf("sending DATA command failed: %w", err))
|
errs = append(errs, fmt.Errorf("sending DATA command failed: %w", err))
|
||||||
m.sendError = SendError{Err: ErrSMTPData, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, err = m.WriteTo(w)
|
_, err = m.WriteTo(w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, fmt.Errorf("sending mail content failed: %w", err))
|
errs = append(errs, fmt.Errorf("sending mail content failed: %w", err))
|
||||||
m.sendError = SendError{Err: ErrWriteContent, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrWriteContent, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := w.Close(); err != nil {
|
if err := w.Close(); err != nil {
|
||||||
errs = append(errs, fmt.Errorf("failed to close DATA writer: %w", err))
|
errs = append(errs, fmt.Errorf("failed to close DATA writer: %w", err))
|
||||||
m.sendError = SendError{Err: ErrSMTPDataClose, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrSMTPDataClose, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Reset(); err != nil {
|
if err := c.Reset(); err != nil {
|
||||||
errs = append(errs, fmt.Errorf("sending RSET command failed: %w", err))
|
errs = append(errs, fmt.Errorf("sending RSET command failed: %w", err))
|
||||||
m.sendError = SendError{Err: ErrSMTPReset, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrSMTPReset, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := c.checkConn(); err != nil {
|
if err := c.checkConn(); err != nil {
|
||||||
errs = append(errs, fmt.Errorf("failed to check server connection: %w", err))
|
errs = append(errs, fmt.Errorf("failed to check server connection: %w", err))
|
||||||
m.sendError = SendError{Err: ErrConnCheck, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,41 +23,42 @@ func (c *Client) Send(ml ...*Msg) (rerr error) {
|
||||||
if m.encoding == NoEncoding {
|
if m.encoding == NoEncoding {
|
||||||
if ok, _ := c.sc.Extension("8BITMIME"); !ok {
|
if ok, _ := c.sc.Extension("8BITMIME"); !ok {
|
||||||
rerr = errors.Join(rerr, ErrServerNoUnencoded)
|
rerr = errors.Join(rerr, ErrServerNoUnencoded)
|
||||||
m.sendError = SendError{Err: ErrServerNoUnencoded}
|
m.sendError = &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f, err := m.GetSender(false)
|
f, err := m.GetSender(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rerr = errors.Join(rerr, err)
|
rerr = errors.Join(rerr, err)
|
||||||
m.sendError = SendError{Err: ErrGetSender, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrGetSender, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rl, err := m.GetRecipients()
|
rl, err := m.GetRecipients()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rerr = errors.Join(rerr, err)
|
rerr = errors.Join(rerr, err)
|
||||||
m.sendError = SendError{Err: ErrGetRcpts, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrGetRcpts, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.mail(f); err != nil {
|
if err := c.mail(f); err != nil {
|
||||||
rerr = errors.Join(rerr, fmt.Errorf("sending MAIL FROM command failed: %w", err))
|
rerr = errors.Join(rerr, fmt.Errorf("sending MAIL FROM command failed: %w", err))
|
||||||
m.sendError = SendError{Err: ErrSMTPMailFrom, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
if reserr := c.sc.Reset(); reserr != nil {
|
if reserr := c.sc.Reset(); reserr != nil {
|
||||||
rerr = errors.Join(rerr, reserr)
|
rerr = errors.Join(rerr, reserr)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
failed := false
|
failed := false
|
||||||
rse := SendError{}
|
rse := &SendError{}
|
||||||
rse.details = make([]error, 0)
|
rse.errlist = make([]error, 0)
|
||||||
rse.rcpt = make([]string, 0)
|
rse.rcpt = make([]string, 0)
|
||||||
for _, r := range rl {
|
for _, r := range rl {
|
||||||
if err := c.rcpt(r); err != nil {
|
if err := c.rcpt(r); err != nil {
|
||||||
rerr = errors.Join(rerr, fmt.Errorf("sending RCPT TO command failed: %w", err))
|
rerr = errors.Join(rerr, fmt.Errorf("sending RCPT TO command failed: %w", err))
|
||||||
rse.Err = ErrSMTPRcptTo
|
rse.Reason = ErrSMTPRcptTo
|
||||||
rse.details = append(rse.details, err)
|
rse.errlist = append(rse.errlist, err)
|
||||||
rse.rcpt = append(rse.rcpt, r)
|
rse.rcpt = append(rse.rcpt, r)
|
||||||
|
rse.isTemp = isTempError(err)
|
||||||
failed = true
|
failed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,30 +72,30 @@ func (c *Client) Send(ml ...*Msg) (rerr error) {
|
||||||
w, err := c.sc.Data()
|
w, err := c.sc.Data()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rerr = errors.Join(rerr, fmt.Errorf("sending DATA command failed: %w", err))
|
rerr = errors.Join(rerr, fmt.Errorf("sending DATA command failed: %w", err))
|
||||||
m.sendError = SendError{Err: ErrSMTPData, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, err = m.WriteTo(w)
|
_, err = m.WriteTo(w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rerr = errors.Join(rerr, fmt.Errorf("sending mail content failed: %w", err))
|
rerr = errors.Join(rerr, fmt.Errorf("sending mail content failed: %w", err))
|
||||||
m.sendError = SendError{Err: ErrWriteContent, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrWriteContent, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := w.Close(); err != nil {
|
if err := w.Close(); err != nil {
|
||||||
rerr = errors.Join(rerr, fmt.Errorf("failed to close DATA writer: %w", err))
|
rerr = errors.Join(rerr, fmt.Errorf("failed to close DATA writer: %w", err))
|
||||||
m.sendError = SendError{Err: ErrSMTPDataClose, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrSMTPDataClose, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Reset(); err != nil {
|
if err := c.Reset(); err != nil {
|
||||||
rerr = errors.Join(rerr, fmt.Errorf("sending RSET command failed: %w", err))
|
rerr = errors.Join(rerr, fmt.Errorf("sending RSET command failed: %w", err))
|
||||||
m.sendError = SendError{Err: ErrSMTPReset, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrSMTPReset, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := c.checkConn(); err != nil {
|
if err := c.checkConn(); err != nil {
|
||||||
rerr = errors.Join(rerr, fmt.Errorf("failed to check server connection: %w", err))
|
rerr = errors.Join(rerr, fmt.Errorf("failed to check server connection: %w", err))
|
||||||
m.sendError = SendError{Err: ErrConnCheck, details: []error{err}}
|
m.sendError = &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1030,9 +1030,12 @@ func TestClient_Send_MsgSendError(t *testing.T) {
|
||||||
if !m.HasSendError() {
|
if !m.HasSendError() {
|
||||||
t.Errorf("message was expected to have a send error, but didn't")
|
t.Errorf("message was expected to have a send error, but didn't")
|
||||||
}
|
}
|
||||||
se := SendError{Err: ErrSMTPRcptTo}
|
se := &SendError{Reason: ErrSMTPRcptTo}
|
||||||
if !errors.As(m.SendError(), &se) {
|
if !errors.Is(m.SendError(), se) {
|
||||||
t.Errorf("message with broken recipient was expected to return a ErrSMTPRcptTo error, but didn't")
|
t.Errorf("error mismatch, expected: %s, got: %s", se, m.SendError())
|
||||||
|
}
|
||||||
|
if m.SendErrorIsTemp() {
|
||||||
|
t.Errorf("message was not expected to be a temporary error, but reported as such")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
msg.go
10
msg.go
|
@ -966,6 +966,16 @@ func (m *Msg) HasSendError() bool {
|
||||||
return m.sendError != nil
|
return m.sendError != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendErrorIsTemp returns true if the Msg experienced an error during the message delivery and the
|
||||||
|
// corresponding error was of temporary nature and should be retried later
|
||||||
|
func (m *Msg) SendErrorIsTemp() bool {
|
||||||
|
e, ok := m.sendError.(*SendError)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return e.isTemp
|
||||||
|
}
|
||||||
|
|
||||||
// SendError returns the senderror field of the Msg
|
// SendError returns the senderror field of the Msg
|
||||||
func (m *Msg) SendError() error {
|
func (m *Msg) SendError() error {
|
||||||
return m.sendError
|
return m.sendError
|
||||||
|
|
110
senderror.go
110
senderror.go
|
@ -5,68 +5,138 @@
|
||||||
package mail
|
package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// List of SendError errors
|
// List of SendError reasons
|
||||||
var (
|
const (
|
||||||
// ErrGetSender is returned if the Msg.GetSender method fails during a Client.Send
|
// ErrGetSender is returned if the Msg.GetSender method fails during a Client.Send
|
||||||
ErrGetSender = errors.New("getting sender address")
|
ErrGetSender SendErrReason = iota
|
||||||
|
|
||||||
// ErrGetRcpts is returned if the Msg.GetRecipients method fails during a Client.Send
|
// ErrGetRcpts is returned if the Msg.GetRecipients method fails during a Client.Send
|
||||||
ErrGetRcpts = errors.New("getting recipient addresses")
|
ErrGetRcpts
|
||||||
|
|
||||||
// ErrSMTPMailFrom is returned if the Msg delivery failed when sending the MAIL FROM command
|
// ErrSMTPMailFrom is returned if the Msg delivery failed when sending the MAIL FROM command
|
||||||
// to the sending SMTP server
|
// to the sending SMTP server
|
||||||
ErrSMTPMailFrom = errors.New("sending SMTP MAIL FROM command")
|
ErrSMTPMailFrom
|
||||||
|
|
||||||
// ErrSMTPRcptTo is returned if the Msg delivery failed when sending the RCPT TO command
|
// ErrSMTPRcptTo is returned if the Msg delivery failed when sending the RCPT TO command
|
||||||
// to the sending SMTP server
|
// to the sending SMTP server
|
||||||
ErrSMTPRcptTo = errors.New("sending SMTP RCPT TO command")
|
ErrSMTPRcptTo
|
||||||
|
|
||||||
// ErrSMTPData is returned if the Msg delivery failed when sending the DATA command
|
// ErrSMTPData is returned if the Msg delivery failed when sending the DATA command
|
||||||
// to the sending SMTP server
|
// to the sending SMTP server
|
||||||
ErrSMTPData = errors.New("sending SMTP DATA command")
|
ErrSMTPData
|
||||||
|
|
||||||
// ErrSMTPDataClose is returned if the Msg delivery failed when trying to close the
|
// ErrSMTPDataClose is returned if the Msg delivery failed when trying to close the
|
||||||
// Client data writer
|
// Client data writer
|
||||||
ErrSMTPDataClose = errors.New("closing SMTP DATA writer")
|
ErrSMTPDataClose
|
||||||
|
|
||||||
// ErrSMTPReset is returned if the Msg delivery failed when sending the RSET command
|
// ErrSMTPReset is returned if the Msg delivery failed when sending the RSET command
|
||||||
// to the sending SMTP server
|
// to the sending SMTP server
|
||||||
ErrSMTPReset = errors.New("sending SMTP RESET command")
|
ErrSMTPReset
|
||||||
|
|
||||||
// ErrWriteContent is returned if the Msg delivery failed when sending Msg content
|
// ErrWriteContent is returned if the Msg delivery failed when sending Msg content
|
||||||
// to the Client writer
|
// to the Client writer
|
||||||
ErrWriteContent = errors.New("sending message content")
|
ErrWriteContent
|
||||||
|
|
||||||
// ErrConnCheck is returned if the Msg delivery failed when checking if the SMTP
|
// ErrConnCheck is returned if the Msg delivery failed when checking if the SMTP
|
||||||
// server connection is still working
|
// server connection is still working
|
||||||
ErrConnCheck = errors.New("checking SMTP connection")
|
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
|
// SendError is an error wrapper for delivery errors of the Msg
|
||||||
type SendError struct {
|
type SendError struct {
|
||||||
Err error
|
Reason SendErrReason
|
||||||
details []error
|
isTemp bool
|
||||||
|
errlist []error
|
||||||
rcpt []string
|
rcpt []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendErrReason represents a comparable reason on why the delivery failed
|
||||||
|
type SendErrReason int
|
||||||
|
|
||||||
// Error implements the error interface for the SendError type
|
// Error implements the error interface for the SendError type
|
||||||
func (e SendError) Error() string {
|
func (e *SendError) Error() string {
|
||||||
|
if e.Reason > 9 {
|
||||||
|
return "client_send: unknown error"
|
||||||
|
}
|
||||||
|
|
||||||
var em strings.Builder
|
var em strings.Builder
|
||||||
_, _ = fmt.Fprintf(&em, "client_send: %s", e.Err)
|
_, _ = fmt.Fprintf(&em, "client_send: %s", e.Reason)
|
||||||
if len(e.details) > 0 {
|
if len(e.errlist) > 0 {
|
||||||
for i := range e.details {
|
em.WriteRune(':')
|
||||||
em.WriteString(fmt.Sprintf(", error_details: %s", e.details[i]))
|
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 {
|
if len(e.rcpt) > 0 {
|
||||||
|
em.WriteString(", affected recipient(s): ")
|
||||||
for i := range e.rcpt {
|
for i := range e.rcpt {
|
||||||
em.WriteString(fmt.Sprintf(", rcpt: %s", e.rcpt[i]))
|
em.WriteString(e.rcpt[i])
|
||||||
|
if i != len(e.rcpt)-1 {
|
||||||
|
em.WriteString(", ")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return em.String()
|
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
|
||||||
|
}
|
||||||
|
|
55
senderror_test.go
Normal file
55
senderror_test.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestSendError_Error tests the SendError and SendErrReason error handling methods
|
||||||
|
func TestSendError_Error(t *testing.T) {
|
||||||
|
tl := []struct {
|
||||||
|
n string
|
||||||
|
r SendErrReason
|
||||||
|
te bool
|
||||||
|
}{
|
||||||
|
{"ErrGetSender/temp", ErrGetSender, true},
|
||||||
|
{"ErrGetSender/perm", ErrGetSender, false},
|
||||||
|
{"ErrGetRcpts/temp", ErrGetRcpts, true},
|
||||||
|
{"ErrGetRcpts/perm", ErrGetRcpts, false},
|
||||||
|
{"ErrSMTPMailFrom/temp", ErrSMTPMailFrom, true},
|
||||||
|
{"ErrSMTPMailFrom/perm", ErrSMTPMailFrom, false},
|
||||||
|
{"ErrSMTPRcptTo/temp", ErrSMTPRcptTo, true},
|
||||||
|
{"ErrSMTPRcptTo/perm", ErrSMTPRcptTo, false},
|
||||||
|
{"ErrSMTPData/temp", ErrSMTPData, true},
|
||||||
|
{"ErrSMTPData/perm", ErrSMTPData, false},
|
||||||
|
{"ErrSMTPDataClose/temp", ErrSMTPDataClose, true},
|
||||||
|
{"ErrSMTPDataClose/perm", ErrSMTPDataClose, false},
|
||||||
|
{"ErrSMTPReset/temp", ErrSMTPReset, true},
|
||||||
|
{"ErrSMTPReset/perm", ErrSMTPReset, false},
|
||||||
|
{"ErrWriteContent/temp", ErrWriteContent, true},
|
||||||
|
{"ErrWriteContent/perm", ErrWriteContent, false},
|
||||||
|
{"ErrConnCheck/temp", ErrConnCheck, true},
|
||||||
|
{"ErrConnCheck/perm", ErrConnCheck, false},
|
||||||
|
{"ErrNoUnencoded/temp", ErrNoUnencoded, true},
|
||||||
|
{"ErrNoUnencoded/perm", ErrNoUnencoded, false},
|
||||||
|
{"Unknown/temp", 9999, true},
|
||||||
|
{"Unknown/perm", 9999, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tl {
|
||||||
|
t.Run(tt.n, func(t *testing.T) {
|
||||||
|
if err := returnSendError(tt.r, tt.te); err != nil {
|
||||||
|
exp := &SendError{Reason: tt.r, isTemp: tt.te}
|
||||||
|
if !errors.Is(err, exp) {
|
||||||
|
t.Errorf("error mismatch, expected: %s (temp: %t), got: %s (temp: %t)", tt.r, tt.te,
|
||||||
|
exp.Error(), exp.isTemp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returnSendError is a helper method to retunr a SendError with a specific reason
|
||||||
|
func returnSendError(r SendErrReason, t bool) error {
|
||||||
|
return &SendError{Reason: r, isTemp: t}
|
||||||
|
}
|
Loading…
Reference in a new issue