Elegant way to check error is wrapped in Go - go

We have one function that returns errors one of them is wrapped by errors.Wrap(), the others are not.
var ErrTest1 = errors.New("error test 1")
var ErrTest2 = errors.New("error test 2")
var ErrRPC = errors.New("error rpc")
func rpcCall() error {
return ErrRPC
}
func testErrWrap(a int) error {
if a == 1 {
return ErrTest1
} else if a == 2 {
return ErrTest2
} else {
err := rpcCall()
if err != nil {
return errors.Wrap(ErrRPC, "call rpc err")
}
}
return nil
}
We have two solutions, one is
err := testErrWrap(3)
if errors.Unwrap(err) != nil {
fmt.Println(errors.Unwrap(err))
}
the other is
err := testErrWrap(3)
if !errors.Is(err, ErrTest2) && !errors.Is(err, ErrTest1) {
tErr := errors.Unwrap(err)
fmt.Println(tErr)
}
We want to know the elegant way to distinguish errors are wrapped or not in Go?

In most cases we're looking to find out if we got a specific type of error in order to do some special logic to handle it. I'd argue there are better ways to do this than looking at whether the error was wrapped or not.
In this case I'd propose using custom error types and using errors.Is or errors.As
First let's create some code that has a custom error type:
type ErrRPC struct {
retryPort int
}
func (e ErrRPC) Error() string {
return "Oh no, an rpc error!"
}
func testErrWrap(a int) error {
switch a {
case 1:
return errors.New("errors test 1")
case 2:
return errors.New("errors test 1")
default:
err := rpcCall(9000)
return fmt.Errorf("errors test: %w", err) // Wraps the ErrRPC
}
}
func rpcCall(port int) error {
return ErrRPC{retryPort: port + 1}
}
If we're only interested in going down a different code path when we get a specific error type I'd go with errors.Is
func main() {
for i := 1; i <= 3; i++ {
if err := testErrWrap(i); err != nil {
if errors.Is(err, ErrRPC{}) {
println("rpc error")
} else {
println("regular error")
}
}
}
}
If we need to use a property of the error value for something, errors.As comes in handy.
func main() {
for i := 1; i <= 3; i++ {
if err := testErrWrap(i); err != nil {
var rpcErr ErrRPC
if errors.As(err, &rpcErr) {
fmt.Printf("rpc error, retrying on port: %d", rpcErr.retryPort)
} else {
println("regular error")
}
}
}
}

Another method from puellanivis
Also consider testing for behavior:
err := testErrWrap(3)
var wrappedErr interface { Unwrap() error }
if errors.As(err, &wrappedErr) {
fmt.Println(errors.Unwrap(err))
}
But also of note, errors.Wrap(…) double wraps your error: one WithMessage and one WithStack. So using errors.Unwrap(err) on the error from errors.Wrap(ErrRPC, "call rpc err") will not give you ErrRPC.

Related

Go - How can I subtype a wrapped error class?

