Skip to content
Permalink
Browse files

Error struct replaced with interface

  • Loading branch information...
ztrue committed Mar 15, 2019
1 parent 26e5523 commit 088bd7ebe18f7e696c81f6f3397b1d21a6834a9a
Showing with 176 additions and 72 deletions.
  1. +10 −0 CHANGELOG.md
  2. +5 −5 README.md
  3. +36 −28 error.go
  4. +1 −1 error_bench_test.go
  5. +3 −3 error_helper_test.go
  6. +71 −4 error_test.go
  7. +19 −0 examples/nil_error.go
  8. +1 −1 print.go
  9. +30 −30 print_test.go
@@ -1,5 +1,15 @@
# Changelog

## [0.3.0] - 2019-03-15

### Added

- `tracerr.CustomError()` that allows to create error with custom stack trace.

### Changed

- `*tracerr.Error` struct replaced with `tracerr.Error` interface.

## [0.2.1] - 2019-02-16

### Added
@@ -69,7 +69,7 @@ err = tracerr.Wrap(err)

### Print Error and Stack Trace

> Stack trace will be printed only if `err` is of type `*tracerr.Error`, otherwise just error text will be shown.
> Stack trace will be printed only if `err` is of type `tracerr.Error`, otherwise just error text will be shown.
This will print error message and stack trace if any:

@@ -131,27 +131,27 @@ text := tracerr.SprintSource(err, 5, 2)

### Get Stack Trace

> Stack trace will be empty if `err` is not an instance of `*tracerr.Error`.
> Stack trace will be empty if `err` is not an instance of `tracerr.Error`.
```go
frames := tracerr.StackTrace(err)
```

Or if `err` is of type `*tracerr.Error`:
Or if `err` is of type `tracerr.Error`:

```go
frames := err.StackTrace()
```

### Get Original Error

> Unwrapped error will be `nil` if `err` is `nil` and will be the same error if `err` is not an instance of `*tracerr.Error`.
> Unwrapped error will be `nil` if `err` is `nil` and will be the same error if `err` is not an instance of `tracerr.Error`.
```go
err = tracerr.Unwrap(err)
```

Or if `err` is of type `*tracerr.Error`:
Or if `err` is of type `tracerr.Error`:

