Error concrete type - go

I have a method CreateProduct(&Product) error that returns a value implementing error interface. It can be a gorm database error or my own error type.
Having the returned value, how can I know which type is the error?
err = api.ProductManager.CreateProduct(product)
if err != nil {
// TODO: how to distinguish that it is a validation error?
response.WriteHeader(422)
response.WriteJson(err)
return
}

You can do a type assertion, and act if the error returned is from the expected type:
if nerr, ok := err.(yourError); ok {
// do something
}
You can also do a type switch for multiple test
switch t := err.(type) {
case yourError:
// t is a yourError
case otherError :
// err is an otherError
case nil:
// err was nil
default:
// t is some other type
}
Note: a type assertion is possible even on nil (when err == nil):
the result of the assertion is a pair of values with types (T, bool).
If the assertion holds, the expression returns the pair (x.(T), true);
otherwise, the expression returns (Z, false) where Z is the zero value for type T
Here, the "zero value" of "Error" would be nil.

You can use type assertions to do that:
if gormError, ok := err.(gorm.RecordNotFound); ok {
// handle RecordNotFound error
}
if myError, ok := err.(MyError); ok {
// handle MyError
}
When dealing with multiple error cases, it can be useful to use type switches for that:
switch actualError := err.(type) {
case gorm.RecordNotFound:
// handle RecordNotFound
case MyError:
// handle MyError
case nil:
// no error
}

The same way you would go about with any other interface. Use type assertion or a type switch:
switch err := err.(type) {
case MyValidationError:
fmt.Printf("validation error")
case MyOtherTypeOfError:
fmt.Printf("my other type of error")
default:
fmt.Printf("error of type %T", err)
}
Go Playground: http://play.golang.org/p/5-OQ3hxmZ5

Related

slice of errors to concrete types in Golang

I'm playing around with error wrapping in Go and have a function that returns a wrapped custom error type. What I would like to do is iterate a list of expected errors and test if the output of the function wraps these the expected errors.
I've found that putting custom errors inside a []error means that the type of the custom errors will be *fmt.wrapError, which means errors.As() pretty much always return true.
As an example, consider the following code:
package main
import (
"errors"
"fmt"
)
type AnotherError struct {
}
func (e *AnotherError) Error() string {
return "another error"
}
type MissingAttrError struct {
missingAttr string
}
func (e *MissingAttrError) Error() string {
return fmt.Sprintf("missing attribute: %s", e.missingAttr)
}
func DoSomething() error {
e := &MissingAttrError{missingAttr: "Key"}
return fmt.Errorf("DoSomething(): %w", e)
}
func main() {
err := DoSomething()
expectedErrOne := &MissingAttrError{}
expectedErrTwo := &AnotherError{}
expectedErrs := []error{expectedErrOne, expectedErrTwo}
fmt.Printf("Is err '%v' type '%T'?: %t\n", err, expectedErrOne, errors.As(err, &expectedErrOne))
fmt.Printf("Is err '%v' type '%T'?: %t\n", err, expectedErrTwo, errors.As(err, &expectedErrTwo))
for i := range expectedErrs {
fmt.Printf("Is err '%v' type '%T'?: %t\n", err, expectedErrs[i], errors.As(err, &expectedErrs[i]))
}
}
The output of this is
Is err 'DoSomething(): missing attribute: Key' type '*main.MissingAttrError'?: true
Is err 'DoSomething(): missing attribute: Key' type '*main.AnotherError'?: false
Is err 'DoSomething(): missing attribute: Key' type '*fmt.wrapError'?: true
Is err 'DoSomething(): missing attribute: Key' type '*fmt.wrapError'?: true
Ideally I'd like the output to be
Is err 'DoSomething(): missing attribute: Key' type '*main.MissingAttrError'?: true
Is err 'DoSomething(): missing attribute: Key' type '*main.AnotherError'?: false
Is err 'DoSomething(): missing attribute: Key' type '*main.MissingAttrError'?: true
Is err 'DoSomething(): missing attribute: Key' type '*main.AnotherError'?: false
The reasons for having the slice of errors is that I'd like to be able to define a list of expected errors per test case entry. Say I know that providing the function with certain input will take it down a path that should return an error that wraps a specific error.
How can I convert the *fmt.wrapError type from the []error slice back into the original type, so I can use it with error.As?
I know I can coerce it into a specific type with .(AnotherError), but to make that work when iterating the slice, I'd have to do that for every possible error the function can return, no?)
You can trick errors.As using this:
func main() {
err := DoSomething()
m := &MissingAttrError{}
a := &AnotherError{}
expected := []any{&m, &a}
for i := range expected {
fmt.Printf("Is err '%v' type '%T'?: %t\n", err, expected[i], errors.As(err, expected[i]))
}
}
The printed type is not what you expect, but errors.As works as it should.
The reason your example did not work is because what you are passing to errors.As is a *error. Thus, the wrapped error value (which is err) is directly assigned to the target value. In my example, the value passed to errors.As is a **AnotherError, and err cannot be assigned to *AnotherError.

