前回はエラーのラッピングについて確認しました。
今回はカスタムエラーを作成して、そのカスタムエラーがエラーを保持(ラッピング)するようなエラーチェーンについて確認します。
目次
やること
エラーを保持するカスタムエラー
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のエラーと同じかどうか判定し続けます。