Handling Aerospike library errors in Go Lang - go

I am quite new to Go Lang development. Recently I am using Aerospike Go client to getObject
err = aer.AeroDB.getObject(nil, key, Record)
if err != nil {
fmt.Println(err)
}
now the above error exposes only one method Error() which returns a string. I need to handle each type of errors differently. How do I do this, as there are no error codes returned. Do I do string matching to get the relevant type?
SOLUTION: Answer and comments below helped me to find an exact answer. I will share it here with the rest. Aerospike libraries export AerospikeError struct. Now, error could be nil or AerospikeError struct. Following code did the work.
import (
"errors"
"fmt"
aerospike "github.com/aerospike/aerospike-client-go"
"github.com/aerospike/aerospike-client-go/types"
)
type ArDB struct {
Host string
Port int
AeroDB *aerospike.Client
}
ArErr, failed := aer.AeroDB.GetObject(nil, key, Record).(types.AerospikeError)
if failed {
if ArErr.ResultCode() == types.KEY_NOT_FOUND_ERROR {
//Key is not present, create new data
Record = NewAudienceRecord()
} else {
//Handle other errors!
}
}

If the function returns errors which are actually of different type, then you should use type switch:
switch err.(type) {
case Error1:
fmt.Println("Error1", err)
case Error2:
fmt.Println("Error2", err)
default:
fmt.Println(err)
}
But if the errors are of same type then you have to see does the package export some "common errors" as variables, so that you can check against those:
if err == aer.Error1 {
// do something specific to Error1
}

Aerospike Go client dev here. In my experience, the concise conditional typecast shown below is more readable, albeit being the same as your own:
if ae, ok := err.(AerospikeError); ok {
println(ae.ResultCode())
}

Related

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 design an individualized error, and then test for it (as opposed to the description in the error)? [duplicate]