I am wrapping errors (to add context) and am afterwards distinguishing between two errors. This is a scenario that I am currently using for tests. (Did the function recognise the error correctly?) My question is how I can reduce the verbosity.
I have two functions that create different errors:
func a() error {
return errors.New("a")
}
func b() error {
return errors.New("b")
}
They are both called by a third function that propagates the erorr.
func doStuff() error {
err := a()
if err != nil {
return WrapA{err}
}
err = b()
if err != nil {
return WrapB{err}
}
return nil
}
In my main function, I distinguish between both errors.
func main() {
fmt.Println("Hello, playground")
err := doStuff()
switch err.(type) {
case WrapA:
fmt.Println("error from doing a")
case WrapB:
fmt.Println("error from doing b")
case nil:
fmt.Println("nil")
default:
fmt.Println("unknown")
}
}
So far, so good. Unfortunately, to implement WrapA and WrapB, I need a lot of code:
type WrapA struct {
wrappedError error
}
func (e WrapA) Error() string {
return e.wrappedError.Error()
}
func (e WrapA) Unwrap() error {
return e.wrappedError
}
type WrapB struct {
wrappedError error
}
func (e WrapB) Error() string {
return e.wrappedError.Error()
}
func (e WrapB) Unwrap() error {
return e.wrappedError
}
In other languages, I would create a single Wrap struct and let WrapA and WrapB inherit from Wrap. But I don't see a way to do this in Go.
Any ideas on how to reduce the clutter?
Go Playground https://play.golang.org/p/ApzHC_miNyV
EDIT:
After seeing jub0bs answer, I want to clarify:
Both a() and b() are callbacks I have no control over. They may return various errors. This is the reason why I wrap them.
If I understand the problem correctly, you can indeed simplify things:
Define a and b as package-level error variables for ease and better performance.
Unless you require programmatic access to values only accessible in the context of the error that you're wrapping, you most likely don't need to declare those custom WrapA and WrapB error types. Instead, you can simply use the %w verb in conjunction with fmt.Errorf to produce a new error value that wraps the lower-level error.
You can then use errors.Is within a tagless switch to inspect the cause of the higher-level error returned by your doStuff function.
(Playground)
package main
import (
"errors"
"fmt"
)
var (
a = errors.New("a")
b = errors.New("b")
)
func doStuff() error {
err := a
if err != nil {
return fmt.Errorf("%w", err)
}
err = b
if err != nil {
return fmt.Errorf("%w", err)
}
return nil
}
func main() {
fmt.Println("Hello, playground")
switch err := doStuff(); {
case errors.Is(err, a):
fmt.Println("error from doing a")
case errors.Is(err, b):
fmt.Println("error from doing b")
case err == nil:
fmt.Println("nil")
default:
fmt.Println("unknown")
}
}
Adding a structured error version which composes a type Wrap along various more specific error types;
package main
import (
"errors"
"fmt"
)
func a() error {
return errors.New("something more specific broke in a")
}
func b() error {
return errors.New("something more specific broke in b")
}
func doStuff() error {
err := a()
if err != nil {
return ErrA{
Wrap: Wrap{err: err},
SpecficProp: "whatever",
}
}
err = b()
if err != nil {
return ErrB{
Wrap: Wrap{err: err},
SpecficProp2: "whatever else",
}
}
return nil
}
func main() {
fmt.Println("Hello, playground")
err := doStuff()
if target := (ErrA{}); errors.As(err, &target) {
fmt.Printf("%v\n", target)
} else if target := (ErrB{}); errors.As(err, &target) {
fmt.Printf("%v\n", target)
} else if err != nil {
fmt.Println("unknown")
} else {
fmt.Println("nil")
}
}
type Wrap struct {
err error
}
func (e Wrap) Error() string {
return e.err.Error()
}
func (e Wrap) Unwrap() error {
return e.err
}
type ErrA struct {
Wrap
SpecficProp interface{}
}
func (e ErrA) Error() string {
return fmt.Sprintf("got error of kind A with %#v, plus %T", e.SpecficProp, e.Unwrap())
}
type ErrB struct {
Wrap
SpecficProp2 interface{}
}
func (e ErrB) Error() string {
return fmt.Sprintf("got error of kind B with %#v, plus %T", e.SpecficProp2, e.Unwrap())
}
Use constant errors if you can. Then you could switch on the error itself.

Keep retrying a function in Golang

