Is there an accepted pattern for grouping custom error types together so that I can handle them differently according to their group?
For example, here's what I would do in C# with exceptions:
abstract class FriendlyException : ApplicationException {
protected FriendlyException(string message) : base(message) { }
}
class MyNiceException : FriendlyException {
public MyNiceException(string message) : base(message) { }
}
I could then do something different in the catch
depending on whether or not it falls into that category:
try { DoSomething(); }
catch (FriendlyException ex) { Console.WriteLine(ex.Message); }
catch { Console.WriteLine("Unhandled error"); }
In Go, I see that I can use As
or switch directly on the type, but since there are implicit interfaces, I'm not seeing that this can be done without a wasted interface member, like this:
type FriendlyError interface { SomeWastedMethod() }
type MyFriendlyError struct {}
func (fe MyFriendlyError) SomeWastedMethod() {}
func (fe MyFriendlyError) Error() string { return "implementing error interface" }
I could then check to see if an error is of that group like this:
var fe FriendlyError
err := DoSomething()
if err != nil {
if errors.As(err, &fe) {
fmt.Println(err.Error())
} else {
fmt.Println("Unhandled error")
}
}
That wasted method in the MyFriendlyError
struct is what bothers me. Is that the only way to accomplish this, or is there some other standard pattern?
In my example, I would want multiple different error types, some of which are safe to return the message to the caller, and some of which I want to just give the caller a generic message, while still retaining the details for internal logging.
CodePudding user response:
I suggest the following way:
package main
import (
"errors"
"fmt"
)
var (
ErrInvalidMsg = errors.New("invalid message")
ErrInvalidParam = errors.New("invalid param")
ErrBadMsg = errors.New("bad message")
)
type GroupError1 struct {
Base error
}
func (ge GroupError1) Error() string {
return ge.Base.Error()
}
type GroupError2 struct {
Base error
}
func (ge GroupError2) Error() string {
return ge.Base.Error()
}
func TheError(n int) error {
switch n % 3 {
case 0:
return GroupError2{
Base: ErrBadMsg,
}
case 1:
return GroupError1{
Base: ErrInvalidParam,
}
case 2:
return GroupError1{
Base: ErrInvalidMsg,
}
}
return nil
}
func main() {
if err := TheError(1); err != nil {
var ge1 GroupError1
var ge2 GroupError2
if ok := errors.As(err, &ge1); ok {
fmt.Println("error group 1")
} else if ok := errors.As(err, &ge2); ok {
fmt.Println("error group 2")
} else {
fmt.Println("no error group")
}
}
}
Here I defined two error groups, so we can detect them with errors.As
and you don't need to define any unused interface, but you need to create them by hand and there isn't any automatic grouping.