トレースの情報を上位のレイヤーに渡すだけならパッケージで定義したエラーの型は公開しない
トレースの情報だけをラップして渡していきたい場合には、Formatを実装していればパッケージ内で独自に定義したエラータイプは非公開にして問題ありません。非公開の場合は、Isでマッチなどはすることはありません。しかし Unwrap
を実装しないと、より下位にラップされた公開されているエラーに到達できなくなってしまうので注意が必要です。
package pkg1
import (
"fmt"
"golang.org/x/xerrors"
)
// 非公開の型
type pkg1Error struct {
msg string
err error
frame xerrors.Frame
}
func (e *pkg1Error) Error() string {
return e.msg
}
// Unwrapメソッドは実装する
func (e *pkg1Error) Unwrap() error {
return e.err
}
func (e *pkg1Error) Format(s fmt.State, v rune) { xerrors.FormatError(e, s, v) }
func (e *pkg1Error) FormatError(p xerrors.Printer) (next error) {
p.Print(e.Error())
e.frame.Format(p)
return e.err
}
func UserSearch(uID string) (string, error) {
pkgErr := &pkg1Error{
msg: "hello",
err: nil,
frame: xerrors.Caller(1),
}
return "", xerrors.Errorf("error user %v not found: %w", uID, pkgErr)
}
func main() {
_, err := pkg1.UserSearch("12345")
fmt.Printf("%+v\n", err)
}
正しく外部パッケージで定義されたエラーのトレース情報が出力されています。
error user 12345 not found:
xerrors/pkg1.UserSearch
/Users/sonatard/tmp/xerrors/pkg1/user.go:37
- hello:
main.main
/Users/sonatard/tmp/xerrors/main.go:9
逆にIsやAsを利用したい場合には、型を公開しなければなりません。
自分で定義した型を他のパッケージに公開したいが、IsやAsでマッチさせたくない場合はUnwrap型を実装しない
Unwrap
を実装しないことで公開されているエラーの型をIsやAsでマッチさせなくすることができます。
またFormatを実装することで、Unwrap
を実装しなくてもエラーのトレースは出力することはできます。
これは標準ライブラリで返ってきた型をラップしたいが、外部パッケージに公開はしたくないときに使います。
package pkg1
import (
"fmt"
"io"
"golang.org/x/xerrors"
)
type Pkg1Error struct {
msg string
err error
frame xerrors.Frame
}
func (e *Pkg1Error) Error() string {
return e.msg
}
func (e *Pkg1Error) Format(s fmt.State, v rune) { xerrors.FormatError(e, s, v) }
func (e *Pkg1Error) FormatError(p xerrors.Printer) (next error) {
p.Print(e.Error())
e.frame.Format(p)
return e.err
}
func UserSearch(uID string) (string, error) {
pkgErr := &Pkg1Error{
msg: "hello",
err: xerrors.Errorf("error: %w", io.ErrUnexpectedEOF), // 標準ライブラリからエラーが返ってきた場合を想定
frame: xerrors.Caller(1),
}
return "", xerrors.Errorf("error user %v not found: %w", uID, pkgErr)
}
func (e *Pkg1Error) Unwrap() error {
return e.err
}
func main() {
_, err := pkg1.UserSearch("12345")
fmt.Printf("%v\n", xerrors.Is(err, io.ErrUnexpectedEOF))
fmt.Printf("%+v\n", err)
}
Unwrapの実装をしている場合の結果
Unwrap
を実装しているため Is
が true
となる。
true
error user 12345 not found:
xerrors/pkg1.UserSearch
/Users/sonatard/tmp/xerrors/pkg1/user.go:35
- hello:
main.main
/Users/sonatard/tmp/xerrors/main.go:31
- error:
xerrors/pkg1.UserSearch
/Users/sonatard/tmp/xerrors/pkg1/user.go:31
- unexpected EOF
-
Unwrap
を実装していない場合の結果
Unwrap
を実装していないため Is
が false
となる。
Unwrap
を実装していなくとも同じようにエラーのトレースは表示される。
false
error user 12345 not found:
xerrors/pkg1.UserSearch
/Users/sonatard/tmp/xerrors/pkg1/user.go:35
- hello:
main.main
/Users/sonatard/tmp/xerrors/main.go:31
- error:
xerrors/pkg1.UserSearch
/Users/sonatard/tmp/xerrors/pkg1/user.go:31
- unexpected EOF