Skip to content

Commit

Permalink
add example docs, add ErrorWrapper interface, add & fix some methods
Browse files Browse the repository at this point in the history
  • Loading branch information
anantadwi13 committed Dec 19, 2021
1 parent 61ce47f commit af582bc
Show file tree
Hide file tree
Showing 6 changed files with 517 additions and 36 deletions.
160 changes: 134 additions & 26 deletions errorwrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,75 @@ import (
"io"
)

type errorContainer struct {
// ErrorWrapper is a multi level error. It is built like a stack (from bottom to top). If there is a 3 level, then each
// level will have ErrorWrapper instance with an error (CurrentError).
//
// ErrorWrapper Z (CurrentError contains any object that implements error, RootCause contains ErrorWrapper X, ParentError contains ErrorWrapper Y)
// V
// ErrorWrapper Y (CurrentError contains any object that implements error, RootCause contains ErrorWrapper X, ParentError contains ErrorWrapper X)
// V
// ErrorWrapper X (CurrentError contains any object that implements error, RootCause contains nil, ParentError contains nil)
//
type ErrorWrapper interface {
// CurrentError represents as the current level error (the top level of the stack).
CurrentError() error
// ContextMessage is used as a modifier to the CurrentError. It is used for elaborating the CurrentError.
ContextMessage() string
// RootCause will return the bottom level of the stack.
RootCause() ErrorWrapper
// ParentError will return an ErrorWrapper below the current level.
ParentError() ErrorWrapper
// StackTrace return a StackTrace of the current ErrorWrapper level only.
StackTrace() StackTrace

error
Unwrap() error
Is(target error) bool
As(target interface{}) bool
fmt.Formatter
}

type errorWrapper struct {
error
contextMsg string
rootCause error
parentError error
rootCause ErrorWrapper
parentError ErrorWrapper
*stack
}

func (e *errorContainer) Error() string {
func (e *errorWrapper) CurrentError() error {
return e.error
}

func (e *errorWrapper) ContextMessage() string {
return e.contextMsg
}

func (e *errorWrapper) RootCause() ErrorWrapper {
return e.rootCause
}

func (e *errorWrapper) ParentError() ErrorWrapper {
return e.parentError
}

func (e *errorWrapper) StackTrace() StackTrace {
return e.stack.StackTrace()
}

func (e *errorWrapper) Error() string {
str := e.error.Error()
if e.contextMsg != "" {
str += ": " + e.contextMsg
}
return str
}

func (e *errorContainer) fullError() string {
func (e *errorWrapper) fullError() string {
str := e.Error()
if e.parentError != nil {
str += "\n"
if ec, ok := e.parentError.(*errorContainer); ok && ec != nil {
if ec, ok := e.parentError.(*errorWrapper); ok && ec != nil {
str += ec.fullError()
} else {
str += e.parentError.Error()
Expand All @@ -35,15 +83,15 @@ func (e *errorContainer) fullError() string {
return str
}

func (e *errorContainer) Unwrap() error {
func (e *errorWrapper) Unwrap() error {
return e.parentError
}

func (e *errorContainer) Is(target error) bool {
func (e *errorWrapper) Is(target error) bool {
return e.error == target
}

func (e *errorContainer) As(target interface{}) bool {
func (e *errorWrapper) As(target interface{}) bool {
if target == nil {
return e.error == target
}
Expand All @@ -54,12 +102,12 @@ func (e *errorContainer) As(target interface{}) bool {
return false
}

func (e *errorContainer) Format(s fmt.State, verb rune) {
func (e *errorWrapper) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v", e.fullError())
if rc, ok := e.rootCause.(*errorContainer); ok && rc != nil {
if rc, ok := e.rootCause.(*errorWrapper); ok && rc != nil {
rc.stack.Format(s, verb)
} else {
e.stack.Format(s, verb)
Expand All @@ -78,34 +126,99 @@ func (e *errorContainer) Format(s fmt.State, verb rune) {
}
}

// New creates a base or root ErrorWrapper.
//
// ErrorWrapper.CurrentError is populated with errors.New(message).
func New(message string) error {
return &errorContainer{
return &errorWrapper{
error: errors.New(message),
stack: callStack(),
}
}

// NewError creates a base or root ErrorWrapper.
//
// ErrorWrapper.CurrentError is populated with err. If err is nil then NewError returns nil.
func NewError(err error) error {
if err == nil {
return nil
}

if ew, ok := err.(*errorWrapper); ok {
if ew == nil {
return nil
}
return ew
}

return &errorWrapper{
error: err,
stack: callStack(),
}
}

// NewErrorWithMessage creates a base or root ErrorWrapper.
//
// ErrorWrapper.CurrentError is populated with err.
// ErrorWrapper.ContextMessage is populated with contextMessage.
func NewErrorWithMessage(err error, contextMessage string) error {
if err == nil {
return nil
}

if ew, ok := err.(*errorWrapper); ok {
if ew == nil {
return nil
}
ew.contextMsg = contextMessage
return ew
}

return &errorWrapper{
error: err,
contextMsg: contextMessage,
stack: callStack(),
}
}

// Wrap returns an ErrorWrapper{CurrentError: wrapper, ParentError: parent, RootCause: parent.RootCause}
func Wrap(parent error, wrapper error) error {
ec := WrapWithMessage(parent, wrapper, "")
ec.(*errorContainer).parentError.(*errorContainer).stack = callStack()

if ec == nil {
return nil
}

if _, ok := parent.(*errorWrapper); !ok {
ec.(*errorWrapper).parentError.(*errorWrapper).stack = callStack()
}
ec.(*errorWrapper).stack = callStack()
return ec
}

// WrapWithMessage returns an ErrorWrapper{CurrentError: wrapper, ParentError: parent, RootCause: parent.RootCause, ContextMessage: contextMessage}
func WrapWithMessage(parent error, wrapper error, contextMessage string) error {
parentContainer, ok := parent.(*errorContainer)
var rootCause error
if parent == nil || wrapper == nil {
return nil
}

parentContainer, ok := parent.(*errorWrapper)
if !ok {
parentContainer = &errorContainer{
parentContainer = &errorWrapper{
error: parent,
stack: callStack(),
}
}

var rootCause ErrorWrapper

if parentContainer.rootCause == nil {
rootCause = parentContainer
} else {
rootCause = parentContainer.rootCause
}

currError := &errorContainer{
currError := &errorWrapper{
error: wrapper,
contextMsg: contextMessage,
rootCause: rootCause,
Expand All @@ -115,20 +228,15 @@ func WrapWithMessage(parent error, wrapper error, contextMessage string) error {
return currError
}

func WrapString(parent error, wrapperMsg string) error {
ec := Wrap(parent, errors.New(wrapperMsg))
ec.(*errorContainer).parentError.(*errorContainer).stack = callStack()
return ec
}

func Wrapper(err error, target error) error {
// Wrapper returns ErrorWrapper instance of err in target level
func Wrapper(err error, target error) ErrorWrapper {
if target == nil || err == nil {
return nil
}

for {
if curContainer, ok := err.(*errorContainer); ok && curContainer.Is(target) {
return err
if curContainer, ok := err.(ErrorWrapper); ok && (curContainer == target || curContainer.Is(target)) {
return curContainer
}

if err = Unwrap(err); err == nil {
Expand Down
4 changes: 2 additions & 2 deletions errorwrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ func infraLayer(intType ...int) error {
}
switch it {
case 1:
return WrapString(ErrorRootCause, "database not found")
return NewErrorWithMessage(ErrorRootCause, "database not found")
case 2:
return WrapString(ErrorInfra, "redis not found")
return NewErrorWithMessage(ErrorInfra, "redis not found")
default:
return Wrap(ErrorRootCause, ErrorInfra)
}
Expand Down
14 changes: 13 additions & 1 deletion example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,21 @@ func main() {
fmt.Println("is ErrorInfraNotFound") // go to this section
}

if err := errorwrap.Wrapper(err, ErrorInfraNotFound); err != nil {
fmt.Printf("%+v\n", err)
fmt.Println("Print file stack trace")
fmt.Printf("%s\n", err.StackTrace())
fmt.Println("Print file+line stack trace")
fmt.Printf("%v\n", err.StackTrace())
fmt.Println("Print function+file+line stack trace")
fmt.Printf("%+v\n", err.StackTrace())
}

fmt.Println(errorwrap.Is(err, ErrorDomain)) // true

fmt.Println(errorwrap.Is(err, ErrorUseCase)) // true

fmt.Printf("%+v\n", errorwrap.Wrapper(err, ErrorDomain).StackTrace()) // print stack trace until domain layer only
}

func infraLayer(intType int) error {
Expand All @@ -49,7 +61,7 @@ func infraLayer(intType int) error {
case 3:
err := errors.New("another error")
if err != nil {
return errorwrap.WrapString(err, "using wrapper message")
return errorwrap.NewErrorWithMessage(err, "using wrapper message")
}
case 4:
err := errors.New("error again")
Expand Down
Loading

0 comments on commit af582bc

Please sign in to comment.