xerrors - エラー設計の注意点



## トレースの情報を上位のレイヤーに渡すだけならパッケージで定義したエラーの型は公開しない

トレースの情報だけをラップして渡していきたい場合には、Formatを実装していればパッケージ内で独自に定義したエラータイプは非公開にして問題ありません。非公開の場合は、Isでマッチなどはすることはありません。しかし `Unwrap` を実装しないと、より下位にラップされた公開されているエラーに到達できなくなってしまうので注意が必要です。

```go
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)
}
```

```go
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` を実装しなくてもエラーのトレースは出力することはできます。

これは標準ライブラリで返ってきた型をラップしたいが、外部パッケージに公開はしたくないときに使います。

```go
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)
}
```
```go
func (e *Pkg1Error) Unwrap() error {
	return e.err
}
```
```go
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
```

## 関連情報
- [xerrors - 関連情報](https://qiita.com/sonatard/items/802db82e7275f17fe702)