I am trying to make a functionality which would work in the following manner:
As soon as the service function is called, it uses the Fetch function to get records from a service (which come in the form of byte array), JSON unmarshal the byte array, populate the struct and then send the struct to a DB function to save to database.
Now, since this needs to be a continuous job, I have added two if conditions such that, if the records received are of length 0, then we use the retry function to retry pulling the records, else we just write to the database.
I have been trying to debug the retry function for a while now, but it is just not working, and basically stops after the first retry (even though I specify the attempts as 100). What can I do to make sure, it keeps retrying pulling the records ?
The code is as Follows:
// RETRY FUNCTION
func retry(attempts int, sleep time.Duration, f func() error) (err error) {
for i := 0; ; i++ {
err = f()
if err == nil {
return
}
if i >= (attempts - 1) {
break
}
time.Sleep(sleep)
sleep *= 2
log.Println("retrying after error:", err)
}
return fmt.Errorf("after %d attempts, last error: %s", attempts, err) }
//Save Data function
type Records struct {
Messages [][]byte
}
func (s *Service) SaveData(records Records, lastSentPlace uint) error {
//lastSentPlace is sent as 0 to begin with.
for i := lastSentPlace; i <= records.Place-1; i++ {
var msg Records
msg.Unmarshal(records.Messages[i])
order := MyStruct{
Fruit: msg.Fruit,
Burger: msg.Burger,
Fries: msg.Fries,
}
err := s.db.UpdateOrder(context.TODO(), nil , order)
if err != nil {
logging.Error("Error occured...")
}
}return nil}
//Service function (This runs as a batch, which is why we need retrying)
func (s *Service) MyServiceFunction(ctx context.Context, place uint, length uint) (err error) {
var lastSentPlace = place
records, err := s.Poll(context.Background(), place, length)
if err != nil {
logging.Info(err)
}
// if no records found then retry.
if len(records.Messages) == 0 {
err = retry(100, 2*time.Minute, func() (err error) {
records, err := s.Poll(context.Background(), place, length)
// if data received, write to DB
if len(records.Messages) != 0 {
err = s.SaveData(records, lastSentPlace)
}
return
})
// if data is not received, or if err is not null, retry
if err != nil || len(records.Messages) == 0 {
log.Println(err)
return
}
// if data received on first try, then no need to retry, write to db
} else if len(records.Messages) >0 {
err = s.SaveData(records, lastSentPlace)
if err != nil {
return err
}
}
return nil }
I think, the issue is with the way I am trying to implement the retry function, I have been trying to debug this for a while, but being new to the language, I am really stuck. What I wanted to do was, implement a backoff if no records are found. Any help is greatly appreciated.
Thanks !!!
I make a simpler retry.
Use simpler logic for loop to ensure correctness.
We sleep before executing a retry, so use i > 0 as the condition for the sleeping.
Here's the code:
func retry(attempts int, sleep time.Duration, f func() error) (err error) {
for i := 0; i < attempts; i++ {
if i > 0 {
log.Println("retrying after error:", err)
time.Sleep(sleep)
sleep *= 2
}
err = f()
if err == nil {
return nil
}
}
return fmt.Errorf("after %d attempts, last error: %s", attempts, err)
}
I know this is an old question, but came across it when searching for retries and used it as the base of a solution.
This version can accept a func with 2 return values and uses generics in golang 1.18 to make that possible. I tried it in 1.17, but couldn't figure out a way to make the method generic.
This could be extended to any number of return values of any type. I have used any here, but that could be limited to a list of types.
func retry[T any](attempts int, sleep int, f func() (T, error)) (result T, err error) {
for i := 0; i < attempts; i++ {
if i > 0 {
log.Println("retrying after error:", err)
time.Sleep(time.Duration(sleep) * time.Second)
sleep *= 2
}
result, err = f()
if err == nil {
return result, nil
}
}
return result, fmt.Errorf("after %d attempts, last error: %s", attempts, err)
}
Usage example:
var config Configuration
something, err := retry(config.RetryAttempts, config.RetrySleep, func() (Something, error) { return GetSomething(config.Parameter) })
func GetSomething(parameter string) (something Something, err error) {
// Do something flakey here that might need a retry...
return something, error
}
Hope that helps someone with the same use case as me.
The function you are calling is using a context. So it is important that you handle that context.
If you don't know what a context is and how to use it, I would recomend that post: https://blog.golang.org/context
Your retry function should also handle the context. Just to get you on the track I give you a simple implementation.
func retryMyServiceFunction(ctx context.Context, place uint, length uint, sleep time.Duration) {
for {
select {
case ctx.Done():
return
default:
err := MyServiceFunction(ctx, place, length)
if err != nil {
log.Println("handle error here!", err)
time.Sleep(sleep)
} else {
return
}
}
}
}
I don't like the sleep part. So you should analyse the returned error. Also you have to think about timeouts. When you let your service sleep to long there could be a timeout.
There is a library for the retry mechanism.
https://github.com/avast/retry-go
url := "http://example.com"
var body []byte
err := retry.Do(
func() error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return nil
},
)
fmt.Println(body)
In the GoPlayground in the comments of the accepted answer, there are some things I would consider adding. Using continue and break in the for loop would make the loop even simpler by not using the if i > 0 { statement. Furthermore I would use early return in all the functions to directly return on an error. And last I would consistently use errors to check if a function failed or not, checking the validity of a value should be inside the executed function itself.
This would be my little attempt:
package main
import (
"errors"
"fmt"
"log"
"time"
)
func main() {
var complicatedFunctionPassing bool = false
var attempts int = 5
// if complicatedFunctionPassing is true retry just makes one try
// if complicatedFunctionPassing is false retry makes ... attempts
err := retry(attempts, time.Second, func() (err error) {
if !complicatedFunctionPassing {
return errors.New("somthing went wrong in the important function")
}
log.Println("Complicated function passed")
return nil
})
if err != nil {
log.Printf("failed after %d attempts with error: %s", attempts, err.Error())
}
}
func retry(attempts int, sleep time.Duration, f func() error) (err error) {
for i := 0; i < attempts; i++ {
fmt.Println("This is attempt number", i+1)
// calling the important function
err = f()
if err != nil {
log.Printf("error occured after attempt number %d: %s", i+1, err.Error())
log.Println("sleeping for: ", sleep.String())
time.Sleep(sleep)
sleep *= 2
continue
}
break
}
return err
}
You can try it out here:
https://go.dev/play/p/Ag8ObCb980U

How can I use errors.Is on an error created by errors.Wrap?

I like to do this:
var ErrMyCustomError = errors.New("something went wrong")
func doAThing(input string) error {
if input == "bad input" {
return ErrMyCustomError
}
return nil
}
And then in my tests:
func TestFailCase() {
err := doAThing("bad input")
require.True(errors.Is(err, ErrMyCustomError)
}
My question is how do I do all that but have return ErrMyCustomError instead wrap an error I got from somewhere else in an ErrMyCustomError (so that the errors.Is still works).
func doAThing(input string) error {
err := doSomething(input)
if err != nil {
return errors.Wrap(err, "some context message") // <-- this line here needs to return an ErrMyCustomError somehow but also wrap err
}
}
// Imagine this is in some third party lib and I can't alter it.
func doSomething(input string) error {
if input == "bad input" {
return errors.New("some error I will later wrap")
}
return nil
}
func TestFailCase() {
err := doAThing("bad input")
require.True(errors.Is(err, ErrMyCustomError) // <-- this is no longer true but I want it to be
}
This is the best I have been able to come up with. I'd prefer to just var MyCustomErr = errors.New("something went wrong") but then I have no way to get it into the error chain. I'd prefer to errors.Is rather than having to declare a receiver pointer to check errors.As but so far I haven't found a way to do that either.
package main
import (
"errors"
"fmt"
)
type MyCustomError struct {
Err error
}
func (e MyCustomError) Error() string {
return "something went wrong: " + e.Err.Error()
}
func main() {
err := myWrapper()
ptr := &MyCustomError{}
print(errors.As(err, ptr)) // prints "true"
}
func myWrapper() error {
err := thirdPartyFunc()
if err != nil {
return fmt.Errorf("some additional context: %w", MyCustomError{Err: err})
}
return nil
}
func thirdPartyFunc() error {
return errors.New("this is the error I want to wrap")
}

Mocked method not working in golang while running the test cases

I am trying to mock an struct method in test cases but it is not working.
I want to mock Validate method here:
`
package main
import (
"fmt"
)
type DemoInterface interface {
Inc(int) (int, error)
Validate(int) error
}
type DemoStruct struct{}
func (l DemoStruct) Inc(num int) (int, error) {
err := l.Validate(num)
if err != nil {
return 0, err
}
num = num + 100
return num, nil
}
func (l DemoStruct) Validate(num int) error {// SOME DB LOGIC IS HERE WHICH I CAN NOT POST at Stackoverflow
if num > 100 {
return fmt.Errorf("INVALID NUM %v", num)
}
return nil
}
func main() {
s, err := DemoStruct{}.Inc(10)
if err != nil {
fmt.Println(err)
}
fmt.Println(s)
}
`
My test cases:
package main
import (
"fmt"
"testing"
)
const (
SUCCESS = "SUCCESS"
ERROR = "ERROR"
)
type MockDemoStruct struct {
DemoStruct
functionality string
}
func (m MockDemoStruct) Validate(num int) error {
switch m.functionality {
case SUCCESS:
return nil
case ERROR:
fmt.Errorf("MOCK ERROR %v", num)
}
return fmt.Errorf("MOCK ERROR %v", num)
}
func TestPath(t *testing.T) {
t.Run("ERROR", func(t *testing.T) {
ls := MockDemoStruct{DemoStruct{}, ERROR}
res, err := ls.Inc(110)
expected := fmt.Errorf("MOCK ERROR %v", 10)
if err != expected {
t.Errorf("NOT MATCH %v %v", err, expected)
//NOT MATCH INVALID NUM 110 MOCK ERROR 10
}
fmt.Println(res)
})
}
Here MockDemoStruct.Validate is not being called.
I am getting INVALID NUM 110 from Validate, but it should be MOCK ERROR 110
In this case the method Inc in the DemoStruct calls the method l.Validate where l is a DemoStruct. The reciever of that method is explicitly a DemoStruct. So the MockDemoStruct.Validate method will not be called.
Go does not have inheritance as you assumed here in your code. You can not override the method of the DemoStruct. The MockDemoStruct composes the DemoStruct. To actually test this method, I suggest passing the DemoStruct a db interface, which can be mocked in your test.
I think you also need to implement 'Inc' receiver for 'MockDemoStruct', here you trying to overuse the inheritance property of struct, looks like GO doesn't support that.
To make the method mockable we will have to use a DI(dependency injection) based code pattern.
**We can mock only those methods which are injectable**.
We have two options to introduce dependency injection in this code.
Use Delegation Design pattern with the help of interface
Introduce Monkey patching using the function as a type
Delegation using interface:
type Deligation interface {
Validate(num int) error
}
type DemoStruct struct {
delegate Deligation
}
func (DemoStruct) Validate(num int) error {
if num > 100 {
return fmt.Errorf("INVALID NUM %v", num)
}
return nil
}
func (l DemoStruct) Inc(num int) (int, error) {
err := l.delegate.Validate(num) // Call method using delegate
if err != nil {
return 0, err
}
num = num + 100
return num, nil
}
func main() {
s, err := DemoStruct{delegate: DemoStruct{}}.Inc(10) // assign delegate inside DemoStruct
if err != nil {
fmt.Println(err)
}
fmt.Println(s)
}
Using Monkey patching:
func Validate(num int) error {
if num > 100 {
return fmt.Errorf("INVALID NUM %v", num)
}
return nil
}
type DemoStruct struct {
Validate func(num int) error // function as a type
}
func (l DemoStruct) Inc(num int) (int, error) {
err := l.Validate(num)// It can be replaced in test cases.
if err != nil {
return 0, err
}
num = num + 100
return num, nil
}
func main() {
s, err := DemoStruct{Validate: Validate}.Inc(10) // assign Validate inside DemoStruct
if err != nil {
fmt.Println(err)
}
fmt.Println(s)
}
Ref: https://blog.myhro.info/2018/06/how-to-mock-golang-methods

Handling multiple errors

I have Function 1:
func Function1() {
if err := Function2(); err != nil {
}
}
and Function2:
func Function2() error {
if err := doSomethingThatMightCauseError(); err != nil {
return errors.New("Error Type 1")
}
if err := doSomethingElseThatMightCauseError(); err != nil {
return errors.New("Error Type 2")
}
}
How can I detect what type of error has happened (internal, no results found in db etc) and then handle accordingly in Function 1?
You have 3 main options:
string based, i.e. looking into the message. This is of course pretty bad because if you later change one letter in the message, you need to rewrite all the checking code, so I'd avoid it.
If the error messages can stay constant, simply create errors as global variables, and then compare the received error with a known pre-defined one.
For example:
var ErrDB = errors.New("Database Error")
var ErrTimeout = errors.New("Timeout") //or whatever
and then
if err := someFunc(); err != nil {
switch err {
case ErrDB:
//do stuff that needs to be done here
case ErrTimeout:
//etc /etc
}
}
Create a custom error type, since errors are just interfaces, that can have some identifier or other contextual data.
For example:
const (
ErrDB = 1
ErrTimeout = 2
...
)
type MyError struct {
Code int
Message string
}
// This is what you need to be an error
func (e MyError)Error() string {
return e.Message
}
func NewError(s string, code int) error {
return MyError{s,code}
}
and then when you return it do something like this:
// Return a MyError with a DB code for db operations
func someFunc() error {
if err := talkToDB(); err != nil {
return NewError(err.Error(), ErrDB)
}
return nil
}
and when analyzing it:
if err := someFunc(); err != nil {
// check if this is a MyError
if me, ok := err.(MyError); ok {
// now we can check the code
switch me.Code {
case ErrDB:
//handle this
....
}
}
}

Resources