Help us understand the problem. What is going on with this article?

xerrors パッケージで途中に独自定義したエラー型をラップする方法

More than 1 year has passed since last update.

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.

関連情報

sonatard
組み込みC言語ネットワークスタック開発者からGoバックエンドエンジニアにジョブチェンジしました。 最近はTypeScript, React(Hooks), GraphQL, SwiftUIに夢中。
https://github.com/sonatard/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした