186 lines
7.3 KiB
Markdown
186 lines
7.3 KiB
Markdown
# 錯誤碼 × HTTP 對照表
|
||
|
||
這份文件專門整理 **infra-core/errors** 的「錯誤碼 → HTTP Status」對照,並提供**實務範例**。
|
||
錯誤系統採用 8 碼格式 `SSCCCDDD`:
|
||
|
||
- `SS` = Scope(服務/模組,兩位數)
|
||
- `CCC` = Category(類別,三位數,影響 HTTP 狀態)
|
||
- `DDD` = Detail(細節,三位數,自定義業務碼)
|
||
|
||
> 例如:`10101000` → Scope=10、Category=101(InputInvalidFormat)、Detail=000。
|
||
|
||
## 目錄
|
||
- [1) 快速查表](#1-快速查表依類別整理)
|
||
- [2) 使用範例](#2-使用範例)
|
||
- [3) 小撇步與慣例](#3-小撇步與慣例)
|
||
- [4) 安裝與測試](#4-安裝與測試)
|
||
- [5) 變更日誌](#5-變更日誌)
|
||
|
||
---
|
||
|
||
## 1) 快速查表(依類別整理)
|
||
|
||
### A. Input(Category 1xx)
|
||
|
||
| Category 常數 | 說明 | HTTP | 原因/說明 |
|
||
|---|---------------|:----:|---|
|
||
| `InputInvalidFormat` (101) | 無效格式 | **400 Bad Request** | 格式不符、缺欄位、型別錯。 |
|
||
| `InputNotValidImplementation` (102) | 非有效實作 | **422 Unprocessable Entity** | 語意正確但無法處理。 |
|
||
| `InputInvalidRange` (103) | 無效範圍 | **422 Unprocessable Entity** | 值超域、邊界條件不合。 |
|
||
|
||
### B. DB(Category 2xx)
|
||
|
||
| Category 常數 | 說明 | HTTP | 原因/說明 |
|
||
|---|-------------|:----:|---|
|
||
| `DBError` (201) | 資料庫一般錯誤 | **500 Internal Server Error** | 後端故障/不可預期。 |
|
||
| `DBDataConvert` (202) | 資料轉換錯誤 | **422 Unprocessable Entity** | 可修正的資料問題(格式/型別轉換失敗)。 |
|
||
| `DBDuplicate` (203) | 資料重複 | **409 Conflict** | 唯一鍵衝突、重複建立。 |
|
||
|
||
### C. Resource(Category 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. Auth(Category 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. System(Category 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. PubSub(Category 7xx)
|
||
|
||
| Category 常數 | 說明 | HTTP | 原因/說明 |
|
||
|---|---------|:----:|---|
|
||
| `PSuPublish` (701) | 發佈失敗 | **502 Bad Gateway** | 中介或外部匯流排錯誤。 |
|
||
| `PSuConsume` (702) | 消費失敗 | **502 Bad Gateway** | 同上。 |
|
||
| `PSuTooLarge` (703) | 訊息過大 | **413 Payload Too Large** | 封包大小超限。 |
|
||
|
||
### G. Service(Category 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
|
||
``` |