Twirp Error Handling¶
概念概覽
mapError 覆蓋不全(Fallthrough 問題)¶
核心知識¶
mapError 覆蓋不全(Fallthrough 問題)¶
mapError 若未涵蓋所有 domain error,未匹配的 error 會 fallthrough 成 generic twirp.Internal,client 無法得到有意義的錯誤碼。
症狀:新增了 domain error(如 ErrUnsupportedCurrency、ErrHeroNotOwned)但忘記更新 mapError。
修復:每新增 domain error 必須同步加入 mapError 的 case,建議加入 code review checklist 或 linter 規則。
err.Error() 洩漏內部細節¶
// WRONG: 洩漏 wrap chain 給 client
return twirp.NewError(twirp.InvalidArgument, err.Error())
// CORRECT: 固定字串,細節留在 server log
return twirp.NewError(twirp.InvalidArgument, "invalid argument")
err.Error() 可能包含 stack trace、DB query、內部 struct 名稱等,直接回傳給 client 是資訊洩漏漏洞。
規範建議¶
- mapError 回傳訊息:固定字串,不用
err.Error() - 詳細錯誤資訊:用
log.Error()記錄 server side,不傳給 client - mapError 維護:與 domain error 定義放同一 PR,強制 review
經驗教訓¶
-
mapError 是 domain error 與 RPC error code 的對應表,必須與 domain error 同步維護
-
err.Error() 回傳給 client 在 wrap error 模式下尤其危險,wrap chain 會洩漏內部路徑
常見陷阱¶
-
新增 domain error 時只測試了 happy path,未測試 error mapping,導致 fallthrough 進 production
-
用 fmt.Errorf("操作失敗: %w", err) wrap 後再 .Error() 回傳,會把完整 chain 暴露
最佳實踐¶
-
mapError 使用固定字串,詳細 error log server side
-
每個 domain error 都要有對應的 mapError case,缺一不可
-
考慮用 exhaustive switch linter 確保 mapError 不漏 case
相關概念¶
相關視角¶
以下頁面與本概念共享主題,但從不同角度切入。保留獨立視角同時提供交叉參考:
- Twirp Error Code Single Channel Architecture — 共享:
rpc,twirp/ 獨特:error-codes,matchrpg - RPC Idempotency for Gacha / Random Pack Opening — 共享:
rpc,twirp/ 獨特:gacha,game-server - PostgreSQL Varchar Overflow Three-Layer Defense — 共享:
error-handling/ 獨特:data-validation,game-backend
來源 Sessions¶
| 日期 | Session | 貢獻摘要 |
|---|---|---|
| 2026-03-17 | 10eef6a0-3a55-4d63-981c-6bc79aff1880 | 發現 mapError 兩類問題:domain error 覆蓋不全導致 fallthrough 為 generic error,以及 err.Error() 直接回傳給 client 洩漏內部細節 |