```go
err = err.Unwrap()
@@ -15,30 +15,44 @@ import (
var DefaultCap = 20

// Error is an error with stack trace.
type Error struct {
// Error contains original error.
Err error
// Frames contains stack trace of an error.
Frames []Frame
type Error interface {
Error() string
StackTrace() []Frame
Unwrap() error
}

type errorData struct {
// err contains original error.
err error
// frames contains stack trace of an error.
frames []Frame
}

// CustomError creates an error with provided frames.
func CustomError(err error, frames []Frame) Error {
return &errorData{
err: err,
frames: frames,
}
}

// Errorf creates new error with stacktrace and formatted message.
// Formatting works the same way as in fmt.Errorf.
func Errorf(message string, args ...interface{}) *Error {
func Errorf(message string, args ...interface{}) Error {
return trace(fmt.Errorf(message, args...), 2)
}

// New creates new error with stacktrace.
func New(message string) *Error {
func New(message string) Error {
return trace(fmt.Errorf(message), 2)
}

// Wrap adds stacktrace to existing error.
func Wrap(err error) *Error {
func Wrap(err error) Error {
if err == nil {
return nil
}
e, ok := err.(*Error)
e, ok := err.(Error)
if ok {
return e
}
@@ -50,32 +64,26 @@ func Unwrap(err error) error {
if err == nil {
return nil
}
e, ok := err.(*Error)
e, ok := err.(Error)
if !ok {
return err
}
return e.Unwrap()
}

// Error returns error message.
func (e *Error) Error() string {
if e == nil {
return ""
}
return e.Err.Error()
func (e *errorData) Error() string {
return e.err.Error()
}

// StackTrace returns stack trace of an error.
func (e *Error) StackTrace() []Frame {
if e == nil {
return nil
}
return e.Frames
func (e *errorData) StackTrace() []Frame {
return e.frames
}

// Unwrap returns the original error.
func (e *Error) Unwrap() error {
return e.Err
func (e *errorData) Unwrap() error {
return e.err
}

// Frame is a single step in stack trace.
@@ -89,9 +97,9 @@ type Frame struct {
}

// StackTrace returns stack trace of an error.
// It will be empty if err is not of type *Error.
// It will be empty if err is not of type Error.
func StackTrace(err error) []Frame {
e, ok := err.(*Error)
e, ok := err.(Error)
if !ok {
return nil
}
@@ -103,7 +111,7 @@ func (f Frame) String() string {
return fmt.Sprintf("%s:%d %s()", f.Path, f.Line, f.Func)
}

func trace(err error, skip int) *Error {
func trace(err error, skip int) Error {
frames := make([]Frame, 0, DefaultCap)
for {
pc, path, line, ok := runtime.Caller(skip)
@@ -119,8 +127,8 @@ func trace(err error, skip int) *Error {
frames = append(frames, frame)
skip++
}
return &Error{
Err: err,
Frames: frames,
return &errorData{
err: err,
frames: frames,
}
}
@@ -26,7 +26,7 @@ func BenchmarkNew(b *testing.B) {
}
}

func addFrames(depth int, message string) *tracerr.Error {
func addFrames(depth int, message string) error {
if depth <= 1 {
return tracerr.New(message)
}
@@ -5,14 +5,14 @@ import (
"github.com/ztrue/tracerr"
)

func addFrameA(message string) *tracerr.Error {
func addFrameA(message string) error {
return addFrameB(message)
}

func addFrameB(message string) *tracerr.Error {
func addFrameB(message string) error {
return addFrameC(message)
}

func addFrameC(message string) *tracerr.Error {
func addFrameC(message string) error {
return tracerr.New(message)
}
@@ -10,7 +10,7 @@ import (
)

type ErrorTestCase struct {
Error *tracerr.Error
Error tracerr.Error
ExpectedMessage string
ExpectedStackTrace []tracerr.Frame
}
@@ -61,7 +61,7 @@ func TestError(t *testing.T) {
},
},
{
Error: addFrameA("error with stack trace"),
Error: addFrameA("error with stack trace").(tracerr.Error),
ExpectedMessage: "error with stack trace",
ExpectedStackTrace: []tracerr.Frame{
{
@@ -115,15 +115,22 @@ func TestError(t *testing.T) {
}

for i, c := range cases {
if c.Error.Error() != c.ExpectedMessage {
if c.Error == nil {
if c.ExpectedMessage != "" {
t.Errorf(
"cases[%#v].Error = nil; want %#v",
i, c.ExpectedMessage,
)
}
} else if c.Error.Error() != c.ExpectedMessage {
t.Errorf(
"cases[%#v].Error.Error() = %#v; want %#v",
i, c.Error.Error(), c.ExpectedMessage,
)
}

if c.ExpectedStackTrace == nil {
if c.Error.StackTrace() != nil {
if c.Error != nil && c.Error.StackTrace() != nil {
t.Errorf(
"cases[%#v].Error.StackTrace() = %#v; want %#v",
i, c.Error.StackTrace(), nil,
@@ -182,6 +189,62 @@ func TestError(t *testing.T) {
}
}

func TestCustomError(t *testing.T) {
err := errors.New("some error")
frames := []tracerr.Frame{
{
Func: "main.foo",
Line: 42,
Path: "/src/github.com/john/doe/foobar.go",
},
{
Func: "main.bar",
Line: 43,
Path: "/src/github.com/john/doe/bazqux.go",
},
}
customErr := tracerr.CustomError(err, frames)
message := customErr.Error()
if message != err.Error() {
t.Errorf(
"customErr.Error() = %#v; want %#v",
message, err.Error(),
)
}
unwrapped := customErr.Unwrap()
if unwrapped != err {
t.Errorf(
"customErr.Unwrap() = %#v; want %#v",
unwrapped, err,
)
}
stackTrace := customErr.StackTrace()
if len(stackTrace) != len(frames) {
t.Errorf(
"len(customErr.StackTrace()) = %#v; want %#v",
len(stackTrace), len(frames),
)
}
for i, frame := range frames {
if stackTrace[i] != frame {
t.Errorf(
"customErr.StackTrace()[%#v] = %#v; want %#v",
i, stackTrace[i], frame,
)
}
}
}

func TestErrorNil(t *testing.T) {
wrapped := wrapError(nil)
if wrapped != nil {
t.Errorf(
"wrapped = %#v; want nil",
wrapped,
)
}
}

func TestFrameString(t *testing.T) {
frame := tracerr.Frame{
Func: "main.read",
@@ -241,3 +304,7 @@ func TestUnwrap(t *testing.T) {
}
}
}

func wrapError(err error) error {
return tracerr.Wrap(err)
}
@@ -0,0 +1,19 @@
package main

import (
"fmt"

"github.com/ztrue/tracerr"
)

func main() {
if err := nilError(); err != nil {
tracerr.PrintSourceColor(err)
} else {
fmt.Println("no error")
}
}

func nilError() error {
return tracerr.Wrap(nil)
}
@@ -154,7 +154,7 @@ func sprint(err error, nums []int, colorized bool) string {
if err == nil {
return ""
}
e, ok := err.(*Error)
e, ok := err.(Error)
if !ok {
return err.Error()
}
Oops, something went wrong.

0 comments on commit 088bd7e

Please sign in to comment.
You can’t perform that action at this time.