diff --git a/pkg/errors/doc.go b/pkg/errors/doc.go new file mode 100644 index 0000000000..f9cd863c72 --- /dev/null +++ b/pkg/errors/doc.go @@ -0,0 +1,3 @@ +// package error contains error related utilities. Use this package when +// a well-defined error has to be shown. +package errors diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go new file mode 100644 index 0000000000..7bd3e1b97e --- /dev/null +++ b/pkg/errors/errors.go @@ -0,0 +1,118 @@ +package errors + +import ( + "fmt" +) + +// base is the fundamental struct that implements the error interface. +// The order of the struct is 'TCMEUA'. +type base struct { + // t denotes the custom type of the error. + t typ + // c denotes the short code for the error message. + c string + // m contains error message passed through errors.New. + m string + // e is the actual error being wrapped. + e error + // u denotes the url for the documentation (if present) for the error. + u string + // a denotes any additional error messages (if present). + a []string +} + +// base implements Error interface. +func (b *base) Error() string { + if b.e != nil { + return b.e.Error() + } + + return fmt.Sprintf("%s(%s): %s", b.t.s, b.c, b.m) +} + +// New returns a base error. It requires type, code and message as input. +func New(t typ, code string, message string) *base { + return &base{ + t: t, + c: code, + m: message, + e: nil, + u: "", + a: []string{}, + } +} + +// Newf returns a new base by formatting the error message with the supplied format specifier. +func Newf(t typ, code string, format string, args ...interface{}) *base { + return &base{ + t: t, + c: code, + m: fmt.Sprintf(format, args...), + e: nil, + } +} + +// Wrapf returns a new error by formatting the error message with the supplied format specifier +// and wrapping another error with base. +func Wrapf(cause error, t typ, code string, format string, args ...interface{}) *base { + return &base{ + t: t, + c: code, + m: fmt.Sprintf(format, args...), + e: cause, + } +} + +// WithUrl adds a url to the base error and returns a new base error. +func (b *base) WithUrl(u string) *base { + return &base{ + t: b.t, + c: b.c, + m: b.m, + e: b.e, + u: u, + a: b.a, + } +} + +// WithUrl adds additional messages to the base error and returns a new base error. +func (b *base) WithAdditional(a ...string) *base { + return &base{ + t: b.t, + c: b.c, + m: b.m, + e: b.e, + u: b.u, + a: a, + } +} + +// Unwrapb is a combination of built-in errors.As and type casting. +// It finds the first error in cause that matches base, +// and if one is found, returns the individual fields of base. +// Otherwise, it returns TypeInternal, the original error string +// and the error itself. +// +//lint:ignore ST1008 we want to return arguments in the 'TCMEUA' order of the struct +func Unwrapb(cause error) (typ, string, string, error, string, []string) { + base, ok := cause.(*base) + if ok { + return base.t, base.c, base.m, base.e, base.u, base.a + } + + return TypeInternal, "", cause.Error(), cause, "", []string{} +} + +// Ast checks if the provided error matches the specified custom error type. +func Ast(cause error, typ typ) bool { + t, _, _, _, _, _ := Unwrapb(cause) + + return t == typ +} + +// Ast checks if the provided error matches the specified custom error code. +func Asc(cause error, code string) bool { + _, c, _, _, _, _ := Unwrapb(cause) + + return c == code +} diff --git a/pkg/errors/errors_test.go b/pkg/errors/errors_test.go new file mode 100644 index 0000000000..4b04e876af --- /dev/null +++ b/pkg/errors/errors_test.go @@ -0,0 +1,53 @@ +package errors + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNew(t *testing.T) { + typ := typ{"test-error"} + err := New(typ, "code", "test error info") + assert.NotNil(t, err) +} + +func TestNewf(t *testing.T) { + typ := typ{"test-error"} + err := Newf(typ, "test-code", "test error info with %s", "string") + assert.NotNil(t, err) + assert.Equal(t, "test-error(test-code): test error info with string", err.Error()) +} + +func TestWrapf(t *testing.T) { + typ := typ{"test-error"} + err := Wrapf(errors.New("original error"), typ, "test-code", "info for err %d", 2) + assert.NotNil(t, err) +} + +func TestError(t *testing.T) { + typ := typ{"test-error"} + err1 := New(typ, "test-code", "info for err1") + assert.Equal(t, "test-error(test-code): info for err1", err1.Error()) + + err2 := Wrapf(err1, typ, "test-code", "info for err2") + assert.Equal(t, "test-error(test-code): info for err1", err2.Error()) +} + +func TestUnwrapb(t *testing.T) { + typ := typ{"test-error"} + oerr := errors.New("original error") + berr := Wrapf(oerr, typ, "test-code", "this is a base err").WithUrl("https://docs").WithAdditional("additional err") + + atyp, acode, amessage, aerr, au, aa := Unwrapb(berr) + assert.Equal(t, typ, atyp) + assert.Equal(t, "test-code", acode) + assert.Equal(t, "this is a base err", amessage) + assert.Equal(t, oerr, aerr) + assert.Equal(t, "https://docs", au) + assert.Equal(t, []string{"additional err"}, aa) + + atyp, _, _, _, _, _ = Unwrapb(oerr) + assert.Equal(t, TypeInternal, atyp) +} diff --git a/pkg/errors/type.go b/pkg/errors/type.go new file mode 100644 index 0000000000..6800a8bc71 --- /dev/null +++ b/pkg/errors/type.go @@ -0,0 +1,14 @@ +package errors + +var ( + TypeInvalidInput typ = typ{"invalid-input"} + TypeInternal = typ{"internal"} + TypeUnsupported = typ{"unsupported"} + TypeNotFound = typ{"not-found"} + TypeMethodNotAllowed = typ{"method-not-allowed"} + TypeAlreadyExists = typ{"already-exists"} + TypeUnauthenticated = typ{"unauthenticated"} +) + +// Defines custom error types +type typ struct{ s string } diff --git a/pkg/errors/type_test.go b/pkg/errors/type_test.go new file mode 100644 index 0000000000..7b7d3a26b3 --- /dev/null +++ b/pkg/errors/type_test.go @@ -0,0 +1,18 @@ +package errors + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestString(t *testing.T) { + typ := typ{"test-error"} + assert.Equal(t, typ.s, "test-error") +} + +func TestEquals(t *testing.T) { + typ1 := typ{"test-error"} + typ2 := typ{"test-error"} + assert.True(t, typ1 == typ2) +}