diff --git a/herror/example_err_test.go b/herror/example_err_test.go deleted file mode 100644 index 2bee195..0000000 --- a/herror/example_err_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package herror_test - -import ( - "fmt" - - "git.sr.ht/~ewintr/go-kit/herror" -) - -var ErrTaskFailed = herror.New("task has failed") - -func step() error { - return fmt.Errorf("cannot move") -} - -func performTask() error { - if err := step(); err != nil { - return ErrTaskFailed.Wrap(err) - } - return nil -} - -func Example() { - if err := performTask(); err != nil { - fmt.Print(err) - return - } - // Output: task has failed - //-> cannot move -} diff --git a/herror/herror.go b/herror/herror.go deleted file mode 100644 index 3b7d61a..0000000 --- a/herror/herror.go +++ /dev/null @@ -1,161 +0,0 @@ -package herror - -import ( - "bytes" - "encoding/json" - "fmt" - - "github.com/davecgh/go-spew/spew" - "golang.org/x/xerrors" -) - -// Err represents an error -type Err struct { - error string - wrapped *Err - details string - stack *Stacktrace -} - -type errJSON struct { - E string `json:"error"` - W *Err `json:"wrapped"` - D string `json:"details"` - S *Stacktrace `json:"stack"` -} - -// New returns a new instance for Err type with assigned error -func New(err interface{}) *Err { - newerror := new(Err) - - switch e := err.(type) { - case string: - newerror.error = e - - case error: - if castErr, ok := e.(*Err); ok { - return castErr - } - newerror.error = e.Error() - } - - return newerror -} - -// Wrap set an error that is wrapped by Err -func Wrap(err, errwrapped error) error { - newerr := New(err) - return newerr.Wrap(errwrapped) -} - -// Unwrap returns a wrapped error if present -func Unwrap(err error) error { - return xerrors.Unwrap(err) -} - -// Is reports whether any error in err's chain matches target. -func Is(err, target error) bool { - return xerrors.Is(err, target) -} - -// Wrap set an error that is wrapped by Err -func (e *Err) Wrap(err error) *Err { - wrapped := New(err) - - if deeper := xerrors.Unwrap(err); deeper != nil { - Wrap(wrapped, deeper) - } - - newerr := &Err{ - error: e.error, - wrapped: e.wrapped, - details: e.details, - stack: e.stack, - } - newerr.wrapped = wrapped - return newerr -} - -// Unwrap returns wrapped error -func (e *Err) Unwrap() error { - if e.wrapped == nil { - return nil - } - return e.wrapped -} - -// Is reports whether an error matches. -func (e *Err) Is(err error) bool { - if e.wrapped != nil { - return e.error == err.Error() || e.wrapped.Is(err) - } - return e.error == err.Error() -} - -// CaptureStack sets stack traces when the method is called -func (e *Err) CaptureStack() *Err { - e.stack = NewStacktrace() - return e -} - -// Stack returns full stack traces -func (e *Err) Stack() *Stacktrace { - return e.stack -} - -// AddDetails is a alias for AppendDetail [DEPRECATED] -func (e *Err) AddDetails(v ...interface{}) *Err { - return e.AppendDetails(v...) -} - -// AppendDetails appends formated variable information at the end of a text -// field in the error mostly for debugging purposes. -func (e *Err) AppendDetails(v ...interface{}) *Err { - buff := new(bytes.Buffer) - fmt.Fprintln(buff, e.details) - spew.Fdump(buff, v...) - e.details = buff.String() - - return e -} - -// Details returns error's details -func (e *Err) Details() string { - return e.details -} - -// Errors return a composed message of the assigned error e wrapped error -func (e *Err) Error() string { - if e.wrapped == nil { - return e.error - } - - return fmt.Sprintf("%s\n-> %s", e.error, e.wrapped.Error()) -} - -// UnmarshalJSON -func (e *Err) UnmarshalJSON(b []byte) error { - var errJSON errJSON - if err := json.Unmarshal(b, &errJSON); err != nil { - return err - } - - *e = Err{ - error: errJSON.E, - wrapped: errJSON.W, - details: errJSON.D, - stack: errJSON.S, - } - - return nil -} - -// MarshalJSON -func (e *Err) MarshalJSON() ([]byte, error) { - return json.Marshal(errJSON{ - E: e.error, - W: e.wrapped, - D: e.details, - S: e.stack, - }) -} diff --git a/herror/herror_test.go b/herror/herror_test.go deleted file mode 100644 index 101f53c..0000000 --- a/herror/herror_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package herror_test - -import ( - "encoding/json" - "fmt" - "testing" - - "git.sr.ht/~ewintr/go-kit/herror" - "git.sr.ht/~ewintr/go-kit/test" -) - -func TestHError(t *testing.T) { - - t.Run("new error", func(t *testing.T) { - errDefault := "this is an error" - for _, tc := range []struct { - m string - input interface{} - expected string - }{ - { - m: "empty", - }, - { - m: "string", - input: errDefault, - expected: errDefault, - }, - { - m: "error", - input: fmt.Errorf(errDefault), - expected: errDefault, - }, - { - m: "herror.Err", - input: herror.New(errDefault), - expected: errDefault, - }, - { - m: "invalid type", - input: 123456789, - expected: "", - }, - } { - t.Run(tc.m, func(t *testing.T) { - test.Equals(t, tc.expected, herror.New(tc.input).Error()) - }) - } - }) - - t.Run("wrap", func(t *testing.T) { - errmain := herror.New("MAIN ERROR") - errfmt := fmt.Errorf("ERROR FORMATTED") - errA := herror.New("ERR A") - errB := herror.New("ERR B") - errC := herror.New("ERR C") - errD := herror.New("ERR D") - errNested := errmain.Wrap( - errA.Wrap( - errB.Wrap( - errC.Wrap(errD), - ), - ), - ) - - for _, tc := range []struct { - m string - err error - expected []error - }{ - { - m: "error", - err: errfmt, - expected: []error{ - errfmt, - }, - }, - { - m: "deeper nested wrap", - err: errNested, - expected: []error{ - errA, errB, errC, errD, - }, - }, - } { - t.Run(tc.m, func(t *testing.T) { - newerr := errmain.Wrap(tc.err) - - for _, e := range tc.expected { - test.Equals(t, true, newerr.Is(e)) - } - }) - } - }) - - t.Run("json marshalling", func(t *testing.T) { - hError := herror.New("this is an error"). - Wrap(fmt.Errorf("this is another error")). - CaptureStack() - marshalled, err := json.Marshal(hError) - test.OK(t, err) - - var unmarshalled *herror.Err - test.OK(t, json.Unmarshal(marshalled, &unmarshalled)) - test.Equals(t, hError, unmarshalled) - }) -} - -func ExampleErr_Wrap() { - errA := herror.New("something went wrong") - errB := fmt.Errorf("because of this error") - newerr := herror.Wrap(errA, errB) - - fmt.Print(herror.Unwrap(newerr), "\n", newerr) - // Output: because of this error - // something went wrong - // -> because of this error -} - -func ExampleErr_Is() { - errA := herror.New("something went wrong") - errB := func() error { - return errA - }() - - fmt.Print(herror.Is(errA, errB)) - // Output: true -} - -func ExampleErr_CaptureStack() { - err := herror.New("something went wrong") - err.CaptureStack() - - fmt.Print(err, "\n", err.Stack().Frames[2].Function) - // Output: something went wrong - // ExampleErr_CaptureStack -} - -func ExampleErr_AddDetails() { - err := herror.New("something went wrong") - err.AddDetails(struct { - number int - }{123}) - - fmt.Print(err, err.Details()) - // Output: something went wrong - // (struct { number int }) { - // number: (int) 123 - // } -} diff --git a/herror/stacktrace.go b/herror/stacktrace.go deleted file mode 100644 index 6483e84..0000000 --- a/herror/stacktrace.go +++ /dev/null @@ -1,151 +0,0 @@ -package herror - -import ( - "fmt" - "go/build" - "path/filepath" - "runtime" - "strings" -) - -// Stacktrace holds information about the frames of the stack. -type Stacktrace struct { - Frames []Frame `json:"frames,omitempty"` -} - -// Frame represents parsed information from runtime.Frame -type Frame struct { - Function string `json:"function,omitempty"` - Type string `json:"type,omitempty"` - Package string `json:"package,omitempty"` - Filename string `json:"filename,omitempty"` - AbsPath string `json:"abs_path,omitempty"` - Line int `json:"line,omitempty"` - InApp bool `json:"in_app,omitempty"` -} - -// FrameFilter represents function to filter frames -type FrameFilter func(Frame) bool - -const unknown string = "unknown" - -// NewStacktrace creates a stacktrace using `runtime.Callers`. -func NewStacktrace(filters ...FrameFilter) *Stacktrace { - pcs := make([]uintptr, 100) - n := runtime.Callers(1, pcs) - - if n == 0 { - return nil - } - frames := extractFrames(pcs[:n]) - - // default filter - frames = filterFrames(frames, func(f Frame) bool { - return f.Package == "runtime" || f.Package == "testing" || - strings.HasSuffix(f.Package, "/herror") - }) - - for _, filter := range filters { - frames = filterFrames(frames, filter) - } - - stacktrace := Stacktrace{ - Frames: frames, - } - - return &stacktrace -} - -// NewFrame assembles a stacktrace frame out of `runtime.Frame`. -func NewFrame(f runtime.Frame) Frame { - abspath := unknown - filename := unknown - if f.File != "" { - abspath = f.File - _, filename = filepath.Split(f.File) - } - - function := unknown - pkgname := unknown - typer := "" - if f.Function != "" { - pkgname, typer, function = deconstructFunctionName(f.Function) - } - - inApp := func() bool { - out := strings.HasPrefix(abspath, build.Default.GOROOT) || - strings.Contains(pkgname, "vendor") - return !out - }() - - return Frame{ - AbsPath: abspath, - Filename: filename, - Line: f.Line, - Package: pkgname, - Type: typer, - Function: function, - InApp: inApp, - } -} - -func filterFrames(frames []Frame, filter FrameFilter) []Frame { - filtered := make([]Frame, 0, len(frames)) - - for _, frame := range frames { - if filter(frame) { - continue - } - filtered = append(filtered, frame) - } - - return filtered -} - -func extractFrames(pcs []uintptr) []Frame { - frames := make([]Frame, 0, len(pcs)) - callersFrames := runtime.CallersFrames(pcs) - - for { - callerFrame, more := callersFrames.Next() - frames = append([]Frame{ - NewFrame(callerFrame), - }, frames...) - - if !more { - break - } - } - - return frames -} - -func deconstructFunctionName(name string) (pkg string, typer string, function string) { - if i := strings.LastIndex(name, "/"); i != -1 { - pkg = name[:i] - function = name[i+1:] - - if d := strings.Index(function, "."); d != -1 { - pkg = fmt.Sprint(pkg, "/", function[:d]) - function = function[d+1:] - } - - if o, c := strings.LastIndex(name, ".("), strings.LastIndex(name, ")."); o != -1 && c != -1 { - pkg = name[:o] - function = name[c+2:] - - typer = name[o+2 : c] - if i := strings.Index(typer, "*"); i != -1 { - typer = typer[1:] - } - } - return - } - - if i := strings.LastIndex(name, "."); i != -1 { - pkg = name[:i] - function = name[i+1:] - } - - return -} diff --git a/herror/stacktrace_test.go b/herror/stacktrace_test.go deleted file mode 100644 index 8306c7c..0000000 --- a/herror/stacktrace_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package herror_test - -import ( - "runtime" - "testing" - - "git.sr.ht/~ewintr/go-kit/herror" - "git.sr.ht/~ewintr/go-kit/test" -) - -func trace() *herror.Stacktrace { - return herror.NewStacktrace() -} - -func traceStepIn(f []herror.FrameFilter) *herror.Stacktrace { - return traceWithFilter(f) -} - -func traceWithFilter(f []herror.FrameFilter) *herror.Stacktrace { - return herror.NewStacktrace(f...) -} - -func TestStacktrace(t *testing.T) { - t.Run("new", func(t *testing.T) { - stack := trace() - - expectedFrames := []herror.Frame{ - herror.Frame{ - Function: "TestStacktrace.func1", - }, - herror.Frame{ - Function: "trace", - }, - } - - test.Equals(t, len(expectedFrames), len(stack.Frames)) - for i, frame := range expectedFrames { - test.Equals(t, frame.Function, stack.Frames[i].Function) - test.Equals(t, "git.sr.ht/~ewintr/go-kit/herror_test", stack.Frames[i].Package) - test.Equals(t, "stacktrace_test.go", stack.Frames[i].Filename) - } - }) - - t.Run("filter frames", func(t *testing.T) { - - for _, tc := range []struct { - m string - filters []herror.FrameFilter - expected []herror.Frame - }{ - { - m: "no filter", - expected: []herror.Frame{ - herror.Frame{ - Function: "TestStacktrace.func2", - }, - herror.Frame{ - Function: "traceStepIn", - }, - herror.Frame{ - Function: "traceWithFilter", - }, - }, - }, - { - m: "single filter", - expected: []herror.Frame{ - herror.Frame{ - Function: "traceStepIn", - }, - herror.Frame{ - Function: "traceWithFilter", - }, - }, - filters: []herror.FrameFilter{ - func(f herror.Frame) bool { - return f.Function == "TestStacktrace.func2" - }, - }, - }, - { - m: "multiple filters", - expected: []herror.Frame{ - herror.Frame{ - Function: "traceWithFilter", - }, - }, - filters: []herror.FrameFilter{ - func(f herror.Frame) bool { - return f.Function == "TestStacktrace.func2" - }, - func(f herror.Frame) bool { - return f.Function == "traceStepIn" - }, - }, - }, - } { - stack := traceStepIn(tc.filters) - - t.Run(tc.m, func(t *testing.T) { - test.Equals(t, len(tc.expected), len(stack.Frames)) - - for i, frame := range tc.expected { - test.Equals(t, frame.Function, stack.Frames[i].Function) - test.Equals(t, "git.sr.ht/~ewintr/go-kit/herror_test", stack.Frames[i].Package) - test.Equals(t, "stacktrace_test.go", stack.Frames[i].Filename) - } - }) - } - }) -} - -func TestFrame(t *testing.T) { - t.Run("new", func(t *testing.T) { - f := func() herror.Frame { - pc := make([]uintptr, 1) - n := runtime.Callers(0, pc) - test.Assert(t, n == 1, "expected available pcs") - - frames := runtime.CallersFrames(pc) - runtimeframe, _ := frames.Next() - return herror.NewFrame(runtimeframe) - } - - frame := f() - test.Equals(t, "Callers", frame.Function) - test.Equals(t, "runtime", frame.Package) - test.Equals(t, "extern.go", frame.Filename) - }) -}