This question already has answers here:
Compare error message in golang
(2 answers)
Closed 1 year ago.
Context: I am an amateur dev coming from Python and I am just starting with Go
(Python background to show how I manage errors today)
In Python, the typical way to raise exceptions (errors) with your own "error type" is
class noMoreBeer(Exception):
pass
try:
a_function()
except noMoreBeer as e:
print("there is no more beer ({e}), go buy some")
except Exception as e:
print(f"unexpected exception happened, namely {e}")
else:
print("thinks went fine")
The main part I would like to port to Go philosophy is that I created my own exception which can have optional explanation text, but I check for noMoreBeer, not the error text.
Now back to Go, I read several pages on how to handle errors (while I was annoyed first, I now find that it makes for better code), among them on the Go Blog. Out of this I tried to replicate the above, but the code below does not work (JetBrain's Goland points to return Error.noMoreBeer() and if err == Error.noMoreBeer())
package main
import "fmt"
type Error interface {
error
noMoreBeer() bool
}
func thisFails() Error {
// returns my specific error, but could also percolate some other errors up
return Error.noMoreBeer()
}
func main() {
err := thisFails()
if err != nil {
if err == Error.noMoreBeer() {
fmt.Println("go buy some beer")
} else {
panic("something unexpected happened")
}
}
}
Is there a way in Go to create such specific errors?
One of the main drivers for them in my case is that I do not rely on the text passed in the error, but on a [class|whatever] which, if it has a typo, will be an error.
There's four functions you should look into. Here's the official Go blog post for this subject. It was introduced in Go 1.13.
fmt.Errorf: Create a new error with some details. Can wrap errors with %w.
errors.New: Create a new error type that can be wrapped and compared with functions introduced in Go 1.13.
errors.Is: Compare an error variable with an error type. It can unwrap errors.
errors.As: Compare an error variable with an error interface implementation. It can unwrap errors.
My first shot at a solution:
package main
import (
"errors"
"fmt"
)
var noMoreBeer = errors.New("no more beer")
func thisFails() error {
// returns my specific error, but could also percolate some other errors up
return noMoreBeer
}
func main() {
err := thisFails()
if err != nil {
if err == noMoreBeer {
fmt.Println("go buy some beer")
} else {
panic("something unexpected happened")
}
}
}
The key point I understood reading https://blog.golang.org/go1.13-errors is that I can test against a variable which type is error. This is functionally equivalent to the Python example I started with.
You could use a type switch.
package main
import "fmt"
// the interface
type NoMoreBeerError interface {
noMoreBeer() bool
}
// two different implementations of the above interface
type noMoreBeerFoo struct { /* ... */ }
type noMoreBeerBar struct { /* ... */ }
func (noMoreBeerFoo) noMoreBeer() bool { return true }
func (noMoreBeerBar) noMoreBeer() bool { return false }
// have the types also implement the standard error interface
func (noMoreBeerFoo) Error() string { return " the foo's error message " }
func (noMoreBeerBar) Error() string { return " the bar's error message " }
func thisFails() error {
if true {
return noMoreBeerFoo{}
} else if false {
return noMoreBeerFoo{}
}
return fmt.Errorf("some other error")
}
func main() {
switch err := thisFails().(type) {
case nil: // all good
case NoMoreBeerError: // my custom error type
if err.noMoreBeer() { // you can invoke the method in this case block
// ...
}
// if you need to you can inspect farther for the concrete types
switch err.(type) {
case noMoreBeerFoo:
// ...
case noMoreBeerBar:
// ...
}
default:
// handle other error type
}
}
We have been using errors by cutome implementation of error interface in production without any issues. This is helping detect the error and find root cause more efficiently.
package errors
type Error struct {
err error // the wrapped error
errType ErrorType
errInfo ErrorInfo
op Op
// domain specific data
userID int
requestID string
}
type (
ErrorType string
ErrorInfo string
Op string
)
var NoMoreBeer ErrorType = "NoMoreBeerError"
func (e Error) Error() string {
return string(e.errInfo)
}
func Encode(op Op, args ...interface{}) error {
e := Error{}
for _, arg := range args {
switch arg := arg.(type) {
case error:
e.err = arg
case ErrorInfo:
e.errInfo = arg
case ErrorType:
e.errType = arg
case Op:
e.op = arg
case int:
e.userID = arg
case string:
e.requestID = arg
}
}
return e
}
func (e Error) GetErrorType() ErrorType {
return e.errType
}
By this, you can use errors as you'd use them but adding the capabilities of a expansion as needed.
e := errors.Encode(
"pkg.beerservice.GetBeer",
errors.NoMoreBeer,
errors.ErrorInfo("No more beer available"),
).(errors.Error)
switch e.GetErrorType() {
case errors.NoMoreBeer:
fmt.Println("no more beer error")
fmt.Println(e.Error()) // this will return "No more beer available"
}
Working example on playground: https://play.golang.org/p/o9QnDOzTwpc

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

How can I check for errors in CRUD operations using GORM?

The official documentation for GORM demonstrates a way in which one can test for the existence of a record, i.e.:
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
// returns true if record hasn’t been saved (primary key `Id` is blank)
db.NewRecord(user) // => true
db.Create(&user)
// will return false after `user` created
db.NewRecord(user) // => false
This can be used to test indirectly for errors in record creation but reports no useful information in the event of a failure.
Having checked the source code for db.Create, there seems to be some sort of stack-frame inspection that checks for errors before proceeding, meaning that transactional errors will fail silently:
func Create(scope *Scope) {
defer scope.Trace(NowFunc())
if !scope.HasError() {
// actually perform the transaction
}
}
Is this a bug, or am I missing something?
How can/should I be informed of a failed transaction?
Where can I get useful debugging information?
DB.Create() returns a new (cloned) gorm.DB which is a struct and has a field Error:
type DB struct {
Value interface{}
Error error
RowsAffected int64
// contains filtered or unexported fields
}
You can store the returned *gorm.DB value and check its DB.Error field like this:
if dbc := db.Create(&user); dbc.Error != nil {
// Create failed, do something e.g. return, panic etc.
return
}
If you don't need anything else from the returned gorm.DB, you can directly check its Error field:
if db.Create(&user).Error != nil {
// Create failed, do something e.g. return, panic etc.
return
}
I have tried the accepted answer, but it doesn't work, db.Error always return nil.
Just change something and it works, hope it helps somebody:
if err := db.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// Create failed, do something e.g. return, panic etc.
return
}
If you want to check type of error, just do it.
if err := db.Create(&user).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
fmt.Println(err.Error())
}
return
}

Resources