diff --git a/readcloser.go b/readcloser.go new file mode 100644 index 0000000..4bc8b05 --- /dev/null +++ b/readcloser.go @@ -0,0 +1,69 @@ +package core + +import ( + "bytes" + "io" + "io/fs" + "strings" +) + +var ( + _ io.Reader = (*ReadCloser)(nil) + _ io.Closer = (*ReadCloser)(nil) +) + +// ReadCloser adds a Close() to Readers without one +type ReadCloser struct { + r io.Reader +} + +// Read passes the Read() call to the underlying [io.Reader] +// and fail if it was Closed() +func (rc *ReadCloser) Read(b []byte) (int, error) { + switch { + case rc.r != nil: + return rc.r.Read(b) + default: + return 0, fs.ErrClosed + } +} + +// Close attempts to Close the underlying [io.Reader], or +// remove it if it doesn't support Close() and fail +// if closed twice +func (rc *ReadCloser) Close() error { + switch { + case rc.r != nil: + rc.r = nil + return nil + default: + return fs.ErrClosed + } +} + +// NewReadCloser wraps a [io.Reader] to satisfy +// [io.ReadCloser] if needed +func NewReadCloser(r io.Reader) io.ReadCloser { + switch p := r.(type) { + case io.ReadCloser: + return p + case nil: + return nil + default: + return &ReadCloser{ + r: r, + } + } +} + +// NewReadCloserBytes wraps a bytes slice to implement +// a [io.ReadCloser] +func NewReadCloserBytes(b []byte) io.ReadCloser { + return NewReadCloser(bytes.NewReader(b)) +} + +// NewReadCloserString wraps a string to implement +// a [io.ReadCloser] +func NewReadCloserString(s string) io.ReadCloser { + return NewReadCloser(strings.NewReader(s)) +}