From b05ae52d48ae7dec1706caf8b49bd25ab3f590a4 Mon Sep 17 00:00:00 2001 From: "daniel.w" Date: Tue, 20 Aug 2024 01:13:31 +0800 Subject: [PATCH 1/3] add error and error easy func --- errors/code/define.go | 99 +++++++++ errors/code/messsage.go | 13 ++ errors/easy_func.go | 442 ++++++++++++++++++++++++++++++++++++++++ errors/errors.go | 218 ++++++++++++++++++++ errors/go.mod | 21 ++ errors/readme.md | 125 ++++++++++++ 6 files changed, 918 insertions(+) create mode 100644 errors/code/define.go create mode 100644 errors/code/messsage.go create mode 100644 errors/easy_func.go create mode 100644 errors/errors.go create mode 100644 errors/go.mod create mode 100644 errors/readme.md diff --git a/errors/code/define.go b/errors/code/define.go new file mode 100644 index 0000000..bc36826 --- /dev/null +++ b/errors/code/define.go @@ -0,0 +1,99 @@ +package code + +const ( + OK uint32 = 0 +) + +// Scope +const ( + Unset uint32 = iota + CloudEPPortalGW + CloudEPMember + CloudEPPermission +) + +// Category for general operations: 100 - 4900 +const ( + _ = iota + CatInput uint32 = iota * 100 + CatDB + CatResource + CatGRPC + CatAuth + CatSystem + CatPubSub +) + +// CatArk Category for specific app/service: 5000 - 9900 +const ( + CatArk uint32 = (iota + 50) * 100 +) + +// Detail - Input 1xx +const ( + _ = iota + CatInput + InvalidFormat + NotValidImplementation + InvalidRange +) + +// Detail - Database 2xx +const ( + _ = iota + CatDB + DBError // general error + DBDataConvert + DBDuplicate +) + +// Detail - Resource 3xx +const ( + _ = iota + CatResource + ResourceNotFound + InvalidResourceFormat + ResourceAlreadyExist + ResourceInsufficient + InsufficientPermission + InvalidMeasurementID + ResourceExpired + ResourceMigrated + InvalidResourceState + InsufficientQuota + ResourceHasMultiOwner +) + +/* Detail - GRPC */ +// The GRPC detail code uses Go GRPC's built-in codes. +// Refer to "google.golang.org/grpc/codes" for more detail. + +// Detail - Auth 5xx +const ( + _ = iota + CatAuth + Unauthorized + AuthExpired + InvalidPosixTime + SigAndPayloadNotMatched + Forbidden +) + +// Detail - System 6xx +const ( + _ = iota + CatSystem + SystemInternalError + SystemMaintainError + SystemTimeoutError +) + +// Detail - PubSub 7xx +const ( + _ = iota + CatPubSub + Publish + Consume + MsgSizeTooLarge +) + +// Detail - Ark 5xxx +const ( + _ = iota + CatArk + ArkInternal + ArkHttp400 +) diff --git a/errors/code/messsage.go b/errors/code/messsage.go new file mode 100644 index 0000000..18a4d4f --- /dev/null +++ b/errors/code/messsage.go @@ -0,0 +1,13 @@ +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", + CatArk: "Internal Service Communication Error", + CatSystem: "System Error", +} diff --git a/errors/easy_func.go b/errors/easy_func.go new file mode 100644 index 0000000..d305435 --- /dev/null +++ b/errors/easy_func.go @@ -0,0 +1,442 @@ +package error + +import ( + "code.30cm.net/digimon/library-go/errors/code" + "errors" + "fmt" + "strings" + + "github.com/zeromicro/go-zero/core/logx" + _ "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func newErr(scope, detail uint32, msg string) *Err { + cat := detail / 100 * 100 + return &Err{ + category: cat, + code: detail, + scope: scope, + msg: msg, + } +} + +func newBuiltinGRPCErr(scope, detail uint32, msg string) *Err { + return &Err{ + category: code.CatGRPC, + code: detail, + scope: scope, + msg: msg, + } +} + +// FromError tries to let error as Err +// it supports to unwrap error that has Err +// return nil if failed to transfer +func FromError(err error) *Err { + if err == nil { + return nil + } + + var e *Err + if errors.As(err, &e) { + return e + } + + return nil +} + +// FromCode parses code as following +// Decimal: 120314 +// 12 represents Scope +// 03 represents Category +// 14 represents Detail error code +func FromCode(code uint32) *Err { + scope := code / 10000 + detail := code % 10000 + return &Err{ + category: detail / 100 * 100, + code: detail, + scope: scope, + msg: "", + } +} + +// FromGRPCError transfer error to Err +// useful for gRPC client +func FromGRPCError(err error) *Err { + 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 +} + +// Deprecated: check GRPCStatus() in Errs struct +// ToGRPCError returns the status.Status +// Useful to return error in gRPC server +func ToGRPCError(e *Err) error { + return status.New(codes.Code(e.FullCode()), e.Error()).Err() +} + +/*** System ***/ + +// SystemTimeoutError returns Err +func SystemTimeoutError(s ...string) *Err { + return newErr(Scope, code.SystemTimeoutError, fmt.Sprintf("system timeout: %s", strings.Join(s, " "))) +} + +// SystemTimeoutErrorL logs error message and returns Err +func SystemTimeoutErrorL(l logx.Logger, s ...string) *Err { + e := SystemTimeoutError(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// SystemInternalError returns Err struct +func SystemInternalError(s ...string) *Err { + return newErr(Scope, code.SystemInternalError, fmt.Sprintf("internal error: %s", strings.Join(s, " "))) +} + +// SystemInternalErrorL logs error message and returns Err +func SystemInternalErrorL(l logx.Logger, s ...string) *Err { + e := SystemInternalError(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// SystemMaintainErrorL logs error message and returns Err +func SystemMaintainErrorL(l logx.Logger, s ...string) *Err { + e := SystemMaintainError(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// SystemMaintainError returns Err struct +func SystemMaintainError(s ...string) *Err { + return newErr(Scope, code.SystemMaintainError, fmt.Sprintf("service under maintenance: %s", strings.Join(s, " "))) +} + +/*** CatInput ***/ + +// InvalidFormat returns Err struct +func InvalidFormat(s ...string) *Err { + return newErr(Scope, code.InvalidFormat, fmt.Sprintf("invalid format: %s", strings.Join(s, " "))) +} + +// InvalidFormatL logs error message and returns Err +func InvalidFormatL(l logx.Logger, s ...string) *Err { + e := InvalidFormat(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// InvalidRange returns Err struct +func InvalidRange(s ...string) *Err { + return newErr(Scope, code.InvalidRange, fmt.Sprintf("invalid range: %s", strings.Join(s, " "))) +} + +// InvalidRangeL logs error message and returns Err +func InvalidRangeL(l logx.Logger, s ...string) *Err { + e := InvalidRange(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// NotValidImplementation returns Err struct +func NotValidImplementation(s ...string) *Err { + return newErr(Scope, code.NotValidImplementation, fmt.Sprintf("not valid implementation: %s", strings.Join(s, " "))) +} + +// NotValidImplementationL logs error message and returns Err +func NotValidImplementationL(l logx.Logger, s ...string) *Err { + e := NotValidImplementation(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +/*** CatDB ***/ + +// DBError returns Err +func DBError(s ...string) *Err { + return newErr(Scope, code.DBError, fmt.Sprintf("db error: %s", strings.Join(s, " "))) +} + +// DBErrorL logs error message and returns Err +func DBErrorL(l logx.Logger, s ...string) *Err { + e := DBError(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// DBDataConvert returns Err +func DBDataConvert(s ...string) *Err { + return newErr(Scope, code.DBDataConvert, fmt.Sprintf("data from db convert error: %s", strings.Join(s, " "))) +} + +// DBDataConvertL logs error message and returns Err +func DBDataConvertL(l logx.Logger, s ...string) *Err { + e := DBDataConvert(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// DBDuplicate returns Err +func DBDuplicate(s ...string) *Err { + return newErr(Scope, code.DBDuplicate, fmt.Sprintf("data Duplicate key error: %s", strings.Join(s, " "))) +} + +// DBDuplicateL logs error message and returns Err +func DBDuplicateL(l logx.Logger, s ...string) *Err { + e := DBDuplicate(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +/*** CatResource ***/ + +// ResourceNotFound returns Err and logging +func ResourceNotFound(s ...string) *Err { + return newErr(Scope, code.ResourceNotFound, fmt.Sprintf("resource not found: %s", strings.Join(s, " "))) +} + +// ResourceNotFoundL logs error message and returns Err +func ResourceNotFoundL(l logx.Logger, s ...string) *Err { + e := ResourceNotFound(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// InvalidResourceFormat returns Err +func InvalidResourceFormat(s ...string) *Err { + return newErr(Scope, code.InvalidResourceFormat, fmt.Sprintf("invalid resource format: %s", strings.Join(s, " "))) +} + +// InvalidResourceFormatL logs error message and returns Err +func InvalidResourceFormatL(l logx.Logger, s ...string) *Err { + e := InvalidResourceFormat(s...) + 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) *Err { + return newErr(Scope, code.InvalidResourceState, fmt.Sprintf("invalid resource state: %s", strings.Join(s, " "))) +} + +// InvalidResourceStateL logs error message and returns status not correct. +func InvalidResourceStateL(l logx.Logger, s ...string) *Err { + e := InvalidResourceState(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +func ResourceInsufficient(s ...string) *Err { + return newErr(Scope, code.ResourceInsufficient, + fmt.Sprintf("insufficient resource: %s", strings.Join(s, " "))) +} + +func ResourceInsufficientL(l logx.Logger, s ...string) *Err { + e := ResourceInsufficient(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// InsufficientPermission returns Err +func InsufficientPermission(s ...string) *Err { + return newErr(Scope, code.InsufficientPermission, + fmt.Sprintf("insufficient permission: %s", strings.Join(s, " "))) +} + +// InsufficientPermissionL returns Err and log +func InsufficientPermissionL(l logx.Logger, s ...string) *Err { + e := InsufficientPermission(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// ResourceAlreadyExist returns Err +func ResourceAlreadyExist(s ...string) *Err { + return newErr(Scope, code.ResourceAlreadyExist, fmt.Sprintf("resource already exist: %s", strings.Join(s, " "))) +} + +// ResourceAlreadyExistL logs error message and returns Err +func ResourceAlreadyExistL(l logx.Logger, s ...string) *Err { + e := ResourceAlreadyExist(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// InvalidMeasurementID returns Err +func InvalidMeasurementID(s ...string) *Err { + return newErr(Scope, code.InvalidMeasurementID, fmt.Sprintf("missing measurement id: %s", strings.Join(s, " "))) +} + +// InvalidMeasurementIDL logs error message and returns Err +func InvalidMeasurementIDL(l logx.Logger, s ...string) *Err { + e := InvalidMeasurementID(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// ResourceExpired returns Err +func ResourceExpired(s ...string) *Err { + return newErr(Scope, code.ResourceExpired, fmt.Sprintf("resource expired: %s", strings.Join(s, " "))) +} + +// ResourceExpiredL logs error message and returns Err +func ResourceExpiredL(l logx.Logger, s ...string) *Err { + e := ResourceExpired(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// ResourceMigrated returns Err +func ResourceMigrated(s ...string) *Err { + return newErr(Scope, code.ResourceMigrated, fmt.Sprintf("resource migrated: %s", strings.Join(s, " "))) +} + +// ResourceMigratedL logs error message and returns Err +func ResourceMigratedL(l logx.Logger, s ...string) *Err { + e := ResourceMigrated(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// InsufficientQuota returns Err +func InsufficientQuota(s ...string) *Err { + return newErr(Scope, code.InsufficientQuota, fmt.Sprintf("insufficient quota: %s", strings.Join(s, " "))) +} + +// InsufficientQuotaL logs error message and returns Err +func InsufficientQuotaL(l logx.Logger, s ...string) *Err { + e := InsufficientQuota(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +/*** CatAuth ***/ + +// Unauthorized returns Err +func Unauthorized(s ...string) *Err { + return newErr(Scope, code.Unauthorized, fmt.Sprintf("unauthorized: %s", strings.Join(s, " "))) +} + +// UnauthorizedL logs error message and returns Err +func UnauthorizedL(l logx.Logger, s ...string) *Err { + e := Unauthorized(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// AuthExpired returns Err +func AuthExpired(s ...string) *Err { + return newErr(Scope, code.AuthExpired, fmt.Sprintf("expired: %s", strings.Join(s, " "))) +} + +// AuthExpiredL logs error message and returns Err +func AuthExpiredL(l logx.Logger, s ...string) *Err { + e := AuthExpired(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// InvalidPosixTime returns Err +func InvalidPosixTime(s ...string) *Err { + return newErr(Scope, code.InvalidPosixTime, fmt.Sprintf("invalid posix time: %s", strings.Join(s, " "))) +} + +// InvalidPosixTimeL logs error message and returns Err +func InvalidPosixTimeL(l logx.Logger, s ...string) *Err { + e := InvalidPosixTime(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// SigAndPayloadNotMatched returns Err +func SigAndPayloadNotMatched(s ...string) *Err { + return newErr(Scope, code.SigAndPayloadNotMatched, fmt.Sprintf("signature and the payload are not match: %s", strings.Join(s, " "))) +} + +// SigAndPayloadNotMatchedL logs error message and returns Err +func SigAndPayloadNotMatchedL(l logx.Logger, s ...string) *Err { + e := SigAndPayloadNotMatched(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// Forbidden returns Err +func Forbidden(s ...string) *Err { + return newErr(Scope, code.Forbidden, fmt.Sprintf("forbidden: %s", strings.Join(s, " "))) +} + +// ForbiddenL logs error message and returns Err +func ForbiddenL(l logx.Logger, s ...string) *Err { + e := Forbidden(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// IsAuthUnauthorizedError check the err is unauthorized error +func IsAuthUnauthorizedError(err *Err) bool { + switch err.Code() { + case code.Unauthorized, code.AuthExpired, code.InvalidPosixTime, + code.SigAndPayloadNotMatched, code.Forbidden, + code.InvalidFormat, code.ResourceNotFound: + return true + default: + return false + } +} + +/*** CatXBC ***/ + +// ArkInternal returns Err +func ArkInternal(s ...string) *Err { + return newErr(Scope, code.ArkInternal, fmt.Sprintf("ark internal error: %s", strings.Join(s, " "))) +} + +// ArkInternalL logs error message and returns Err +func ArkInternalL(l logx.Logger, s ...string) *Err { + e := ArkInternal(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +/*** CatPubSub ***/ + +// Publish returns Err +func Publish(s ...string) *Err { + return newErr(Scope, code.Publish, fmt.Sprintf("publish: %s", strings.Join(s, " "))) +} + +// PublishL logs error message and returns Err +func PublishL(l logx.Logger, s ...string) *Err { + e := Publish(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} + +// Consume returns Err +func Consume(s ...string) *Err { + return newErr(Scope, code.Consume, fmt.Sprintf("consume: %s", strings.Join(s, " "))) +} + +// MsgSizeTooLarge returns Err +func MsgSizeTooLarge(s ...string) *Err { + return newErr(Scope, code.MsgSizeTooLarge, fmt.Sprintf("kafka error: %s", strings.Join(s, " "))) +} + +// MsgSizeTooLargeL logs error message and returns Err +func MsgSizeTooLargeL(l logx.Logger, s ...string) *Err { + e := MsgSizeTooLarge(s...) + l.WithCallerSkip(1).Error(e.Error()) + return e +} diff --git a/errors/errors.go b/errors/errors.go new file mode 100644 index 0000000..8786848 --- /dev/null +++ b/errors/errors.go @@ -0,0 +1,218 @@ +package error + +import ( + code2 "code.30cm.net/digimon/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, + } +} diff --git a/errors/go.mod b/errors/go.mod new file mode 100644 index 0000000..3f8b861 --- /dev/null +++ b/errors/go.mod @@ -0,0 +1,21 @@ +module code.30cm.net/digimon/library-go/errors + +go 1.22 + +require ( + github.com/zeromicro/go-zero v1.7.0 + google.golang.org/grpc v1.65.0 +) + +require ( + github.com/fatih/color v1.17.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect + golang.org/x/sys v0.22.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) diff --git a/errors/readme.md b/errors/readme.md new file mode 100644 index 0000000..5af9e87 --- /dev/null +++ b/errors/readme.md @@ -0,0 +1,125 @@ +## 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) +} +``` \ No newline at end of file -- 2.40.1 From f96950a8a1b33804db403535cf390a4983f7aa72 Mon Sep 17 00:00:00 2001 From: "daniel.w" Date: Tue, 20 Aug 2024 01:13:50 +0800 Subject: [PATCH 2/3] add error and error easy func --- go.work | 2 +- go.work.sum | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 go.work.sum diff --git a/go.work b/go.work index 09b6b3e..bdcdb40 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ go 1.22 use ./errors -use ./validator + diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..9409ba3 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,198 @@ +cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w= +cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= +github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA= +github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bufbuild/protocompile v0.10.0 h1:+jW/wnLMLxaCEG8AX9lD0bQ5v9h1RUiMKOBOT5ll9dM= +github.com/bufbuild/protocompile v0.10.0/go.mod h1:G9qQIQo0xZ6Uyj6CMNz0saGmx2so+KONo8/KrELABiY= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw= +github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI= +github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/fullstorydev/grpcurl v1.9.1 h1:YxX1aCcCc4SDBQfj9uoWcTLe8t4NWrZe1y+mk83BQgo= +github.com/fullstorydev/grpcurl v1.9.1/go.mod h1:i8gKLIC6s93WdU3LSmkE5vtsCxyRmihUj5FK1cNW5EM= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= +github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg= +github.com/jhump/protoreflect v1.16.0/go.mod h1:oYPd7nPvcBw/5wlDfm/AVmU9zH9BgqGCI469pGxfj/8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= +github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk= +go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM= +go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA= +go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU= +go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4= +go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU= +go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4= +go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= +go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY= +go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= +google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= +gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= +gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw= +k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80= +k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q= +k8s.io/apimachinery v0.29.4/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= +k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg= +k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= -- 2.40.1 From 9b0caf42d8858272eac9c3213b252aa16dec69d2 Mon Sep 17 00:00:00 2001 From: "daniel.w" Date: Tue, 20 Aug 2024 01:14:54 +0800 Subject: [PATCH 3/3] add ut --- errors/easy_func_test.go | 1031 ++++++++++++++++++++++++++++++++++++++ errors/errors_test.go | 297 +++++++++++ 2 files changed, 1328 insertions(+) create mode 100644 errors/easy_func_test.go create mode 100644 errors/errors_test.go diff --git a/errors/easy_func_test.go b/errors/easy_func_test.go new file mode 100644 index 0000000..146f696 --- /dev/null +++ b/errors/easy_func_test.go @@ -0,0 +1,1031 @@ +package error + +import ( + "code.30cm.net/digimon/library-go/errors/code" + "context" + "errors" + "fmt" + "reflect" + "strconv" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestFromGRPCError_GivenStatusWithCodeAndMessage_ShouldReturnErr(t *testing.T) { + // setup + s := status.Error(codes.Code(102399), "FAKE ERROR") + + // act + e := FromGRPCError(s) + + // assert + assert.Equal(t, uint32(10), e.Scope()) + assert.Equal(t, uint32(2300), e.Category()) + assert.Equal(t, uint32(2399), e.Code()) + assert.Equal(t, "FAKE ERROR", e.Error()) +} + +func TestFromGRPCError_GivenNilError_ShouldReturnErr_Scope0_Cat0_Detail0(t *testing.T) { + // setup + var nilError error = nil + + // act + e := FromGRPCError(nilError) + + // assert + assert.Equal(t, uint32(0), e.Scope()) + assert.Equal(t, uint32(0), e.Category()) + assert.Equal(t, uint32(0), e.Code()) + assert.Equal(t, "", e.Error()) +} + +func TestFromGRPCError_GivenGRPCNativeError_ShouldReturnErr_Scope0_CatGRPC_DetailGRPCUnavailable(t *testing.T) { + // setup + msg := "GRPC Unavailable ERROR" + s := status.Error(codes.Code(codes.Unavailable), msg) + + // act + e := FromGRPCError(s) + + // assert + assert.Equal(t, code.Unset, e.Scope()) + assert.Equal(t, code.CatGRPC, e.Category()) + assert.Equal(t, uint32(codes.Unavailable), e.Code()) + assert.Equal(t, msg, e.Error()) +} + +func TestFromGRPCError_GivenGeneralError_ShouldReturnErr_Scope0_CatGRPC_DetailGRPCUnknown(t *testing.T) { + // setup + generalErr := errors.New("general error") + + // act + e := FromGRPCError(generalErr) + + // assert + assert.Equal(t, code.Unset, e.Scope()) + assert.Equal(t, code.CatGRPC, e.Category()) + assert.Equal(t, uint32(codes.Unknown), e.Code()) +} + +func TestToGRPCError_GivenErr_StatusShouldHave_Code112233(t *testing.T) { + // setup + e := Err{scope: 11, code: 2233, msg: "FAKE MSG"} + + // act + err := ToGRPCError(&e) + s, _ := status.FromError(err) + + // assert + assert.Equal(t, 112233, int(s.Code())) + assert.Equal(t, "FAKE MSG", s.Message()) +} + +func TestInvalidFormat_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := InvalidFormat("field A", "Error description") + + // assert + assert.Equal(t, code.CatInput, e.Category()) + assert.Equal(t, code.InvalidFormat, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Equal(t, e.Error(), "invalid format: field A Error description") +} + +func TestInvalidFormatL_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := context.Background() + // act + e := InvalidFormatL(logx.WithContext(ctx), "field A", "Error description") + + // assert + assert.Equal(t, code.CatInput, e.Category()) + assert.Equal(t, code.InvalidFormat, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestInvalidRange_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := InvalidRange("field A", "Error description") + + // assert + assert.Equal(t, code.CatInput, e.Category()) + assert.Equal(t, code.InvalidRange, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Equal(t, e.Error(), "invalid range: field A Error description") +} + +func TestInvalidRangeL_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := context.Background() + // act + e := InvalidRangeL(logx.WithContext(ctx), "field A", "Error description") + + // assert + assert.Equal(t, code.CatInput, e.Category()) + assert.Equal(t, code.InvalidRange, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestNotValidImplementation_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := NotValidImplementation("field A", "Error description") + + // assert + assert.Equal(t, code.CatInput, e.Category()) + assert.Equal(t, code.NotValidImplementation, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Equal(t, e.Error(), "not valid implementation: field A Error description") +} + +func TestNotValidImplementationL_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logx.WithContext(context.Background()) + // act + e := NotValidImplementationL(l, "field A", "Error description") + + // assert + assert.Equal(t, code.CatInput, e.Category()) + assert.Equal(t, code.NotValidImplementation, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestDBError_WithStrings_ShouldHasCatDBAndDetailCodeDBError(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := DBError("field A", "Error description") + + // assert + assert.Equal(t, code.CatDB, e.Category()) + assert.Equal(t, code.DBError, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestDBDataConvert_WithStrings_ShouldHasCatDBAndDetailCodeDBDataConvert(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := DBDataConvert("field A", "Error description") + + // assert + assert.Equal(t, code.CatDB, e.Category()) + assert.Equal(t, code.DBDataConvert, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestResourceNotFound_WithStrings_ShouldHasCatResource_DetailCodeResourceNotFound(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := ResourceNotFound("field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.ResourceNotFound, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestInvalidResourceFormat_WithStrings_ShouldHasCatResource_DetailCodeInvalidResourceFormat(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := InvalidResourceFormat("field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.InvalidResourceFormat, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestInvalidResourceState_OK(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := InvalidResourceState("field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.InvalidResourceState, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.EqualError(t, e, "invalid resource state: field A Error description") +} + +func TestInvalidResourceStateL_LogError(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logx.WithContext(context.Background()) + + // act + e := InvalidResourceStateL(l, "field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.InvalidResourceState, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.EqualError(t, e, "invalid resource state: field A Error description") +} + +func TestAuthExpired_OK(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := AuthExpired("field A", "Error description") + + // assert + assert.Equal(t, code.CatAuth, e.Category()) + assert.Equal(t, code.AuthExpired, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestUnauthorized_WithStrings_ShouldHasCatAuth_DetailCodeUnauthorized(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := Unauthorized("field A", "Error description") + + // assert + assert.Equal(t, code.CatAuth, e.Category()) + assert.Equal(t, code.Unauthorized, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestInvalidPosixTime_WithStrings_ShouldHasCatAuth_DetailCodeInvalidPosixTime(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := InvalidPosixTime("field A", "Error description") + + // assert + assert.Equal(t, code.CatAuth, e.Category()) + assert.Equal(t, code.InvalidPosixTime, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestSigAndPayloadNotMatched_WithStrings_ShouldHasCatAuth_DetailCodeSigAndPayloadNotMatched(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := SigAndPayloadNotMatched("field A", "Error description") + + // assert + assert.Equal(t, code.CatAuth, e.Category()) + assert.Equal(t, code.SigAndPayloadNotMatched, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestForbidden_WithStrings_ShouldHasCatAuth_DetailCodeForbidden(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := Forbidden("field A", "Error description") + + // assert + assert.Equal(t, code.CatAuth, e.Category()) + assert.Equal(t, code.Forbidden, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestXBCInternal_WithStrings_ShouldHasCatResource_DetailCodeXBCInternal(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := ArkInternal("field A", "Error description") + + // assert + assert.Equal(t, code.CatArk, e.Category()) + assert.Equal(t, code.ArkInternal, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestGeneralInternalError_WithStrings_DetailInternalError(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := SystemInternalError("field A", "Error description") + + // assert + assert.Equal(t, code.CatSystem, e.Category()) + assert.Equal(t, code.SystemInternalError, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestGeneralInternalErrorL_WithStrings_DetailInternalError(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logx.WithContext(context.Background()) + + // act + e := SystemInternalErrorL(l, "field A", "Error description") + + // assert + assert.Equal(t, code.CatSystem, e.Category()) + assert.Equal(t, code.SystemInternalError, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestSystemMaintainError_WithStrings_DetailSystemMaintainError(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logx.WithContext(context.Background()) + + // act + e := SystemMaintainErrorL(l, "field A", "Error description") + + // assert + assert.Equal(t, code.CatSystem, e.Category()) + assert.Equal(t, code.SystemMaintainError, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestResourceAlreadyExist_WithStrings_DetailInternalError(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := ResourceAlreadyExist("field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.ResourceAlreadyExist, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestResourceAlreadyExistL_WithStrings_DetailInternalError(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logx.WithContext(context.Background()) + + // act + e := ResourceAlreadyExistL(l, "field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.ResourceAlreadyExist, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestResourceInsufficient_WithStrings_DetailInternalError(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := ResourceInsufficient("field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.ResourceInsufficient, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestResourceInsufficientL_WithStrings_DetailInternalError(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logx.WithContext(context.Background()) + + // act + e := ResourceInsufficientL(l, "field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.ResourceInsufficient, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestInsufficientPermission_WithStrings_DetailInternalError(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := InsufficientPermission("field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.InsufficientPermission, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestInsufficientPermissionL_WithStrings_DetailInternalError(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logx.WithContext(context.Background()) + + // act + e := InsufficientPermissionL(l, "field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.InsufficientPermission, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestInvalidMeasurementID_WithErrorStrings_ShouldReturnCorrectCodeAndErrorString(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := InvalidMeasurementID("field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.InvalidMeasurementID, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestInvalidMeasurementIDL_WithErrorStrings_ShouldReturnCorrectCodeAndErrorStringAndCallLogger(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logx.WithContext(context.Background()) + + // act + e := InvalidMeasurementIDL(l, "field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.InvalidMeasurementID, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestResourceExpired_OK(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := ResourceExpired("field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.ResourceExpired, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestResourceExpiredL_LogError(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logx.WithContext(context.Background()) + + // act + e := ResourceExpiredL(l, "field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.ResourceExpired, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestResourceMigrated_OK(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := ResourceMigrated("field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.ResourceMigrated, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestResourceMigratedL_LogError(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logx.WithContext(context.Background()) + + // act + e := ResourceMigratedL(l, "field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.ResourceMigrated, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestInsufficientQuota_OK(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := InsufficientQuota("field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.InsufficientQuota, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestInsufficientQuotaL_LogError(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logx.WithContext(context.Background()) + + // act + e := InsufficientQuotaL(l, "field A", "Error description") + + // assert + assert.Equal(t, code.CatResource, e.Category()) + assert.Equal(t, code.InsufficientQuota, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestPublish_WithErrorStrings_ShouldReturnCorrectCodeAndErrorString(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := Publish("field A", "Error description") + + // assert + assert.Equal(t, code.CatPubSub, e.Category()) + assert.Equal(t, code.Publish, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestPublishL_WithErrorStrings_ShouldReturnCorrectCodeAndErrorStringAndCallLogger(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logx.WithContext(context.Background()) + + // act + e := PublishL(l, "field A", "Error description") + + // assert + assert.Equal(t, code.CatPubSub, e.Category()) + assert.Equal(t, code.Publish, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "field A") + assert.Contains(t, e.Error(), "Error description") +} + +func TestMsgSizeTooLarge_WithErrorStrings_ShouldReturnCorrectCodeAndErrorString(t *testing.T) { + // setup + Scope = 99 + defer func() { + Scope = code.Unset + }() + + // act + e := MsgSizeTooLarge("Error description") + + // assert + assert.Equal(t, code.CatPubSub, e.Category()) + assert.Equal(t, code.MsgSizeTooLarge, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "kafka error: Error description") +} + +func TestMsgSizeTooLargeL_WithErrorStrings_ShouldReturnCorrectCodeAndErrorStringAndCallLogger(t *testing.T) { + // setup + Scope = 99 + defer func() { Scope = code.Unset }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logx.WithContext(context.Background()) + + // act + e := MsgSizeTooLargeL(l, "Error description") + + // assert + assert.Equal(t, code.CatPubSub, e.Category()) + assert.Equal(t, code.MsgSizeTooLarge, e.Code()) + assert.Equal(t, uint32(99), e.Scope()) + assert.Contains(t, e.Error(), "kafka error: Error description") +} + +// func TestStructErr_WithInternalErr_ShouldIsFuncReportCorrectly(t *testing.T) { +// // setup +// Scope = 99 +// defer func() { Scope = code.Unset }() +// // arrange 2 layers err +// layer1Err := fmt.Errorf("layer 1 error") +// layer2Err := fmt.Errorf("layer 2: %w", layer1Err) +// +// // act with error chain: InvalidFormat -> layer 2 err -> layer 1 err +// e := InvalidFormat("field A", "Error description") +// err := e.Wrap(layer2Err) +// if err != nil { +// t.Fatalf("Failed to wrap error: %v", err) +// } +// +// // assert +// assert.Equal(t, code.CatInput, e.Category()) +// assert.Equal(t, code.InvalidFormat, e.Code()) +// assert.Equal(t, uint32(99), e.Scope()) +// assert.Contains(t, e.Error(), "field A") +// assert.Contains(t, e.Error(), "Error description") +// +// // errors.Is should report correctly +// assert.True(t, errors.Is(e, layer1Err)) +// assert.True(t, errors.Is(e, layer2Err)) +// } + +// func TestStructErr_WithInternalErr_ShouldErrorOutputChainErrMessage(t *testing.T) { +// // setup +// Scope = 99 +// defer func() { Scope = code.Unset }() +// +// // arrange 2 layers err +// layer1Err := fmt.Errorf("layer 1 error") +// // act with error chain: InvalidFormat -> layer 1 err +// e := InvalidFormat("field A", "Error description") +// err := e.Wrap(layer1Err) +// if err != nil { +// t.Fatalf("Failed to wrap error: %v", err) +// } +// +// // assert +// assert.Equal(t, "invalid format: field A Error description: layer 1 error", e.Error()) +// } + +// arrange a specific err type just for UT +type testErr struct { + code int +} + +func (e *testErr) Error() string { + return strconv.Itoa(e.code) +} + +// func TestStructErr_WithInternalErr_ShouldAsFuncReportCorrectly(t *testing.T) { +// // setup +// Scope = 99 +// defer func() { Scope = code.Unset }() +// +// testE := &testErr{code: 123} +// layer2Err := fmt.Errorf("layer 2: %w", testE) +// +// // act with error chain: InvalidFormat -> layer 2 err -> testErr +// e := InvalidFormat("field A", "Error description") +// err := e.Wrap(layer2Err) +// if err != nil { +// t.Fatalf("Failed to wrap error: %v", err) +// } +// +// // assert +// assert.Equal(t, code.CatInput, e.Category()) +// assert.Equal(t, code.InvalidFormat, e.Code()) +// assert.Equal(t, uint32(99), e.Scope()) +// assert.Contains(t, e.Error(), "field A") +// assert.Contains(t, e.Error(), "Error description") +// +// // errors.As should report correctly +// var internalErr *testErr +// assert.True(t, errors.As(e, &internalErr)) +// assert.Equal(t, testE, internalErr) +// } + +/* +benchmark run for 1 second: +Benchmark_ErrorsIs_OneLayerError-4 148281332 8.68 ns/op 0 B/op 0 allocs/op +Benchmark_ErrorsIs_TwoLayerError-4 35048202 32.4 ns/op 0 B/op 0 allocs/op +Benchmark_ErrorsIs_FourLayerError-4 15309349 81.7 ns/op 0 B/op 0 allocs/op + +Benchmark_ErrorsAs_OneLayerError-4 16893205 70.4 ns/op 0 B/op 0 allocs/op +Benchmark_ErrorsAs_TwoLayerError-4 10568083 112 ns/op 0 B/op 0 allocs/op +Benchmark_ErrorsAs_FourLayerError-4 6307729 188 ns/op 0 B/op 0 allocs/op +*/ +func Benchmark_ErrorsIs_OneLayerError(b *testing.B) { + layer1Err := &testErr{code: 123} + var err error = layer1Err + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + errors.Is(err, layer1Err) + } +} + +func Benchmark_ErrorsIs_TwoLayerError(b *testing.B) { + layer1Err := &testErr{code: 123} + + // act with error chain: InvalidFormat(layer 2) -> testErr(layer 1) + layer2Err := InvalidFormat("field A", "Error description") + err := layer2Err.Wrap(layer1Err) + if err != nil { + b.Fatalf("Failed to wrap error: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + errors.Is(layer2Err, layer1Err) + } +} + +func Benchmark_ErrorsIs_FourLayerError(b *testing.B) { + layer1Err := &testErr{code: 123} + layer2Err := fmt.Errorf("layer 2: %w", layer1Err) + layer3Err := fmt.Errorf("layer 3: %w", layer2Err) + // act with error chain: InvalidFormat(layer 4) -> Error(layer 3) -> Error(layer 2) -> testErr(layer 1) + layer4Err := InvalidFormat("field A", "Error description") + err := layer4Err.Wrap(layer3Err) + if err != nil { + b.Fatalf("Failed to wrap error: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + errors.Is(layer4Err, layer1Err) + } +} + +func Benchmark_ErrorsAs_OneLayerError(b *testing.B) { + layer1Err := &testErr{code: 123} + var err error = layer1Err + + b.ReportAllocs() + b.ResetTimer() + var internalErr *testErr + for i := 0; i < b.N; i++ { + errors.As(err, &internalErr) + } +} + +func Benchmark_ErrorsAs_TwoLayerError(b *testing.B) { + layer1Err := &testErr{code: 123} + + // act with error chain: InvalidFormat(layer 2) -> testErr(layer 1) + layer2Err := InvalidFormat("field A", "Error description") + err := layer2Err.Wrap(layer1Err) + if err != nil { + b.Fatalf("Failed to wrap error: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + var internalErr *testErr + for i := 0; i < b.N; i++ { + errors.As(layer2Err, &internalErr) + } +} + +func Benchmark_ErrorsAs_FourLayerError(b *testing.B) { + layer1Err := &testErr{code: 123} + layer2Err := fmt.Errorf("layer 2: %w", layer1Err) + layer3Err := fmt.Errorf("layer 3: %w", layer2Err) + // act with error chain: InvalidFormat(layer 4) -> Error(layer 3) -> Error(layer 2) -> testErr(layer 1) + layer4Err := InvalidFormat("field A", "Error description") + err := layer4Err.Wrap(layer3Err) + if err != nil { + b.Fatalf("Failed to wrap error: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + var internalErr *testErr + for i := 0; i < b.N; i++ { + errors.As(layer4Err, &internalErr) + } +} + +func TestFromError(t *testing.T) { + tests := []struct { + name string + givenError error + want *Err + }{ + { + "given nil error should return nil", + nil, + nil, + }, + { + "given normal error should return nil", + errors.New("normal error"), + nil, + }, + { + "given Err should return Err", + ResourceNotFound("fake error"), + ResourceNotFound("fake error"), + }, + { + "given error wraps Err should return Err", + fmt.Errorf("outter error wraps %w", ResourceNotFound("fake error")), + ResourceNotFound("fake error"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := FromError(tt.givenError); !reflect.DeepEqual(got, tt.want) { + t.Errorf("FromError() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/errors/errors_test.go b/errors/errors_test.go new file mode 100644 index 0000000..dc31791 --- /dev/null +++ b/errors/errors_test.go @@ -0,0 +1,297 @@ +package error + +import ( + code2 "code.30cm.net/digimon/library-go/errors/code" + "errors" + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestCode_GivenNilReceiver_CodeReturnOK_CodeStrReturns00000(t *testing.T) { + // setup + var e *Err = nil + + // act & assert + assert.Equal(t, code2.OK, e.Code()) + assert.Equal(t, "00000", e.CodeStr()) + assert.Equal(t, "", e.Error()) +} + +func TestCode_GivenScope99DetailCode6687_ShouldReturn996687(t *testing.T) { + // setup + e := Err{scope: 99, code: 6687} + + // act & assert + assert.Equal(t, uint32(6687), e.Code()) + assert.Equal(t, "996687", e.CodeStr()) +} + +func TestCode_GivenScope0DetailCode87_ShouldReturn87(t *testing.T) { + // setup + e := Err{scope: 0, code: 87} + + // act & assert + assert.Equal(t, uint32(87), e.Code()) + assert.Equal(t, "00087", e.CodeStr()) +} + +func TestFromCode_Given870005_ShouldHasScope87_Cat0_Detail5(t *testing.T) { + // setup + e := FromCode(870005) + + // assert + assert.Equal(t, uint32(87), e.Scope()) + assert.Equal(t, uint32(0), e.Category()) + assert.Equal(t, uint32(5), e.Code()) + assert.Equal(t, "", e.Error()) +} + +func TestFromCode_Given0_ShouldHasScope0_Cat0_Detail0(t *testing.T) { + // setup + e := FromCode(0) + + // assert + assert.Equal(t, uint32(0), e.Scope()) + assert.Equal(t, uint32(0), e.Category()) + assert.Equal(t, uint32(0), e.Code()) + assert.Equal(t, "", e.Error()) +} + +func TestFromCode_Given9105_ShouldHasScope0_Cat9100_Detail9105(t *testing.T) { + // setup + e := FromCode(9105) + + // assert + assert.Equal(t, uint32(0), e.Scope()) + assert.Equal(t, uint32(9100), e.Category()) + assert.Equal(t, uint32(9105), e.Code()) + assert.Equal(t, "", e.Error()) +} + +func TestErr_ShouldImplementErrorFunction(t *testing.T) { + // setup a func return error + f := func() error { return InvalidFormat("fake field") } + + // act + err := f() + + // assert + assert.NotNil(t, err) + assert.Contains(t, fmt.Sprint(err), "fake field") // can be printed +} + +func TestGeneralError_GivenNilErr_ShouldReturnEmptyString(t *testing.T) { + // setup + var e *Err = nil + + // act & assert + assert.Equal(t, "", e.GeneralError()) +} + +func TestGeneralError_GivenNotExistCat_ShouldReturnEmptyString(t *testing.T) { + // setup + e := Err{category: 123456} + + // act & assert + assert.Equal(t, "", e.GeneralError()) +} + +func TestGeneralError_GivenCatDB_ShouldReturnDBError(t *testing.T) { + // setup + e := Err{category: code2.CatDB} + catErrStr := code2.CatToStr[code2.CatDB] + + // act & assert + assert.Equal(t, catErrStr, e.GeneralError()) +} + +func TestError_GivenEmptyMsg_ShouldReturnCatGeneralErrorMessage(t *testing.T) { + // setup + e := Err{category: code2.CatDB, msg: ""} + + // act + errMsg := e.Error() + + // assert + assert.Equal(t, code2.CatToStr[code2.CatDB], errMsg) +} + +func TestError_GivenMsg_ShouldReturnGiveMsg(t *testing.T) { + // setup + e := Err{msg: "FAKE"} + + // act + errMsg := e.Error() + + // assert + assert.Equal(t, "FAKE", errMsg) +} + +func TestIs_GivenNilErr_ShouldReturnFalse(t *testing.T) { + var nilErrs *Err + // act + result := errors.Is(nilErrs, DBError()) + result2 := errors.Is(DBError(), nilErrs) + + // assert + assert.False(t, result) + assert.False(t, result2) +} + +func TestIs_GivenNil_ShouldReturnFalse(t *testing.T) { + // act + result := errors.Is(nil, DBError()) + result2 := errors.Is(DBError(), nil) + + // assert + assert.False(t, result) + assert.False(t, result2) +} + +func TestIs_GivenNilReceiver_ShouldReturnCorrectResult(t *testing.T) { + var nilErr *Err = nil + + // test 1: nilErr != DBError + var dbErr error = DBError("fake db error") + assert.False(t, nilErr.Is(dbErr)) + + // test 2: nilErr != nil error + var nilError error + assert.False(t, nilErr.Is(nilError)) + + // test 3: nilErr == another nilErr + var nilErr2 *Err = nil + assert.True(t, nilErr.Is(nilErr2)) +} + +func TestIs_GivenDBError_ShouldReturnTrue(t *testing.T) { + // setup + dbErr := DBError("fake db error") + + // act + result := errors.Is(dbErr, DBError("not care")) + result2 := errors.Is(DBError(), dbErr) + + // assert + assert.True(t, result) + assert.True(t, result2) +} + +func TestIs_GivenDBErrorAssignToErrorType_ShouldReturnTrue(t *testing.T) { + // setup + var dbErr error = DBError("fake db error") + + // act + result := errors.Is(dbErr, DBError("not care")) + result2 := errors.Is(DBError(), dbErr) + + // assert + assert.True(t, result) + assert.True(t, result2) +} + +func TestWrap_GivenNilErr_ShouldNoPanic(t *testing.T) { + // act & assert + assert.NotPanics(t, func() { + var e *Err = nil + _ = e.Wrap(fmt.Errorf("test")) + }) +} + +func TestWrap_GivenErrorToWrap_ShouldReturnErrorWithWrappedError(t *testing.T) { + // act & assert + wrappedErr := fmt.Errorf("test") + wrappingErr := SystemInternalError("WrappingError").Wrap(wrappedErr) + unWrappedErr := wrappingErr.Unwrap() + + assert.Equal(t, wrappedErr, unWrappedErr) +} + +func TestUnwrap_GivenNilErr_ShouldReturnNil(t *testing.T) { + var e *Err = nil + internalErr := e.Unwrap() + assert.Nil(t, internalErr) +} + +func TestErrorsIs_GivenNilErr_ShouldReturnFalse(t *testing.T) { + var e *Err = nil + assert.False(t, errors.Is(e, fmt.Errorf("test"))) +} + +func TestErrorsAs_GivenNilErr_ShouldReturnFalse(t *testing.T) { + var internalErr *testErr + var e *Err = nil + assert.False(t, errors.As(e, &internalErr)) +} + +func TestGRPCStatus(t *testing.T) { + // setup table driven tests + tests := []struct { + name string + given *Err + expect *status.Status + expectConvert error + }{ + { + "nil errs.Err", + nil, + status.New(codes.OK, ""), + nil, + }, + { + "InvalidFormat Err", + InvalidFormat("fake"), + status.New(codes.Code(101), "invalid format: fake"), + status.New(codes.Code(101), "invalid format: fake").Err(), + }, + } + + // act & assert + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + s := test.given.GRPCStatus() + assert.Equal(t, test.expect.Code(), s.Code()) + assert.Equal(t, test.expect.Message(), s.Message()) + assert.Equal(t, test.expectConvert, status.Convert(test.given).Err()) + }) + } +} + +func TestErr_HTTPStatus(t *testing.T) { + tests := []struct { + name string + err *Err + want int + }{ + {name: "nil error", err: nil, want: http.StatusOK}, + {name: "invalid measurement id", err: &Err{category: code2.CatResource, code: code2.InvalidMeasurementID}, want: http.StatusInternalServerError}, + {name: "resource already exists", err: &Err{category: code2.CatResource, code: code2.ResourceAlreadyExist}, want: http.StatusConflict}, + {name: "invalid resource state", err: &Err{category: code2.CatResource, code: code2.InvalidResourceState}, want: http.StatusConflict}, + {name: "invalid posix time", err: &Err{category: code2.CatAuth, code: code2.InvalidPosixTime}, want: http.StatusForbidden}, + {name: "unauthorized", err: &Err{category: code2.CatAuth, code: code2.Unauthorized}, want: http.StatusUnauthorized}, + {name: "db error", err: &Err{category: code2.CatDB, code: code2.DBError}, want: http.StatusInternalServerError}, + {name: "insufficient permission", err: &Err{category: code2.CatResource, code: code2.InsufficientPermission}, want: http.StatusUnauthorized}, + {name: "resource insufficient", err: &Err{category: code2.CatResource, code: code2.ResourceInsufficient}, want: http.StatusBadRequest}, + {name: "invalid format", err: &Err{category: code2.CatInput, code: code2.InvalidFormat}, want: http.StatusBadRequest}, + {name: "resource not found", err: &Err{code: code2.ResourceNotFound}, want: http.StatusNotFound}, + {name: "ok", err: &Err{code: code2.OK}, want: http.StatusOK}, + {name: "not valid implementation", err: &Err{category: code2.CatInput, code: code2.NotValidImplementation}, want: http.StatusNotImplemented}, + {name: "forbidden", err: &Err{category: code2.CatAuth, code: code2.Forbidden}, want: http.StatusForbidden}, + {name: "insufficient quota", err: &Err{category: code2.CatResource, code: code2.InsufficientQuota}, want: http.StatusPaymentRequired}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + // act + got := tt.err.HTTPStatus() + + // assert + assert.Equal(t, tt.want, got) + }) + } +} -- 2.40.1