Compare commits
No commits in common. "345e6d2454465365c0f3a92f6ce58e63db11ca07" and "04e1b0c12210d48b53ef52eb2fd1fd5884d40d27" have entirely different histories.
345e6d2454
...
04e1b0c122
3
Makefile
3
Makefile
|
@ -1,6 +1,3 @@
|
||||||
GOFMT ?= gofmt
|
|
||||||
GOFILES := $(shell find . -name "*.go")
|
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: # 進行測試
|
test: # 進行測試
|
||||||
go test -v --cover ./...
|
go test -v --cover ./...
|
||||||
|
|
|
@ -1030,40 +1030,3 @@ func TestFromError(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_newErr(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
scope uint32
|
|
||||||
detail uint32
|
|
||||||
msg string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "ok",
|
|
||||||
args: args{
|
|
||||||
scope: code.CloudEPMember,
|
|
||||||
detail: code.InvalidFormat,
|
|
||||||
msg: "gg88g88",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
er := newErr(tt.args.scope, tt.args.detail, tt.args.msg)
|
|
||||||
// er.CodeStr() 會補滿6碼,業務邏輯要回應這個
|
|
||||||
// 105,060 前面兩位會乘一萬做計算,中間兩位乘100 來做計算最後用補的
|
|
||||||
fmt.Println(er.Scope(), er.Category(), er.Code(), er.FullCode(), er.CodeStr())
|
|
||||||
fmt.Println(er.Error()) // 建立十原始錯誤 -> 業務邏輯,給客人看 gg88g88
|
|
||||||
er2 := fmt.Errorf("test origin err")
|
|
||||||
er = er.Wrap(er2) // 包裝錯誤
|
|
||||||
fmt.Println(er.Error()) // gg88g88: test origin err
|
|
||||||
err := er.Unwrap()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -77,10 +77,10 @@ func (e *LibError) CodeStr() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Category() == code2.CatGRPC {
|
if e.Category() == code2.CatGRPC {
|
||||||
return fmt.Sprintf("%02d%04d", e.Scope(), e.Category()+e.Code())
|
return fmt.Sprintf("%d%04d", e.Scope(), e.Category()+e.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%02d%04d", e.Scope(), e.Code())
|
return fmt.Sprintf("%d%04d", e.Scope(), e.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code 私有屬性 "code" 的 getter 函數
|
// Code 私有屬性 "code" 的 getter 函數
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
package code
|
|
||||||
|
|
||||||
// Category for general operations: 10 - 490
|
|
||||||
const (
|
|
||||||
_ = iota
|
|
||||||
CatInput uint32 = iota * 10
|
|
||||||
CatDB
|
|
||||||
CatResource
|
|
||||||
CatGRPC
|
|
||||||
CatAuth
|
|
||||||
CatSystem
|
|
||||||
CatPubSub
|
|
||||||
CatService
|
|
||||||
)
|
|
|
@ -1,74 +0,0 @@
|
||||||
package code
|
|
||||||
|
|
||||||
const (
|
|
||||||
OK uint32 = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
// 詳細代碼 - 輸入類 01x
|
|
||||||
const (
|
|
||||||
_ = iota + CatInput
|
|
||||||
InvalidFormat // 無效格式
|
|
||||||
NotValidImplementation // 非有效實現
|
|
||||||
InvalidRange // 無效範圍
|
|
||||||
)
|
|
||||||
|
|
||||||
// 詳細代碼 - 資料庫類 02x
|
|
||||||
const (
|
|
||||||
_ = iota + CatDB
|
|
||||||
DBError // 資料庫一般錯誤
|
|
||||||
DBDataConvert // 資料轉換錯誤
|
|
||||||
DBDuplicate // 資料重複
|
|
||||||
)
|
|
||||||
|
|
||||||
// 詳細代碼 - 資源類 03x
|
|
||||||
const (
|
|
||||||
_ = iota + CatResource
|
|
||||||
ResourceNotFound // 資源未找到
|
|
||||||
InvalidResourceFormat // 無效的資源格式
|
|
||||||
ResourceAlreadyExist // 資源已存在
|
|
||||||
ResourceInsufficient // 資源不足
|
|
||||||
InsufficientPermission // 權限不足
|
|
||||||
InvalidMeasurementID // 無效的測量ID
|
|
||||||
ResourceExpired // 資源過期
|
|
||||||
ResourceMigrated // 資源已遷移
|
|
||||||
InvalidResourceState // 無效的資源狀態
|
|
||||||
InsufficientQuota // 配額不足
|
|
||||||
ResourceHasMultiOwner // 資源有多個所有者
|
|
||||||
)
|
|
||||||
|
|
||||||
/* 詳細代碼 - GRPC */
|
|
||||||
// GRPC 的詳細代碼使用 Go GRPC 的內建代碼。
|
|
||||||
// 參考 "google.golang.org/grpc/codes" 獲取更多詳細資訊。
|
|
||||||
|
|
||||||
// 詳細代碼 - 驗證類 05x
|
|
||||||
const (
|
|
||||||
_ = iota + CatAuth
|
|
||||||
Unauthorized // 未授權
|
|
||||||
AuthExpired // 授權過期
|
|
||||||
InvalidPosixTime // 無效的 POSIX 時間
|
|
||||||
SigAndPayloadNotMatched // 簽名和載荷不匹配
|
|
||||||
Forbidden // 禁止訪問
|
|
||||||
)
|
|
||||||
|
|
||||||
// 詳細代碼 - 系統類 06x
|
|
||||||
const (
|
|
||||||
_ = iota + CatSystem
|
|
||||||
SystemInternalError // 系統內部錯誤
|
|
||||||
SystemMaintainError // 系統維護錯誤
|
|
||||||
SystemTimeoutError // 系統超時錯誤
|
|
||||||
)
|
|
||||||
|
|
||||||
// 詳細代碼 - PubSub 07x
|
|
||||||
const (
|
|
||||||
_ = iota + CatPubSub
|
|
||||||
Publish // 發佈錯誤
|
|
||||||
Consume // 消費錯誤
|
|
||||||
MsgSizeTooLarge // 訊息過大
|
|
||||||
)
|
|
||||||
|
|
||||||
// 詳細代碼 - 特定服務類 08x
|
|
||||||
const (
|
|
||||||
_ = iota + CatService
|
|
||||||
ArkInternal // Ark 內部錯誤
|
|
||||||
ArkHTTP400 // Ark HTTP 400 錯誤
|
|
||||||
)
|
|
|
@ -1,13 +0,0 @@
|
||||||
package code
|
|
||||||
|
|
||||||
// CatToStr collects general error messages for each Category
|
|
||||||
// It is used to send back to API caller
|
|
||||||
var CatToStr = map[uint32]string{
|
|
||||||
CatInput: "Invalid Input Data",
|
|
||||||
CatDB: "Database Error",
|
|
||||||
CatResource: "Resource Error",
|
|
||||||
CatGRPC: "Internal Service Communication Error",
|
|
||||||
CatAuth: "Authentication Error",
|
|
||||||
CatService: "Internal Service Communication Error",
|
|
||||||
CatSystem: "System Error",
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package code
|
|
||||||
|
|
||||||
// Scope
|
|
||||||
const (
|
|
||||||
Unset uint32 = iota
|
|
||||||
CloudEPPortalGW
|
|
||||||
CloudEPMember
|
|
||||||
CloudEPPermission
|
|
||||||
)
|
|
|
@ -1,535 +0,0 @@
|
||||||
package errs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.30cm.net/digimon/library-go/errs/code"
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultDetailCode = 00
|
|
||||||
)
|
|
||||||
|
|
||||||
func newBuiltinGRPCErr(scope, detail uint32, msg string) *LibError {
|
|
||||||
return &LibError{
|
|
||||||
category: code.CatGRPC,
|
|
||||||
code: detail,
|
|
||||||
scope: scope,
|
|
||||||
msg: msg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromError tries to let error as Err
|
|
||||||
// it supports to unwrap error that has Error
|
|
||||||
// return nil if failed to transfer
|
|
||||||
func FromError(err error) *LibError {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var e *LibError
|
|
||||||
if errors.As(err, &e) {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromCode parses code as following 7 碼
|
|
||||||
// Decimal: 1200314
|
|
||||||
// 12 represents Scope
|
|
||||||
// 003 represents Category
|
|
||||||
// 14 represents Detail error code
|
|
||||||
func FromCode(code uint32) *LibError {
|
|
||||||
// 獲取 scope,前兩位數
|
|
||||||
scope := code / 100000
|
|
||||||
|
|
||||||
// 獲取 detail,最後兩位數
|
|
||||||
detail := code % 100
|
|
||||||
|
|
||||||
// 獲取 category,中間三位數
|
|
||||||
category := (code / 100) % 1000
|
|
||||||
|
|
||||||
return &LibError{
|
|
||||||
category: category * 100, // category 放大為三位數的整百數
|
|
||||||
code: category*100 + detail, // 重構完整的 code
|
|
||||||
scope: scope,
|
|
||||||
msg: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromGRPCError transfer error to Err
|
|
||||||
// useful for gRPC client
|
|
||||||
func FromGRPCError(err error) *LibError {
|
|
||||||
s, _ := status.FromError(err)
|
|
||||||
e := FromCode(uint32(s.Code()))
|
|
||||||
e.msg = s.Message()
|
|
||||||
|
|
||||||
// For GRPC built-in code
|
|
||||||
if e.Scope() == code.Unset && e.Category() == 0 && e.Code() != code.OK {
|
|
||||||
e = newBuiltinGRPCErr(Scope, e.Code(), s.Message())
|
|
||||||
}
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** System ***/
|
|
||||||
|
|
||||||
// SystemTimeoutError xxx6300 returns Error 系統超時
|
|
||||||
func SystemTimeoutError(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.SystemTimeoutError, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SystemTimeoutErrorL logs error message and returns Err
|
|
||||||
func SystemTimeoutErrorL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := SystemTimeoutError(s...)
|
|
||||||
if filed != nil {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// SystemInternalError xxx6100 returns Err struct
|
|
||||||
func SystemInternalError(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.SystemInternalError, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SystemInternalErrorL logs error message and returns Err
|
|
||||||
func SystemInternalErrorL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := SystemInternalError(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** CatInput ***/
|
|
||||||
|
|
||||||
// InvalidFormat returns Err struct
|
|
||||||
func InvalidFormat(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.InvalidFormat, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidFormatL logs error message and returns Err
|
|
||||||
func InvalidFormatL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := InvalidFormat(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidRange returns Err struct
|
|
||||||
func InvalidRange(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.InvalidRange, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidRangeL logs error message and returns Err
|
|
||||||
func InvalidRangeL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := InvalidRange(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotValidImplementation returns Err struct
|
|
||||||
func NotValidImplementation(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.NotValidImplementation, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotValidImplementationL logs error message and returns Err
|
|
||||||
func NotValidImplementationL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := NotValidImplementation(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** CatDB ***/
|
|
||||||
|
|
||||||
// DBError returns Err
|
|
||||||
func DBError(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.DBError, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DBErrorL logs error message and returns Err
|
|
||||||
func DBErrorL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := DBError(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// DBDataConvert returns Err
|
|
||||||
func DBDataConvert(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.DBDataConvert, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DBDataConvertL logs error message and returns Err
|
|
||||||
func DBDataConvertL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := DBDataConvert(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// DBDuplicate returns Err
|
|
||||||
func DBDuplicate(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.DBDuplicate, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DBDuplicateL logs error message and returns Err
|
|
||||||
func DBDuplicateL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := DBDuplicate(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** CatResource ***/
|
|
||||||
|
|
||||||
// ResourceNotFound returns Err and logging
|
|
||||||
func ResourceNotFound(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.ResourceNotFound, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceNotFoundL logs error message and returns Err
|
|
||||||
func ResourceNotFoundL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := ResourceNotFound(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidResourceFormat returns Err
|
|
||||||
func InvalidResourceFormat(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.InvalidResourceFormat, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidResourceFormatL logs error message and returns Err
|
|
||||||
func InvalidResourceFormatL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := InvalidResourceFormat(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidResourceState returns status not correct.
|
|
||||||
// for example: company should be destroy, agent should be no-sensor/fail-install ...
|
|
||||||
func InvalidResourceState(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.InvalidResourceState, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidResourceStateL logs error message and returns status not correct.
|
|
||||||
func InvalidResourceStateL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := InvalidResourceState(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResourceInsufficient(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.ResourceInsufficient, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResourceInsufficientL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := ResourceInsufficient(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsufficientPermission returns Err
|
|
||||||
func InsufficientPermission(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.InsufficientPermission,
|
|
||||||
defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsufficientPermissionL returns Err and log
|
|
||||||
func InsufficientPermissionL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := InsufficientPermission(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceAlreadyExist returns Err
|
|
||||||
func ResourceAlreadyExist(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.ResourceAlreadyExist, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceAlreadyExistL logs error message and returns Err
|
|
||||||
func ResourceAlreadyExistL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := ResourceAlreadyExist(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidMeasurementID returns Err
|
|
||||||
func InvalidMeasurementID(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.InvalidMeasurementID, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidMeasurementIDL logs error message and returns Err
|
|
||||||
func InvalidMeasurementIDL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := InvalidMeasurementID(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceExpired returns Err
|
|
||||||
func ResourceExpired(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.ResourceExpired,
|
|
||||||
defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceExpiredL logs error message and returns Err
|
|
||||||
func ResourceExpiredL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := ResourceExpired(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceMigrated returns Err
|
|
||||||
func ResourceMigrated(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.ResourceMigrated, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceMigratedL logs error message and returns Err
|
|
||||||
func ResourceMigratedL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := ResourceMigrated(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsufficientQuota returns Err
|
|
||||||
func InsufficientQuota(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.InsufficientQuota, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsufficientQuotaL logs error message and returns Err
|
|
||||||
func InsufficientQuotaL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := InsufficientQuota(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** CatAuth ***/
|
|
||||||
|
|
||||||
// Unauthorized returns Err
|
|
||||||
func Unauthorized(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.Unauthorized, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnauthorizedL logs error message and returns Err
|
|
||||||
func UnauthorizedL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := Unauthorized(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthExpired returns Err
|
|
||||||
func AuthExpired(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.AuthExpired, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthExpiredL logs error message and returns Err
|
|
||||||
func AuthExpiredL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := AuthExpired(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidPosixTime returns Err
|
|
||||||
func InvalidPosixTime(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.InvalidPosixTime, defaultDetailCode,
|
|
||||||
fmt.Sprintf("i%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidPosixTimeL logs error message and returns Err
|
|
||||||
func InvalidPosixTimeL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := InvalidPosixTime(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// SigAndPayloadNotMatched returns Err
|
|
||||||
func SigAndPayloadNotMatched(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.SigAndPayloadNotMatched, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SigAndPayloadNotMatchedL logs error message and returns Err
|
|
||||||
func SigAndPayloadNotMatchedL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := SigAndPayloadNotMatched(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forbidden returns Err
|
|
||||||
func Forbidden(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.Forbidden, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForbiddenL logs error message and returns Err
|
|
||||||
func ForbiddenL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := Forbidden(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAuthUnauthorizedError check the err is unauthorized error
|
|
||||||
func IsAuthUnauthorizedError(err *LibError) bool {
|
|
||||||
switch err.Code() / 100 {
|
|
||||||
case code.Unauthorized, code.AuthExpired, code.InvalidPosixTime,
|
|
||||||
code.SigAndPayloadNotMatched, code.Forbidden,
|
|
||||||
code.InvalidFormat, code.ResourceNotFound:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** CatPubSub ***/
|
|
||||||
|
|
||||||
// Publish returns Err
|
|
||||||
func Publish(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.Publish, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublishL logs error message and returns Err
|
|
||||||
func PublishL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := Publish(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume returns Err
|
|
||||||
func Consume(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.Consume, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConsumeL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := Consume(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// MsgSizeTooLarge returns Err
|
|
||||||
func MsgSizeTooLarge(s ...string) *LibError {
|
|
||||||
return NewError(Scope, code.MsgSizeTooLarge, defaultDetailCode,
|
|
||||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MsgSizeTooLargeL logs error message and returns Err
|
|
||||||
func MsgSizeTooLargeL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
|
||||||
e := MsgSizeTooLarge(s...)
|
|
||||||
if filed != nil || len(filed) >= 0 {
|
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
|
||||||
}
|
|
||||||
l.WithCallerSkip(1).Error(e.Error())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
|
@ -1,184 +0,0 @@
|
||||||
package errs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"code.30cm.net/digimon/library-go/errs/code"
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFromError(t *testing.T) {
|
|
||||||
t.Run("nil error", func(t *testing.T) {
|
|
||||||
if err := FromError(nil); err != nil {
|
|
||||||
t.Errorf("expected nil, got %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("LibError type", func(t *testing.T) {
|
|
||||||
libErr := NewError(1, 200, 10, "test error")
|
|
||||||
if err := FromError(libErr); err != libErr {
|
|
||||||
t.Errorf("expected %v, got %v", libErr, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFromCode(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
code uint32
|
|
||||||
expected *LibError
|
|
||||||
}{
|
|
||||||
{"valid code", 1200314, NewError(12, 3, 14, "")},
|
|
||||||
{"invalid code", 9999999, NewError(99, 999, 99, "")},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := FromCode(tt.code)
|
|
||||||
if err.FullCode() != tt.expected.FullCode() {
|
|
||||||
t.Errorf("expected %v, got %v", tt.expected.FullCode(), err.FullCode())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSystemTimeoutError(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input []string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"single string", []string{"timeout"}, "timeout"},
|
|
||||||
{"multiple strings", []string{"timeout", "occurred"}, "timeout occurred"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := SystemTimeoutError(tt.input...)
|
|
||||||
if err.Error() != tt.expected {
|
|
||||||
t.Errorf("expected %s, got %s", tt.expected, err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSystemInternalErrorL(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input []string
|
|
||||||
}{
|
|
||||||
{"internal error", []string{"internal error"}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
ctx := context.TODO()
|
|
||||||
err := SystemInternalErrorL(logx.WithContext(ctx), nil, tt.input...)
|
|
||||||
if err.Error() != tt.input[0] {
|
|
||||||
t.Errorf("expected %s, got %s", tt.input[0], err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInvalidFormatL(t *testing.T) {
|
|
||||||
mockLogger := logx.WithContext(context.Background())
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input []string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"invalid format", []string{"invalid format"}, "invalid format"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := InvalidFormatL(mockLogger, nil, tt.input...)
|
|
||||||
if err.Error() != tt.expected {
|
|
||||||
t.Errorf("expected %s, got %s", tt.expected, err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDBErrorL(t *testing.T) {
|
|
||||||
mockLogger := logx.WithContext(context.Background())
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input []string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"DB error", []string{"DB error"}, "DB error"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := DBErrorL(mockLogger, nil, tt.input...)
|
|
||||||
if err.Error() != tt.expected {
|
|
||||||
t.Errorf("expected %s, got %s", tt.expected, err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResourceNotFoundL(t *testing.T) {
|
|
||||||
mockLogger := logx.WithContext(context.Background())
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input []string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"resource not found", []string{"resource not found"}, "resource not found"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := ResourceNotFoundL(mockLogger, nil, tt.input...)
|
|
||||||
if err.Error() != tt.expected {
|
|
||||||
t.Errorf("expected %s, got %s", tt.expected, err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInsufficientPermissionL(t *testing.T) {
|
|
||||||
mockLogger := logx.WithContext(context.Background())
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input []string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"insufficient permission", []string{"permission denied"}, "permission denied"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := InsufficientPermissionL(mockLogger, nil, tt.input...)
|
|
||||||
if err.Error() != tt.expected {
|
|
||||||
t.Errorf("expected %s, got %s", tt.expected, err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsAuthUnauthorizedError(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
err *LibError
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{"Unauthorized error", NewError(1, code.Unauthorized, 0, ""), true},
|
|
||||||
{"AuthExpired error", NewError(1, code.AuthExpired, 0, ""), true},
|
|
||||||
{"Other error", NewError(1, code.ArkInternal, 0, ""), false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
res := IsAuthUnauthorizedError(tt.err)
|
|
||||||
if res != tt.expected {
|
|
||||||
t.Errorf("expected %t, got %t", tt.expected, res)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
214
errs/errors.go
214
errs/errors.go
|
@ -1,214 +0,0 @@
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,177 +0,0 @@
|
||||||
package errs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"code.30cm.net/digimon/library-go/errs/code"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewError(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
scope uint32
|
|
||||||
category uint32
|
|
||||||
detail uint32
|
|
||||||
displayMsg string
|
|
||||||
expectedMsg string
|
|
||||||
expectedCat uint32
|
|
||||||
expectedDet uint32
|
|
||||||
}{
|
|
||||||
{"valid error", 1, 200, 10, "test error", "test error", 200, 20010},
|
|
||||||
{"category overflow", 1, 1000, 10, "test error", "test error", 999, 99910},
|
|
||||||
{"detail overflow", 1, 200, 150, "test error", "test error", 200, 20099},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := NewError(tt.scope, tt.category, tt.detail, tt.displayMsg)
|
|
||||||
if err.Error() != tt.expectedMsg {
|
|
||||||
t.Errorf("expected %s, got %s", tt.expectedMsg, err.Error())
|
|
||||||
}
|
|
||||||
if err.Category() != tt.expectedCat {
|
|
||||||
t.Errorf("expected category %d, got %d", tt.expectedCat, err.Category())
|
|
||||||
}
|
|
||||||
if err.Code() != tt.expectedDet {
|
|
||||||
t.Errorf("expected code %d, got %d", tt.expectedDet, err.Code())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLibError_FullCode(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
scope uint32
|
|
||||||
category uint32
|
|
||||||
detail uint32
|
|
||||||
expectedCode uint32
|
|
||||||
}{
|
|
||||||
{"valid code", 1, 200, 10, 120010},
|
|
||||||
{"category overflow", 1, 1000, 10, 199910},
|
|
||||||
{"detail overflow", 1, 200, 150, 120099},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := NewError(tt.scope, tt.category, tt.detail, "test")
|
|
||||||
if err.FullCode() != tt.expectedCode {
|
|
||||||
t.Errorf("expected %d, got %d", tt.expectedCode, err.FullCode())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLibError_HTTPStatus(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
err *LibError
|
|
||||||
expected int
|
|
||||||
}{
|
|
||||||
{"bad request", NewError(1, code.CatService, code.ResourceInsufficient, "bad request"), http.StatusBadRequest},
|
|
||||||
{"unauthorized", NewError(1, code.CatAuth, code.Unauthorized, "unauthorized"), http.StatusUnauthorized},
|
|
||||||
{"forbidden", NewError(1, code.CatAuth, code.Forbidden, "forbidden"), http.StatusForbidden},
|
|
||||||
{"not found", NewError(1, code.CatResource, code.ResourceNotFound, "not found"), http.StatusNotFound},
|
|
||||||
{"internal server error", NewError(1, code.CatDB, 1095, "not found"), http.StatusInternalServerError},
|
|
||||||
{"input err", NewError(1, code.CatInput, 1095, "not found"), http.StatusBadRequest},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if status := tt.err.HTTPStatus(); status != tt.expected {
|
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLibError_Error(t *testing.T) {
|
|
||||||
err := NewError(0, 100, 5, "test error")
|
|
||||||
expected := "test error"
|
|
||||||
if err.Error() != expected {
|
|
||||||
t.Errorf("expected '%s', got '%s'", expected, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLibError_Is(t *testing.T) {
|
|
||||||
err1 := NewError(0, 1, 1, "error 1")
|
|
||||||
err2 := NewError(0, 1, 1, "error 2")
|
|
||||||
err3 := errors.New("other error")
|
|
||||||
|
|
||||||
if !err1.Is(err2) {
|
|
||||||
t.Error("expected errors to be equal")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err1.Is(err3) {
|
|
||||||
t.Error("expected errors to not be equal")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLibError_DisplayErrorCode(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
err *LibError
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"valid code", NewError(1, 200, 10, "test error"), "120010"},
|
|
||||||
{"nil error", nil, "000000"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if code := tt.err.DisplayErrorCode(); code != tt.expected {
|
|
||||||
t.Errorf("expected %s, got %s", tt.expected, code)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLibError_Unwrap(t *testing.T) {
|
|
||||||
originalErr := errors.New("original error")
|
|
||||||
libErr := NewError(0, 1, 1, "wrapped error").Wrap(originalErr)
|
|
||||||
|
|
||||||
if unwrappedErr := libErr.Unwrap(); unwrappedErr != originalErr {
|
|
||||||
t.Errorf("expected original error, got %v", unwrappedErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLibError_InternalError(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
internalErr error
|
|
||||||
expected error
|
|
||||||
}{
|
|
||||||
{"valid internal error", errors.New("internal"), errors.New("internal")},
|
|
||||||
{"nil internal error", nil, errors.New("failed to get internal error")},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := NewError(1, 200, 10, "test").Wrap(tt.internalErr)
|
|
||||||
if internalErr := err.InternalError(); internalErr.Error() != tt.expected.Error() {
|
|
||||||
t.Errorf("expected %v, got %v", tt.expected, internalErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLibError_GRPCStatus(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
err *LibError
|
|
||||||
expected codes.Code
|
|
||||||
}{
|
|
||||||
{"valid GRPC status", NewError(1, 200, 10, "test error"), codes.Code(120010)},
|
|
||||||
{"nil error", nil, codes.OK},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if status := tt.err.GRPCStatus().Code(); status != tt.expected {
|
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
module code.30cm.net/digimon/library-go/errs
|
|
||||||
|
|
||||||
go 1.22.3
|
|
125
errs/readme.md
125
errs/readme.md
|
@ -1,125 +0,0 @@
|
||||||
## Purpose of errs package
|
|
||||||
1. compatible with `error` interface
|
|
||||||
2. encapsulate error message with functions in `easy_function.go`
|
|
||||||
3. easy for gRPC client/server
|
|
||||||
4. support err's chain by [Working with Errors in Go 1.13](https://blog.golang.org/go1.13-errors)
|
|
||||||
|
|
||||||
|
|
||||||
## Example - Normal function
|
|
||||||
Using builtin functions `InvalidInput` to generate `Err struct`
|
|
||||||
|
|
||||||
**please add your own functions if not exist**
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "adc.github.trendmicro.com/commercial-mgcp/library-go/pkg/errs"
|
|
||||||
|
|
||||||
func handleParam(s string) error {
|
|
||||||
// check user_id format
|
|
||||||
if ok := userIDFormat(s); !ok {
|
|
||||||
return errs.InvalidFormat("param user_id")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example - gRPC Server
|
|
||||||
`GetAgent` is a method of gRPC server, it wraps `Err` struct to `status.Status` struct
|
|
||||||
```go
|
|
||||||
func (as *agentService) GetAgent(ctx context.Context, req *cloudep.GetAgentRequest) (*cloudep.GetAgentResponse, error) {
|
|
||||||
l := log.WithFields(logger.Fields{"tenant_id": req.TenantId, "agent_id": req.AgentId, "method": "GetAgent"})
|
|
||||||
|
|
||||||
tenantID, err := primitive.ObjectIDFromHex(req.TenantId)
|
|
||||||
if err != nil {
|
|
||||||
// err maybe errs.Err or general error
|
|
||||||
// it's safe to use Convert() here
|
|
||||||
return nil, status.Convert(err).Err()
|
|
||||||
}
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Example - gRPC Client
|
|
||||||
Calling `GetAgent` and retry when Category is "DB"
|
|
||||||
```go
|
|
||||||
client := cloudep.NewAgentServiceClient(conn)
|
|
||||||
req := cloudep.GetAgentRequest{
|
|
||||||
TenantId: "not-a-valid-object-id",
|
|
||||||
AgentId: "5eb4fa99006d53c0cb6f9cfe",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retry if DB error
|
|
||||||
for retry := 3; retry > 0 ; retry-- {
|
|
||||||
resp, err := client.GetAgent(context.Background(), &req)
|
|
||||||
if err != nil {
|
|
||||||
e := errs.FromGRPCError(err)
|
|
||||||
if e.Category() == code.CatGRPC {
|
|
||||||
if e.Code() == uint32(codes.Unavailable) {
|
|
||||||
log.warn("GRPC service unavailable. Retrying...")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.errorf("GRPC built-in error: %v", e)
|
|
||||||
}
|
|
||||||
if e.Category() == code.CatDB {
|
|
||||||
log.warn("retry...")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example - REST server
|
|
||||||
1. handling gRPC client error
|
|
||||||
2. transfer to HTTP code
|
|
||||||
3. transfer to Error body
|
|
||||||
|
|
||||||
```go
|
|
||||||
func Handler(c *gin.Context) {
|
|
||||||
|
|
||||||
// handle error from gRPC client
|
|
||||||
resp, err := client.GetAgent(context.Background(), &req)
|
|
||||||
if err != nil {
|
|
||||||
// to Err
|
|
||||||
e := errs.FromGRPCError(err)
|
|
||||||
|
|
||||||
// get HTTP code & response struct
|
|
||||||
// 2nd parameter true means return general error message to user
|
|
||||||
c.JSON(e.HTTPStatus(), general.NewError(e, true))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example - Error Chain
|
|
||||||
1. set internal error by func `Wrap`
|
|
||||||
2. check Err has any error in err's chain matches the target by `errors.Is`
|
|
||||||
3. finds the first error in err's chain that matches target by `errors.As`
|
|
||||||
|
|
||||||
```go
|
|
||||||
// define a specific err type
|
|
||||||
type testErr struct {
|
|
||||||
code int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *testErr) Error() string {
|
|
||||||
return strconv.Itoa(e.code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
layer1Err := &testErr{code: 123}
|
|
||||||
// error chain: InvalidFormat -> layer 1 err
|
|
||||||
layer2Err := InvalidFormat("field A", "")
|
|
||||||
layer2Err.Wrap(layer1Err) //set internal error
|
|
||||||
|
|
||||||
// errors.Is should report true
|
|
||||||
hasLayer1Err := errors.Is(layer2Err, layer1Err)
|
|
||||||
|
|
||||||
// errors.As should return internal error
|
|
||||||
var internalErr *testErr
|
|
||||||
ok := errors.As(layer2Err, &internalErr)
|
|
||||||
}
|
|
||||||
```
|
|
Loading…
Reference in New Issue