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, } }