Reputation: 626
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.
Upvotes: 2
Views: 1043
Reputation: 66244
If I understand the problem correctly, you can indeed simplify things:
a
and b
as package-level error
variables for ease and better performance.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.errors.Is
within a tagless switch
to inspect the cause of the higher-level error returned by your doStuff
function.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")
}
}
Upvotes: 5
Reputation:
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())
}
Upvotes: 2
Reputation: 32873
Use constant errors if you can. Then you could switch on the error itself.
Upvotes: 0