how to check if two wrap error are equal in golang? - go

I have a simple error wrap type in errdefs.go:
package errdefs
type errInvalidAttribute struct{ error }
func (e errInvalidAttribute) Unwrap() error {
return e.error
}
func InvalidAttribute(err error) error {
if err == nil || IsInvalidAttribute(err) {
return err
}
return errInvalidAttribute{err}
}
func IsInvalidAttribute(err error) bool {
return errors.As(err, &errInvalidAttribute{})
}
The following is a unit test of this file:
package errdefs_test
func TestWrapErrorEqual(t *testing.T) {
err1 := errdefs.InvalidAttribute(fmt.Errorf("this is a wrap error"))
err2 := errdefs.InvalidAttribute(fmt.Errorf("this is a wrap error"))
if err1 != err2 {
t.Errorf(" != now work")
}
if !errors.Is(err1, err2) {
t.Errorf("errors.Is not work")
}
}
then I run unit-test:
$ go test .
--- FAIL: TestInvali (0.00s)
errdefs_test.go:62: != not work
errdefs_test.go:66: errors.Is not work
FAIL
FAIL errdefs 0.496s
FAIL
!= and errors.Is not work well,so how do I check if two wrap errors are equal in golang?

The equality operator as well as errors.Is check the equality of the references of the error objects. So, two instantiated error objects will never be equal.
Most libraries instantiate error variables on startup and use the references for equality checks.
var (
ErrInvalidArgument = errors.New("invalid argument")
)
func ErrInvalidArgument(err error) bool {
return err == ErrInvalidArgument
}
Of course, you can also check the equality of the string returned by error#Error, if it is not dynamically assembled.
So, you test function would look like following.
func TestWrapErrorEqual(t *testing.T) {
wrappedErr := fmt.Errorf("this is a wrap error")
err1 := errdefs.InvalidAttribute(wrappedErr )
err2 := errdefs.InvalidAttribute(wrappedErr )
if err1.Unwrap() != err2.Unwrap() {
t.Errorf(" != now work")
}
if !errors.Is(err1.Unwrap(), err2.Unwrap()) {
t.Errorf("errors.Is not work")
}
}
I hope I got your question right and this is somewhat helpful.

Related

Idiomatic way of returning a not-found error in Golang?

I have a function with this signature in Go:
func GetAccount(ctx context.Context, id uuid.UUID) (*Account, error)
It returns an error if there's an internal error (like the database query fails for some reason), but I'm not sure what I should return if the account is not found. I can think of two different approaches:
Just return a nil account and nil error if no account is found
Return a custom error type like this:
type accountNotFoundErr struct {
id uuid.UUID
}
func (err accountNotFoundErr) Error() string {
return fmt.Sprintf("account not found for user: %v", err.id)
}
func IsAccountNotFoundErr(err error) bool {
_, ok := err.(accountNotFoundErr)
return ok
}
func GetAccount(ctx context.Context, id uuid.UUID) (*Account, error) {
// if the account is not found
return nil, accountNotFoundErr{id}
}
I like the first one because it's simple, but I don't often see Go code which returns a nil result if the error is non-nil. I think the expectation is that, if the error is nil, the result is valid. The second approach fixes that, but it's also a bit more complicated for callers.
What is an idiomatic approach for handling cases like this in Go?
I have read a lot of posts about custom errors in go. Most of them created their own struct that implements the error interface.
The issue I found with that approach was that I did not manage to easily check if an error was of a certain type. The same way, you may be able to check some std lib error like if error == EOF.
Therefore, my favourite way to do that is creating a simple var with erros.New.
var ErrNotFound = errors.New("Resource was not found")
func main() {
err := raise()
if err == ErrNotFound {
fmt.Println("impossibru")
return
}
if err != nil {
fmt.Println("unexpected error")
return
}
}
func raise() error {
return ErrNotFound
}
https://play.golang.com/p/s0ZQfsdLqxB
As #Gavin pointed out in the comments, if you want to provide more context to the error by wrapping it with fmt.Errorf, you need to use errors.Is to check if the specific error was wrapped.
var ErrNotFound = errors.New("Resource was not found")
func main() {
err := raise(42)
if errors.Is(err, ErrNotFound) {
fmt.Println(err)
return
}
if err != nil {
fmt.Println("unexpected error")
return
}
}
func raise(id int) error {
return fmt.Errorf("id %d does not exist, error: %w", id, ErrNotFound)
}
https://play.golang.com/p/hSrkb1Xp4Hn

Comparing errors in Go

In my test file, I am trying to compare an actual error detected with the expected error. However, this comparison evaluates to false, and I'm unsure why. This even happens when I create two identical errors and compare them.
Code snippet:
func TestCompareErrors(t *testing.T) {
if fmt.Errorf("Test Error") != fmt.Errorf("Test Error") {
t.Errorf("Test failed")
}
}
This results in "Test failed"
You are comparing two different values which happen to have the same error message. You want to compare predefined error values, just like you would with common values like io.EOF.
http://play.golang.org/p/II8ZeASwir
var errTest = fmt.Errorf("test error")
func do() error {
return errTest
}
func main() {
err := do()
if err == errTest {
log.Fatal("received error: ", err)
}
}
You can read "Errors are Values" for a more in-depth explanation.
If you need to provide more information with the error, you can create your own error type. You can then attach whatever information you want to the error, and check for that type of error via a type assertion.
type myError string
func (e myError) Error() string {
return string(e)
}
func do() error {
return myError("oops")
}
func main() {
err := do()
if err, ok := err.(myError); ok {
log.Fatal("received myError: ", err)
}
}
Use reflect.DeepEqual to compare values.
if reflect.DeepEqual(fmt.Errorf("Test Error"), fmt.Errorf("Test Error")) {
// the error values are same.
}
Example in playground

Combine multiple error strings

