backend/pkg/library/errors/README.md

186 lines
7.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 錯誤碼 × HTTP 對照表
這份文件專門整理 **infra-core/errors** 的「錯誤碼 → HTTP Status」對照並提供**實務範例**。
錯誤系統採用 8 碼格式 `SSCCCDDD`
- `SS` = Scope服務/模組,兩位數)
- `CCC` = Category類別三位數影響 HTTP 狀態)
- `DDD` = Detail細節三位數自定義業務碼
> 例如:`10101000` → Scope=10、Category=101InputInvalidFormat、Detail=000。
## 目錄
- [1) 快速查表](#1-快速查表依類別整理)
- [2) 使用範例](#2-使用範例)
- [3) 小撇步與慣例](#3-小撇步與慣例)
- [4) 安裝與測試](#4-安裝與測試)
- [5) 變更日誌](#5-變更日誌)
---
## 1) 快速查表(依類別整理)
### A. InputCategory 1xx
| Category 常數 | 說明 | HTTP | 原因/說明 |
|---|---------------|:----:|---|
| `InputInvalidFormat` (101) | 無效格式 | **400 Bad Request** | 格式不符、缺欄位、型別錯。 |
| `InputNotValidImplementation` (102) | 非有效實作 | **422 Unprocessable Entity** | 語意正確但無法處理。 |
| `InputInvalidRange` (103) | 無效範圍 | **422 Unprocessable Entity** | 值超域、邊界條件不合。 |
### B. DBCategory 2xx
| Category 常數 | 說明 | HTTP | 原因/說明 |
|---|-------------|:----:|---|
| `DBError` (201) | 資料庫一般錯誤 | **500 Internal Server Error** | 後端故障/不可預期。 |
| `DBDataConvert` (202) | 資料轉換錯誤 | **422 Unprocessable Entity** | 可修正的資料問題(格式/型別轉換失敗)。 |
| `DBDuplicate` (203) | 資料重複 | **409 Conflict** | 唯一鍵衝突、重複建立。 |
### C. ResourceCategory 3xx
| Category 常數 | 說明 | HTTP | 原因/說明 |
|---|-------------------|:----:|---|
| `ResNotFound` (301) | 資源未找到 | **404 Not Found** | 目標不存在/無此 ID。 |
| `ResInvalidFormat` (302) | 無效資源格式 | **422 Unprocessable Entity** | 表示層/Schema 不符。 |
| `ResAlreadyExist` (303) | 資源已存在 | **409 Conflict** | 重複建立/命名衝突。 |
| `ResInsufficient` (304) | 資源不足 | **400 Bad Request** | 數量/容量不足(用戶可改參數再試)。 |
| `ResInsufficientPerm` (305) | 權限不足 | **403 Forbidden** | 已驗證但無權限。 |
| `ResInvalidMeasureID` (306) | 無效測量ID | **400 Bad Request** | ID 本身不合法。 |
| `ResExpired` (307) | 資源過期 | **410 Gone** | 已不可用(可於上層補 Location。 |
| `ResMigrated` (308) | 資源已遷移 | **410 Gone** | 同上,如需導引請於上層處理。 |
| `ResInvalidState` (309) | 無效狀態 | **409 Conflict** | 當前狀態不允許此操作。 |
| `ResInsufficientQuota` (310) | 配額不足 | **429 Too Many Requests** | 達配額/速率限制。 |
| `ResMultiOwner` (311) | 多所有者 | **409 Conflict** | 所有權歧異造成衝突。 |
### D. AuthCategory 5xx
| Category 常數 | 說明 | HTTP | 原因/說明 |
|---|-------------------------|:----:|---|
| `AuthUnauthorized` (501) | 未授權/未驗證 | **401 Unauthorized** | 缺 Token、無效 Token。 |
| `AuthExpired` (502) | 授權過期 | **401 Unauthorized** | Token 過期或時效失效。 |
| `AuthInvalidPosixTime` (503) | 無效 POSIX 時間 | **401 Unauthorized** | 時戳異常導致驗簽失敗。 |
| `AuthSigPayloadMismatch` (504) | 簽名與載荷不符 | **401 Unauthorized** | 驗簽失敗。 |
| `AuthForbidden` (505) | 禁止存取 | **403 Forbidden** | 已驗證但沒有操作權限。 |
### E. SystemCategory 6xx
| Category 常數 | 說明 | HTTP | 原因/說明 |
|---|---------------|:----:|---|
| `SysInternal` (601) | 系統內部錯誤 | **500 Internal Server Error** | 未預期的系統錯。 |
| `SysMaintain` (602) | 系統維護中 | **503 Service Unavailable** | 維護/停機。 |
| `SysTimeout` (603) | 系統超時 | **504 Gateway Timeout** | 下游/處理逾時。 |
| `SysTooManyRequest` (604) | 請求過多 | **429 Too Many Requests** | 節流/限流。 |
### F. PubSubCategory 7xx
| Category 常數 | 說明 | HTTP | 原因/說明 |
|---|---------|:----:|---|
| `PSuPublish` (701) | 發佈失敗 | **502 Bad Gateway** | 中介或外部匯流排錯誤。 |
| `PSuConsume` (702) | 消費失敗 | **502 Bad Gateway** | 同上。 |
| `PSuTooLarge` (703) | 訊息過大 | **413 Payload Too Large** | 封包大小超限。 |
### G. ServiceCategory 8xx
| Category 常數 | 說明 | HTTP | 原因/說明 |
|---|---------------|:----:|---|
| `SvcInternal` (801) | 服務內部錯誤 | **500 Internal Server Error** | 非基礎設施層的內錯。 |
| `SvcThirdParty` (802) | 第三方失敗 | **502 Bad Gateway** | 呼叫外部服務失敗。 |
| `SvcHTTP400` (803) | 明確指派 400 | **400 Bad Request** | 自行指定。 |
| `SvcMaintenance` (804) | 服務維護中 | **503 Service Unavailable** | 模組級維運中。 |
---
## 2) 使用範例
### 2.1 在 Handler 中回傳錯誤
```go
import (
"net/http"
errs "gitlab.supermicro.com/infra/infra-core/errors"
"gitlab.supermicro.com/infra/infra-core/errors/code"
)
func init() {
errs.Scope = code.Gateway // 設定當前服務的 Scope
}
func GetUser(w http.ResponseWriter, r *http.Request) error {
id := r.URL.Query().Get("id")
if id == "" {
return errs.InputInvalidFormatError("缺少參數: id") // 現在是 8 位碼
}
u, err := repo.Find(r.Context(), id)
switch {
case errors.Is(err, repo.ErrNotFound):
return errs.ResNotFoundError("user", id)
case err != nil:
return errs.DBErrorError("查詢使用者失敗").Wrap(err) // Wrap 內部錯誤
}
// … 寫入回應
return nil
}
// 統一寫出 HTTP 錯誤
func writeHTTP(w http.ResponseWriter, e *errs.Error) {
http.Error(w, e.Error(), e.HTTPStatus())
}
```
### 2.2 取出 Wrap 的內部錯誤
```go
if internal := e.Unwrap(); internal != nil {
log.Error("Internal error: ", internal)
}
```
### 2.3 搭配日誌裝飾器(`WithLog` / `WithLogWrap`
```go
log := logger.WithFields(errs.LogField{Key: "req_id", Val: rid})
if badInput {
return errs.WithLog(log, nil, errs.InputInvalidFormatError, "email 無效")
}
if err := repo.Save(ctx, u); err != nil {
return errs.WithLogWrap(
log,
[]errs.LogField{{Key: "entity", Val: "user"}, {Key: "op", Val: "save"}},
errs.DBErrorError,
err,
"儲存失敗",
)
}
```
### 2.4 只知道 Category+Detail 的動態場景(`EL` / `ELWrap`
```go
// 依流程動態產生
return errs.EL(log, nil, code.SysTimeout, 123, "下游逾時") // 自定義 detail=123
// 或需保留 cause
return errs.ELWrap(log, nil, code.SvcThirdParty, 456, err, "金流商失敗")
```
### 2.5 gRPC 互通
```go
// 由 *errs.Error 轉為 gRPC status
st := e.GRPCStatus() // *status.Status
// 客戶端收到 gRPC error → 轉回 *errs.Error
e := errs.FromGRPCError(grpcErr)
fmt.Println(e.DisplayCode(), e.Error()) // e.g., "10101000" "error msg"
```
### 2.6 從 8 碼反解(`FromCode`
```go
e := errs.FromCode(10101000) // 10101000
fmt.Println(e.Scope(), e.Category(), e.Detail()) // 10, 101, 000
```