errors - errors.go

// Package errors is a simple package with a few error related types.
package errors // import "vimagination.zapto.org/errors"

import "runtime"

// Error represents a constant error string.
type Error string

// New returns an error that returns the given string.
func New(str string) error {
	return Error(str)
}

// Error returns the error string.
func (e Error) Error() string {
	return string(e)
}

// Call represents where a particular error was created.
type Call struct {
	File, Function string
	LineNum        int
}

// String returns a human-friendly representation of the call site.
func (c Call) String() string {
	return c.File + ": (" + string(itobs(c.LineNum)) + ")" + c.Function
}

// Trace represents the call stack for an error.
type Trace struct {
	error
	Traces []Call
}

// AddTrace wraps an error with a call stack.
func AddTrace(e error) error {
	var trace [100]uintptr

	num := runtime.Callers(2, trace[:])
	traces := make([]Call, num)

	for i := 0; i < num; i++ {
		f := runtime.FuncForPC(trace[i])
		file, ln := f.FileLine(trace[i])
		traces[i] = Call{
			File:     file,
			Function: f.Name(),
			LineNum:  ln,
		}
	}

	return &Trace{
		error:  e,
		Traces: traces,
	}
}

// Trace returns a byte slice containing a description of the call stack.
func (t Trace) Trace() []byte {
	var buf []byte

	for _, c := range t.Traces {
		buf = append(buf, c.File...)
		buf = append(buf, ':', ' ', '(')
		buf = append(buf, itobs(c.LineNum)...)
		buf = append(buf, ')', ' ')
		buf = append(buf, c.Function...)
		buf = append(buf, '\n')
	}

	return buf
}

// Wrapper is used to get the underlying error for a wrapped error.
type Wrapper interface {
	Unwrap() error
}

// Unwrap repeatedly checks for an underlying error to returned the original
// wrapped error.
func Unwrap(err error) error {
	for {
		if err == nil {
			return nil
		}

		u, ok := err.(Wrapper)
		if !ok {
			return err
		}

		err = u.Unwrap()
	}
}

// Unwrap returns the underlying error.
func (t Trace) Unwrap() error {
	if u, ok := t.error.(Wrapper); ok {
		return u.Unwrap()
	}

	return nil
}

func itobs(i int) []byte {
	if i == 0 {
		return []byte{'0'}
	}

	neg := false

	if i < 0 {
		neg = true
		i = -i
	}

	pos := 21

	var b [21]byte

	for ; i > 0; i /= 10 {
		pos--
		b[pos] = byte(i%10) + '0'
	}

	if neg {
		pos--
		b[pos] = '-'
	}

	return b[pos:]
}

type contextual struct {
	context string
	error
}

// WithContext wraps an error, adding textural context to the error message.
//
// The wrapped error can be accessed via the Unwrap method.
//
// A nil error will not be wrapped.
func WithContext(context string, err error) error {
	if err == nil {
		return nil
	}

	return &contextual{
		context: context,
		error:   err,
	}
}

func (c *contextual) Error() string {
	return c.context + c.error.Error()
}

func (c *contextual) Unwrap() error {
	return c.error
}