errorをmapで保持する小さなerrorライブラリを作った
errorをmapで保持するライブラリを作った。いわゆるmultierrorのような複数のエラーをsliceとしてまとめるライブラリは存在するのだけれど、意外とmapで保持するライブラリがなかったので。代わりのライブラリがあるならそちらを使いたい。
なぜ作ったか?
これは個人的な体験によるものなのかもしれないが、REST APIのエラーとして返すときにはmapとして返されたほうが嬉しいような気がする。加えてconfig用のvalidationにもこれをそのまま適用すれば手軽につかえて便利なのではと思った。
(要は、func (io.Reader, interface {}) error
となるような関数が存在すれば良い)
validationライブラリやschemaライブラリとしなかったのは、そもそもvalidationは自分の手で実装するから、ほぼほぼエラーメッセージの管理と表示が主になるため。validationに関しては各自頑張ってくださいと言う感じ。
特徴
特殊な点としては以下。
- formatの
%v
と%+v
でshort versionとlong versionの表示が使い分けられる - エラーの表示がJSON
こんな感じ。
https://godoc.org/github.com/podhmo/maperr#example-Error-Format
使い方
代表的な使い方はreadmeに書いた通り。
- AddSummary()で全体のエラーを登録
- Add()で各フィールドのエラーを登録
- (AddSummary()がなければ、最もpriorityの高いエラーがSummaryとして自動で登録される)
package main import ( "bytes" "encoding/json" "log" "github.com/podhmo/maperr" ) type Person struct { Name string `json:"name"` // required Age int `json:"age"` } func (p *Person) UnmarshalJSON(b []byte) error { var err *maperr.Error var inner struct { Name *string `json:"name"` Age *int `json:"age"` } if rawerr := json.Unmarshal(b, &inner); rawerr != nil { err = err.AddSummary(rawerr.Error()) } if inner.Name != nil { p.Name = *inner.Name } else { err = err.Add("name", maperr.Message{Text: "required"}) } if inner.Age != nil { p.Age = *inner.Age } return err.Untyped() } func main() { b := bytes.NewBufferString(`{"age": 20}`) decoder := json.NewDecoder(b) var p Person if err := decoder.Decode(&p); err != nil { log.Fatalf("%+v", err) } }
こういう結果になる。こちらはlong versionの表示。
2020/05/03 17:16:34 Error -- { "summary": "name, required", "messages": { "name": [ { "text": "required" } ] } } exit status 1
特にlockが必要になる機会が思いつかなかったので、更新時にはlockをかけたりしていない。
validationを手書きする?
毎回UnmarshalJSON()
を手書きするのは手間かなとは思ったけれど。現状の用途はこれなので生成してしまえば良いかなと思っている。
さいごに
mapでerrorを管理する小さなライブラリを書いた。もし仮に代わりになるような何かを知っていれば教えてほしい。
あと、テキトーにgoのライブラリ用のリポジトリを書いたのでCIとかREADMEのバッチとかで良い感じの設定の情報があればお待ちしてます。:pray:
(multi errorのライブラリ)
multi errorのライブラリは例えばこういうもの。どうも今回の用途とは違っていた。
あとapplication errorのような豪華なerrorが欲しい場合はこのへんは良さそうな気がしている(が、今回の目的とは違っていた)