We now return a Reader instead of the io.Reader interface

This commit is contained in:
Winni Neessen 2022-10-20 18:03:57 +02:00
parent 4a309dc73a
commit 5faa6dfbd6
Signed by: wneessen
GPG key ID: 385AC9889632126E
3 changed files with 111 additions and 18 deletions

29
msg.go
View file

@ -807,24 +807,31 @@ func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sp string, a ...st
return nil
}
// NewReader returns a Msg reader that satisfies the io.Reader interface.
// **Please note:** when creating a new reader, the current state of the Msg is taken, as
// basis for the reader. If you perform changes on Msg after creating the reader, you need
// to perform a call to Msg.UpdateReader first
func (m *Msg) NewReader() io.Reader {
// NewReader returns a Reader type that satisfies the io.Reader interface.
//
// IMPORTANT: when creating a new Reader, the current state of the Msg is taken, as
// basis for the Reader. If you perform changes on Msg after creating the Reader, these
// changes will not be reflected in the Reader. You will have to use Msg.UpdateReader
// first to update the Reader's buffer with the current Msg content
func (m *Msg) NewReader() *Reader {
r := &Reader{}
wbuf := bytes.Buffer{}
_, _ = m.Write(&wbuf)
r := &reader{buf: wbuf.Bytes()}
_, err := m.Write(&wbuf)
if err != nil {
r.err = fmt.Errorf("failed to write Msg to Reader buffer: %w", err)
}
r.buf = wbuf.Bytes()
return r
}
// UpdateReader will update a reader with the content of the current Msg and reset the reader position
// to the start
func (m *Msg) UpdateReader(r *reader) {
// UpdateReader will update a Reader with the content of the given Msg and reset the
// Reader position to the start
func (m *Msg) UpdateReader(r *Reader) {
wbuf := bytes.Buffer{}
_, _ = m.Write(&wbuf)
_, err := m.Write(&wbuf)
r.Reset()
r.buf = wbuf.Bytes()
r.err = err
}
// Read outputs the length of p into p to satisfy the io.Reader interface

View file

@ -1784,6 +1784,86 @@ func TestMsg_Read_ioCopy(t *testing.T) {
}
}
// TestMsg_NewReader tests the Msg.NewReader method
func TestMsg_NewReader(t *testing.T) {
m := NewMsg()
m.SetBodyString(TypeTextPlain, "TEST123")
mr := m.NewReader()
if mr == nil {
t.Errorf("NewReader failed: Reader is nil")
}
}
// TestMsg_NewReader_broken tests the Msg.NewReader method error handling
func TestMsg_NewReader_broken(t *testing.T) {
m := &Msg{}
mr := m.NewReader()
if mr == nil {
t.Errorf("NewReader failed: Reader is nil")
}
}
// TestMsg_NewReader_ioCopy tests the Msg.NewReader method using io.Copy
func TestMsg_NewReader_ioCopy(t *testing.T) {
wbuf1 := bytes.Buffer{}
wbuf2 := bytes.Buffer{}
m := NewMsg()
m.SetBodyString(TypeTextPlain, "TEST123")
mr := m.NewReader()
if mr == nil {
t.Errorf("NewReader failed: Reader is nil")
}
// 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, mr)
if err != nil {
t.Errorf("failed to use io.Copy on Reader: %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")
}
}
// TestMsg_UpdateReader tests the Msg.UpdateReader method
func TestMsg_UpdateReader(t *testing.T) {
m := NewMsg()
m.Subject("Subject-Run 1")
mr := m.NewReader()
if mr == nil {
t.Errorf("NewReader failed: Reader is nil")
}
wbuf1 := bytes.Buffer{}
_, err := io.Copy(&wbuf1, mr)
if err != nil {
t.Errorf("io.Copy on Reader failed: %s", err)
}
if !strings.Contains(wbuf1.String(), "Subject: Subject-Run 1") {
t.Errorf("io.Copy on Reader failed. Expected to find %q but string in Subject was not found",
"Subject-Run 1")
}
m.Subject("Subject-Run 2")
m.UpdateReader(mr)
wbuf2 := bytes.Buffer{}
_, err = io.Copy(&wbuf2, mr)
if err != nil {
t.Errorf("2nd io.Copy on Reader failed: %s", err)
}
if !strings.Contains(wbuf2.String(), "Subject: Subject-Run 2") {
t.Errorf("io.Copy on Reader failed. Expected to find %q but string in Subject was not found",
"Subject-Run 2")
}
}
// TestMsg_SetBodyTextTemplate tests the Msg.SetBodyTextTemplate method
func TestMsg_SetBodyTextTemplate(t *testing.T) {
tests := []struct {

View file

@ -4,16 +4,22 @@
package mail
import "io"
import (
"io"
)
// reader is a type that implements the io.Reader interface for a Msg
type reader struct {
// Reader is a type that implements the io.Reader interface for a Msg
type Reader struct {
buf []byte // contents are the bytes buf[off : len(buf)]
off int // read at &buf[off], write at &buf[len(buf)]
err error // initalization error
}
// Read reads the length of p of the Msg buffer to satisfy the io.Reader interface
func (r *reader) Read(p []byte) (n int, err error) {
func (r *Reader) Read(p []byte) (n int, err error) {
if r.err != nil {
return 0, r.err
}
if r.empty() {
r.Reset()
if len(p) == 0 {
@ -23,15 +29,15 @@ func (r *reader) Read(p []byte) (n int, err error) {
}
n = copy(p, r.buf[r.off:])
r.off += n
return n, nil
return n, err
}
// Reset resets the Reader buffer to be empty, but it retains the underlying storage
// for use by future writes.
func (r *reader) Reset() {
func (r *Reader) Reset() {
r.buf = r.buf[:0]
r.off = 0
}
// empty reports whether the unread portion of the Reader buffer is empty.
func (r *reader) empty() bool { return len(r.buf) <= r.off }
func (r *Reader) empty() bool { return len(r.buf) <= r.off }