Skip to content

Commit

Permalink
Introduce Argo errors package
Browse files Browse the repository at this point in the history
  • Loading branch information
jessesuen committed Oct 18, 2017
1 parent 37b7de8 commit 74baac7
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 1 deletion.
20 changes: 19 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@
[[constraint]]
name = "k8s.io/client-go"
branch = "release-5.0"

[[constraint]]
name = "github.com/stretchr/testify"
version = "1.1.4"
146 changes: 146 additions & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package errors

import (
"encoding/json"
"fmt"
"io"

"github.com/pkg/errors"
)

// Externally visible error codes
var (
CodeUnauthorized = "ERR_UNAUTHORIZED"
CodeBadRequest = "ERR_BAD_REQUEST"
CodeForbidden = "ERR_FORBIDDEN"
CodeNotFound = "ERR_NOT_FOUND"
CodeInternal = "ERR_INTERNAL"
)

// ArgoError is an error interface that additionally adds support for
// stack trace, error code, and a JSON representation of the error
type ArgoError interface {
Error() string
Code() string
JSON() []byte
StackTrace() errors.StackTrace
Format(s fmt.State, verb rune)
}

// argoerr is the internal implementation of an Argo error which wraps the error from pkg/errors
type argoerr struct {
code string
message string
stracer stackTracer
}

// stackTracer is interface for error types that have a stack trace
type stackTracer interface {
Error() string
StackTrace() errors.StackTrace
}

// New returns an error with the supplied message.
// New also records the stack trace at the point it was called.
func New(code string, message string) error {
err := errors.New(message)
return argoerr{code, message, err.(stackTracer)}
}

// Errorf returns an error and formats according to a format specifier
func Errorf(code string, format string, args ...interface{}) error {
return New(code, fmt.Sprintf(format, args...))
}

// InternalError is a convenience function to create a Internal error with a message
func InternalError(message string) error {
return New(CodeInternal, message)
}

// InternalErrorf is a convenience function to format an Internal error
func InternalErrorf(format string, args ...interface{}) error {
return Errorf(CodeInternal, format, args)
}

// InternalWrapError annotates the error with the ERR_INTERNAL code and a stack trace, optional message
func InternalWrapError(err error, message ...string) error {
if len(message) == 0 {
return Wrap(err, CodeInternal, err.Error())
}
return Wrap(err, CodeInternal, message[0])
}

// InternalWrapErrorf annotates the error with the ERR_INTERNAL code and a stack trace, optional message
func InternalWrapErrorf(err error, format string, args ...interface{}) error {
return Wrap(err, CodeInternal, fmt.Sprintf(format, args...))
}

// Wrap returns an error annotating err with a stack trace at the point Wrap is called,
// and a new supplied message. The previous original is preserved and accessible via Cause().
// If err is nil, Wrap returns nil.
func Wrap(err error, code string, message string) error {
if err == nil {
return nil
}
err = errors.Wrap(err, message)
return argoerr{code, message, err.(stackTracer)}
}

// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements the following
// interface:
//
// type causer interface {
// Cause() error
// }
//
// If the error does not implement Cause, the original error will
// be returned. If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {
if argoErr, ok := err.(argoerr); ok {
return errors.Cause(argoErr.stracer)
}
return errors.Cause(err)
}

func (e argoerr) Error() string {
return fmt.Sprintf("[%s] %s", e.code, e.message)
}

func (e argoerr) Code() string {
return e.code
}

func (e argoerr) StackTrace() errors.StackTrace {
return e.stracer.StackTrace()
}

func (e argoerr) JSON() []byte {
type errBean struct {
Code string `json:"code"`
Message string `json:"message"`
}
eb := errBean{e.code, e.message}
j, _ := json.Marshal(eb)
return j
}

func (e argoerr) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, e.Error())
for _, pc := range e.StackTrace() {
f := errors.Frame(pc)
fmt.Fprintf(s, "\n%+v", f)
}
return
}
fallthrough
case 's':
io.WriteString(s, e.Error())
case 'q':
fmt.Fprintf(s, "%q", e.Error())
}
}
53 changes: 53 additions & 0 deletions errors/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package errors_test

import (
"fmt"
"testing"

"github.com/argoproj/argo/errors"
pkgerr "github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)

// stackTracer is interface for error types that have a stack trace
type stackTracer interface {
StackTrace() pkgerr.StackTrace
}

// TestErrorf tests the initializer of error package
func TestErrorf(t *testing.T) {
err := errors.Errorf(errors.CodeInternal, "test internal")
assert.Equal(t, err.Error(), "[ERR_INTERNAL] test internal")
}

// TestWrap ensures we can wrap an error and use Cause() to retrieve the original error
func TestWrap(t *testing.T) {
err := fmt.Errorf("original error message")
argoErr := errors.Wrap(err, "WRAPPED", "wrapped message")
assert.Equal(t, "[WRAPPED] wrapped message", argoErr.Error())
orig := errors.Cause(argoErr)
assert.Equal(t, err.Error(), orig.Error())
}

// TestInternalError verifies
func TestInternalError(t *testing.T) {
err := errors.InternalError("test internal")
assert.Equal(t, "[ERR_INTERNAL] test internal", err.Error())

// Test wrapping errors
err = fmt.Errorf("random error")
intWrap := errors.InternalWrapError(err)
_ = intWrap.(stackTracer)
assert.Equal(t, "[ERR_INTERNAL] random error", intWrap.Error())
intWrap = errors.InternalWrapError(err, "different message")
_ = intWrap.(stackTracer)
assert.Equal(t, "[ERR_INTERNAL] different message", intWrap.Error())
intWrap = errors.InternalWrapErrorf(err, "hello %s", "world")
_ = intWrap.(stackTracer)
assert.Equal(t, "[ERR_INTERNAL] hello world", intWrap.Error())
}

func TestStackTrace(t *testing.T) {
err := errors.New("MYCODE", "my message")
assert.Contains(t, fmt.Sprintf("%+v", err), "errors_test.go")
}

0 comments on commit 74baac7

Please sign in to comment.