Validate io.Write
and io.Read
calls
In Go it’s common to have an io.Writer
wrapping another io.Writer
.
The gzip.Writer
type in the standard library is a good example. Writes
to a gzip.Writer
eventually result in writes to the wrapped
io.Writer
(the one that was provided to gzip.NewWriter
).
package gzip // import "compress/gzip"
type Writer struct { ... }
func NewWriter(w io.Writer) *Writer
func (z *Writer) Write(p []byte) (int, error)
Misbehaving Writers
If your package implements an io.Writer
that wraps another io.Writer
it helps
to check for misbehaving wrapped io.Writer
s. A misbehaving io.Writer
,
for example, is one that returns a nil
error along with n <
len(p)
,1 either because it is unaware of the io.Writer
interface requirements
or because of a true logical bug in its implementation.
If your package propagates (n, err)
return values from a misbehaving
underlying io.Writer
without checking, as in:
package mypkg
type Writer struct{ w io.Writer }
func (w *Writer) Write(p []byte) (int, error) {
return w.w.Write(p)
}
then it makes it harder for clients of your package to detect and debug the
underlying misbehaving io.Writer
, because the bug is propagated instead of
being detected at the earliest. Additionally your Write
method too now
violates the io.Writer
interface.
So it helps to validate the return values of Write
calls to the underlying
io.Writer
.
Validating a Write
Validating a Write involves checking two requirements from the
io.Writer
interface.
Write returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early.
and
Write must return a non-nil error if it returns n < len(p).
func (w *Writer) Write(p []byte) (int, error) {
return validatedWrite(w.w, p)
}
func validatedWrite(w io.Writer, p []byte) (int, error) {
m, err := w.Write(p)
if m < 0 || m > len(p) {
panic("invalid Write count")
}
if m < len(p) && err == nil {
return m, io.ErrShortWrite
}
return m, err
}
Reads
The same applies to io.Readers
and Read
calls!
func validatedRead(r io.Reader, p []byte) (int, error) {
n, err := r.Read(p)
if n < 0 || n > len(p) {
panic("invalid Read count")
}
return n, err
}
-
This violates the
io.Writer
interface, whose documentation says, “Write must return a non-nil error if it returns n < len(p).” ↩