How to detect json.SyntaxError with errors.Is - go

I can't detect a json.SyntaxError while checking the error returned from a failed decoder.Decode operation!
Here you can see a working example in playground.
As you can see the debugger confirm to me it's a pointer to a json.SyntaxError, but errors.Is can't detect it.
I checked the errors.Is implementation:
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflectlite.TypeOf(target).Comparable()
for {
if isComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
// TODO: consider supporting target.Is(err). This would allow
// user-definable predicates, but also may allow for coping with sloppy
// APIs, thereby making it easier to get away with them.
if err = Unwrap(err); err == nil {
return false
}
}
}
And they are comparable (isComparable variable is true) but, when I would expect it to return true when it does if isComparable && err == target { it goes on...
What am I doing wrong?
Thanks in advance.

What is currently happening is that you compare the memory address of a new json.SyntaxError to the error returned from Decode. As you have noticed this will never be true.
What you want to do is a bit different: check if err is of type SyntaxError and then work with that object directly. This is possible using type assertions, which basically check if the underlying type of an interface (error in this case) is a more specific type.
This is what errors.As does. It populates a specific error type that you specify. Using this method, one lands at the following code:
if err != nil {
var serr *json.SyntaxError
if errors.As(err, &serr) {
fmt.Println("Syntax error:", serr)
fmt.Println("Offset:", serr.Offset)
} else {
fmt.Println("Other error:", err)
}
}

Related

errors.Is() doesn't function propertly

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.

Checking for error message from os.Remove

What's the most idiomatic way to check error messages? My use case is that in err := os.Remove(path), I consider a success either:
A) if err == nil
or
B) if err != nil but the error is thrown because the file was not found.
any other error should cause the removal to retry. Currently I've wrapped this in a for { ... } loop and am checking:
if err == nil || strings.Contains(err.Error(), "no such file") {
// Success
} else {
// Fail
}
Since the docs say:
If there is an error, it will be of type *PathError.
I don't think there's a way to check by type assertion. Am I missing something fundamental? My error handling in Go has always felt somewhat slapdash.
I just dealt with this the other day. An error from os.Remove() will be syscall.ENOENT if the file did not exist.
So you can use logic like this:
if err != nil {
e, ok := err.(*os.PathError)
if ok && e.Err == syscall.ENOENT {
// The file didn't exist
w.WriteHeader(http.StatusNotFound)
return
} else {
// Error other than syscall.ENOENT
}
}
Of course, as shown in another answer, os.IsNotExist() is simple and idiomatic. Wish I'd seen that before today.
The "type" error is an interface. Interfaces don't have an concrete type. To get the type of the value you could use a type assertion or a type switch:
// Type assertion
_, ok := err.(*PathError)
// Type switch
switch err.(type) {
case *PathError:
// You know the type now
}
This is an idiomatic way to find out of which type an error is. As in the comments specified there is already a function inside the os package which does this for you (https://golang.org/pkg/os/#IsNotExist)

ERROR: need type assertion

I thought I have asserted (as far as I've learnt Go), but I keep getting this error
cannot use readBack["SomePIN"] (type interface {}) as type string in argument to c.String: need type assertion
Here is my code (this snippet is from a Request Handler function and I'm using Echo Web framework and Tiedot NoSQL database)
// To get query result document, simply
// read it [as stated in the Tiedot readme.md]
for id := range queryResult {
readBack, err := aCollection.Read(id)
if err != nil {
panic(err)
}
if readBack["OtherID"] == otherID {
if _, ok := readBack["SomePIN"].(string); ok {
return c.String(http.StatusOK, readBack["SomePIN"])
}
}
}
You are asserting readBack["SomePIN"] as a string - in the if statement. That doesn't make any change to readBack["SomePIN"], however - it's still an interface{}. In Go, nothing ever changes type. Here's what will work:
for id := range queryResult {
readBack, err := aCollection.Read(id)
if err != nil {
panic(err)
}
if readBack["OtherID"] == otherID {
if somePIN, ok := readBack["SomePIN"].(string); ok {
return c.String(http.StatusOK, somePIN)
}
}
}
You were tossing the string value from your type assertion, but you want it. So keep it, as somePIN, and then use it.
Final note - using the value, ok = interfaceVal.(type) syntax is a good practice. If interfaceVal turns out to be a non-string, you'll get value = "" and ok = false. If you eliminate the ok value from the type assertion and interfaceVal is a non-string, the program will panic.
It looks like your converting to a concrete type and throwing away the conversion, I think this should work:
if somePinString, ok := readBack["SomePIN"].(string); ok {
return c.String(http.StatusOK, somePinString)
}

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.

Is it possible to "return" a function from another function?

Due the verbose error handling syntax I've created a function check as below which acts as a "global" error handler. If I want to panic instead to log I change only the check function. The issue is that now I want to simply return so that the other go routines can still run if one has an error. So the question is: How can I do that ? Is it possible?
func main() {
for k, v := range foo {
go func() {
err = doSomething()
check("this one failed", err)
}()
}
}
func check(errMsg string, err error) {
if err != nil {
log.Fatalf(errMsg, err)
}
}
However now I've found that I need return the anonymous function if there is any error rather than exit ( log.Fatal ) so I'm wondering if it's possible to return the anyonymou
You could make your check function returns a bool:
func checkIfFailed(errMsg string, err error) bool {
if err != nil {
log.Printf(errMsg, err)
return true
}
return false
}
That way, you can still call your check (and do all kind of checks in it), while returning early from the anonymous function:
err = doSomething()
if checkIfFailed("this one failed", err) {
return
}
There isn't a language feature that allows you to cause the parent function to automatically return in response to a simple function call.
However, there is a way to cause the current goroutine to exit, which might be what you are after: runtime.Goexit. Note that this has similar disruptive potential to calling os.Exit, so it would be bad to call it in the context of goroutines created by other packages or other unrelated code.

Resources