package error import ( code2 "code.30cm.net/wanderland/library-go/errors/code" "errors" "fmt" "net/http" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // Scope 全域變數應由服務或模組設置 var Scope = code2.Unset // Err 6 碼,服務 2, 大類 2, 詳細錯誤 2 type Err struct { category uint32 code uint32 scope uint32 msg string internalErr error } // Error 是錯誤的介面 // 私有屬性 "msg" 的 getter 函數 func (e *Err) Error() string { if e == nil { return "" } // 如果 internal err 存在,連接錯誤字串 var internalErrStr string if e.internalErr != nil { internalErrStr = e.internalErr.Error() } if e.msg != "" { if internalErrStr != "" { return fmt.Sprintf("%s: %s", e.msg, internalErrStr) } return e.msg } generalErrStr := e.GeneralError() if internalErrStr != "" { return fmt.Sprintf("%s: %s", generalErrStr, internalErrStr) } return generalErrStr } // Category 私有屬性 "category" 的 getter 函數 func (e *Err) Category() uint32 { if e == nil { return 0 } return e.category } // Scope 私有屬性 "scope" 的 getter 函數 func (e *Err) Scope() uint32 { if e == nil { return code2.Unset } return e.scope } // CodeStr 返回帶有零填充的錯誤代碼字串 func (e *Err) CodeStr() string { if e == nil { return "00000" } if e.Category() == code2.CatGRPC { return fmt.Sprintf("%d%04d", e.Scope(), e.Category()+e.Code()) } return fmt.Sprintf("%d%04d", e.Scope(), e.Code()) } // Code 私有屬性 "code" 的 getter 函數 func (e *Err) Code() uint32 { if e == nil { return code2.OK } return e.code } func (e *Err) FullCode() uint32 { if e == nil { return 0 } if e.Category() == code2.CatGRPC { return e.Scope()*10000 + e.Category() + e.Code() } return e.Scope()*10000 + e.Code() } // HTTPStatus 返回對應的 HTTP 狀態碼 func (e *Err) HTTPStatus() int { if e == nil || e.Code() == code2.OK { return http.StatusOK } // 根據 code 判斷狀態碼 switch e.Code() { case code2.ResourceInsufficient: // 400 return http.StatusBadRequest case code2.Unauthorized, code2.InsufficientPermission: // 401 return http.StatusUnauthorized case code2.InsufficientQuota: // 402 return http.StatusPaymentRequired case code2.InvalidPosixTime, code2.Forbidden: // 403 return http.StatusForbidden case code2.ResourceNotFound: // 404 return http.StatusNotFound case code2.ResourceAlreadyExist, code2.InvalidResourceState: // 409 return http.StatusConflict case code2.NotValidImplementation: // 501 return http.StatusNotImplemented default: } // 根據 category 判斷狀態碼 switch e.Category() { case code2.CatInput: return http.StatusBadRequest default: // 如果沒有符合的條件,返回狀態碼 500 return http.StatusInternalServerError } } // GeneralError 轉換 category 級別錯誤訊息 // 這是給客戶或 API 調用者的一般錯誤訊息 func (e *Err) GeneralError() string { if e == nil { return "" } errStr, ok := code2.CatToStr[e.Category()] if !ok { return "" } return errStr } // Is 在執行 errors.Is() 時調用。 // 除非你非常確定你在做什麼,否則不要直接使用這個函數。 // 請使用 errors.Is 代替。 // 此函數比較兩個錯誤變量是否都是 *Err,並且具有相同的 code(不檢查包裹的內部錯誤) func (e *Err) Is(f error) bool { var err *Err ok := errors.As(f, &err) if !ok { return false } return e.Code() == err.Code() } // Unwrap 返回底層錯誤 // 解除包裹錯誤的結果本身可能具有 Unwrap 方法; // 我們稱通過反覆解除包裹產生的錯誤序列為錯誤鏈。 func (e *Err) Unwrap() error { if e == nil { return nil } return e.internalErr } // Wrap 將內部錯誤設置到 Err 結構 func (e *Err) Wrap(internalErr error) *Err { if e != nil { e.internalErr = internalErr } return e } func (e *Err) GRPCStatus() *status.Status { if e == nil { return status.New(codes.OK, "") } return status.New(codes.Code(e.FullCode()), e.Error()) } // 工廠函數 // NewErr 創建新的 Err func NewErr(scope, category, detail uint32, msg string) *Err { return &Err{ category: category, code: detail, scope: scope, msg: msg, } } // NewGRPCErr 創建新的 gRPC Err func NewGRPCErr(scope, detail uint32, msg string) *Err { return &Err{ category: code2.CatGRPC, code: detail, scope: scope, msg: msg, } }