Golang error handling
In Golang, it is common to see errors return as:
errors.New(msg)
And:
fmt.Error(msg)
fmt.Errorf("...",msg,foo,bar)
If there are nested errors, these can be wrapped into a new error using the %w formatting directive:
if err!=nil {
msg := "something went wrong"
return ...,fmt.Errorf("%s: %w", msg, err)
}
It is good practice to create custom error types.
Not only can this improve readability but it enables a mechanism where the error type can be used to clarify code.
These can be trivial:
type MyError = errors.New("something went wrong")
And elsewhere:
err := MyError
And to test:
if NotImplementedError == nil {
t.Error("NotImplementedError is nil")
}
if NotImplementedError.Error() != msg {
t.Errorf("got:\n%q\nwant:\n%q", NotImplementedError.Error(), msg)
}
// Return NotImplementedError
err := func() error {
return NotImplementedError
}()
if !errors.Is(err, NotImplementedError) {
t.Error("expected sucess")
}
And more complex:
// MyError is a type representing a custom error
type MyError struct {
Code: int32
Message: string
Err: error
}
// NewMyError is a function that create a new MyError
func NewMyError(code int32, msg string, err error) *MyError {
return &MyError{
Code: code,
Message: msg,
Err: err,
}
}
// Error is a method that converts a MyError to a string
// It implements the error interface
// It handles wrapped errors if any
func (e *MyError) Error() string {
if e.Err != nil {
return fmt.Sprintf("[%d] %s - %d", e.Code, e.Message, e.Err.Error())
}
return fmt.Sprintf("[%d] %s", e.Code, e.Message))
}
// Unwrap is a method that unwraps MyError wrapped errors
func (e *MyError) Unwrap() error {
return e.Err
}
And elsewhere:
err := NewMyError(code, msg, err)
And to test we need to have 2 branches for not-wrapped and wrapped messages:
// Wrapped
// Create underlying|base error
baseMsg := "underlying issue"
baseErr := errors.New(baseMsg)
// Create ContainerRuntimeError with base error
msg := "some issue"
err := NewContainerRuntimeError(msg, baseErr)
// Its message should match msg
if err.Message != msg {
t.Errorf("got: %q; want: %q", err.Message, msg)
}
// It should stringify combining the msg and baseMsg
want := fmt.Sprintf("%s: %s", msg, baseMsg)
if err.Error() != want {
t.Errorf("got: %q; want: %q", err.Error(), want)
}
// Its error should be the base error
if err.Err != baseErr {
t.Errorf("got: %q; want: %q", err.Err, baseErr)
}
// It should include the base error
if !errors.Is(err, baseErr) {
t.Errorf("got: %q; want: %q", err, baseErr)
}
// Unwrapping should retun the base error
if err.Unwrap() != baseErr {
t.Errorf("got: %q; want: %q", err.Unwrap(), baseErr)
}
// It should contain the base error
if !errors.Is(err.Unwrap(), baseErr) {
t.Errorf("got: %q; want: %q", err.Unwrap(), baseErr)
}
// It should find ErrContainerRuntime
var target *ErrContainerRuntime
if !errors.As(err, &target) {
t.Error("expected success")
}
That’s all!