e4

package module
v0.0.0-...-9eab321 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 24, 2022 License: Apache-2.0 Imports: 10 Imported by: 25

README

e4

Error handling utilities

Features

  • Ad-hoc error wrapping
    • easy to chain multiple error values
    • no need to implement Unwrap / Is / As for every error type
    • inspect error hierarchy with standard errors.Is / As
  • Alternatives to if err != nil { return ... } statement
    • utilizing panic / recover
    • but not crossing the function boundary
    • not forced to use, play well with existing codes

CopyFile demo

handling errors with Check and Handle:

package main

import (
	"errors"
	"io"
	"io/fs"
	"os"

	"github.com/reusee/e4"
)

var (
	// ergonomic aliases
	check, handle = e4.Check, e4.Handle
)

func CopyFile(src, dst string) (err error) {
	defer handle(&err,
		e4.Info("copy %s to %s", src, dst),
	)

	r, err := os.Open(src)
	check(err)
	defer r.Close()

	w, err := os.Create(dst)
	check(err)
	defer handle(&err,
		e4.Close(w),
		e4.Do(func() {
			os.Remove(dst)
		}),
	)

	_, err = io.Copy(w, r)
	check(err)

	check(w.Close())

	return
}

func main() {

	err := CopyFile("demo.go", "/")

	var pathError *fs.PathError
	if !errors.As(err, &pathError) {
		panic("should be path error")
	}

	check(err)

}

handling errors with Wrap

package main

import (
	"errors"
	"io"
	"io/fs"
	"os"

	"github.com/reusee/e4"
)

func CopyFile(src, dst string) (err error) {
	wrap := e4.Wrap.With(
		e4.WrapStacktrace,
		e4.Info("copy %s to %s", src, dst),
	)

	r, err := os.Open(src)
	if err != nil {
		return wrap(err)
	}
	defer r.Close()

	w, err := os.Create(dst)
	if err != nil {
		return wrap(err)
	}
	wrap = wrap.With(
		e4.Close(w),
		e4.Do(func() {
			os.Remove(dst)
		}),
	)

	_, err = io.Copy(w, r)
	if err != nil {
		return wrap(err)
	}

	if err := w.Close(); err != nil {
		return wrap(err)
	}

	return
}

func main() {

	err := CopyFile("demo.go", "/")

	var pathError *fs.PathError
	if !errors.As(err, &pathError) {
		panic("should be path error")
	}

	if err != nil {
		panic(err)
	}

}

Usages

Error wrapping

errors can be wrapped with e4.Wrap and various util functions.

err := foo()

if err != nil {
  return e4.Wrap.With(

    // wrap another error value
    io.ErrUnexpectedEOF,

    // wrap a lazy-formatted message
    e4.Info("unexpected %s", "EOF"),

    // close a io.Closer
    e4.Close(w),

    // do something
    e4.Do(func() {
      fmt.Printf("error occur\n")
    }),

    // cumstom wrap function
    func(err error) error {
      fmt.Printf("wrap EOF\n")
      return e4.MakeErr(err, io.EOF)
    },

  )(err)
}

wrapped errors can be inspected with errors.Is or errors.As

errFoo := errors.New("foo")

err := e4.Wrap.With(
  io.ErrUnexpectedEOF,
  errFoo,
  new(fs.PathError),
  // wrap a nested error
  e4.Wrap.With(
    fs.ErrInvalid,
    e4.Wrap.With(
      io.ErrClosedPipe,
      io.ErrShortWrite,
    ),
  ),
)(io.EOF)

errors.Is(err, io.EOF) // true
errors.Is(err, io.ErrUnexpectedEOF) // true
errors.Is(err, io.ErrShortWrite) // true for deeply nested values
var pathError *fs.PathError
errors.As(err, &pathError) // true
Alternatives to if and return statement

error values can be thrown and catch

func foo() (err error) {
  defer e4.Handle(&err)
  if err := bar(); err != nil {
    e4.Throw(err)
  }
}

Further, if and e4.Throw can be replaced with e4.Check

func foo() (err error) {
  defer e4.Handle(&err)
  e4.Check(bar())
}

Error wrapping also works in the Check site or the Handle site

func foo() (err error) {
  defer e4.Handle(&err,
    fmt.Errorf("foo error"),
    e4.Do(func() {
      fmt.Printf("foo error\n")
    }),
  )
  e4.Check(bar(),
    fmt.Errorf("bar error"),
    // wrap stack trace
    e4.WrapStacktrace,
    // ignore errors that errors.Is return true
    e4.Ignore(io.EOF),
    // ignore errors that errors.As return true
    e4.IgnoreAs(new(*fs.PathError)),
    // ignore errors that Error() contains specific string
    e4.IgnoreContains("EOF"),
  )
}
Check with stacktrace

Errors checked by e4.CheckWithStacktrace are implicitly wrapped by stacktrace.

func foo() (err error) {
  defer e4.Handle(&err)
  e4.CheckWithStacktrace(io.EOF)
}

err := foo()

var trace *e4.Stacktrace
errors.As(err, &trace) // true

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Check = CheckFunc(func(err error, args ...error) error {
	if err == nil {
		return nil
	}
	err = Wrap.With(args...)(err)
	return Throw(err)
})

Check is for error checking. if err is not nil, it will be wrapped by DefaultWrap then raised by Throw

View Source
var CheckWithStacktrace = Check.With(WrapStacktrace)
View Source
var ErrorLevel = InfoLevel
View Source
var Fatal = Must
View Source
var NewDebug = Debug
View Source
var NewInfo = Info
View Source
var Wrap = WrapFunc(func(err error) error {
	return err
})
View Source
var WrapStacktrace = WrapFunc(func(prev error) error {
	if prev == nil {
		return nil
	}
	if stacktraceIncluded(prev) {
		return prev
	}

	stacktrace := new(Stacktrace)
	v, put := pcsPool.Get()
	defer put()
	pcs := *(v.(*[]uintptr))
	skip := 1
	for {
		n := runtime.Callers(skip, pcs)
		if n == 0 {
			break
		}
		for i := 0; i < n; i++ {
			var slice []Frame
			frames := runtime.CallersFrames(pcs[i : i+1])
			for {
				skip++
				frame, more := frames.Next()
				if strings.HasPrefix(frame.Function, "github.com/reusee/e4.") &&
					!strings.HasPrefix(frame.Function, "github.com/reusee/e4.Test") {

					if !more {
						break
					}
					continue
				}
				dir, file := filepath.Split(frame.File)
				mod, fn := path.Split(frame.Function)
				if i := strings.Index(dir, mod); i > 0 {
					dir = dir[i:]
				}
				pkg := fn[:strings.IndexByte(fn, '.')]
				pkgPath := mod + pkg
				f := Frame{
					File:     file,
					Dir:      dir,
					Line:     frame.Line,
					Pkg:      pkg,
					Function: fn,
					PkgPath:  pkgPath,
				}
				slice = append(slice, f)
				if !more {
					break
				}
			}
			stacktrace.Frames = append(stacktrace.Frames, slice...)
		}
		if n < len(pcs) {
			break
		}
	}

	err := MakeErr(stacktrace, prev)
	err.flag |= flagStacktraceIncluded
	return err
})

WrapStacktrace wraps current stacktrace

View Source
var WrapWithStacktrace = Wrap.With(WrapStacktrace)

Functions

func Handle

func Handle(errp *error, args ...error)

Handle is for error handling

Error raised by Throw will be catched if any. If errp point to non-nil error, the error will be chained. If the result error is not nil, wrap functions will be applied. The result error will be assigned to errp if errp is not nil, otherwise Throw will be raised.

func Must

func Must(err error, args ...error) error

Must checks the error, if not nil, raise a panic which will not be catched by Handle

func TestWrapFunc

func TestWrapFunc(t *testing.T, fn WrapFunc)

TestWrapFunc tests a WrapFunc instance

func Throw

func Throw(err error, args ...error) error

Throw checks the error and if not nil, raise a panic which will be recovered by Handle

func Try

func Try[R any](r R, err error) func(fn CheckFunc) R

