Last active
January 21, 2020 08:53
-
-
Save wtask/bc20851893e684f85fb3fd2600c011d5 to your computer and use it in GitHub Desktop.
Helper to handle errors in Go 1.13.x on idiomatic way
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package errcode | |
import ( | |
"fmt" | |
) | |
// Fault type is implementation of standard error interface with minimal details like error scope and error code | |
// and the error scope is a "namespace" of error code used. | |
// Also the Fault type contain Cause field to save original (underlying) error and to support error-wrapping chains. | |
// | |
// Examples of use: | |
// res, err := DoSomething() | |
// if err != nil { | |
// return nil, &errcode.Fault{"your_package.YourAction", ERR_CUSTOM_CODE, err} | |
// } | |
// or | |
// var pkgError = &errcode.Fault{"package"} | |
// res, err := DoSomething() | |
// if err != nil { | |
// return nil, pkgError.New(ERR_CUSTOM_CODE, err) | |
// } | |
// or | |
// res, err := DoSomething() | |
// if err != nil { | |
// return nil, fmt.Errorf("failed to do something: %w", &errcode.Fault{"main", ERR_CUSTOM_CODE, err}) | |
// } | |
type Fault struct { | |
ErrorScope string | |
ErrorCode int | |
Cause error | |
} | |
func (f *Fault) Error() string { | |
c := "" | |
if f.Cause != nil { | |
if c = f.Cause.Error(); c != "" { | |
c = " " + c | |
} | |
} | |
return fmt.Sprintf("[fault:%s:%d]%s", f.ErrorScope, f.ErrorCode, c) | |
} | |
// Unwrap is expected to use for error-unwrapping by errors.Is() and errors.As() methods. | |
func (f *Fault) Unwrap() error { | |
return f.Cause | |
} | |
// New builds new Fault instance for the same error scope, but with specified error code and error cause. | |
func (f *Fault) New(code int, cause error) *Fault { | |
return &Fault{ | |
ErrorScope: f.ErrorScope, | |
ErrorCode: code, | |
Cause: cause, | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package errcode_test | |
import ( | |
"errors" | |
"fmt" | |
"testing" | |
"change/to/your/location/errcode" | |
) | |
func ExampleFault() { | |
fault := &errcode.Fault{} | |
fmt.Println(fault) | |
// Output: | |
// [fault::0] | |
} | |
func ExampleFault_underlying() { | |
fault := &errcode.Fault{"example.underlying", 1, errors.New("underlying error")} | |
fmt.Println(fault) | |
// Output: | |
// [fault:example.underlying:1] underlying error | |
} | |
func ExampleFault_wrapped() { | |
fault := &errcode.Fault{"example.wrapped", 1, errors.New("underlying error")} | |
fmt.Println(fmt.Errorf("extra error: %w", fault)) | |
// Output: | |
// extra error: [fault:example.wrapped:1] underlying error | |
} | |
func TestFault_errorInterface(t *testing.T) { | |
failure := func() error { | |
return &errcode.Fault{} | |
} | |
if err := failure(); err == nil { | |
t.Fail() | |
} | |
} | |
func TestFault_errorsIs(t *testing.T) { | |
underlying := errors.New("underlying error") | |
err := fmt.Errorf("next error: %w", underlying) | |
fault := &errcode.Fault{"errcode_test", 0, err} | |
err = fmt.Errorf("last error: %w", fault) | |
if !errors.Is(err, underlying) { | |
t.Fatal("err is not underlying error") | |
} | |
if !errors.Is(err, fault) { | |
t.Fatal("err is not fault") | |
} | |
} | |
func TestFault_errorsAs(t *testing.T) { | |
// error chain | |
err := errors.New("underlying error") | |
scope, code := "errcode_test", 1 | |
err = &errcode.Fault{scope, code, err} | |
err = fmt.Errorf("next error: %w", err) | |
err = fmt.Errorf("last error: %w", err) | |
var fault *errcode.Fault | |
if !errors.As(err, &fault) { | |
t.Fatal("can't find fault in error chain") | |
} | |
if fault.ErrorScope != scope { | |
t.Error("expected error scope:", scope, "actual:", fault.ErrorScope) | |
} | |
if fault.ErrorCode != code { | |
t.Error("expected error code:", code, "actual:", fault.ErrorCode) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
After a couple of days, I think that in general, we must not to generate error chains, like shown in test above!!! Every application has a layered structure. Every layer exports its own errors and only this errors MUST wrapped! All errors from lower levels should be unwrapped into new errors of current package. If you think about error chains as error stack, you are wrong. Only you need to do is add context of original error like this: