前回はエラーのラッピングについて確認しました。
今回はカスタムエラーを作成して、そのカスタムエラーがエラーを保持(ラッピング)するようなエラーチェーンについて確認します。
やること
エラーを保持するカスタムエラー
3つのカスタムエラー(TopError、MidError、LowError)を作成します。
例えばTopの場合は以下のようになります。
type TopError struct {
	err error
}
func (t TopError) Error() string {
	return "TopError!!"
}エラーを返すインスタンス
3つの構造体(Top、Mid、Low)を定義してdoメソッドを作成します。doメソッドは下層のエラーを保持してエラー返します。
Topの場合は以下のようになります。
type Top struct {
}
// Midのエラーを保持してエラーを返す
func (t *Top) do() error {
	m := Mid{}
	return TopError{err: m.do()}
}階層と関係
- main が 「Top.do()メソッド実行」
- Top.do()メソッド が 「Mid.do()メソッド実行」
- Mid.do()メソッド が 「Low.do()メソッド実行」
各々の階層で下層のエラーを保持してエラー返します。
各層の実装
最下層 – Low.go
package main
type Low struct {
}
func (l *Low) do() error {
	return LowError{}
}
type LowError struct {
}
func (l LowError) Error() string {
	return "LowError!!"
}中層 – Mid.go
package main
type Mid struct {
}
// Lowのエラーを保持してエラーを返す
func (m *Mid) do() error {
	l := Low{}
	return MidError{err: l.do()}
}
type MidError struct {
	err error
}
func (m MidError) Error() string {
	return "MidError!!"
}
func (m MidError) Unwrap() error { // 重要
	return m.err
}最上層 – Top.go
package main
type Top struct {
}
// Midのエラーを保持してエラーを返す
func (t *Top) do() error {
	m := Mid{}
	return TopError{err: m.do()}
}
type TopError struct {
	err error
}
func (t TopError) Error() string {
	return "TopError!!"
}
func (t TopError) Unwrap() error { // 重要
	return t.err
}メイン
各々の層のエラーがあるかを判定します。
package main
import (
	"errors"
	"fmt"
)
func main() {
	t := Top{}
	// 最下層のエラーを取得する
	if errors.As(t.do(), &TopError{}) {
		fmt.Println("Topのエラーがあった!!")
	} else {
		fmt.Println("Topのエラーはない!!")
	}
	// 中間層のエラーを取得する
	if errors.As(t.do(), &MidError{}) {
		fmt.Println("Midのエラーがあった!!")
	} else {
		fmt.Println("Midのエラーはない!!")
	}
	// 最上層のエラーを取得する
	if errors.As(t.do(), &LowError{}) {
		fmt.Println("Lowのエラーがあった!!")
	} else {
		fmt.Println("Lowのエラーはない!!")
	}
}
結果とポイント
結果
ちゃんと判定してくれました。
ポイント
Unwrap関数を実装しないとerrors.As関数は判定してくれません。errors.As関数は第一引数のエラーのUnwrap関数を実行し、第二引数(targetと呼称する)のエラーと比較します。一致しない場合、同じ事を繰り返します。なので、各階層でUnwrap関数を実装する事でerrors.As関数はtargetのエラーと同じかどうか判定し続けます。
