解答 05.3 — 統合プロジェクト(classifyRegistrationError)¶
このノートブックは 05.3-integration-project.ipynb の練習問題の解答です。
先に自分の力で解いてから、答え合わせに使ってください。
前提コード(問題ノートブックと同じ定義)¶
In [1]:
import (
"database/sql"
"errors"
"fmt"
"strings"
_ "modernc.org/sqlite"
)
// ErrUnanswered は、練習問題が未回答のときにプレースホルダ関数が返す特別なエラー。
var ErrUnanswered = errors.New("未回答: この関数はまだ実装されていません")
// openCourseDB は、このコース共通の SQLite 接続を開く(1セル完結で使う。04.3 と同じ契約)。
func openCourseDB(path string) *sql.DB {
db, err := sql.Open("sqlite", path)
if err != nil {
panic(err)
}
db.SetMaxOpenConns(1)
return db
}
解答: classifyRegistrationError¶
nil はそのまま透過、UNIQUE制約違反は意味のあるエラーに変換、それ以外はそのまま返します。
In [2]:
func classifyRegistrationError(err error) error {
if err == nil {
return nil
}
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
return errors.New("email already registered")
}
return err
}
チェック(問題ノートブックと同じ期待値)¶
In [3]:
import "reflect"
import "fmt"
func mustEqual(got, want any, name string) {
if reflect.DeepEqual(got, want) {
fmt.Printf("✅ Passed: %s\n", name)
return
}
panic(fmt.Sprintf("❌ %s\n got = %v (%T)\n want = %v (%T)", name, got, got, want, want))
}
In [4]:
%%
mustEqual(classifyRegistrationError(nil), nil, "nilエラーはnilのまま")
otherErr := errors.New("some other error")
mustEqual(classifyRegistrationError(otherErr), otherErr, "無関係なエラーはそのまま透過する")
dupSourceErr := errors.New("UNIQUE constraint failed: users.email")
classified := classifyRegistrationError(dupSourceErr)
if classified == nil || classified.Error() != "email already registered" {
panic(fmt.Sprintf("❌ UNIQUE制約違反は email already registered に変換されるはず(got=%v)", classified))
}
fmt.Println("✅ Passed: UNIQUE制約違反は email already registered に変換される")
// 実際にDBへ重複INSERTして、本物のsqliteエラーでも判定できるか確認する。
db := openCourseDB("file:_05_3_exercise_check_solutions.db")
defer db.Close()
db.Exec(`DROP TABLE IF EXISTS exercise_users`)
db.Exec(`CREATE TABLE exercise_users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)`)
db.Exec(`INSERT INTO exercise_users (email) VALUES (?)`, "hanako@example.com")
_, dupErr := db.Exec(`INSERT INTO exercise_users (email) VALUES (?)`, "hanako@example.com")
realClassified := classifyRegistrationError(dupErr)
if realClassified == nil || realClassified.Error() != "email already registered" {
panic(fmt.Sprintf("❌ 実際のsqliteエラーも変換されるはず(got=%v)", realClassified))
}
fmt.Println("✅ Passed: 実際のsqlite UNIQUE制約違反も email already registered に変換される")
fmt.Println("🎉 すべてのチェックが通りました")
✅ Passed: nilエラーはnilのまま ✅ Passed: 無関係なエラーはそのまま透過する ✅ Passed: UNIQUE制約違反は email already registered に変換される
✅ Passed: 実際のsqlite UNIQUE制約違反も email already registered に変換される 🎉 すべてのチェックが通りました
解説¶
nilを最初に処理する — 成功時(err == nil)はそのままnilを返し、余計な変換をしませんstrings.ContainsでSQLiteのエラーメッセージを判定する —modernc.org/sqliteが返す UNIQUE制約違反のエラーメッセージには"UNIQUE constraint failed"という共通の文字列が含まれます。 ①で実際にDBへ重複INSERTしたときのエラーも、この文字列を含んでいることをチェックセルで確認しています- それ以外のエラーは握りつぶさずそのまま返す — DB接続エラーやSQL構文エラーなど、UNIQUE制約以外の エラーまで「登録に失敗しました」のような曖昧なメッセージに変換してしまうと、原因調査が難しくなります
この関数を実際のユーザー登録ハンドラに組み込めば、①で見た UNIQUE constraint failed という
DB層の生エラーを、API層では 409 Conflict + "email already registered" という分かりやすい
レスポンスに変換できます(このノートブックでは変換ロジックの実装までを扱い、ハンドラへの統合は
発展課題とします)。