私はZero dependencyな error パッケージ github.com/k1LoW/errors をメンテナンスしています。
以前書いたエントリはこちら。
今回、github.com/k1LoW/errors に errors.Join でまとめられた error を []error に分割できる errors.Errors 関数を追加しました。
なぜ Errors 関数を追加したか
Go 1.20 で errors.Join が追加されました。複数のエラーを1つにまとめることができる便利な関数です。
err := errors.Join(errA, errB, errC)
しかし、まとめられたエラーを取り出す方法は用意されていません。
errors.Join が返すエラーは Unwrap() []error メソッドを実装しているので自分で取り出すことはできますが、ネストしたケースに対応しようとすると少し面倒です。
// これはネストしていない場合のみ if je, ok := err.(interface{ Unwrap() []error }); ok { errs := je.Unwrap() // ... }
そこで、再帰的にすべての error を取り出してフラットな []error として返す errors.Errors 関数を追加しました。
使い方
使い方はシンプルです。
import "github.com/k1LoW/errors" errA := errors.New("error a") errB := errors.New("error b") errC := errors.New("error c") // errors.Join でまとめる joined := errors.Join(errA, errors.Join(errB, errC)) // errors.Errors で分割 errs := errors.Errors(joined) // errs[0] == errA // errs[1] == errB // errs[2] == errC
ネストした errors.Join にも対応しており、すべてのエラーをフラットに取り出せます。
errors.Join されていない普通のエラーを渡した場合は、そのエラーだけを含む []error を返します。
err := errors.New("single error") errs := errors.Errors(err) // errs[0] == err // len(errs) == 1
実装
実装もシンプルです。
// Errors returns all joined errors in the given error. func Errors(err error) []error { je, ok := err.(joinError) if !ok { return []error{err} } errs := je.Unwrap() var splitted []error for _, e := range errs { splitted = append(splitted, Errors(e)...) } return splitted }
Unwrap() []error を持つエラーかどうかを判定し、持っていれば再帰的に各エラーを展開していきます。持っていなければそのまま返します。
ユースケース
どういうときに使うかというと、例えば複数のエラーをまとめて返す処理があり、それを受け取った側でエラーごとに異なる処理をしたい場合などです。
// 複数のバリデーションエラーをまとめて返す func validate(input Input) error { var errs []error if input.Name == "" { errs = append(errs, &ValidationError{Field: "name", Message: "required"}) } if input.Age < 0 { errs = append(errs, &ValidationError{Field: "age", Message: "must be positive"}) } return errors.Join(errs...) } // 受け取った側でエラーごとに処理 err := validate(input) if err != nil { for _, e := range errors.Errors(err) { var ve *ValidationError if errors.As(e, &ve) { // フィールドごとにエラーメッセージを表示 fmt.Printf("%s: %s\n", ve.Field, ve.Message) } } }
go.uber.org/multierr や github.com/hashicorp/go-multierror も同じようなエラーの分割する機能はありますが、github.com/k1LoW/errors はZero dependencyですし、標準のerrorsパッケージと互換があり、(github.com/hashicorp/go-multierrorでは必要な)型アサーションも必要ありません。
まとめ
github.com/k1LoW/errors パッケージに errors.Errors 関数を追加しました。
依然としてZero dependencyなので使い勝手は良いのではないかと思います。
是非採用のご検討のほどよろしくお願いいたします。