215 lines
5.5 KiB
Go
215 lines
5.5 KiB
Go
package errs
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"net/http"
|
||
|
||
"code.30cm.net/digimon/library-go/errs/code"
|
||
"google.golang.org/grpc/codes"
|
||
"google.golang.org/grpc/status"
|
||
)
|
||
|
||
// Scope 全域變數應由服務或模組設置
|
||
var Scope = code.Unset
|
||
|
||
// LibError 7 碼,服務 2 碼,詳細錯誤 2 碼 ,Cat 3 碼 不參與,獨立的 code 組成為( 000 category + 00 detail)
|
||
type LibError struct {
|
||
scope uint32 // 系統代號,*100000 來操作,顯示時不夠會補足 7 位數, Library 定義 -> 輸入時都只要輸入兩位數
|
||
category uint32 // 類別代碼 Library 定義 -> 不客製化業務訊息時,用這個大類別來給錯誤 3 碼
|
||
code uint32 // 細項 ,每個 repo 裡自行定義 -> 一萬以下都可以 2 碼
|
||
msg string // 顯示用的,給前端看的 Msg
|
||
internalErr error // 紀錄且包含真正的錯誤,通常用這個
|
||
}
|
||
|
||
// Error 是錯誤的介面
|
||
// 私有屬性 "displayMsg" 的 getter 函數,這邊只顯示業務邏輯錯誤,因為可能會帶出去給客戶端
|
||
// 要如何定位系統真的發生什麼錯?請使用 internalErr 來做定位,通常是會印 Log 的所以用這個
|
||
func (e *LibError) Error() string {
|
||
if e == nil {
|
||
return ""
|
||
}
|
||
|
||
return e.msg
|
||
}
|
||
|
||
// Category 私有屬性 "category" 的 getter 函數
|
||
func (e *LibError) Category() uint32 {
|
||
if e == nil {
|
||
return 0
|
||
}
|
||
|
||
return e.category
|
||
}
|
||
|
||
// Scope 私有屬性 "scope" 的 getter 函數
|
||
func (e *LibError) Scope() uint32 {
|
||
if e == nil {
|
||
return code.Unset
|
||
}
|
||
|
||
return e.scope
|
||
}
|
||
|
||
// Code 私有屬性 "code" 的 getter 函數
|
||
func (e *LibError) Code() uint32 {
|
||
if e == nil {
|
||
return code.OK
|
||
}
|
||
|
||
return e.code
|
||
}
|
||
|
||
func (e *LibError) FullCode() uint32 {
|
||
if e == nil {
|
||
return 0
|
||
}
|
||
|
||
return e.Scope()*100000 + e.Code()
|
||
}
|
||
|
||
// DisplayErrorCode 要顯示的 Error Code
|
||
func (e *LibError) DisplayErrorCode() string {
|
||
if e == nil {
|
||
return "000000"
|
||
}
|
||
|
||
return fmt.Sprintf("%06d", e.FullCode())
|
||
}
|
||
|
||
// InternalError 帶入真正的 error
|
||
func (e *LibError) InternalError() error {
|
||
var err error = fmt.Errorf("failed to get internal error")
|
||
if e == nil {
|
||
return err
|
||
}
|
||
|
||
if e.internalErr != nil {
|
||
err = e.internalErr
|
||
}
|
||
|
||
return err
|
||
}
|
||
|
||
// GeneralError 轉換 category 級別錯誤訊息,模糊化
|
||
func (e *LibError) GeneralError() string {
|
||
if e == nil {
|
||
return ""
|
||
}
|
||
|
||
errStr, ok := code.CatToStr[e.Category()]
|
||
if !ok {
|
||
return ""
|
||
}
|
||
|
||
return errStr
|
||
}
|
||
|
||
// Is 在執行 errors.Is() 時調用。
|
||
// 除非你非常確定你在做什麼,否則不要直接使用這個函數。
|
||
// 請使用 errors.Is 代替。
|
||
// 此函數比較兩個錯誤變量是否都是 *Err,並且具有相同的 code(不檢查包裹的內部錯誤)
|
||
func (e *LibError) Is(f error) bool {
|
||
var err *LibError
|
||
ok := errors.As(f, &err)
|
||
if !ok {
|
||
return false
|
||
}
|
||
|
||
return e.Code() == err.Code()
|
||
}
|
||
|
||
// Unwrap 返回底層錯誤
|
||
// 解除包裹錯誤的結果本身可能具有 Unwrap 方法;
|
||
// 我們稱通過反覆解除包裹產生的錯誤序列為錯誤鏈。
|
||
func (e *LibError) Unwrap() error {
|
||
if e == nil {
|
||
return nil
|
||
}
|
||
|
||
return e.internalErr
|
||
}
|
||
|
||
// Wrap 將內部錯誤設置到 Err 結構
|
||
func (e *LibError) Wrap(internalErr error) *LibError {
|
||
if e != nil {
|
||
e.internalErr = internalErr
|
||
}
|
||
|
||
return e
|
||
}
|
||
|
||
func (e *LibError) GRPCStatus() *status.Status {
|
||
if e == nil {
|
||
return status.New(codes.OK, "")
|
||
}
|
||
|
||
return status.New(codes.Code(e.FullCode()), e.Error())
|
||
}
|
||
|
||
// HTTPStatus 返回對應的 HTTP 狀態碼
|
||
func (e *LibError) HTTPStatus() int {
|
||
// 如果錯誤為空或錯誤碼為 OK,則返回 200 狀態碼
|
||
if e == nil || e.Code() == code.OK {
|
||
return http.StatusOK
|
||
}
|
||
// 根據錯誤碼判斷對應的 HTTP 狀態碼
|
||
switch e.Code() % 100 {
|
||
case code.ResourceInsufficient:
|
||
// 如果資源不足,返回 400 狀態碼
|
||
return http.StatusBadRequest
|
||
case code.Unauthorized, code.InsufficientPermission:
|
||
// 如果未授權或權限不足,返回 401 狀態碼
|
||
return http.StatusUnauthorized
|
||
case code.InsufficientQuota:
|
||
// 如果配額不足,返回 402 狀態碼
|
||
return http.StatusPaymentRequired
|
||
case code.InvalidPosixTime, code.Forbidden:
|
||
// 如果時間無效或禁止訪問,返回 403 狀態碼
|
||
return http.StatusForbidden
|
||
case code.ResourceNotFound:
|
||
// 如果資源未找到,返回 404 狀態碼
|
||
return http.StatusNotFound
|
||
case code.ResourceAlreadyExist, code.InvalidResourceState:
|
||
// 如果資源已存在或狀態無效,返回 409 狀態碼
|
||
return http.StatusConflict
|
||
case code.NotValidImplementation:
|
||
// 如果實現無效,返回 501 狀態碼
|
||
return http.StatusNotImplemented
|
||
default:
|
||
// 如果沒有匹配的錯誤碼,則繼續下一步
|
||
}
|
||
|
||
// 根據錯誤的類別判斷對應的 HTTP 狀態碼
|
||
switch e.Category() {
|
||
case code.CatInput:
|
||
// 如果錯誤屬於輸入錯誤類別,返回 400 狀態碼
|
||
return http.StatusBadRequest
|
||
default:
|
||
// 如果沒有符合的條件,返回 500 狀態碼
|
||
return http.StatusInternalServerError
|
||
}
|
||
}
|
||
|
||
// NewError 創建新的 Error
|
||
// 確保 category 在 0 到 999 之間,將超出的 category 設為最大值 999
|
||
// 確保 detail 在 0 到 99 之間,將超出的 detail 設為最大值 99
|
||
func NewError(scope, category, detail uint32, displayMsg string) *LibError {
|
||
// 確保 category 在 0 到 999 之間
|
||
if category > 999 {
|
||
category = 999 // 將超出的 category 設為最大值 999
|
||
}
|
||
|
||
// 確保 detail 在 0 到 99 之間
|
||
if detail > 99 {
|
||
detail = 99 // 將超出的 detail 設為最大值 99
|
||
}
|
||
|
||
return &LibError{
|
||
category: category,
|
||
code: category*100 + detail,
|
||
scope: scope,
|
||
msg: displayMsg,
|
||
}
|
||
}
|