DB設計において、主キーを設定して重複を防ぐことがあると思います。
そこからアプリケーションでDBからデータを取り出して、データを整形して、ハンドラーでJSON等でデータを返したりすると思います。
その中でもエラーの内容によって、JSONによって表示させるエラーの内容を分岐させたいという時に、Goでは下のようにエラーハンドリングを行うと思います。
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, custom_error.NewNotFoundError(custom_error.NotFoundRecord)
}
return nil, custom_error.NewInternalServerError(custom_error.InternalErrorInternal, err.Error())
}
例えば、gormでのデータベース操作でデータが見つからなかったときにgorm.ErrRecordNotFound
というカスタムエラーを返してくれます。
そのエラーを受けとったら、JSONで返す内容のテンプレートを使用して、フロントエンドにレコードがないことをわかりやすく伝えます。
上の例ではレコードが存在しないErrNotFoundですが、それ以外にもデータの重複等のエラーを出すことが必要になってくると思います。
gormの先ほど紹介したカスタムエラーには、入力データがすでにあるデータであることを表すDuplicate entry
を表すエラーがないので、別の方法でエラーハンドリングをする必要があります。
今回は、そのようなORMライブラリ等で準備されていないエラーを対処する方法を紹介したいと思います。
mysqlパッケージのMySQLErrorを使う
Duplicate entryのエラーコードは1062です。
このエラーコードを簡単に取り出す方法として、goの標準パッケージにmysql向けのエラーコードやメッセージを扱いやすくするMySQLErrorの構造体が準備されています。
アプリケーションコード内での使用例
アプリケーションコード内では、返ってきたエラーをMySQLErrorに変換してエラーハンドリングを行います。
var mysqlErr *mysql.MySQLError
if ok := errors.As(err, &mysqlErr); ok {
if mysqlErr.Number == 1062 {
return repository.ErrDuplicateEntry
}
}
mysql.MySQLError
型の変数を準備して、errorsパッケージのAsメソッド使用してエラーをmysql.MySQLError
型の変数に格納しています。
errors.Asについて
Asメソッドは、エラーが別の第二引数に指定してる変数に代入可能かどうかboolとして結果を返しつつ、第二引数に結果を代入します。
var pathError *fs.PathError
if errors.As(err, &pathError) {
fmt.Println("Failed at path:", pathError.Path)
} else {
fmt.Println(err)
}
このもう一つ別のメソッドとしてIsメソッドが使われることが多いです。
if errors.Is(err, fs.ErrNotExist) {
fmt.Println("file does not exist")
} else {
fmt.Println(err)
}
Isメソッドは、エラ-が同じかどうかを調べることができます。
エラーは、エラーチェーンと呼ばれるエラーをどんどんラップしていくことができ、子メソッドで起きたエラーも親メソッドで見ることができます。
テストコードでの使用例
テストコードで該当エラーが返ってくるかどうかを調べるには下のように使います。
wantErr := &mysql.MySQLError{
Number: 1452,
Message: "Cannot add or update a child row: a foreign key constraint fails (`report_generator_test`.`blueprints`, CONSTRAINT `fk_users_blueprints` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`))",
}
err := repo.Create(ctx, value)
assert.ErrorIs(t,err,wantErr)
testifyパッケージのassertのErrorIsでMySQLのエラーコードを扱いたいときには、MySQLError型で変数を作成します。
ErrorIsの引数にはerrorインターフェースが満たされているものであれば設定することができます。
MySQLErrorはerrorインターフェースを実装しているため、引数として当てることができます。
まとめ
MySQLErrorを使用することでORMライブラリに標準で準備されていないエラーを簡単にハンドリングすることができます。
こうすることで、string型のエラーメッセージの一致でハンドリングするなどの難しいことを考えず、エラーコード等で簡単にハンドリングできるようになるのでおすすめです。
ぜひ参考にしてください。