Documentation ¶
Overview ¶
Package errors provides detailed stack-traced description for error type and exception(handle-check) style handling
This package has two goals.
1. Provide convenient error type which has error code to easily distinguish errors, which can be extended by additional messages(annotations here) while error flows through the program, can be printed with stack trace.
2. Provide convenient way to handle errors in exception style.
With the first point everything is clear, let's talk a little about second. Go's error handling idiom usually looks like
x, err := DoSmth() if err != nil { logger.Error(err) // may be some err processing return err }
It is very "holy war" question which way to handle errors, exception like or "returning err" like, is better. Everyone chooses himself which way is more appropriate to him. But doubtlessly "returning err" style has some disadvantages.
1. Error handling logic is mixed with "business" logic so it shadows it
2. Code repetitions, which can cause a bug
3. Verbosity
That's why Go 2 draft https://go.googlesource.com/proposal/+/master/design/go2draft.md contains new Handle-Check error handling approach, which is quite similar to exception approach. This package provides similar to this Handle-Check mechanism, based on panics. Details are below =)
Errors ¶
All errors created by this package are returned as golang native error interface. Underlying type is immutable, so one can't change smth in already existing error, but can use wrap functions, which create new one based on underlying. New, Errorf, Codef - creates new error. Wrap, WrapAnnotated - wrap existing.
Error codes ¶
Error can be created with error code, which has interface{} type. Error code can be obtained from error by ErrCode function.
err := errors.New("description", 1) if errors.ErrCode(err) == 1 { fmt.Println("Hooray, it works!") }
Missing error code in creation is equal to nil error code.
err := errors.New("description") if errors.ErrCode(err) == nil { fmt.Println("Hooray, it works!") }
When one wraps error without providing error code, it is preserved. Otherwise - overridden.
err := errors.New("description", 1) err = errors.Wrap(err) if errors.ErrCode(err) == 1 { fmt.Println("Hooray, it works!") } err = errors.Wrap(err, 2) if errors.ErrCode(err) == 2 { fmt.Println("Hooray, it still works!") }
Annotations ¶
With WrapAnnotated one can add additional messages to an error. Annotations are printed in stacktrace, bound to corresponding function(where it was added). If annotation was added out of functions in stacktrace, it will be printed in separate section.
Format ¶
%v modifier will print full info, with stack trace and annotations. %s only error message. %q only error message quoted.
E.g. with %v:
ERROR: connection failed ERR CODE: connection_failed github.com/pashaosipyants/errors/example_auxiliary.SaveTaskToDbMockConnectionError D:/work/go/src/github.com/pashaosipyants/errors/example_auxiliary/example_auxiliary.go:19 github.com/pashaosipyants/errors/example_auxiliary.CreateTaskInitedByUser1 D:/work/go/src/github.com/pashaosipyants/errors/example_auxiliary/example_auxiliary.go:63 ANNOTATIONS: Inited by user 1 github.com/pashaosipyants/errors_test.apiCreateTask D:/work/go/src/github.com/pashaosipyants/errors/example_compehensive_test.go:69 github.com/pashaosipyants/errors_test.Example.func1 D:/work/go/src/github.com/pashaosipyants/errors/example_compehensive_test.go:40 github.com/pashaosipyants/errors_test.Example D:/work/go/src/github.com/pashaosipyants/errors/example_compehensive_test.go:44 testing.runExample C:/Go/src/testing/example.go:121 testing.runExamples C:/Go/src/testing/example.go:45 testing.(*M).Run C:/Go/src/testing/testing.go:1035 main.main _testmain.go:70 runtime.main C:/Go/src/runtime/proc.go:201 runtime.goexit C:/Go/src/runtime/asm_amd64.s:1333
Cause ¶
To get underlying error use Cause func.
err := errors.Wrap(io.EOF) if reflect.DeepEqual(err, io.EOF) { fmt.Println("Hooray, it works!") }
Skipstack management ¶
All creation & wrapping functions have their duplicates with _skipstack suffix and skip argument. With those functions one can specify correct first stack frame to print in stack trace. skip==0 means starting from caller of this function. It can be useful for example to skip stack frames of wrapper object, if you want your own wrapper of this package.
type MyErr struct { error X AddInfo } func NewMyErr(message string, errcode ...interface{}, x AddInfo) MyErr { return MyErr{New_skipstack(message, 1, errcode...), x} }
Handle check mechanism ¶
example:
defer errors.Handler(func(err errors.Handleable) { log.Error(err) switch err.ErrCode() { case 1: repairError1() case 2: repairError2() default: panic("Unknown err code") } }) err := task1() errors.Check(err, 1) x := twoPlusTwo() errors.Check(x == 4, 2)
Example ¶
This is comprehensive, pretending to be close to real-life example of using this package. It's easier to see it in code, but if you use godoc, please, notice https://godoc.org/github.com/pashaosipyants/errors/example_auxiliary package, which is used here.
Imagine there is an api to create a task that executes another service. Besides user of this api wants to hold info whether this task is already done. This api, ofc, can return error. E.g. certain task may be already created. If so, error should report whether it is done or not.
package main import ( "fmt" "github.com/pashaosipyants/errors" "github.com/pashaosipyants/errors/example_auxiliary" ) // This is comprehensive, pretending to be close to real-life example of using this package. // It's easier to see it in code, but if you use godoc, please, notice // https://godoc.org/github.com/pashaosipyants/errors/example_auxiliary package, // which is used here. // // Imagine there is an api to create a task that executes another service. // Besides user of this api wants to hold info whether this task is already done. // This api, ofc, can return error. E.g. certain task may be already created. // If so, error should report whether it is done or not. func main() { // loop to work out different cases for i := 0; i < 4; i++ { // func is smth like try block here func() { // smth like catch block defer errors.Handler(func(err errors.Handleable) { switch errors.ErrCode(err) { case errcode_apicreatetaskfailed: fmt.Printf("%s\n\n\n", err) // log // may be some specific actions case errcode_apiuserloginfailed: // may be some specific actions panic("Assertion failed") // but in our example can't be here default: panic("Assertion failed") } }) err := apiUserLogin() errors.Check(err, errcode_apiuserloginfailed) err = apiCreateTask(i) errors.Check(err, errcode_apicreatetaskfailed) // override errcode fmt.Println("Success!!!") // log }() } } const errcode_apicreatetaskfailed = "api_create_task_failed" const errcode_apiuserloginfailed = "api_user_login_failed" func apiCreateTask(i int) (reterr error) { defer errors.Handler(func(err errors.Handleable) { switch errors.ErrCode(err) { case example_auxiliary.ErrCode_ConnectionFailed, example_auxiliary.ErrCode_TaskAlreadyExistButNotDone: fmt.Print(err, "\n\n\n\n") // log reterr = err case example_auxiliary.ErrCode_TaskAlreadyExistAndDone: fmt.Print(err, "\n\n\n\n") // log // do some specific logic - e.g. mark task in db as done reterr = err default: panic("Assertion failed") } }) err := example_auxiliary.CreateTaskInitedByUser1(i) errors.Check(err) return nil } // pretends to be always success func apiUserLogin() error { // some work return nil }
Output: wrong output specially to make this function be executed and see output of this example
Index ¶
- func Cause(err error) error
- func Check(err error, errcode ...interface{})
- func CheckIf(ifErr bool, err error, errcode ...interface{})
- func CheckIfNew(ifErr bool, message string, errcode ...interface{})
- func Codef(errcode interface{}, format string, args ...interface{}) error
- func Codef_skipstack(skip int, errcode interface{}, format string, args ...interface{}) error
- func ErrCode(err error) interface{}
- func Errorf(format string, args ...interface{}) error
- func Errorf_skipstack(skip int, format string, args ...interface{}) error
- func Handler(handle func(err Handleable))
- func New(message string, errcode ...interface{}) error
- func New_skipstack(message string, skip int, errcode ...interface{}) error
- func Wrap(err error, errcode ...interface{}) error
- func WrapAnnotated(err error, annotation string, errcode ...interface{}) error
- func WrapAnnotated_skipstack(skip int, err error, annotation string, errcode ...interface{}) error
- func Wrap_skipstack(skip int, err error, errcode ...interface{}) error
- type Handleable
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Cause ¶
If it is possible, gets underlying error wrapped with this package's error type. Otherwise returns err itself.
func Check ¶
func Check(err error, errcode ...interface{})
Check panics with an error, first wrapping it with errcode. One can handle this panic defering Handler func.
func CheckIf ¶
CheckIf panics with an error if ifErr is true, first wrapping err it with errcode. One can handle this panic defering Handler func.
func CheckIfNew ¶
CheckIfNew panics with an error if ifErr is true. error is made from message and errcode. One can handle this panic defering Handler func.
func Codef_skipstack ¶
see https://godoc.org/github.com/pashaosipyants/errors/#hdr-Skipstack_management
func ErrCode ¶
func ErrCode(err error) interface{}
If it is possible, gets errcode of the error. Otherwise returns nil.
func Errorf_skipstack ¶
see https://godoc.org/github.com/pashaosipyants/errors/#hdr-Skipstack_management
func Handler ¶
func Handler(handle func(err Handleable))
defer Handle and provide handler to process panics made by Check...(). Panics with type different to this package's one are just forwarded.
func New ¶
New returns error with error code, if specified.
errcode is optional param. First of variadic parameters is used, else are ignored.
func New_skipstack ¶
see https://godoc.org/github.com/pashaosipyants/errors/#hdr-Skipstack_management
func Wrap ¶
Wrap returns error with underlying err and error code, if specified. If err is nil returns nil. If err already has error code(it means it was created earlier by one of this package's functions) and errcode is specified, it is overridden.
errcode is optional param. First of variadic parameters is used, else are ignored.
func WrapAnnotated ¶
WrapAnnotated returns annotated error with underlying err and error code, if specified. If err is nil returns nil. If err already has error code(it means it was created earlier by one of this package's functions) and errcode is specified, it is overridden. See https://godoc.org/github.com/pashaosipyants/errors/#hdr-Annotations to learn about annotations.
errcode is optional param. First of variadic parameters is used, else are ignored.
func WrapAnnotated_skipstack ¶
see https://godoc.org/github.com/pashaosipyants/errors/#hdr-Skipstack_management
Types ¶
type Handleable ¶
Type to handle Check...() produced panic.