Skip to content

Instantly share code, notes, and snippets.

@wtask
Last active January 21, 2020 08:53
Show Gist options
  • Save wtask/bc20851893e684f85fb3fd2600c011d5 to your computer and use it in GitHub Desktop.
Save wtask/bc20851893e684f85fb3fd2600c011d5 to your computer and use it in GitHub Desktop.
Helper to handle errors in Go 1.13.x on idiomatic way
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,
}
}
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)
}
}
@wtask
Copy link
Author

wtask commented Jan 21, 2020

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:

package my_package

import (
    path/to/ext_package
    errors
    fmt
)

var ErrFault = errors.New("error")

func DoSomething() error {
    if err := ext_package.Action(); err != nil {
         return fmt.Errorf("%w, external action failed: %v", ErrFault, err)
    }
    return nil
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment