errors.Is() doesn't function propertly - go

I pasted a section of code that was supposed to catch an AllTopologyNodesDownError error which doesn't work and I have no idea why.
func (sc *ServerConfig) addNodesToCluster(store *ravendb.DocumentStore) error {
clusterTopology, err := sc.getClusterTopology(store)
if errors.Is(err, &ravendb.AllTopologyNodesDownError{}) {
for _, url := range sc.Url.List {
err = addNodeToCluster(store, url)
if err != nil {
return err
}
}
} else if err != nil {
return err
}
the structure of the ravendb.AllTopologyNodesDownError is
// AllTopologyNodesDownError represents "all topology nodes are down" error
type AllTopologyNodesDownError struct {
errorBase
}
type errorBase struct {
wrapped error
ErrorStr string
}
screen shot of the error when debugging the code

errors.Is() is used to tell if any error in the chain is the same instance as the provided error1, that can never be the case here because you provided a literal of your error type, no other code could hold that instance or a reference to it.
Your error looks like a type, to tell if any error in the chain is a given type you should use errors.As():
clusterTopology, err := sc.getClusterTopology(store)
var errAllDown *AllTopologyNodesDownError
if errors.As(err, &errAllDown) {
// err had an *AllTopologyNodesDownError in its
// chain and errAllDown now contains it.
}
Can be overridden by implementing the Unwrap() interface which your error type does not.

Related

How to make an addressable getter that casts from an interface

I have the concept of Context which is a map that can hold any structure. Basically, I want to create a generic getter that adddressably 'populates' the destination interface (similarly to how json decoding works).
Here's an example of how I want this to work:
type Context map[string]interface{}
// Random struct that will be saved in the context
type Step struct {
Name string
}
func main() {
stepA := &Step{Name: "Cool Name"}
c := Context{}
c["stepA"] = stepA
var stepB *Step
err := c.Get("stepA", stepB)
if err != nil {
panic(err)
}
fmt.Println(stepB.Name) // Cool Name
stepB.Name = "CoolName2"
fmt.Println(stepA.Name) // I want to say: CoolName2
}
func (c Context) Get(stepId string, dest interface{}) error {
context, ok := c[stepId]
if !ok {
return nil
}
destinationValue := reflect.ValueOf(dest)
contextValue := reflect.ValueOf(context)
destinationValue.Set(contextValue) // Errors here
return nil
}
I leaned towards using reflect, but maybe I don't need it? - so opened to other suggestions (except for generics as that complicates other matters) I'm getting the following error with the above:
panic: reflect: reflect.Value.Set using unaddressable value
You can test it here.
The argument passed to Get must be a pointer type whose element type is identical to the type in the context map. So if the value in the context map is of type *Step, then the argument's type must be **Step. Also the passed in argument cannot be nil, it can be a pointer to nil, but it itself cannot be nil.
So in your case you should do:
var stepB *Step
err := c.Get("stepA", &stepB) // pass pointer-to-pointer
if err != nil {
panic(err)
}
And the Get method, fixed up a bit:
func (c Context) Get(stepId string, dest interface{}) error {
context, ok := c[stepId]
if !ok {
return nil
}
dv := reflect.ValueOf(dest)
if dv.Kind() != reflect.Ptr || dv.IsNil() {
return errors.New("dest must be non-nil pointer")
}
dv = dv.Elem()
cv := reflect.ValueOf(context)
if dv.Type() != cv.Type() {
return errors.New("dest type does not match context value type")
}
dv.Set(cv)
return nil
}
https://go.dev/play/p/OECttqp1aVg

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

Error handling: returning build-in error vs custom type

In Go's standard libraries, the built-in error is used for error handling. This post used some examples from the std libs to demonstrate how error handling works in Go.
package net
type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
time.Sleep(1e9)
continue
}
if err != nil {
log.Fatal(err)
}
I am wondering what would be the benefits of sticking to this convention.
What if we return the custom type? e.g. net.Error type.
I assume that you are talking about the difference between DoRequest() error vs DoRequest() net.SomeSpecificError.
Following code
type CustomError struct {
}
func (CustomError) Error() string {
return "custom error"
}
func DoRequest() *CustomError {
return nil
}
func MarkTaskComplete() error {
return nil
}
func main() {
err := DoRequest()
if err != nil {
log.Fatal(err)
}
err = MarkTaskComplete()
if err != nil {
log.Fatal(err)
}
}
will fail to compile because error returned by MarkTaskComplete cannot be assigned to err variable which is of type *SomeError.
Second disadvantage is that you cannot return from DoRequest any other error than SomeError. There may be a few situations where you would benefit from such a restriction, but in general in most cases the approach with error is more flexible. Especially when your function calls another one.
It is also worth to read about the errors wrapping
The benefits of the customer type, ie net.Error is:
type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}
The above has the advantage of being more descriptive. We get more information about the error like whether it is a Timeout error or a Temporary error.
In addition to that, we also get to know the primary data which is in the primary field "error", in the above example, which is a string.
type error interface {
Error() string
}

How to match error with dependency injection?

What is the best practice for error matching when using dependency injection?
If we have an interface like this used in a function:
type MyIF interface {
Send(reciever, msg string) error
}
function mySender(s MyIF) {
err := s.Send("me#myself.com", "hello")
if err != nil {
if err == ??? {
}
}
}
How can err be matched? In contrast to an import from a package, I can not import a defined error variable which can be used for comparison or check with .Is().
The solution linked by #Christian works for me (see blog.golang.org/error-handling-and-go). By adding an Error interface as in the net package in the blog post I can do what I want.
Thanks.
The code snippet would then look like this:
type MyIF interface {
Send(reciever, msg string) error
}
type MyError interface {
Error() string
ReceiverUnknown() bool
}
function mySender(s MyIF) {
err := s.Send("me#myself.com", "hello")
if err != nil {
if nerr, ok := err.(MyError); ok && nerr.ReceiverUnknown() {
// handle unknown Receiver
}
}
}

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.

Resources