From 1b1c4cd94d151ff11181524c9257fb3111e70f19 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Wed, 6 Jun 2018 21:24:28 -0700 Subject: [PATCH] Reduce Errors T/Cause/Message into single Data --- common/async_test.go | 2 +- common/errors.go | 173 +++++++++++++++++++----------------------- common/errors_test.go | 62 +++++++-------- 3 files changed, 107 insertions(+), 130 deletions(-) diff --git a/common/async_test.go b/common/async_test.go index 037afcaa..f565b4bd 100644 --- a/common/async_test.go +++ b/common/async_test.go @@ -135,7 +135,7 @@ func checkResult(t *testing.T, taskResultSet *TaskResultSet, index int, val inte if err != nil { assert.Equal(t, err, taskResult.Error, taskName) } else if pnk != nil { - assert.Equal(t, pnk, taskResult.Error.(Error).Cause(), taskName) + assert.Equal(t, pnk, taskResult.Error.(Error).Data(), taskName) } else { assert.Nil(t, taskResult.Error, taskName) } diff --git a/common/errors.go b/common/errors.go index 5992b234..7259b29d 100644 --- a/common/errors.go +++ b/common/errors.go @@ -6,106 +6,81 @@ import ( ) //---------------------------------------- -// Convenience methods +// Convenience method. -// ErrorWrap will just call .TraceFrom(), or create a new *cmnError. func ErrorWrap(cause interface{}, format string, args ...interface{}) Error { - msg := Fmt(format, args...) if causeCmnError, ok := cause.(*cmnError); ok { - return causeCmnError.TraceFrom(1, msg) + msg := Fmt(format, args...) + return causeCmnError.Stacktrace().Trace(1, msg) + } else if cause == nil { + return newCmnError(FmtError{format, args}).Stacktrace() + } else { + // NOTE: causeCmnError is a typed nil here. + msg := Fmt(format, args...) + return newCmnError(cause).Stacktrace().Trace(1, msg) } - // NOTE: cause may be nil. - // NOTE: do not use causeCmnError here, not the same as nil. - return newError(msg, cause, cause).Stacktrace() } //---------------------------------------- // Error & cmnError /* -Usage: + +Usage with arbitrary error data: ```go // Error construction - var someT = errors.New("Some err type") - var err1 error = NewErrorWithT(someT, "my message") + type MyError struct{} + var err1 error = NewErrorWithData(MyError{}, "my message") ... // Wrapping var err2 error = ErrorWrap(err1, "another message") if (err1 != err2) { panic("should be the same") ... // Error handling - switch err2.T() { - case someT: ... + switch err2.Data().(type){ + case MyError: ... default: ... } ``` - */ type Error interface { Error() string - Message() string Stacktrace() Error - Trace(format string, args ...interface{}) Error - TraceFrom(offset int, format string, args ...interface{}) Error - Cause() interface{} - WithT(t interface{}) Error - T() interface{} - Format(s fmt.State, verb rune) + Trace(offset int, format string, args ...interface{}) Error + Data() interface{} } -// New Error with no cause where the type is the format string of the message.. +// New Error with formatted message. +// The Error's Data will be a FmtError type. func NewError(format string, args ...interface{}) Error { - msg := Fmt(format, args...) - return newError(msg, nil, format) - + err := FmtError{format, args} + return newCmnError(err) } -// New Error with specified type and message. -func NewErrorWithT(t interface{}, format string, args ...interface{}) Error { - msg := Fmt(format, args...) - return newError(msg, nil, t) -} - -// NOTE: The name of a function "NewErrorWithCause()" implies that you are -// creating a new Error, yet, if the cause is an Error, creating a new Error to -// hold a ref to the old Error is probably *not* what you want to do. -// So, use ErrorWrap(cause, format, a...) instead, which returns the same error -// if cause is an Error. -// IF you must set an Error as the cause of an Error, -// then you can use the WithCauser interface to do so manually. -// e.g. (error).(tmlibs.WithCauser).WithCause(causeError) - -type WithCauser interface { - WithCause(cause interface{}) Error +// New Error with specified data. +func NewErrorWithData(data interface{}) Error { + return newCmnError(data) } type cmnError struct { - msg string // first msg which also appears in msg - cause interface{} // underlying cause (or panic object) - t interface{} // for switching on error + data interface{} // associated data msgtraces []msgtraceItem // all messages traced stacktrace []uintptr // first stack trace } -var _ WithCauser = &cmnError{} var _ Error = &cmnError{} // NOTE: do not expose. -func newError(msg string, cause interface{}, t interface{}) *cmnError { +func newCmnError(data interface{}) *cmnError { return &cmnError{ - msg: msg, - cause: cause, - t: t, + data: data, msgtraces: nil, stacktrace: nil, } } -func (err *cmnError) Message() string { - return err.msg -} - +// Implements error. func (err *cmnError) Error() string { return fmt.Sprintf("%v", err) } @@ -121,42 +96,17 @@ func (err *cmnError) Stacktrace() Error { } // Add tracing information with msg. -func (err *cmnError) Trace(format string, args ...interface{}) Error { - msg := Fmt(format, args...) - return err.doTrace(msg, 0) -} - -// Same as Trace, but traces the line `offset` calls out. -// If n == 0, the behavior is identical to Trace(). -func (err *cmnError) TraceFrom(offset int, format string, args ...interface{}) Error { +// Set n=0 unless wrapped with some function, then n > 0. +func (err *cmnError) Trace(offset int, format string, args ...interface{}) Error { msg := Fmt(format, args...) return err.doTrace(msg, offset) } -// Return last known cause. -// NOTE: The meaning of "cause" is left for the caller to define. -// There exists no "canonical" definition of "cause". -// Instead of blaming, try to handle it, or organize it. -func (err *cmnError) Cause() interface{} { - return err.cause -} - -// Overwrites the Error's cause. -func (err *cmnError) WithCause(cause interface{}) Error { - err.cause = cause - return err -} - -// Overwrites the Error's type. -func (err *cmnError) WithT(t interface{}) Error { - err.t = t - return err -} - -// Return the "type" of this message, primarily for switching -// to handle this Error. -func (err *cmnError) T() interface{} { - return err.t +// Return the "data" of this error. +// Data could be used for error handling/switching, +// or for holding general error/debug information. +func (err *cmnError) Data() interface{} { + return err.data } func (err *cmnError) doTrace(msg string, n int) Error { @@ -177,12 +127,8 @@ func (err *cmnError) Format(s fmt.State, verb rune) { default: if s.Flag('#') { s.Write([]byte("--= Error =--\n")) - // Write msg. - s.Write([]byte(fmt.Sprintf("Message: %s\n", err.msg))) - // Write cause. - s.Write([]byte(fmt.Sprintf("Cause: %#v\n", err.cause))) - // Write type. - s.Write([]byte(fmt.Sprintf("T: %#v\n", err.t))) + // Write data. + s.Write([]byte(fmt.Sprintf("Data: %#v\n", err.data))) // Write msg trace items. s.Write([]byte(fmt.Sprintf("Msg Traces:\n"))) for i, msgtrace := range err.msgtraces { @@ -200,11 +146,7 @@ func (err *cmnError) Format(s fmt.State, verb rune) { s.Write([]byte("--= /Error =--\n")) } else { // Write msg. - if err.cause != nil { - s.Write([]byte(fmt.Sprintf("Error{`%s` (cause: %v)}", err.msg, err.cause))) // TODO tick-esc? - } else { - s.Write([]byte(fmt.Sprintf("Error{`%s`}", err.msg))) // TODO tick-esc? - } + s.Write([]byte(fmt.Sprintf("Error{%v}", err.data))) // TODO tick-esc? } } } @@ -232,6 +174,45 @@ func (mti msgtraceItem) String() string { ) } +//---------------------------------------- +// fmt error + +/* + +FmtError is the data type for NewError() (e.g. NewError().Data().(FmtError)) +Theoretically it could be used to switch on the format string. + +```go + // Error construction + var err1 error = NewError("invalid username %v", "BOB") + var err2 error = NewError("another kind of error") + ... + // Error handling + switch err1.Data().(cmn.FmtError).Format { + case "invalid username %v": ... + case "another kind of error": ... + default: ... + } +``` +*/ +type FmtError struct { + format string + args []interface{} +} + +func (fe FmtError) Error() string { + return fmt.Sprintf(fe.format, fe.args...) +} + +func (fe FmtError) String() string { + return fmt.Sprintf("FmtError{format:%v,args:%v}", + fe.format, fe.args) +} + +func (fe FmtError) Format() string { + return fe.format +} + //---------------------------------------- // Panic wrappers // XXX DEPRECATED diff --git a/common/errors_test.go b/common/errors_test.go index 2c5234f9..16aede22 100644 --- a/common/errors_test.go +++ b/common/errors_test.go @@ -25,11 +25,9 @@ func TestErrorPanic(t *testing.T) { var err = capturePanic() - assert.Equal(t, pnk{"something"}, err.Cause()) - assert.Equal(t, pnk{"something"}, err.T()) - assert.Equal(t, "This is the message in ErrorWrap(r, message).", err.Message()) - assert.Equal(t, "Error{`This is the message in ErrorWrap(r, message).` (cause: {something})}", fmt.Sprintf("%v", err)) - assert.Contains(t, fmt.Sprintf("%#v", err), "Message: This is the message in ErrorWrap(r, message).") + assert.Equal(t, pnk{"something"}, err.Data()) + assert.Equal(t, "Error{{something}}", fmt.Sprintf("%v", err)) + assert.Contains(t, fmt.Sprintf("%#v", err), "This is the message in ErrorWrap(r, message).") assert.Contains(t, fmt.Sprintf("%#v", err), "Stack Trace:\n 0") } @@ -37,11 +35,9 @@ func TestErrorWrapSomething(t *testing.T) { var err = ErrorWrap("something", "formatter%v%v", 0, 1) - assert.Equal(t, "something", err.Cause()) - assert.Equal(t, "something", err.T()) - assert.Equal(t, "formatter01", err.Message()) - assert.Equal(t, "Error{`formatter01` (cause: something)}", fmt.Sprintf("%v", err)) - assert.Regexp(t, `Message: formatter01\n`, fmt.Sprintf("%#v", err)) + assert.Equal(t, "something", err.Data()) + assert.Equal(t, "Error{something}", fmt.Sprintf("%v", err)) + assert.Regexp(t, `formatter01\n`, fmt.Sprintf("%#v", err)) assert.Contains(t, fmt.Sprintf("%#v", err), "Stack Trace:\n 0") } @@ -49,11 +45,11 @@ func TestErrorWrapNothing(t *testing.T) { var err = ErrorWrap(nil, "formatter%v%v", 0, 1) - assert.Equal(t, nil, err.Cause()) - assert.Equal(t, nil, err.T()) - assert.Equal(t, "formatter01", err.Message()) - assert.Equal(t, "Error{`formatter01`}", fmt.Sprintf("%v", err)) - assert.Regexp(t, `Message: formatter01\n`, fmt.Sprintf("%#v", err)) + assert.Equal(t, + FmtError{"formatter%v%v", []interface{}{0, 1}}, + err.Data()) + assert.Equal(t, "Error{formatter01}", fmt.Sprintf("%v", err)) + assert.Contains(t, fmt.Sprintf("%#v", err), `Data: common.FmtError{format:"formatter%v%v", args:[]interface {}{0, 1}}`) assert.Contains(t, fmt.Sprintf("%#v", err), "Stack Trace:\n 0") } @@ -61,11 +57,11 @@ func TestErrorNewError(t *testing.T) { var err = NewError("formatter%v%v", 0, 1) - assert.Equal(t, nil, err.Cause()) - assert.Equal(t, "formatter%v%v", err.T()) - assert.Equal(t, "formatter01", err.Message()) - assert.Equal(t, "Error{`formatter01`}", fmt.Sprintf("%v", err)) - assert.Regexp(t, `Message: formatter01\n`, fmt.Sprintf("%#v", err)) + assert.Equal(t, + FmtError{"formatter%v%v", []interface{}{0, 1}}, + err.Data()) + assert.Equal(t, "Error{formatter01}", fmt.Sprintf("%v", err)) + assert.Contains(t, fmt.Sprintf("%#v", err), `Data: common.FmtError{format:"formatter%v%v", args:[]interface {}{0, 1}}`) assert.NotContains(t, fmt.Sprintf("%#v", err), "Stack Trace") } @@ -73,26 +69,26 @@ func TestErrorNewErrorWithStacktrace(t *testing.T) { var err = NewError("formatter%v%v", 0, 1).Stacktrace() - assert.Equal(t, nil, err.Cause()) - assert.Equal(t, "formatter%v%v", err.T()) - assert.Equal(t, "formatter01", err.Message()) - assert.Equal(t, "Error{`formatter01`}", fmt.Sprintf("%v", err)) - assert.Regexp(t, `Message: formatter01\n`, fmt.Sprintf("%#v", err)) + assert.Equal(t, + FmtError{"formatter%v%v", []interface{}{0, 1}}, + err.Data()) + assert.Equal(t, "Error{formatter01}", fmt.Sprintf("%v", err)) + assert.Contains(t, fmt.Sprintf("%#v", err), `Data: common.FmtError{format:"formatter%v%v", args:[]interface {}{0, 1}}`) assert.Contains(t, fmt.Sprintf("%#v", err), "Stack Trace:\n 0") } func TestErrorNewErrorWithTrace(t *testing.T) { var err = NewError("formatter%v%v", 0, 1) - err.Trace("trace %v", 1) - err.Trace("trace %v", 2) - err.Trace("trace %v", 3) + err.Trace(0, "trace %v", 1) + err.Trace(0, "trace %v", 2) + err.Trace(0, "trace %v", 3) - assert.Equal(t, nil, err.Cause()) - assert.Equal(t, "formatter%v%v", err.T()) - assert.Equal(t, "formatter01", err.Message()) - assert.Equal(t, "Error{`formatter01`}", fmt.Sprintf("%v", err)) - assert.Regexp(t, `Message: formatter01\n`, fmt.Sprintf("%#v", err)) + assert.Equal(t, + FmtError{"formatter%v%v", []interface{}{0, 1}}, + err.Data()) + assert.Equal(t, "Error{formatter01}", fmt.Sprintf("%v", err)) + assert.Contains(t, fmt.Sprintf("%#v", err), `Data: common.FmtError{format:"formatter%v%v", args:[]interface {}{0, 1}}`) dump := fmt.Sprintf("%#v", err) assert.NotContains(t, dump, "Stack Trace") assert.Regexp(t, `common/errors_test\.go:[0-9]+ - trace 1`, dump)