From b05ae52d48ae7dec1706caf8b49bd25ab3f590a4 Mon Sep 17 00:00:00 2001 From: "daniel.w" Date: Tue, 20 Aug 2024 01:13:31 +0800 Subject: [PATCH] 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