GoのWebアプリケーションのエラー設計 の最後で紹介したように、エラーを変換する際に下位の情報をラップすることが xerrors.Errorf ではできません。
var ErrUserNotFound = NewApplicationError("Error", 101, "not found")
func NewApplicationError(level string, code int, msg string) *ApplicationError {
err := &ApplicationError{
level: level,
code: code,
msg: msg,
}
return err
}
func main() error {
user, err := userSearch()
if err != nil {
// return ErrUserNotFound ではなく以下のようにしたいが、複数のエラーのラップには対応していない
return xerrors.Errorf(": %w: %w", ErrUserNotFound, err)
}
fmt.Println(user)
}
xerrorsにはこれを解決するための方法が用意されているため紹介します。
独自のエラー型に xerrors.Formatter
を実装する。
標準の errors
パッケージに入るまでは fmt.Formatter
も実装する必要があります。
type ApplicationError struct {
level string
code int
msg string
// ラップする
err error
// frame情報を保持する
frame xerrors.Frame
}
func NewApplicationError(level string, code int, msg string) *ApplicationError {
return &ApplicationError{
level: level,
code: code,
msg: msg,
err: nil,
frame: xerrors.Caller(1),
}
}
// Here is not pointer receiver.
func (e ApplicationError) Wrap(next error) error {
// ラップするエラーを渡す
e.err = next
// frameにCallerを設定
e.frame = xerrors.Caller(1)
return &e
}
func (e *ApplicationError) Error() string {
return fmt.Sprintf("%s: code=%d, msg=%s", e.level, e.code, e.msg)
}
func (e *ApplicationError) Unwrap() error {
return e.err
}
// fmt.Formatterを実装
func (e *ApplicationError) Format(s fmt.State, v rune) { xerrors.FormatError(e, s, v) }
// xerrors.Formatterを実装
func (e *ApplicationError) FormatError(p xerrors.Printer) (next error) {
p.Print(e.Error())
e.frame.Format(p)
return e.err
}
使い方
var ErrUserNotFound = NewApplicationError("Error", 101, "not found")
func main() {
user, err := userSearch("12345")
if err != nil {
// return ErrUserNotFound
return ErrUserNotFound.Wrap(err)
}
fmt.Println(user)
}
おおげさな実装ではありませんが、自分で定義したエラー型すべてに追加するのは多少手間になるのでライブラリを作ってみました。
sonatard/werrorは、定義したエラー型に埋め込むことで利用します。
Go 1.12までは werror "github.com/sonatard/werror/xerrors"
、 Go 1.13以降は "github.com/sonatard/werror"
を import してご利用ください。
package main
import (
// Before Go 1.13
werror "github.com/sonatard/werror/xerrors"
// From Go 1.13
// "github.com/sonatard/werror"
)
type ApplicationError struct {
level string
code int
msg string
werror.WrapError
}
// Here is not pointer receiver.
func (e ApplicationError) Wrap(next error) error {
e.WrapError = werror.Wrap(&e, next, 2)
return &e
}
func (e *ApplicationError) Error() string {
return fmt.Sprintf("%s: code=%d, msg=%s", e.level, e.code, e.msg)
}
また独自に定義した型を単純に比較してしまうとトレース情報が含まれた構造体を比較するため false
になってしまうことがあります。
そのため必ず Is
メソッドを定義することをオススメします。
xerrors パッケージ - 独自に定義したエラー型はIsメソッドとAsメソッドでデフォルトの振る舞いを変更可能
さいごに
Proposal: Go 2 Error Inspection には、以下のように書いてあり、フィードバックとして複数のエラーをラップすることを期待している人が多いみたいですが、現時点では実装の予定はないようです。
Lastly, we want to acknowledge the several comments on the feedback wiki that suggested that we go further by incorporating a way to represent multiple errors as a single error value. We understand that this is a popular request, but at this point we feel we have introduced enough new features for one proposal, and we’d like to see how these work out before adding more. We can always add a multi-error type in a later proposal, and meanwhile it remains easy to write your own.