How to detect custom errors

Motivation
I have custom error type named CustomError and I want to write a method that parses any type of error into my error structure. So I wrote parse method to show you. I will use the parse method on any function that returns error interface. So all errors will be structured with my type.
Problem
When I use parse method with nil value to return error interface, the returning error is not nil. Code is below. First test succeeded but second did not.
const (
UnHandledError = "UnHandledError"
CircuitBreakerError = "CircuitBreakerError"
)
type CustomErrorType struct {
Key string
Message string
Status int
}
func (c *CustomErrorType) Error() string {
return "Custom error message"
}
func parse(err error) *CustomErrorType {
if err == nil {
return nil
}
if e, ok := err.(*CustomErrorType); ok {
return e
}
if e, ok := err.(hystrix.CircuitError); ok {
return &CustomErrorType{CircuitBreakerError, e.Message, http.StatusTooManyRequests}
}
return &CustomErrorType{UnHandledError, err.Error(), http.StatusInternalServerError}
}
func TestParse_it_should_return_nil_when_error_is_nil(t *testing.T) {
result := parse(nil)
if result != nil {
t.Error("result is not nil")
}
}
func TestParse_it_should_return_nil_when_error_is_nil_2(t *testing.T) {
aFunc := func() error {
return parse(nil)
}
result := aFunc()
if result != nil {
t.Error("result is not nil")
}
}
Can you explain what am I missing or what is wrong?
This is an instance of a common "problem" of go's interfaces that is caused by the actual implementation of interfaces under the hood: an interface containing a nil pointer is not-nil.
it is described in go's faq with an example that resembles your situation with the error interface: Why is my nil error value not equal to nil?
Under the covers, interfaces are implemented as two elements, a type T and a value V. V is a concrete value such as an int, struct or pointer, never an interface itself, and has type T.
...
An interface value is nil only if the V and T are both unset, (T=nil, V is not set), In particular, a nil interface will always hold a nil type. If we store a nil pointer of type *int inside an interface value, the inner type will be *int regardless of the value of the pointer: (T=*int, V=nil). Such an interface value will therefore be non-nil even when the pointer value V inside is nil.
This situation can be confusing, and arises when a nil value is stored inside an interface value such as an error return:
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // Will always return a non-nil error.
}
this example is similar to what is happening in your code when you do:
aFunc := func() error {
return parse(nil)
}
parse() returns *CustomErrorType, but the function just error making the value returned an interface that contains a type and a nil value: (T=*CustomErrorType, V=nil) that in turn evaluates to not-nil.
the faq then goes on providing explanation and showing a "correct" example:
If all goes well, the function returns a nil p, so the return value is an error interface value holding (T=*MyError, V=nil). This means that if the caller compares the returned error to nil, it will always look as if there was an error even if nothing bad happened. To return a proper nil error to the caller, the function must return an explicit nil:
func returnsError() error {
if bad() {
return ErrBad
}
return nil
}
the behavior can also be observed in your example adding a
fmt.Printf("%#v\n", result)
to print result's value:
(*e.CustomErrorType)(nil)
if we change parse() return type to just error it will print:
<nil>

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)
}

Typed map does not support indexing

I want to extend go-validator to return a better type:
type Error map[string][]error
// Will output the first error when stringified (e.g. for json response).
func (err Error) Error() string {
for k, errs := range err {
return fmt.Sprintf("%s value had %s", k, errs[0].Error())
}
return "no error"
}
func Validate(v interface{}) error {
if ok, errs := DefaultValidator.Validate(v); !ok {
return Error(errs)
}
return nil
}
As I am now writing tests for this I ran into the problem that it the typed map Error seems to have lost it indexing capabilities:
err = Validate(Value{
Foo: "bar",
Email: "foo#bar.me",
Required: "1234",
})
c.Check(err, check.IsNil)
err, ok := Validate(Value{}).(Error)
c.Check(ok, check.Equals, true)
c.Check(err["Foo"], check.DeepEquals, []error{validator.ErrMin})
c.Check(err["Email"], check.DeepEquals, []error{validator.ErrInvalid})
c.Check(err["Required"], check.DeepEquals, []error{validator.ErrZeroValue})
Returns:
model/validation/validation_test.go:42: invalid operation: err["Foo"] (type error does not support indexing)
model/validation/validation_test.go:43: invalid operation: err["Email"] (type error does not support indexing)
model/validation/validation_test.go:44: invalid operation: err["Required"] (type error does not support indexing)
I also tried to cast to type map[string][]error but got "impossible type assertion".
What is wrong with my approach? How can I get indexing back to work?
It seems like your err variable is initialised with error type. When you do
err, ok := Validate(Value{}).(Error)
you merely check if err is actually an Error. If you change err there to say errs, it should work.
Playground example: http://play.golang.org/p/ljNroPzVbd

Resources