I am new to golang, my application needs to return multiple errors in a loop, later requires to be combined and returned as a single error string. I am not able to use the string functions to combine the error messages. What methods can be use to combine these errors into a single error before returning ?
package main
import (
"fmt"
"strings"
)
func Servreturn() (err error) {
err1 = fmt.Errorf("Something else occured")
err2 = fmt.Errorf("Something else occured again")
// concatenate both the error
return err3
}
UPDATE for Go 1.13:
As of Go version 1.13, the language's errors package now supports error wrapping directly.
You can wrap an error by using the %w verb in fmt.Errorf:
err := errors.New("Original error")
err = fmt.Errorf("%w; Second error", err)
Use Unwrap to remove the last error added, and return what remains: previousErrors := errors.Unwrap(err)
Playground Example for errors.Unwrap
Two more functions, errors.Is and errors.As provide ways to check for and retrieve a specific type of error.
Playground Example for errors.As and errors.Is
Dave Cheney's excellent errors package (https://github.com/pkg/errors) include a Wrap function for this purpose:
package main
import "fmt"
import "github.com/pkg/errors"
func main() {
err := errors.New("error")
err = errors.Wrap(err, "open failed")
err = errors.Wrap(err, "read config failed")
fmt.Println(err) // "read config failed: open failed: error"
}
This also allows additional functionality, such as unpacking the cause of the error:
package main
import "fmt"
import "github.com/pkg/errors"
func main() {
err := errors.New("original error")
err = errors.Wrap(err, "now this")
fmt.Println(errors.Cause(err)) // "original error"
}
As well as the option to output a stack trace when specifying fmt.Printf("%+v\n", err).
You can find additional information about the package on his blog: here and here.
String functions don't work on errors because error is really an interface that implements the function Error() string.
You can use string functions on err1.Error() and err2.Error()
but not on the "err1" reference itself.
Some errors are structs, like the ones you get from database drivers.
So there's no natural way to use string functions on errors since they may not actually be strings underneath.
As for combining two errors:
Easy, just use fmt.Errorf again.
fmt.Errorf("Combined error: %v %v", err1, err2)
Alternatively:
errors.New(err1.Error() + err2.Error())
You could use the strings.Join() and append() function to acheive this slice.
example: golang playgorund
package main
import (
"fmt"
"strings"
"syscall"
)
func main() {
// create a slice for the errors
var errstrings []string
// first error
err1 := fmt.Errorf("First error:server error")
errstrings = append(errstrings, err1.Error())
// do something
err2 := fmt.Errorf("Second error:%s", syscall.ENOPKG.Error())
errstrings = append(errstrings, err2.Error())
// do something else
err3 := fmt.Errorf("Third error:%s", syscall.ENOTCONN.Error())
errstrings = append(errstrings, err3.Error())
// combine and print all the error
fmt.Println(fmt.Errorf(strings.Join(errstrings, "\n")))
}
This would output a single string which you can send back to the client.
First error:server1
Second error:Package not installed
Third error:Socket is not connected
hope this helps!
To expand on what #WillC had mentioned in a comment it is possible to define your own error type as error is an interface type. Any type that implements a Error() string function implements the error interface. Therefore, you could create a CollectionError which aggregates errors and returns a concatenated error string.
type ErrorCollector []error
func (c *ErrorCollector) Collect(e error) { *c = append(*c, e) }
func (c *ErrorCollector) Error() (err string) {
err = "Collected errors:\n"
for i, e := range *c {
err += fmt.Sprintf("\tError %d: %s\n", i, e.Error())
}
return err
}
This provides a collection function that appends a given error to a slice. Upon calling Error() string it iterates over the slice and creates a concatenated error string.
func main() {
collector := new(ErrorCollector)
for i := 0; i < 10; i++ {
collector.Collect(errors.New(fmt.Sprintf("%d Error", i)))
}
fmt.Println(collector)
}
There is a great golang.org blog post going over errors in more detail. A full example of the example is available on The Go Playground.
Uber has a multierr package for this use case:
return multierr.Combine(err1, err2)
People may be interested in https://github.com/hashicorp/go-multierror which describes itself as "A Go (golang) package for representing a list of errors as a single error.".
As of Go 1.20, we'll be able to wrap multiple errors using errors.Join.
See this proposal for more details.
First Option
You can print the errors; they will be separated by a newline character when you do that.
var (
ErrIncorrectUsername = errors.New("incorrect username")
ErrIncorrectPassword = errors.New("incorrect password")
)
func main() {
err := validate("ruster", "4321")
// You can print multi-line errors
// Each will be separated by a newline character (\n).
if err != nil {
fmt.Println(err)
// incorrect username
// incorrect password
}
}
func validate(username, password string) error {
var errs []error
// errors.Join the errors into a single error
if username != "gopher" {
errs = append(errs, ErrIncorrectUsername)
}
if password != "1234" {
errs = append(errs, ErrIncorrectPassword)
}
// Join returns a single `error`.
// Underlying, the error contains all the errors we add.
return errors.Join(errs...)
}
Second Option
errors.Join returns an error that contains each error you add. So you can use errors.Is and errors.As to check for individual errors for finer granularity.
// ...
func main() {
err := validate("ruster", "4321")
// You can detect each one:
if errors.Is(err, ErrIncorrectUsername) {
// handle the error here
}
// Or detect the other one:
if errors.Is(err, ErrIncorrectPassword) {
// handle the error here
}
}
func validate(username, password string) error {
// ...
}
Note: This naive validate example is here to convey the idea. Instead of chaining errors, think errors like a tree. Join allows you to do that when combined with other Joins.
Run both on Go Playground.
This seems to work well for me (space separated errors):
Put all your errors in a error slice/list/array, ie: var errors [] error
fmt.Sprintf("%s", errors)
var errors []error
errors = append(errors, fmt.Errorf("error 1"))
errors = append(errors, fmt.Errorf("error 2"))
errors = append(errors, fmt.Errorf("and yet another error"))
s := fmt.Sprintf("%s", errors)
fmt.Println(s)
Sometimes i need the way to detect if there some error in the chain. the standard way, provided with https://pkg.go.dev/errors is pretty convenient:
someErr:=errors.New("my error")
fmt.Errorf("can't process request: %w",someErr)
...
err:=f()
if errors.Is(err,someErr){...}
But it could be applied only in the case of detection of the last error in the chain. You can't wrap someErr1 and then someErr2 and then get true from both of checks: errors.Is(err,someErr1) and errors.Is(err,someErr2)
I solved this problem with following type:
func NewJoinedErrors(err1 error, err2 error) JoinedErrors {
return JoinedErrors{err1: err1, err2: err2}
}
type JoinedErrors struct {
err1 error
err2 error
}
func (e JoinedErrors) Error() string {
return fmt.Sprintf("%s: %s", e.err1, e.err2)
}
func (e JoinedErrors) Unwrap() error {
return e.err2
}
func (e JoinedErrors) Is(target error) bool {
return errors.Is(e.err1, target)
}
It uses the fact, that
An error is considered to match a target if it is equal to that target
or if it implements a method Is(error) bool such that Is(target)
returns true.
So you can join two errors and get positive result on both checks:
someErr1:=errors.New("my error 1")
someErr2:=errors.New("my error 2")
err:=NewJoinedErrors(someErr1, someErr2)
// this will be true because
// (e JoinedErrors) Is(target error)
// will return true
if errors.Is(err, someErr1){...}
// this will be true because
// (e JoinedErrors) Unwrap() error
// will return err2
if errors.Is(err, someErr2){...}
you can check it out here: https://play.golang.org/p/W7NGyfvr0v_N
func condenseErrors(errs []error) error {
switch len(errs) {
case 0:
return nil
case 1:
return errs[0]
}
err := errs[0]
for _, e := range errs[1:] {
err = errors.Wrap(err, e.Error())
}
return err
}
Use this function:
func JoinErrs(errs ...error) error {
var joinErrsR func(string, int, ...error) error
joinErrsR = func(soFar string, count int, errs ...error) error {
if len(errs) == 0 {
if count == 0 {
return nil
}
return fmt.Errorf(soFar)
}
current := errs[0]
next := errs[1:]
if current == nil {
return joinErrsR(soFar, count, next...)
}
count++
if count == 1 {
return joinErrsR(fmt.Sprintf("%s", current), count, next...)
} else if count == 2 {
return joinErrsR(fmt.Sprintf("1: %s\n2: %s", soFar, current), count, next...)
}
return joinErrsR(fmt.Sprintf("%s\n%d: %s", soFar, count, current), count, next...)
}
return joinErrsR("", 0, errs...)
}
It will give you nil when all errors are nil, will give you the same error when just one non-nil error, will give you numbered list of non-nil errors when multiple non-nil errors
Try it out here: https://play.golang.org/p/WttztCr-xHG

