Let's say I need an abstraction for some type that has a method Do
, which returns an error
package main
import "myproject/complex"
type Doer interface {
Do(i int) error
}
func main() {
var doer Doer
doer = complex.Doer{}
i := 1 // or any integer
err = doer.Do(i)
// How to check which error was returned?
}
package complex
type Doer struct{}
func (Doer) Do(i int) error {
if i < 0 {
return fmt.Errorf("negative numbers not allowed") // or any complex nested error but this can be expected (bad input, etc)
}
if i == 40 {
return fmt.Errorf("this number cannot be used") // or any complex nested error but this can be expected (bad input, etc)
}
if i > 100 {
return fmt.Errorf("some unknown error, everything broke") // or any complex nested error which is unexpected (e. g. some 3rd party failure)
}
}
I would need a better architecture to be able to identify if this is known error or not. I could do it java-style and make implementation know about our error types. e. g.
package do
type Doer interface {
Do(i int) error
}
var (
ErrNegative = fmt.Errorf("negative numbers not allowed")
ErrCannotBeUsed = fmt.Errorf("this number cannot be used")
)
package complex
import (
"myproject/do"
)
type Doer struct{}
func (Doer) Do(i int) error {
if i < 0 {
return do.ErrNegative
}
if i == 40 {
return do.ErrCannotBeUsed
}
if i > 100 {
return fmt.Errorf("some unknown error, everything broke")
}
}
package main
import (
"errors"
"myproject/complex"
"myproject/do"
)
func main() {
var doer do.Doer
doer = complex.Doer{}
i := 1 // or any integer
err = doer.Do(i)
if err == nil {
return
}
if errors.Is(err, do.ErrNegative) {
// some handling
} else if errors.Is(err, do.ErrCannotBeUsed) {
// some handling
} else {
// handle unknown error
}
}
However, in such architecture, I'd force implementation (complex.Doer
) to adapt to the interface (do.Doer
), which, according to Go Wiki, is not how interfaces are supposed to be used.
What would be a proper way to handle errors in such case?