func Try2

func Try2[R, R2 any](r R, r2 R2, err error) func(fn CheckFunc) (R, R2)

func Try3

func Try3[R, R2, R3 any](r R, r2 R2, r3 R3, err error) func(fn CheckFunc) (R, R2, R3)

func Try4

func Try4[R, R2, R3, R4 any](r R, r2 R2, r3 R3, r4 R4, err error) func(fn CheckFunc) (R, R2, R3, R4)

Types

type CheckFunc

type CheckFunc func(err error, args ...error) error

CheckFunc is the type of Check, see Check's doc for details

func (CheckFunc) With

func (c CheckFunc) With(moreArgs ...error) CheckFunc

With returns a new CheckFunc that do additional wrapping with moreWraps

type ErrDebug

type ErrDebug struct {
	*ErrInfo
}

func (ErrDebug) ErrorLevel

func (d ErrDebug) ErrorLevel() Level

type ErrInfo

type ErrInfo struct {
	// contains filtered or unexported fields
}

ErrInfo represents a lazy-evaluaed formatted string

func (*ErrInfo) Error

func (i *ErrInfo) Error() string

Error implements error interface

type Error

type Error struct {
	Err  error
	Prev error
	// contains filtered or unexported fields
}

Error represents a chain of errors

func MakeErr

func MakeErr(err error, prev error) Error

MakeErr creates an Error with internal manipulations. It's safe to construct Error without calling this function but not encouraged

func (Error) As

func (c Error) As(target interface{}) bool

As reports whether any error in the chain matches target. And if so, assign the first matching error to target

func (Error) Error

func (c Error) Error() string

Error implements error interface

func (Error) Is

func (c Error) Is(target error) bool

Is reports whether any error in the chain matches target

func (Error) Unwrap

func (c Error) Unwrap() error

Unwrap returns Prev error

type ErrorLeveler

type ErrorLeveler interface {
	ErrorLevel() Level
}

type Frame

type Frame struct {
	File     string
	Dir      string
	Pkg      string
	Function string
	Line     int
	PkgPath  string
}

Frame represents a call frame

type Level

type Level int
const (
	CriticalLevel Level = iota + 1
	InfoLevel
	DebugLevel
)

type Stacktrace

type Stacktrace struct {
	Frames []Frame
}

Stacktrace represents call stack frames

func (*Stacktrace) Error

func (s *Stacktrace) Error() string

Error implements error interface

type WrapFunc

type WrapFunc func(err error) error

WrapFunc wraps an error to form a chain.

Instances must follow these rules: if argument is nil, return value must be nil

func Close

func Close(c io.Closer) WrapFunc

Close returns a WrapFunc that closes the Closer

func Debug

func Debug(format string, args ...any) WrapFunc

func Do

func Do(fn func()) WrapFunc

Do returns a WrapFunc that calls fn

func DropFrame

func DropFrame(fn func(Frame) bool) WrapFunc

DropFrame returns a WrapFunc that drop Frames matching fn. If there is no existed stacktrace in chain, a new one will be created

func Ignore

func Ignore(err error) WrapFunc

Ignore returns a WrapFunc that returns nil if errors.Is(prev, err) is true

func IgnoreAs

func IgnoreAs(target any) WrapFunc

IgnoreAs returns a WrapFunc that returns nil if errors.As(prev, target) is true

func IgnoreContains

func IgnoreContains(str string) WrapFunc

IgnoreContains returns a WrapFunc that returns nil if prev.Error() contains str

func Info

func Info(format string, args ...any) WrapFunc

Info returns a WrapFunc that wraps an *ErrInfo error value

func TestingFatal

func TestingFatal(t *testing.T) WrapFunc

TestingFatal returns a WrapFunc that calls t.Fatal if error occur

func With

func With(err error) WrapFunc

With returns a WrapFunc that wraps an error value

func (WrapFunc) Assign

func (w WrapFunc) Assign(p *error)

func (WrapFunc) Error

func (w WrapFunc) Error() string

func (WrapFunc) With

func (w WrapFunc) With(args ...error) WrapFunc

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL