diff --git a/README.md b/README.md index 7d60e39..c24abf5 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,9 @@ Some of the features of this library: * [X] Support for common mail header field generation (Message-ID, Date, Bulk-Precedence, Priority, etc.) * [X] Reusing the same SMTP connection to send multiple mails * [X] Support for attachments and inline embeds -* [X] Support for different encodings (existing but not fully tested) +* [X] Support for different encodings * [X] Support sending mails via a local sendmail command +* [X] Message object satisfies `io.WriteTo` and `io.Reader` interfaces go-mail works like a programatic email client and provides lots of methods and functionalities you would consider standard in a MUA. diff --git a/msg.go b/msg.go index b55daa8..c20989b 100644 --- a/msg.go +++ b/msg.go @@ -552,6 +552,16 @@ func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sp string, a ...st return nil } +// Read outputs the length of p into p to satisfy the io.Reader interface +func (m *Msg) Read(p []byte) (int, error) { + wbuf := bytes.Buffer{} + _, err := m.WriteTo(&wbuf) + if err != nil { + return 0, fmt.Errorf("failed to write message to internal write buffer: %w", err) + } + return wbuf.Read(p) +} + // encodeString encodes a string based on the configured message encoder and the corresponding // charset for the Msg func (m *Msg) encodeString(s string) string { diff --git a/msg_test.go b/msg_test.go index 7387740..746e624 100644 --- a/msg_test.go +++ b/msg_test.go @@ -1272,3 +1272,69 @@ func TestMsg_multipleWrites(t *testing.T) { t.Errorf("second WriteTo() body does not contain unique string: %s", ts) } } + +// TestMsg_Read tests the Msg.Read method that implements the io.Reader interface +func TestMsg_Read(t *testing.T) { + tests := []struct { + name string + plen int + }{ + {"P length is bigger than the mail", 32000}, + {"P length is smaller than the mail", 128}, + } + + m := NewMsg() + m.SetBodyString(TypeTextPlain, "TEST123") + wbuf := bytes.Buffer{} + _, err := m.Write(&wbuf) + if err != nil { + t.Errorf("failed to write message into temporary buffer: %s", err) + } + elen := wbuf.Len() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := make([]byte, tt.plen) + n, err := m.Read(p) + if err != nil { + t.Errorf("failed to Read(): %s", err) + } + if n == 0 { + t.Errorf("failed to Read() - received 0 bytes of data") + } + if tt.plen >= elen && n != elen { + t.Errorf("failed to Read() - not all data received. Expected: %d, got: %d", elen, n) + } + if tt.plen < elen && n != tt.plen { + t.Errorf("failed to Read() - full length of p wasn't filled with data. Expected: %d, got: %d", + tt.plen, n) + } + }) + } +} + +// TestMsg_Read_ioCopy tests the Msg.Read method using io.Copy +func TestMsg_Read_ioCopy(t *testing.T) { + wbuf1 := bytes.Buffer{} + wbuf2 := bytes.Buffer{} + m := NewMsg() + m.SetBodyString(TypeTextPlain, "TEST123") + + // First we use WriteTo to have something to compare to + _, err := m.WriteTo(&wbuf1) + if err != nil { + t.Errorf("failed to write body to buffer: %s", err) + } + + // Then we write to wbuf2 via io.Copy + n, err := io.Copy(&wbuf2, m) + if err != nil { + t.Errorf("failed to use io.Copy on Msg: %s", err) + } + if n != int64(wbuf1.Len()) { + t.Errorf("message length of WriteTo and io.Copy differ. Expected: %d, got: %d", wbuf1.Len(), n) + } + if wbuf1.String() != wbuf2.String() { + t.Errorf("message content of WriteTo and io.Copy differ") + } +}