package api import ( "github.com/gin-gonic/gin" ) // 錯誤碼常數 — 對齊 api-spec.md §11。 const ( ErrCodeUnauthorized = "UNAUTHORIZED" ErrCodeForbidden = "FORBIDDEN" ErrCodeNotFound = "NOT_FOUND" ErrCodeValidationFailed = "VALIDATION_FAILED" ErrCodeTunnelDisconnect = "TUNNEL_DISCONNECTED" ErrCodeTunnelError = "TUNNEL_ERROR" ErrCodeNotImplemented = "NOT_IMPLEMENTED" ErrCodeRateLimited = "RATE_LIMITED" ErrCodeInternalError = "INTERNAL_ERROR" // ErrCodePayloadTooLarge 對齊 HTTP 413(例:模型上傳超過 MaxUploadSizeMB)。 ErrCodePayloadTooLarge = "PAYLOAD_TOO_LARGE" // ErrCodeInvalidSignature 用於 /storage/* 驗簽失敗 / URL 過期。 ErrCodeInvalidSignature = "INVALID_SIGNATURE" // ErrCodeConflict 對齊 HTTP 409(例:unique 約束衝突 — 同 owner+serial 重複註冊)。 ErrCodeConflict = "CONFLICT" // ErrCodeServiceUnavailable 對齊 HTTP 503。 // DB 接入塊 5.4 fail-fast 策略:PG 連線失敗 / context 逾時 → 503,讓 load balancer 知道 // 這台不健康,而非回假資料或 500(500 會誤導為「程式 bug」,503 才是「依賴暫時不可用」)。 ErrCodeServiceUnavailable = "SERVICE_UNAVAILABLE" ) // ErrorBody 是 API 錯誤回應的 envelope 結構。 // // 對齊 api-spec.md: // // { "success": false, "error": { "code": "...", "message": "...", "request_id": "..." } } // // 為什麼用 envelope 而非裸 error:方便前端統一處理 + 與成功回應形狀一致。 type ErrorBody struct { Success bool `json:"success"` Error *ErrorDetail `json:"error"` } // ErrorDetail 是錯誤的具體資訊。 type ErrorDetail struct { Code string `json:"code"` Message string `json:"message"` Details []FieldError `json:"details,omitempty"` // 例如 validation 細節 RequestID string `json:"request_id,omitempty"` Extra map[string]any `json:"extra,omitempty"` // 給 specific error 帶結構化資料 } // FieldError 描述單一欄位的驗證錯誤。 type FieldError struct { Field string `json:"field"` Message string `json:"message"` } // SuccessBody 是成功回應的 envelope。 // // 對齊 api-spec.md:`{ "success": true, "data": ... }`。 type SuccessBody struct { Success bool `json:"success"` Data any `json:"data,omitempty"` } // WriteError 統一寫錯誤回應(會自動帶上 request_id)。 // // 注意:呼叫後 caller 仍需自行 c.Abort()(如果是在 middleware 中要終止 chain); // 在 handler 中只需 return 即可。 func WriteError(c *gin.Context, status int, code, message string, details []FieldError) { c.JSON(status, ErrorBody{ Success: false, Error: &ErrorDetail{ Code: code, Message: message, Details: details, RequestID: RequestIDFrom(c), }, }) } // WriteSuccess 統一寫成功回應。 func WriteSuccess(c *gin.Context, status int, data any) { c.JSON(status, SuccessBody{ Success: true, Data: data, }) } // WriteNotImplemented 回應 501,給 B5 還沒實作的 handler 用。 func WriteNotImplemented(c *gin.Context, hint string) { WriteError(c, 501, ErrCodeNotImplemented, hint, nil) }