Returning an optional value and an error

What's the best signature for a function that returns an optional value and a possible error?
For example:
func findColor(name string) (RGB, error) {
...
}
(The empty RGB value is black, a valid color, so you can't use it to infer that no value was found. Assume the error might come from something like a database connection.)
The two options that seem best are a boolean return value:
func findColor(name string) (RGB, bool, error) {
...
}
c, ok, err := findColor(myname)
if !ok {
...
} else if err != nil {
...
}
...
Or a special error value:
var ColorNotFound = errors.New(...)
func findColor(name string) (RGB, error) {
...
}
c, err := findColor(...)
if err == ColorNotFound {
...
} else if err != nil {
...
}
...
(Making special errors seems like a pain.)
What's the most idiomatic approach?
The convention in Go is to return (value, error) and if error != nil then value is (or may be) invalid.
If you have special errors you need to do something with (like io.EOF) then making a specific error is normal practice. So I would say your 3rd example is the most idiomatic, if you want to do something different for ColorNotFound.
var ColorNotFound = errors.New(...)
func findColor(name string) (RGB, error) {
// ...
}
c, err := findColor(...)
if err == ColorNotFound {
// Do something special if ColorNotFound...
} else if err != nil {
// Some other kind of error...
}
You could make findColor return *RGB and then compare it to nil:
c, err := findColor(name)
if err != nil { /* Handle error. */ }
if c == nil { /* Handle no color. */ }
This is unsafe though, since if you try to call methods on a nil pointer, they can cause a panic.
I'd recommend sticking with a special ErrColorNotFound approach.

Built-in helper for "Must" pattern in Go

Is there a more built-in wrapper to make a function that returns (X, error) successfully execute or abort, like regexp.MustCompile?
I'm talking about something like this, but more "built-in".
There is not. The best you'll get is something like this:
func Must(fn func() (interface{}, error)) interface{} {
v, err := fn()
if err != nil {
log.Fatalln(err)
}
return v
}
Then to use it:
Must(func() (interface{}, error) {
return template.ParseGlob(pattern)
}).(*template.Template)
Assuming that template.ParseGlob(pattern) is the call you wanted to wrap.
Go does not have parametric polymorphism, so this kind of code will end up requiring type assertions to restore the original type and so (in my opinion) is more effort than it's worth. The tidiest, idiomatic error handling you'll get for long chains of potential failure is simply to check for an error, and return it. Defer your cleanup handlers:
func MyFunc() (err error) {
a, err := blah1()
if err != nil {
return
}
defer a.Close()
b, err := blah2(a)
if err != nil {
return
}
defer b.Close()
// ad nauseam
}
Long and tedious, but at least it's explicit and easy to follow. Here are two modules I wrote that are crying out for parametric polymorphism that might give you some ideas for dealing without it:
bitbucket.org/anacrolix/dms/futures
bitbucket.org/anacrolix/dms/cache
Since Go 1.18 we can define typed Must instead of interface{}:
func Must[T any](obj T, err error) T {
if err != nil {
panic(err)
}
return obj
}
How to use: https://go.dev/play/p/ajQAjfro0HG
func success() (int, error) {
return 0, nil
}
func fail() (int, error) {
return -1, fmt.Errorf("Failed")
}
func main() {
n1 := Must(success())
fmt.Println(n1)
var n2 int = Must(fail())
fmt.Println(n2)
}
Must fails inside main, when fail() returns non-nil error
You can even define Mustn for more than 1 return parameter, e.g.
func Must2[T1 any, T2 any](obj1 T1, obj2 T2, err error) (T1, T2) {
if err != nil {
panic(err)
}
return obj1, obj2
}
I don't think a built-in mechanism would make sense since you could very well handle a non-nil error in various ways, as does the examples in the template package itself: see "text/template/examplefiles_test.go", illustrating 2 different usage of 'err':
// Here starts the example proper.
// T0.tmpl is the first name matched, so it becomes the starting template,
// the value returned by ParseGlob.
tmpl := template.Must(template.ParseGlob(pattern))
err := tmpl.Execute(os.Stdout, nil)
if err != nil {
log.Fatalf("template execution: %s", err)
}
// Output:
// T0 invokes T1: (T1 invokes T2: (This is T2))
In the particular case of the helper function (*Template) Must(), transforming an error into an exception (panic) isn't always the right course for all go programs (as debated in this thread), and to cover all the possible way to handle an error would mean to create a lot of "built-in" mechanisms.
I have encountered the same problem myself and decided to develop the following solution: https://github.com/boramalper/must
Example:
database := must.MV(sql.Open("sqlite3", "...")).(*sql.DB)
defer must.M(database.Close())
must.M(database.Ping())
// Use MustValVoid (or MVV shortly) if you don't care about
// the return value.
must.MVV(database.Exec(`
PRAGMA foreign_key_check;
...
`))
I am not sure why all the answers here are using the log package, when the
source itself uses panic:
func MustCompile(str string) *Regexp {
regexp, err := Compile(str)
if err != nil {
panic(`regexp: Compile(` + quote(str) + `): ` + err.Error())
}
return regexp
}
My recommendation would be instead of a generic Must wrapper, just implement
Must variants as needed in your code.
https://github.com/golang/go/blob/go1.16.5/src/regexp/regexp.go#L305-L314

Resources