Compare commits
27 Commits
validator/
...
main
Author | SHA1 | Date |
---|---|---|
daniel.w | 31098996a6 | |
daniel.w | 720178fe0d | |
daniel.w | 5c4dcaa0be | |
daniel.w | f578e82046 | |
daniel.w | 7b6ec04405 | |
daniel.w | 4857756e2f | |
daniel.w | 73ba2660c0 | |
daniel.w | 1ee022c237 | |
daniel.w | b10b979c7e | |
王性驊 | d6473d617f | |
daniel.w | c1e7a00363 | |
daniel.w | 84d8afa2ba | |
daniel.w | 49b13e2481 | |
daniel.w | d9dde23a7e | |
daniel.w | 40fb45386f | |
王性驊 | a2f0e212fa | |
daniel.w | 345e6d2454 | |
daniel.w | 09cb6fd077 | |
daniel.w | 04e1b0c122 | |
daniel.w | 30bff1e64b | |
daniel.w | 2a933425d7 | |
daniel.w | 0db1affc10 | |
daniel.w | d88caaf55c | |
王性驊 | f9c90a90f5 | |
daniel.w | 26a14a8a9c | |
daniel.w | 8442e50139 | |
daniel.w | f0331c8a30 |
|
@ -0,0 +1,140 @@
|
||||||
|
run:
|
||||||
|
timeout: 3m
|
||||||
|
# Exit code when at least one issue was found.
|
||||||
|
# Default: 1
|
||||||
|
issues-exit-code: 2
|
||||||
|
# Include test files or not.
|
||||||
|
# Default: true
|
||||||
|
tests: false
|
||||||
|
|
||||||
|
# Reference URL: https://golangci-lint.run/usage/linters/
|
||||||
|
linters:
|
||||||
|
# Disable everything by default so upgrades to not include new - default
|
||||||
|
# enabled- linters.
|
||||||
|
disable-all: true
|
||||||
|
# Specifically enable linters we want to use.
|
||||||
|
enable:
|
||||||
|
# - depguard
|
||||||
|
- errcheck
|
||||||
|
# - godot
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
- revive
|
||||||
|
# - staticcheck
|
||||||
|
- typecheck
|
||||||
|
- unused
|
||||||
|
# - wsl
|
||||||
|
- asasalint
|
||||||
|
- asciicheck
|
||||||
|
- bidichk
|
||||||
|
- bodyclose
|
||||||
|
# - containedctx
|
||||||
|
- contextcheck
|
||||||
|
# - cyclop
|
||||||
|
# - varnamelen
|
||||||
|
# - gci
|
||||||
|
- wastedassign
|
||||||
|
- whitespace
|
||||||
|
# - wrapcheck
|
||||||
|
- thelper
|
||||||
|
- tparallel
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- usestdlibvars
|
||||||
|
- tenv
|
||||||
|
- testableexamples
|
||||||
|
- stylecheck
|
||||||
|
- sqlclosecheck
|
||||||
|
- nosprintfhostport
|
||||||
|
- paralleltest
|
||||||
|
- prealloc
|
||||||
|
- predeclared
|
||||||
|
- promlinter
|
||||||
|
- reassign
|
||||||
|
- rowserrcheck
|
||||||
|
- nakedret
|
||||||
|
- nestif
|
||||||
|
- nilerr
|
||||||
|
- nilnil
|
||||||
|
- nlreturn
|
||||||
|
- noctx
|
||||||
|
- nolintlint
|
||||||
|
- nonamedreturns
|
||||||
|
- decorder
|
||||||
|
- dogsled
|
||||||
|
# - dupl
|
||||||
|
- dupword
|
||||||
|
- durationcheck
|
||||||
|
- errchkjson
|
||||||
|
- errname
|
||||||
|
- errorlint
|
||||||
|
# - execinquery
|
||||||
|
- exhaustive
|
||||||
|
- exportloopref
|
||||||
|
- forbidigo
|
||||||
|
- forcetypeassert
|
||||||
|
# - gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
|
- gocognit
|
||||||
|
- goconst
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
# - godox
|
||||||
|
# - goerr113
|
||||||
|
# - gofumpt
|
||||||
|
- goheader
|
||||||
|
- gomoddirectives
|
||||||
|
# - gomodguard always failed
|
||||||
|
- goprintffuncname
|
||||||
|
- gosec
|
||||||
|
- grouper
|
||||||
|
- importas
|
||||||
|
- interfacebloat
|
||||||
|
# - ireturn
|
||||||
|
- lll
|
||||||
|
- loggercheck
|
||||||
|
- maintidx
|
||||||
|
- makezero
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- funlen
|
||||||
|
- goconst
|
||||||
|
- interfacer
|
||||||
|
- dupl
|
||||||
|
- lll
|
||||||
|
- goerr113
|
||||||
|
- errcheck
|
||||||
|
- gocritic
|
||||||
|
- cyclop
|
||||||
|
- wrapcheck
|
||||||
|
- gocognit
|
||||||
|
- contextcheck
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard # Standard section: captures all standard packages.
|
||||||
|
- default # Default section: contains all imports that could not be matched to another section type.
|
||||||
|
gocognit:
|
||||||
|
# Minimal code complexity to report.
|
||||||
|
# Default: 30 (but we recommend 10-20)
|
||||||
|
min-complexity: 40
|
||||||
|
nestif:
|
||||||
|
# Minimal complexity of if statements to report.
|
||||||
|
# Default: 5
|
||||||
|
min-complexity: 10
|
||||||
|
lll:
|
||||||
|
# Max line length, lines longer will be reported.
|
||||||
|
# '\t' is counted as 1 character by default, and can be changed with the tab-width option.
|
||||||
|
# Default: 120.
|
||||||
|
line-length: 200
|
||||||
|
# Tab width in spaces.
|
||||||
|
# Default: 1
|
||||||
|
tab-width: 1
|
|
@ -0,0 +1,12 @@
|
||||||
|
GOFMT ?= gofmt
|
||||||
|
GOFILES := $(shell find . -name "*.go")
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test: # 進行測試
|
||||||
|
go test -v --cover ./...
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
fmt: # 格式優化
|
||||||
|
$(GOFMT) -w $(GOFILES)
|
||||||
|
goimports -w ./
|
||||||
|
golangci-lint run
|
|
@ -1,99 +0,0 @@
|
||||||
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
|
|
||||||
)
|
|
|
@ -1,442 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
218
errors/errors.go
218
errors/errors.go
|
@ -1,218 +0,0 @@
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,297 +0,0 @@
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
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
|
|
||||||
)
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package code
|
||||||
|
|
||||||
|
// Category for general operations: 10 - 490
|
||||||
|
const (
|
||||||
|
_ = iota
|
||||||
|
CatInput uint32 = iota * 10
|
||||||
|
CatDB
|
||||||
|
CatResource
|
||||||
|
CatGRPC
|
||||||
|
CatAuth
|
||||||
|
CatSystem
|
||||||
|
CatPubSub
|
||||||
|
CatService
|
||||||
|
)
|
|
@ -0,0 +1,75 @@
|
||||||
|
package code
|
||||||
|
|
||||||
|
const (
|
||||||
|
OK uint32 = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// 詳細代碼 - 輸入類 01x
|
||||||
|
const (
|
||||||
|
_ = iota + CatInput
|
||||||
|
InvalidFormat // 無效格式
|
||||||
|
NotValidImplementation // 非有效實現
|
||||||
|
InvalidRange // 無效範圍
|
||||||
|
)
|
||||||
|
|
||||||
|
// 詳細代碼 - 資料庫類 02x
|
||||||
|
const (
|
||||||
|
_ = iota + CatDB
|
||||||
|
DBError // 資料庫一般錯誤
|
||||||
|
DBDataConvert // 資料轉換錯誤
|
||||||
|
DBDuplicate // 資料重複
|
||||||
|
)
|
||||||
|
|
||||||
|
// 詳細代碼 - 資源類 03x
|
||||||
|
const (
|
||||||
|
_ = iota + CatResource
|
||||||
|
ResourceNotFound // 資源未找到
|
||||||
|
InvalidResourceFormat // 無效的資源格式
|
||||||
|
ResourceAlreadyExist // 資源已存在
|
||||||
|
ResourceInsufficient // 資源不足
|
||||||
|
InsufficientPermission // 權限不足
|
||||||
|
InvalidMeasurementID // 無效的測量ID
|
||||||
|
ResourceExpired // 資源過期
|
||||||
|
ResourceMigrated // 資源已遷移
|
||||||
|
InvalidResourceState // 無效的資源狀態
|
||||||
|
InsufficientQuota // 配額不足
|
||||||
|
ResourceHasMultiOwner // 資源有多個所有者
|
||||||
|
)
|
||||||
|
|
||||||
|
/* 詳細代碼 - GRPC */
|
||||||
|
// GRPC 的詳細代碼使用 Go GRPC 的內建代碼。
|
||||||
|
// 參考 "google.golang.org/grpc/codes" 獲取更多詳細資訊。
|
||||||
|
|
||||||
|
// 詳細代碼 - 驗證類 05x
|
||||||
|
const (
|
||||||
|
_ = iota + CatAuth
|
||||||
|
Unauthorized // 未授權
|
||||||
|
AuthExpired // 授權過期
|
||||||
|
InvalidPosixTime // 無效的 POSIX 時間
|
||||||
|
SigAndPayloadNotMatched // 簽名和載荷不匹配
|
||||||
|
Forbidden // 禁止訪問
|
||||||
|
)
|
||||||
|
|
||||||
|
// 詳細代碼 - 系統類 06x
|
||||||
|
const (
|
||||||
|
_ = iota + CatSystem
|
||||||
|
SystemInternalError // 系統內部錯誤
|
||||||
|
SystemMaintainError // 系統維護錯誤
|
||||||
|
SystemTimeoutError // 系統超時錯誤
|
||||||
|
)
|
||||||
|
|
||||||
|
// 詳細代碼 - PubSub 07x
|
||||||
|
const (
|
||||||
|
_ = iota + CatPubSub
|
||||||
|
Publish // 發佈錯誤
|
||||||
|
Consume // 消費錯誤
|
||||||
|
MsgSizeTooLarge // 訊息過大
|
||||||
|
)
|
||||||
|
|
||||||
|
// 詳細代碼 - 特定服務類 08x
|
||||||
|
const (
|
||||||
|
_ = iota + CatService
|
||||||
|
ArkInternal // Ark 內部錯誤
|
||||||
|
ThirdParty
|
||||||
|
ArkHTTP400 // Ark HTTP 400 錯誤
|
||||||
|
)
|
|
@ -8,6 +8,6 @@ var CatToStr = map[uint32]string{
|
||||||
CatResource: "Resource Error",
|
CatResource: "Resource Error",
|
||||||
CatGRPC: "Internal Service Communication Error",
|
CatGRPC: "Internal Service Communication Error",
|
||||||
CatAuth: "Authentication Error",
|
CatAuth: "Authentication Error",
|
||||||
CatArk: "Internal Service Communication Error",
|
CatService: "Internal Service Communication Error",
|
||||||
CatSystem: "System Error",
|
CatSystem: "System Error",
|
||||||
}
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package code
|
||||||
|
|
||||||
|
// Scope
|
||||||
|
const (
|
||||||
|
Unset uint32 = iota
|
||||||
|
CloudEPPortalGW
|
||||||
|
CloudEPMember
|
||||||
|
CloudEPPermission
|
||||||
|
CloudEPNotification
|
||||||
|
CloudEPTweeting
|
||||||
|
CloudEPOrder
|
||||||
|
CloudEPFileStorage
|
||||||
|
)
|
|
@ -0,0 +1,535 @@
|
||||||
|
package errs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultDetailCode = 00
|
||||||
|
)
|
||||||
|
|
||||||
|
func newBuiltinGRPCErr(scope, detail uint32, msg string) *LibError {
|
||||||
|
return &LibError{
|
||||||
|
category: code.CatGRPC,
|
||||||
|
code: detail,
|
||||||
|
scope: scope,
|
||||||
|
msg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromError tries to let error as Err
|
||||||
|
// it supports to unwrap error that has Error
|
||||||
|
// return nil if failed to transfer
|
||||||
|
func FromError(err error) *LibError {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var e *LibError
|
||||||
|
if errors.As(err, &e) {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromCode parses code as following 7 碼
|
||||||
|
// Decimal: 1200314
|
||||||
|
// 12 represents Scope
|
||||||
|
// 003 represents Category
|
||||||
|
// 14 represents Detail error code
|
||||||
|
func FromCode(code uint32) *LibError {
|
||||||
|
// 獲取 scope,前兩位數
|
||||||
|
scope := code / 100000
|
||||||
|
|
||||||
|
// 獲取 detail,最後兩位數
|
||||||
|
detail := code % 100
|
||||||
|
|
||||||
|
// 獲取 category,中間三位數
|
||||||
|
category := (code / 100) % 1000
|
||||||
|
|
||||||
|
return &LibError{
|
||||||
|
category: category * 100, // category 放大為三位數的整百數
|
||||||
|
code: category*100 + detail, // 重構完整的 code
|
||||||
|
scope: scope,
|
||||||
|
msg: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromGRPCError transfer error to Err
|
||||||
|
// useful for gRPC client
|
||||||
|
func FromGRPCError(err error) *LibError {
|
||||||
|
s, _ := status.FromError(err)
|
||||||
|
e := FromCode(uint32(s.Code()))
|
||||||
|
e.msg = s.Message()
|
||||||
|
|
||||||
|
// For GRPC built-in code
|
||||||
|
if e.Scope() == code.Unset && e.Category() == 0 && e.Code() != code.OK {
|
||||||
|
e = newBuiltinGRPCErr(Scope, e.Code(), s.Message())
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** System ***/
|
||||||
|
|
||||||
|
// SystemTimeoutError xxx6300 returns Error 系統超時
|
||||||
|
func SystemTimeoutError(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.SystemTimeoutError, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemTimeoutErrorL logs error message and returns Err
|
||||||
|
func SystemTimeoutErrorL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := SystemTimeoutError(s...)
|
||||||
|
if filed != nil {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemInternalError xxx6100 returns Err struct
|
||||||
|
func SystemInternalError(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.SystemInternalError, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemInternalErrorL logs error message and returns Err
|
||||||
|
func SystemInternalErrorL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := SystemInternalError(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** CatInput ***/
|
||||||
|
|
||||||
|
// InvalidFormat returns Err struct
|
||||||
|
func InvalidFormat(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.InvalidFormat, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidFormatL logs error message and returns Err
|
||||||
|
func InvalidFormatL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := InvalidFormat(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidRange returns Err struct
|
||||||
|
func InvalidRange(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.InvalidRange, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidRangeL logs error message and returns Err
|
||||||
|
func InvalidRangeL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := InvalidRange(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotValidImplementation returns Err struct
|
||||||
|
func NotValidImplementation(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.NotValidImplementation, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotValidImplementationL logs error message and returns Err
|
||||||
|
func NotValidImplementationL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := NotValidImplementation(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** CatDB ***/
|
||||||
|
|
||||||
|
// DBError returns Err
|
||||||
|
func DBError(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.DBError, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DBErrorL logs error message and returns Err
|
||||||
|
func DBErrorL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := DBError(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// DBDataConvert returns Err
|
||||||
|
func DBDataConvert(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.DBDataConvert, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DBDataConvertL logs error message and returns Err
|
||||||
|
func DBDataConvertL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := DBDataConvert(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// DBDuplicate returns Err
|
||||||
|
func DBDuplicate(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.DBDuplicate, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DBDuplicateL logs error message and returns Err
|
||||||
|
func DBDuplicateL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := DBDuplicate(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** CatResource ***/
|
||||||
|
|
||||||
|
// ResourceNotFound returns Err and logging
|
||||||
|
func ResourceNotFound(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.ResourceNotFound, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceNotFoundL logs error message and returns Err
|
||||||
|
func ResourceNotFoundL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := ResourceNotFound(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidResourceFormat returns Err
|
||||||
|
func InvalidResourceFormat(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.InvalidResourceFormat, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidResourceFormatL logs error message and returns Err
|
||||||
|
func InvalidResourceFormatL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := InvalidResourceFormat(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidResourceState returns status not correct.
|
||||||
|
// for example: company should be destroy, agent should be no-sensor/fail-install ...
|
||||||
|
func InvalidResourceState(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.InvalidResourceState, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidResourceStateL logs error message and returns status not correct.
|
||||||
|
func InvalidResourceStateL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := InvalidResourceState(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResourceInsufficient(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.ResourceInsufficient, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResourceInsufficientL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := ResourceInsufficient(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsufficientPermission returns Err
|
||||||
|
func InsufficientPermission(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.InsufficientPermission,
|
||||||
|
defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsufficientPermissionL returns Err and log
|
||||||
|
func InsufficientPermissionL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := InsufficientPermission(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceAlreadyExist returns Err
|
||||||
|
func ResourceAlreadyExist(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.ResourceAlreadyExist, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceAlreadyExistL logs error message and returns Err
|
||||||
|
func ResourceAlreadyExistL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := ResourceAlreadyExist(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidMeasurementID returns Err
|
||||||
|
func InvalidMeasurementID(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.InvalidMeasurementID, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidMeasurementIDL logs error message and returns Err
|
||||||
|
func InvalidMeasurementIDL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := InvalidMeasurementID(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceExpired returns Err
|
||||||
|
func ResourceExpired(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.ResourceExpired,
|
||||||
|
defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceExpiredL logs error message and returns Err
|
||||||
|
func ResourceExpiredL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := ResourceExpired(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceMigrated returns Err
|
||||||
|
func ResourceMigrated(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.ResourceMigrated, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceMigratedL logs error message and returns Err
|
||||||
|
func ResourceMigratedL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := ResourceMigrated(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsufficientQuota returns Err
|
||||||
|
func InsufficientQuota(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.InsufficientQuota, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsufficientQuotaL logs error message and returns Err
|
||||||
|
func InsufficientQuotaL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := InsufficientQuota(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** CatAuth ***/
|
||||||
|
|
||||||
|
// Unauthorized returns Err
|
||||||
|
func Unauthorized(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.Unauthorized, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnauthorizedL logs error message and returns Err
|
||||||
|
func UnauthorizedL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := Unauthorized(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthExpired returns Err
|
||||||
|
func AuthExpired(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.AuthExpired, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthExpiredL logs error message and returns Err
|
||||||
|
func AuthExpiredL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := AuthExpired(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidPosixTime returns Err
|
||||||
|
func InvalidPosixTime(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.InvalidPosixTime, defaultDetailCode,
|
||||||
|
fmt.Sprintf("i%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidPosixTimeL logs error message and returns Err
|
||||||
|
func InvalidPosixTimeL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := InvalidPosixTime(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigAndPayloadNotMatched returns Err
|
||||||
|
func SigAndPayloadNotMatched(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.SigAndPayloadNotMatched, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigAndPayloadNotMatchedL logs error message and returns Err
|
||||||
|
func SigAndPayloadNotMatchedL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := SigAndPayloadNotMatched(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forbidden returns Err
|
||||||
|
func Forbidden(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.Forbidden, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForbiddenL logs error message and returns Err
|
||||||
|
func ForbiddenL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := Forbidden(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAuthUnauthorizedError check the err is unauthorized error
|
||||||
|
func IsAuthUnauthorizedError(err *LibError) bool {
|
||||||
|
switch err.Code() / 100 {
|
||||||
|
case code.Unauthorized, code.AuthExpired, code.InvalidPosixTime,
|
||||||
|
code.SigAndPayloadNotMatched, code.Forbidden,
|
||||||
|
code.InvalidFormat, code.ResourceNotFound:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** CatPubSub ***/
|
||||||
|
|
||||||
|
// Publish returns Err
|
||||||
|
func Publish(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.Publish, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublishL logs error message and returns Err
|
||||||
|
func PublishL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := Publish(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume returns Err
|
||||||
|
func Consume(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.Consume, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConsumeL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := Consume(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgSizeTooLarge returns Err
|
||||||
|
func MsgSizeTooLarge(s ...string) *LibError {
|
||||||
|
return NewError(Scope, code.MsgSizeTooLarge, defaultDetailCode,
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgSizeTooLargeL logs error message and returns Err
|
||||||
|
func MsgSizeTooLargeL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := MsgSizeTooLarge(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
package errs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFromError(t *testing.T) {
|
||||||
|
t.Run("nil error", func(t *testing.T) {
|
||||||
|
if err := FromError(nil); err != nil {
|
||||||
|
t.Errorf("expected nil, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("LibError type", func(t *testing.T) {
|
||||||
|
libErr := NewError(1, 200, 10, "test error")
|
||||||
|
if err := FromError(libErr); err != libErr {
|
||||||
|
t.Errorf("expected %v, got %v", libErr, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromCode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
code uint32
|
||||||
|
expected *LibError
|
||||||
|
}{
|
||||||
|
{"valid code", 1200314, NewError(12, 3, 14, "")},
|
||||||
|
{"invalid code", 9999999, NewError(99, 999, 99, "")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := FromCode(tt.code)
|
||||||
|
if err.FullCode() != tt.expected.FullCode() {
|
||||||
|
t.Errorf("expected %v, got %v", tt.expected.FullCode(), err.FullCode())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSystemTimeoutError(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"single string", []string{"timeout"}, "timeout"},
|
||||||
|
{"multiple strings", []string{"timeout", "occurred"}, "timeout occurred"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := SystemTimeoutError(tt.input...)
|
||||||
|
if err.Error() != tt.expected {
|
||||||
|
t.Errorf("expected %s, got %s", tt.expected, err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSystemInternalErrorL(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
}{
|
||||||
|
{"internal error", []string{"internal error"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctx := context.TODO()
|
||||||
|
err := SystemInternalErrorL(logx.WithContext(ctx), nil, tt.input...)
|
||||||
|
if err.Error() != tt.input[0] {
|
||||||
|
t.Errorf("expected %s, got %s", tt.input[0], err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidFormatL(t *testing.T) {
|
||||||
|
mockLogger := logx.WithContext(context.Background())
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"invalid format", []string{"invalid format"}, "invalid format"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := InvalidFormatL(mockLogger, nil, tt.input...)
|
||||||
|
if err.Error() != tt.expected {
|
||||||
|
t.Errorf("expected %s, got %s", tt.expected, err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDBErrorL(t *testing.T) {
|
||||||
|
mockLogger := logx.WithContext(context.Background())
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"DB error", []string{"DB error"}, "DB error"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := DBErrorL(mockLogger, nil, tt.input...)
|
||||||
|
if err.Error() != tt.expected {
|
||||||
|
t.Errorf("expected %s, got %s", tt.expected, err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceNotFoundL(t *testing.T) {
|
||||||
|
mockLogger := logx.WithContext(context.Background())
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"resource not found", []string{"resource not found"}, "resource not found"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := ResourceNotFoundL(mockLogger, nil, tt.input...)
|
||||||
|
if err.Error() != tt.expected {
|
||||||
|
t.Errorf("expected %s, got %s", tt.expected, err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsufficientPermissionL(t *testing.T) {
|
||||||
|
mockLogger := logx.WithContext(context.Background())
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"insufficient permission", []string{"permission denied"}, "permission denied"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := InsufficientPermissionL(mockLogger, nil, tt.input...)
|
||||||
|
if err.Error() != tt.expected {
|
||||||
|
t.Errorf("expected %s, got %s", tt.expected, err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsAuthUnauthorizedError(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err *LibError
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"Unauthorized error", NewError(1, code.Unauthorized, 0, ""), true},
|
||||||
|
{"AuthExpired error", NewError(1, code.AuthExpired, 0, ""), true},
|
||||||
|
{"Other error", NewError(1, code.ArkInternal, 0, ""), false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
res := IsAuthUnauthorizedError(tt.err)
|
||||||
|
if res != tt.expected {
|
||||||
|
t.Errorf("expected %t, got %t", tt.expected, res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package errs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
"fmt"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorCode uint32
|
||||||
|
|
||||||
|
func (e ErrorCode) ToUint32() uint32 {
|
||||||
|
return uint32(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ThirdPartyError(scope uint32, ec ErrorCode, s ...string) *LibError {
|
||||||
|
return NewError(scope, code.ThirdParty, ec.ToUint32(), fmt.Sprintf("thirty error: %s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ThirdPartyErrorL(scope uint32, ec ErrorCode,
|
||||||
|
l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := ThirdPartyError(scope, ec, s...)
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func DatabaseErrorWithScope(scope uint32, ec ErrorCode, s ...string) *LibError {
|
||||||
|
return NewError(scope, code.DBError, ec.ToUint32(), strings.Join(s, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func DatabaseErrorWithScopeL(scope uint32,
|
||||||
|
ec ErrorCode,
|
||||||
|
l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := DatabaseErrorWithScope(scope, ec, s...)
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResourceNotFoundWithScope(scope uint32, ec ErrorCode, s ...string) *LibError {
|
||||||
|
return NewError(scope, code.ResourceNotFound, ec.ToUint32(), fmt.Sprintf("resource not found: %s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResourceNotFoundWithScopeL(scope uint32, ec ErrorCode,
|
||||||
|
l logx.Logger, filed []logx.LogField, s ...string) *LibError {
|
||||||
|
e := ResourceNotFoundWithScope(scope, ec, s...)
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func InvalidRangeWithScope(scope uint32, ec ErrorCode, s ...string) *LibError {
|
||||||
|
return NewError(scope, code.CatInput, ec.ToUint32(), fmt.Sprintf("invalid range: %s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func InvalidFormatWithScope(scope uint32, s ...string) *LibError {
|
||||||
|
return NewError(scope, code.CatInput, code.InvalidFormat, fmt.Sprintf("invalid range: %s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ForbiddenWithScope(scope uint32, ec ErrorCode, s ...string) *LibError {
|
||||||
|
return NewError(scope, code.Forbidden, ec.ToUint32(), fmt.Sprintf("forbidden: %s", strings.Join(s, " ")))
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
package errs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scope 全域變數應由服務或模組設置
|
||||||
|
var Scope = code.Unset
|
||||||
|
|
||||||
|
// LibError 7 碼,服務 2 碼,詳細錯誤 2 碼 ,Cat 3 碼 不參與,獨立的 code 組成為( 000 category + 00 detail)
|
||||||
|
type LibError struct {
|
||||||
|
scope uint32 // 系統代號,*100000 來操作,顯示時不夠會補足 7 位數, Library 定義 -> 輸入時都只要輸入兩位數
|
||||||
|
category uint32 // 類別代碼 Library 定義 -> 不客製化業務訊息時,用這個大類別來給錯誤 3 碼
|
||||||
|
code uint32 // 細項 ,每個 repo 裡自行定義 -> 一萬以下都可以 2 碼
|
||||||
|
msg string // 顯示用的,給前端看的 Msg
|
||||||
|
internalErr error // 紀錄且包含真正的錯誤,通常用這個
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error 是錯誤的介面
|
||||||
|
// 私有屬性 "displayMsg" 的 getter 函數,這邊只顯示業務邏輯錯誤,因為可能會帶出去給客戶端
|
||||||
|
// 要如何定位系統真的發生什麼錯?請使用 internalErr 來做定位,通常是會印 Log 的所以用這個
|
||||||
|
func (e *LibError) Error() string {
|
||||||
|
if e == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Category 私有屬性 "category" 的 getter 函數
|
||||||
|
func (e *LibError) Category() uint32 {
|
||||||
|
if e == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.category
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scope 私有屬性 "scope" 的 getter 函數
|
||||||
|
func (e *LibError) Scope() uint32 {
|
||||||
|
if e == nil {
|
||||||
|
return code.Unset
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.scope
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code 私有屬性 "code" 的 getter 函數
|
||||||
|
func (e *LibError) Code() uint32 {
|
||||||
|
if e == nil {
|
||||||
|
return code.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.code
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *LibError) FullCode() uint32 {
|
||||||
|
if e == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.Scope()*100000 + e.Code()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisplayErrorCode 要顯示的 Error Code
|
||||||
|
func (e *LibError) DisplayErrorCode() string {
|
||||||
|
if e == nil {
|
||||||
|
return "000000"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%06d", e.FullCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalError 帶入真正的 error
|
||||||
|
func (e *LibError) InternalError() error {
|
||||||
|
var err error = fmt.Errorf("failed to get internal error")
|
||||||
|
if e == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.internalErr != nil {
|
||||||
|
err = e.internalErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneralError 轉換 category 級別錯誤訊息,模糊化
|
||||||
|
func (e *LibError) GeneralError() string {
|
||||||
|
if e == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
errStr, ok := code.CatToStr[e.Category()]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return errStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is 在執行 errors.Is() 時調用。
|
||||||
|
// 除非你非常確定你在做什麼,否則不要直接使用這個函數。
|
||||||
|
// 請使用 errors.Is 代替。
|
||||||
|
// 此函數比較兩個錯誤變量是否都是 *Err,並且具有相同的 code(不檢查包裹的內部錯誤)
|
||||||
|
func (e *LibError) Is(f error) bool {
|
||||||
|
var err *LibError
|
||||||
|
ok := errors.As(f, &err)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.Code() == err.Code()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap 返回底層錯誤
|
||||||
|
// 解除包裹錯誤的結果本身可能具有 Unwrap 方法;
|
||||||
|
// 我們稱通過反覆解除包裹產生的錯誤序列為錯誤鏈。
|
||||||
|
func (e *LibError) Unwrap() error {
|
||||||
|
if e == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.internalErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap 將內部錯誤設置到 Err 結構
|
||||||
|
func (e *LibError) Wrap(internalErr error) *LibError {
|
||||||
|
if e != nil {
|
||||||
|
e.internalErr = internalErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *LibError) GRPCStatus() *status.Status {
|
||||||
|
if e == nil {
|
||||||
|
return status.New(codes.OK, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return status.New(codes.Code(e.FullCode()), e.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPStatus 返回對應的 HTTP 狀態碼
|
||||||
|
func (e *LibError) HTTPStatus() int {
|
||||||
|
// 如果錯誤為空或錯誤碼為 OK,則返回 200 狀態碼
|
||||||
|
if e == nil || e.Code() == code.OK {
|
||||||
|
return http.StatusOK
|
||||||
|
}
|
||||||
|
// 根據錯誤碼判斷對應的 HTTP 狀態碼
|
||||||
|
switch e.Code() % 100 {
|
||||||
|
case code.ResourceInsufficient:
|
||||||
|
// 如果資源不足,返回 400 狀態碼
|
||||||
|
return http.StatusBadRequest
|
||||||
|
case code.Unauthorized, code.InsufficientPermission:
|
||||||
|
// 如果未授權或權限不足,返回 401 狀態碼
|
||||||
|
return http.StatusUnauthorized
|
||||||
|
case code.InsufficientQuota:
|
||||||
|
// 如果配額不足,返回 402 狀態碼
|
||||||
|
return http.StatusPaymentRequired
|
||||||
|
case code.InvalidPosixTime, code.Forbidden:
|
||||||
|
// 如果時間無效或禁止訪問,返回 403 狀態碼
|
||||||
|
return http.StatusForbidden
|
||||||
|
case code.ResourceNotFound:
|
||||||
|
// 如果資源未找到,返回 404 狀態碼
|
||||||
|
return http.StatusNotFound
|
||||||
|
case code.ResourceAlreadyExist, code.InvalidResourceState:
|
||||||
|
// 如果資源已存在或狀態無效,返回 409 狀態碼
|
||||||
|
return http.StatusConflict
|
||||||
|
case code.NotValidImplementation:
|
||||||
|
// 如果實現無效,返回 501 狀態碼
|
||||||
|
return http.StatusNotImplemented
|
||||||
|
default:
|
||||||
|
// 如果沒有匹配的錯誤碼,則繼續下一步
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根據錯誤的類別判斷對應的 HTTP 狀態碼
|
||||||
|
switch e.Category() {
|
||||||
|
case code.CatInput:
|
||||||
|
// 如果錯誤屬於輸入錯誤類別,返回 400 狀態碼
|
||||||
|
return http.StatusBadRequest
|
||||||
|
default:
|
||||||
|
// 如果沒有符合的條件,返回 500 狀態碼
|
||||||
|
return http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewError 創建新的 Error
|
||||||
|
// 確保 category 在 0 到 999 之間,將超出的 category 設為最大值 999
|
||||||
|
// 確保 detail 在 0 到 99 之間,將超出的 detail 設為最大值 99
|
||||||
|
func NewError(scope, category, detail uint32, displayMsg string) *LibError {
|
||||||
|
// 確保 category 在 0 到 999 之間
|
||||||
|
if category > 999 {
|
||||||
|
category = 999 // 將超出的 category 設為最大值 999
|
||||||
|
}
|
||||||
|
|
||||||
|
// 確保 detail 在 0 到 99 之間
|
||||||
|
if detail > 99 {
|
||||||
|
detail = 99 // 將超出的 detail 設為最大值 99
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LibError{
|
||||||
|
category: category,
|
||||||
|
code: category*100 + detail,
|
||||||
|
scope: scope,
|
||||||
|
msg: displayMsg,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
package errs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewError(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
scope uint32
|
||||||
|
category uint32
|
||||||
|
detail uint32
|
||||||
|
displayMsg string
|
||||||
|
expectedMsg string
|
||||||
|
expectedCat uint32
|
||||||
|
expectedDet uint32
|
||||||
|
}{
|
||||||
|
{"valid error", 1, 200, 10, "test error", "test error", 200, 20010},
|
||||||
|
{"category overflow", 1, 1000, 10, "test error", "test error", 999, 99910},
|
||||||
|
{"detail overflow", 1, 200, 150, "test error", "test error", 200, 20099},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := NewError(tt.scope, tt.category, tt.detail, tt.displayMsg)
|
||||||
|
if err.Error() != tt.expectedMsg {
|
||||||
|
t.Errorf("expected %s, got %s", tt.expectedMsg, err.Error())
|
||||||
|
}
|
||||||
|
if err.Category() != tt.expectedCat {
|
||||||
|
t.Errorf("expected category %d, got %d", tt.expectedCat, err.Category())
|
||||||
|
}
|
||||||
|
if err.Code() != tt.expectedDet {
|
||||||
|
t.Errorf("expected code %d, got %d", tt.expectedDet, err.Code())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLibError_FullCode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
scope uint32
|
||||||
|
category uint32
|
||||||
|
detail uint32
|
||||||
|
expectedCode uint32
|
||||||
|
}{
|
||||||
|
{"valid code", 1, 200, 10, 120010},
|
||||||
|
{"category overflow", 1, 1000, 10, 199910},
|
||||||
|
{"detail overflow", 1, 200, 150, 120099},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := NewError(tt.scope, tt.category, tt.detail, "test")
|
||||||
|
if err.FullCode() != tt.expectedCode {
|
||||||
|
t.Errorf("expected %d, got %d", tt.expectedCode, err.FullCode())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLibError_HTTPStatus(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err *LibError
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{"bad request", NewError(1, code.CatService, code.ResourceInsufficient, "bad request"), http.StatusBadRequest},
|
||||||
|
{"unauthorized", NewError(1, code.CatAuth, code.Unauthorized, "unauthorized"), http.StatusUnauthorized},
|
||||||
|
{"forbidden", NewError(1, code.CatAuth, code.Forbidden, "forbidden"), http.StatusForbidden},
|
||||||
|
{"not found", NewError(1, code.CatResource, code.ResourceNotFound, "not found"), http.StatusNotFound},
|
||||||
|
{"internal server error", NewError(1, code.CatDB, 1095, "not found"), http.StatusInternalServerError},
|
||||||
|
{"input err", NewError(1, code.CatInput, 1095, "not found"), http.StatusBadRequest},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if status := tt.err.HTTPStatus(); status != tt.expected {
|
||||||
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLibError_Error(t *testing.T) {
|
||||||
|
err := NewError(0, 100, 5, "test error")
|
||||||
|
expected := "test error"
|
||||||
|
if err.Error() != expected {
|
||||||
|
t.Errorf("expected '%s', got '%s'", expected, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLibError_Is(t *testing.T) {
|
||||||
|
err1 := NewError(0, 1, 1, "error 1")
|
||||||
|
err2 := NewError(0, 1, 1, "error 2")
|
||||||
|
err3 := errors.New("other error")
|
||||||
|
|
||||||
|
if !err1.Is(err2) {
|
||||||
|
t.Error("expected errors to be equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err1.Is(err3) {
|
||||||
|
t.Error("expected errors to not be equal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLibError_DisplayErrorCode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err *LibError
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"valid code", NewError(1, 200, 10, "test error"), "120010"},
|
||||||
|
{"nil error", nil, "000000"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if code := tt.err.DisplayErrorCode(); code != tt.expected {
|
||||||
|
t.Errorf("expected %s, got %s", tt.expected, code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLibError_Unwrap(t *testing.T) {
|
||||||
|
originalErr := errors.New("original error")
|
||||||
|
libErr := NewError(0, 1, 1, "wrapped error").Wrap(originalErr)
|
||||||
|
|
||||||
|
if unwrappedErr := libErr.Unwrap(); unwrappedErr != originalErr {
|
||||||
|
t.Errorf("expected original error, got %v", unwrappedErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLibError_InternalError(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
internalErr error
|
||||||
|
expected error
|
||||||
|
}{
|
||||||
|
{"valid internal error", errors.New("internal"), errors.New("internal")},
|
||||||
|
{"nil internal error", nil, errors.New("failed to get internal error")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := NewError(1, 200, 10, "test").Wrap(tt.internalErr)
|
||||||
|
if internalErr := err.InternalError(); internalErr.Error() != tt.expected.Error() {
|
||||||
|
t.Errorf("expected %v, got %v", tt.expected, internalErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLibError_GRPCStatus(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err *LibError
|
||||||
|
expected codes.Code
|
||||||
|
}{
|
||||||
|
{"valid GRPC status", NewError(1, 200, 10, "test error"), codes.Code(120010)},
|
||||||
|
{"nil error", nil, codes.OK},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if status := tt.err.GRPCStatus().Code(); status != tt.expected {
|
||||||
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module code.30cm.net/digimon/library-go/errs
|
||||||
|
|
||||||
|
go 1.22.3
|
11
go.work
11
go.work
|
@ -1,4 +1,11 @@
|
||||||
go 1.22.3
|
go 1.22.3
|
||||||
|
|
||||||
use ./errors
|
use (
|
||||||
use ./validator
|
./validator
|
||||||
|
./worker_pool
|
||||||
|
./jwt
|
||||||
|
./errs
|
||||||
|
.
|
||||||
|
./utils/invited_code
|
||||||
|
./utils/bitmap
|
||||||
|
)
|
||||||
|
|
|
@ -46,8 +46,6 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
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 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
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 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4=
|
||||||
github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
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 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
|
@ -170,6 +168,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IV
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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 h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||||
|
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||||
|
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||||
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
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/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 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
type DataClaims map[string]string
|
||||||
|
|
||||||
|
const (
|
||||||
|
idCode = "id"
|
||||||
|
roleCode = "role"
|
||||||
|
deviceIDCode = "device_id"
|
||||||
|
scopeCode = "scope"
|
||||||
|
uidCode = "uid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ============ 使用具體的 setter ============
|
||||||
|
|
||||||
|
// Set 通用的 setter 方法
|
||||||
|
func (c DataClaims) Set(key, value string) {
|
||||||
|
c[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DataClaims) SetID(id string) {
|
||||||
|
c.Set(idCode, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DataClaims) SetRole(role string) {
|
||||||
|
c.Set(roleCode, role)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DataClaims) SetDeviceID(deviceID string) {
|
||||||
|
c.Set(deviceIDCode, deviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DataClaims) SetScope(scope string) {
|
||||||
|
c.Set(scopeCode, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DataClaims) SetUID(uid string) {
|
||||||
|
c.Set(uidCode, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 使用具體的 getter ============
|
||||||
|
|
||||||
|
func (c DataClaims) Get(key string) string {
|
||||||
|
if val, ok := c[key]; ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DataClaims) Scope() {
|
||||||
|
c.Get(scopeCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DataClaims) Role() string {
|
||||||
|
return c.Get(roleCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DataClaims) ID() string {
|
||||||
|
return c.Get(idCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DataClaims) DeviceID() string {
|
||||||
|
return c.Get(deviceIDCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DataClaims) UID() string {
|
||||||
|
return c.Get(uidCode)
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDataClaimsSettersAndGetters(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setterFunc func(c DataClaims, value string)
|
||||||
|
getterFunc func(c DataClaims) string
|
||||||
|
value string
|
||||||
|
expectedVal string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Set and Get ID",
|
||||||
|
setterFunc: func(c DataClaims, value string) { c.SetID(value) },
|
||||||
|
getterFunc: func(c DataClaims) string { return c.ID() },
|
||||||
|
value: "12345",
|
||||||
|
expectedVal: "12345",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set and Get Role",
|
||||||
|
setterFunc: func(c DataClaims, value string) { c.SetRole(value) },
|
||||||
|
getterFunc: func(c DataClaims) string { return c.Role() },
|
||||||
|
value: "admin",
|
||||||
|
expectedVal: "admin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set and Get Device ID",
|
||||||
|
setterFunc: func(c DataClaims, value string) { c.SetDeviceID(value) },
|
||||||
|
getterFunc: func(c DataClaims) string { return c.DeviceID() },
|
||||||
|
value: "device123",
|
||||||
|
expectedVal: "device123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set and Get Scope",
|
||||||
|
setterFunc: func(c DataClaims, value string) { c.SetScope(value) },
|
||||||
|
getterFunc: func(c DataClaims) string { return c.Get(scopeCode) },
|
||||||
|
value: "read",
|
||||||
|
expectedVal: "read",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set and Get UID",
|
||||||
|
setterFunc: func(c DataClaims, value string) { c.SetUID(value) },
|
||||||
|
getterFunc: func(c DataClaims) string { return c.UID() },
|
||||||
|
value: "user123",
|
||||||
|
expectedVal: "user123",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
claims := DataClaims{}
|
||||||
|
|
||||||
|
// Call the setter function
|
||||||
|
tt.setterFunc(claims, tt.value)
|
||||||
|
|
||||||
|
// Call the getter function and verify the result
|
||||||
|
require.Equal(t, tt.expectedVal, tt.getterFunc(claims), "Expected value does not match")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDataClaimsGetNonExistentKey(t *testing.T) {
|
||||||
|
claims := DataClaims{}
|
||||||
|
|
||||||
|
// 對於不存在的鍵,應返回空字串
|
||||||
|
require.Equal(t, "", claims.Get("nonexistent_key"), "Should return empty string for non-existent key")
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
UID string `json:"uid"`
|
||||||
|
DeviceID string `json:"device_id"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
AccessCreateAt time.Time `json:"access_create_at"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
RefreshExpiresIn int `json:"refresh_expires_in"`
|
||||||
|
RefreshCreateAt time.Time `json:"refresh_create_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) AccessTokenExpires() time.Duration {
|
||||||
|
return time.Duration(t.ExpiresIn) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) RefreshTokenExpires() time.Duration {
|
||||||
|
return time.Duration(t.RefreshExpiresIn) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) RefreshTokenExpiresUnix() int64 {
|
||||||
|
return time.Now().Add(t.RefreshTokenExpires()).Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) IsExpires() bool {
|
||||||
|
return t.AccessCreateAt.Add(t.AccessTokenExpires()).Before(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) RedisExpiredSec() int64 {
|
||||||
|
sec := time.Unix(int64(t.ExpiresIn), 0).Sub(time.Now().UTC())
|
||||||
|
|
||||||
|
return int64(sec.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) RedisRefreshExpiredSec() int64 {
|
||||||
|
sec := time.Unix(int64(t.RefreshExpiresIn), 0).Sub(time.Now().UTC())
|
||||||
|
|
||||||
|
return int64(sec.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
type Claims struct {
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccessTokenExpires(t *testing.T) {
|
||||||
|
token := &Token{
|
||||||
|
ExpiresIn: 3600, // 1小時
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedDuration := time.Hour
|
||||||
|
actualDuration := token.AccessTokenExpires()
|
||||||
|
|
||||||
|
require.Equal(t, expectedDuration, actualDuration, "Access token expiration duration should be 1 hour")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRefreshTokenExpires(t *testing.T) {
|
||||||
|
token := &Token{
|
||||||
|
RefreshExpiresIn: 7200, // 2小時
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedDuration := 2 * time.Hour
|
||||||
|
actualDuration := token.RefreshTokenExpires()
|
||||||
|
|
||||||
|
require.Equal(t, expectedDuration, actualDuration, "Refresh token expiration duration should be 2 hours")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRefreshTokenExpiresUnix(t *testing.T) {
|
||||||
|
token := &Token{
|
||||||
|
RefreshExpiresIn: 3600, // 1小時
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedUnix := time.Now().Add(1 * time.Hour).Unix()
|
||||||
|
actualUnix := token.RefreshTokenExpiresUnix()
|
||||||
|
|
||||||
|
// 設定允許範圍,確保結果在1秒的範圍內
|
||||||
|
require.InEpsilon(t, expectedUnix, actualUnix, 1, "Refresh token expires Unix time should match the expected time")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsExpires(t *testing.T) {
|
||||||
|
// 測試過期情況
|
||||||
|
tokenExpired := &Token{
|
||||||
|
ExpiresIn: 3600, // 1小時
|
||||||
|
AccessCreateAt: time.Now().Add(-2 * time.Hour), // 2小時前生成的 token,應該過期
|
||||||
|
}
|
||||||
|
require.True(t, tokenExpired.IsExpires(), "Token should be expired")
|
||||||
|
|
||||||
|
// 測試未過期情況
|
||||||
|
tokenNotExpired := &Token{
|
||||||
|
ExpiresIn: 3600, // 1小時
|
||||||
|
AccessCreateAt: time.Now().Add(-30 * time.Minute), // 30分鐘前生成的 token,應該未過期
|
||||||
|
}
|
||||||
|
require.False(t, tokenNotExpired.IsExpires(), "Token should not be expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisExpiredSec(t *testing.T) {
|
||||||
|
token := &Token{
|
||||||
|
ExpiresIn: int(time.Now().Add(1 * time.Hour).Unix()), // 1小時後過期
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedSec := int64(3600) // 1小時
|
||||||
|
actualSec := token.RedisExpiredSec()
|
||||||
|
|
||||||
|
// 確保時間在合理範圍內
|
||||||
|
require.InDelta(t, expectedSec, actualSec, 1, "Redis expired seconds should be close to 3600 seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisRefreshExpiredSec(t *testing.T) {
|
||||||
|
token := &Token{
|
||||||
|
RefreshExpiresIn: int(time.Now().Add(2 * time.Hour).Unix()), // 2小時後過期
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedSec := int64(7200) // 2小時
|
||||||
|
actualSec := token.RedisRefreshExpiredSec()
|
||||||
|
|
||||||
|
// 確保時間在合理範圍內
|
||||||
|
require.InDelta(t, expectedSec, actualSec, 1, "Redis refresh expired seconds should be close to 7200 seconds")
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
module code.30cm.net/digimon/library-go/jwt
|
||||||
|
|
||||||
|
go 1.22.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,91 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateAccessToken(token Token, data any, sign string, issuer string) (string, error) {
|
||||||
|
claim := Claims{
|
||||||
|
Data: data,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ID: token.ID,
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Unix(int64(token.ExpiresIn), 0)),
|
||||||
|
Issuer: issuer,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claim).
|
||||||
|
SignedString([]byte(sign))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseToken(accessToken string, secret string, validate bool) (jwt.MapClaims, error) {
|
||||||
|
// 跳過驗證的解析
|
||||||
|
var token *jwt.Token
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if validate {
|
||||||
|
token, err = jwt.Parse(accessToken, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
return nil, fmt.Errorf("token unexpected signing method: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(secret), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return jwt.MapClaims{}, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
|
||||||
|
token, err = parser.Parse(accessToken, func(_ *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte(secret), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return jwt.MapClaims{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
|
if !ok && token.Valid {
|
||||||
|
return jwt.MapClaims{}, fmt.Errorf("token valid error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseClaims(accessToken string, secret string, validate bool) (DataClaims, error) {
|
||||||
|
claimMap, err := ParseToken(accessToken, secret, validate)
|
||||||
|
if err != nil {
|
||||||
|
return DataClaims{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
claimsData, ok := claimMap["data"].(map[string]any)
|
||||||
|
if ok {
|
||||||
|
return convertMap(claimsData), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return DataClaims{}, fmt.Errorf("get data from claim map error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertMap(input map[string]interface{}) map[string]string {
|
||||||
|
output := make(map[string]string)
|
||||||
|
for key, value := range input {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case string:
|
||||||
|
output[key] = v
|
||||||
|
case fmt.Stringer:
|
||||||
|
output[key] = v.String()
|
||||||
|
default:
|
||||||
|
output[key] = fmt.Sprintf("%v", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateAccessToken(t *testing.T) {
|
||||||
|
// 定義測試參數
|
||||||
|
token := Token{
|
||||||
|
ID: "12345",
|
||||||
|
ExpiresIn: int(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
|
}
|
||||||
|
sign := "secret_sign"
|
||||||
|
data := map[string]string{
|
||||||
|
"role": "admin",
|
||||||
|
"uid": "user123",
|
||||||
|
}
|
||||||
|
issuer := "test_issuer"
|
||||||
|
|
||||||
|
// 調用生成 access token
|
||||||
|
accessToken, err := GenerateAccessToken(token, data, sign, issuer)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, accessToken)
|
||||||
|
|
||||||
|
// 檢查 access token 是否可以解析
|
||||||
|
claims, err := ParseToken(accessToken, sign, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// 驗證 Claims 是否正確
|
||||||
|
require.Equal(t, token.ID, claims["jti"])
|
||||||
|
require.Equal(t, issuer, claims["iss"])
|
||||||
|
require.Equal(t, "admin", claims["data"].(map[string]interface{})["role"])
|
||||||
|
require.Equal(t, "user123", claims["data"].(map[string]interface{})["uid"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseToken(t *testing.T) {
|
||||||
|
// 測試生成並解析 token
|
||||||
|
token := Token{
|
||||||
|
ID: "67890",
|
||||||
|
ExpiresIn: int(time.Now().Add(2 * time.Hour).Unix()),
|
||||||
|
}
|
||||||
|
sign := "another_secret_sign"
|
||||||
|
data := map[string]string{
|
||||||
|
"role": "user",
|
||||||
|
"uid": "user456",
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, err := GenerateAccessToken(token, data, sign, "example_issuer")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, accessToken)
|
||||||
|
|
||||||
|
// 測試有驗證的解析
|
||||||
|
claims, err := ParseToken(accessToken, sign, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "user", claims["data"].(map[string]interface{})["role"])
|
||||||
|
require.Equal(t, "user456", claims["data"].(map[string]interface{})["uid"])
|
||||||
|
|
||||||
|
// 測試不驗證的解析
|
||||||
|
claimsNoValidation, err := ParseToken(accessToken, sign, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "user", claimsNoValidation["data"].(map[string]interface{})["role"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseClaims(t *testing.T) {
|
||||||
|
// 測試生成並解析 claims
|
||||||
|
token := Token{
|
||||||
|
ID: "54321",
|
||||||
|
ExpiresIn: int(time.Now().Add(3 * time.Hour).Unix()),
|
||||||
|
}
|
||||||
|
sign := "test_sign"
|
||||||
|
data := map[string]string{
|
||||||
|
"role": "moderator",
|
||||||
|
"uid": "user789",
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, err := GenerateAccessToken(token, data, sign, "sample_issuer")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// 測試 claims 解析
|
||||||
|
parsedClaims, err := ParseClaims(accessToken, sign, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "moderator", parsedClaims["role"])
|
||||||
|
require.Equal(t, "user789", parsedClaims["uid"])
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
// Bitmap 基礎結構
|
||||||
|
// Bitmap 是一個位圖結構,使用 byte slice 來表示大量的位(bit)。
|
||||||
|
// 每個 byte 由 8 個位組成,因此可以高效地管理大量的開關狀態(true/false)。
|
||||||
|
// Bitmap 的優點在於它能節省空間,尤其是在需要大量布爾值的場合。
|
||||||
|
// 缺點是,如果需要動態擴充 Bitmap 的大小,會導致效率下降,因為需要重新分配和移動內存。
|
||||||
|
// 因此,最好在初始化時就規劃好所需的位數大小,避免在之後頻繁擴充。
|
||||||
|
|
||||||
|
type Bitmap []byte
|
||||||
|
|
||||||
|
// MakeBitmapWithBitSize 通過指定的 bit 數創建一個新的 Bitmap。
|
||||||
|
// 參數 nBits 表示所需的位(bit)數。
|
||||||
|
// 如果指定的位數少於 64,則默認將 Bitmap 初始化為 64 位(這是最低的限制)。
|
||||||
|
// 此外,位數(nBits)會被自動調整為 8 的倍數,以適配 byte 的長度(每 8 位為一個 byte)。
|
||||||
|
// 返回值是一個 Bitmap(byte slice),其大小根據位數確定。
|
||||||
|
func MakeBitmapWithBitSize(nBits int) Bitmap {
|
||||||
|
// 如果指定的位數少於 64,則設置為 64 位(8 個 byte)
|
||||||
|
if nBits < 64 {
|
||||||
|
nBits = 64
|
||||||
|
}
|
||||||
|
// 計算需要的 byte 數,確保每 8 位為一個 byte,並調整 nBits 以達到 8 的倍數
|
||||||
|
return MustBitMap((nBits + 7) / 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustBitMap 根據指定的 byte 數創建一個 Bitmap(byte slice)。
|
||||||
|
// 參數 nBytes 表示所需的 byte 數。
|
||||||
|
// 返回值是一個長度為 nBytes 的 Bitmap。
|
||||||
|
func MustBitMap(nBytes int) Bitmap {
|
||||||
|
// 使用 make 函數創建一個 byte slice,大小為 nBytes。
|
||||||
|
return make([]byte, nBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrue 設置指定位置的 bit 為 true(1)。
|
||||||
|
// 參數 bitPos 是需要設置的位的位置(以 0 為基準的位索引)。
|
||||||
|
// 這個操作會找到該 bit 所在的 byte,然後通過位運算將該位置的 bit 設置為 1。
|
||||||
|
func (b Bitmap) SetTrue(bitPos uint32) {
|
||||||
|
// |= 是一種位運算的複合賦值運算符,表示將左邊的變數與右邊的值進行 位或運算(bitwise OR),並將結果賦值
|
||||||
|
b[bitPos/8] |= 1 << (bitPos % 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFalse 設置指定位置的 bit 為 false(0)。
|
||||||
|
// 參數 bitPos 是需要設置的位的位置(以 0 為基準的位索引)。
|
||||||
|
// 這個操作會找到該 bit 所在的 byte,然後通過位運算將該位置的 bit 設置為 0。
|
||||||
|
func (b Bitmap) SetFalse(bitPos uint32) {
|
||||||
|
// 取出對應 byte,使用位與和取反運算將對應的 bit 設置為 0
|
||||||
|
// 假設我們有以下情況:
|
||||||
|
|
||||||
|
// • b[1](即 b[bitPos/8])是 10101111(十進制 175)。
|
||||||
|
// • bitPos = 10,也就是我們想清除第 10 位的值。
|
||||||
|
//
|
||||||
|
// 操作步驟:
|
||||||
|
//
|
||||||
|
// 1. bitPos/8 = 1:所以我們要修改 b[1] 這個 byte。
|
||||||
|
// 2. bitPos % 8 = 2:表示我們要清除的位是這個 byte 中的第 3 位(從右數起第 3 位)。
|
||||||
|
// 3. 1 << (bitPos % 8) = 1 << 2 = 00000100:生成位掩碼 00000100。
|
||||||
|
// 4. 取反:^(1 << 2) = ^00000100 = 11111011,這樣的掩碼表示除了第 3 位,其他位都是 1。
|
||||||
|
// 5. 位與運算:10101111 & 11111011 = 10101011,結果將第 3 位清除,其餘位保持不變。即,b[1] 變成了 10101011(十進制 171)。
|
||||||
|
// &= 是一種 位與運算的複合賦值運算符,表示將左邊的變數與右邊的值進行 位與運算(bitwise AND),然後將結果賦值給左邊的變數。
|
||||||
|
b[bitPos/8] &= ^(1 << (bitPos % 8))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTrue 檢查指定位置的 bit 是否為 true(1)。
|
||||||
|
// 參數 bitPos 是要檢查的位的位置(以 0 為基準的位索引)。
|
||||||
|
// 如果該 bit 是 1,則返回 true;否則返回 false。
|
||||||
|
func (b Bitmap) IsTrue(bitPos uint32) bool {
|
||||||
|
/*
|
||||||
|
這一行程式碼 b[bitPos/8]&(1<<(bitPos%8)) != 0 是用來檢查 指定位(bit) 是否為 true(1),
|
||||||
|
它的核心是位運算。讓我們逐步拆解並解釋這一行程式碼:
|
||||||
|
|
||||||
|
1. 背景知識:
|
||||||
|
|
||||||
|
• 位運算 是在二進制層面操作數字。每個 byte 有 8 個位(bit),所以位圖結構是以 byte 來表示位的集合。
|
||||||
|
• b 是一個 Bitmap 結構,也就是 []byte,即 byte 的切片。
|
||||||
|
• bitPos 是一個 uint32 類型的變數,表示我們想要檢查的位(bit)在整個位圖中的索引(位置)。
|
||||||
|
3. 完整流程舉例:
|
||||||
|
假設:
|
||||||
|
• b = []byte{0b10101010, 0b01010101} (即二進制表示的兩個 byte,分別是 10101010 和 01010101)。
|
||||||
|
• bitPos = 10(我們要檢查第 10 位是否為 1)。
|
||||||
|
操作順序:
|
||||||
|
|
||||||
|
1. 計算 bitPos/8 = 10/8 = 1,所以我們要檢查的是第二個 byte:0b01010101。
|
||||||
|
2. 計算 bitPos%8 = 10%8 = 2,所以我們要檢查的是該 byte 中的第 3 位(從右數起第 3 位)。
|
||||||
|
3. 位移:1 << 2 = 00000100。
|
||||||
|
4. 位與:0b01010101 & 0b00000100 = 0b00000100(因為該 byte 的第 3 位是 1,結果不等於 0)。
|
||||||
|
5. 判斷結果:結果不等於 0,因此第 10 位是 1(true)。
|
||||||
|
|
||||||
|
4. 總結:
|
||||||
|
|
||||||
|
• b[bitPos/8]&(1<<(bitPos%8)) != 0 是一個經典的位操作,用來檢查位圖中某一個位是否為 1。
|
||||||
|
• bitPos/8 找到對應的 byte,bitPos % 8 找到該位在這個 byte 中的具體位置。
|
||||||
|
• 最後的位與運算和比較用來確定該位的狀態是 true(1)還是 false(0)。
|
||||||
|
*/
|
||||||
|
return b[bitPos/8]&(1<<(bitPos%8)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset 重置 Bitmap,使所有的 bit 都設置為 false(0)。
|
||||||
|
// 這個操作會將整個 Bitmap 的所有 byte 都設置為 0,從而達到重置的效果。
|
||||||
|
func (b Bitmap) Reset() {
|
||||||
|
// 迭代 Bitmap 中的每個 byte,並將其設置為 0
|
||||||
|
for i := range b {
|
||||||
|
b[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteSize 返回 Bitmap 的 byte 長度。
|
||||||
|
// 這個函數返回 Bitmap 目前占用的 byte 數量,該值等於 Bitmap 的長度(slice 的長度)。
|
||||||
|
func (b Bitmap) ByteSize() int {
|
||||||
|
return len(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BitSize 返回 Bitmap 的位(bit)長度。
|
||||||
|
// 這個函數通過將 byte 長度乘以 8 來計算 Bitmap 中的總位數。
|
||||||
|
func (b Bitmap) BitSize() int {
|
||||||
|
// 每個 byte 包含 8 個 bit,因此將 byte 長度乘以 8
|
||||||
|
return len(b) * 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize 重新調整 Bitmap 的大小,將其擴展為指定的 bit 大小。
|
||||||
|
// 原來的數據會被保留,新增加的部分位將初始化為 0。
|
||||||
|
// 參數 newBitSize 是 Bitmap 需要擴展到的位大小。
|
||||||
|
func (b Bitmap) Resize(newBitSize int) Bitmap {
|
||||||
|
newByteSize := (newBitSize + 7) / 8
|
||||||
|
if newByteSize <= len(b) {
|
||||||
|
// 如果新大小比原大小小或相等,不做任何操作,直接返回原 Bitmap
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// 創建新的 Bitmap
|
||||||
|
newBitmap := make([]byte, newByteSize)
|
||||||
|
|
||||||
|
// 將原 Bitmap 複製到新 Bitmap 中
|
||||||
|
copy(newBitmap, b)
|
||||||
|
|
||||||
|
// 返回新的 Bitmap
|
||||||
|
return newBitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFrom 會將傳入的 Bitmap 的值更新到當前 Bitmap 中。
|
||||||
|
// 如果傳入的 Bitmap 大於當前 Bitmap,則當前 Bitmap 會自動擴展並保存新 Bitmap 的數據。
|
||||||
|
// 如果傳入的 Bitmap 小於當前 Bitmap,僅更新前面部分的數據。
|
||||||
|
func (b Bitmap) UpdateFrom(newBitmap Bitmap) Bitmap {
|
||||||
|
// 如果 newBitmap 比當前 Bitmap 長,則擴展當前 Bitmap 的大小
|
||||||
|
if len(newBitmap) > len(b) {
|
||||||
|
// 創建一個擴展過的 Bitmap
|
||||||
|
expandedBitmap := make([]byte, len(newBitmap))
|
||||||
|
copy(expandedBitmap, b) // 將當前 Bitmap 的數據複製到擴展的 Bitmap 中
|
||||||
|
b = expandedBitmap // 更新當前 Bitmap 的引用
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAllTrue 將 Bitmap 中的所有位設置為 true(1)。
|
||||||
|
func (b Bitmap) SetAllTrue() Bitmap {
|
||||||
|
// 將 Bitmap 中的每個 byte 設置為 0xFF,表示所有 bit 都為 1
|
||||||
|
for i := range b {
|
||||||
|
b[i] = 0xFF
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAllFalse 將 Bitmap 中的所有位設置為 false(0)。
|
||||||
|
func (b Bitmap) SetAllFalse() Bitmap {
|
||||||
|
// 將 Bitmap 中的每個 byte 設置為 0,表示所有 bit 都為 0
|
||||||
|
for i := range b {
|
||||||
|
b[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// 基準測試 SetTrue 函數,測試在不同大小的 Bitmap 上設置位元為 true 的效能
|
||||||
|
func BenchmarkBitmapSetTrue(b *testing.B) {
|
||||||
|
// 以 10^6 位元作為基準測試的 Bitmap 大小
|
||||||
|
bitmap := MakeBitmapWithBitSize(1000000)
|
||||||
|
b.ResetTimer() // 重設計時器,排除初始化的時間
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bitmap.SetTrue(uint32(i % 1000000)) // 設置位元為 true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基準測試 SetFalse 函數,測試在不同大小的 Bitmap 上清除位元為 false 的效能
|
||||||
|
func BenchmarkBitmapSetFalse(b *testing.B) {
|
||||||
|
// 以 10^6 位元作為基準測試的 Bitmap 大小
|
||||||
|
bitmap := MakeBitmapWithBitSize(1000000)
|
||||||
|
b.ResetTimer() // 重設計時器,排除初始化的時間
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bitmap.SetFalse(uint32(i % 1000000)) // 清除位元為 false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基準測試 IsTrue 函數,測試在不同大小的 Bitmap 上檢查位元狀態的效能
|
||||||
|
func BenchmarkBitmapIsTrue(b *testing.B) {
|
||||||
|
// 以 10^6 位元作為基準測試的 Bitmap 大小
|
||||||
|
bitmap := MakeBitmapWithBitSize(1000000)
|
||||||
|
b.ResetTimer() // 重設計時器,排除初始化的時間
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = bitmap.IsTrue(uint32(i % 1000000)) // 檢查位元是否為 true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基準測試 Reset 函數,測試重置不同大小的 Bitmap 的效能
|
||||||
|
func BenchmarkBitmapReset(b *testing.B) {
|
||||||
|
// 以 10^6 位元作為基準測試的 Bitmap 大小
|
||||||
|
bitmap := MakeBitmapWithBitSize(1000000)
|
||||||
|
b.ResetTimer() // 重設計時器,排除初始化的時間
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bitmap.Reset() // 重置 Bitmap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基準測試 BitSize 函數,測試返回位圖的 bit 長度的效能
|
||||||
|
func BenchmarkBitmapBitSize(b *testing.B) {
|
||||||
|
// 以 10^6 位元作為基準測試的 Bitmap 大小
|
||||||
|
bitmap := MakeBitmapWithBitSize(1000000)
|
||||||
|
b.ResetTimer() // 重設計時器,排除初始化的時間
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = bitmap.BitSize() // 測試返回位圖的 bit 長度
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基準測試 ByteSize 函數,測試返回位圖的 byte 長度的效能
|
||||||
|
func BenchmarkBitmapByteSize(b *testing.B) {
|
||||||
|
// 以 10^6 位元作為基準測試的 Bitmap 大小
|
||||||
|
bitmap := MakeBitmapWithBitSize(1000000)
|
||||||
|
b.ResetTimer() // 重設計時器,排除初始化的時間
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = bitmap.ByteSize() // 測試返回位圖的 byte 長度
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBitmap_SetTrueAndIsTrue(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
bitPos uint32
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"Set bit 0 to true", 0, true},
|
||||||
|
{"Set bit 1 to true", 1, true},
|
||||||
|
{"Set bit 63 to true", 63, true},
|
||||||
|
{"Set bit 64 to true", 64, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
bitmap := MakeBitmapWithBitSize(128) // 初始化一個 128 位的 Bitmap
|
||||||
|
bitmap.SetTrue(tt.bitPos)
|
||||||
|
result := bitmap.IsTrue(tt.bitPos)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmap_SetFalse(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
bitPos uint32
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"Set bit 0 to false", 0, false},
|
||||||
|
{"Set bit 1 to false", 1, false},
|
||||||
|
{"Set bit 63 to false", 63, false},
|
||||||
|
{"Set bit 64 to false", 64, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
bitmap := MakeBitmapWithBitSize(128) // 初始化一個 128 位的 Bitmap
|
||||||
|
bitmap.SetTrue(tt.bitPos) // 先設置該 bit 為 true
|
||||||
|
bitmap.SetFalse(tt.bitPos) // 然後設置該 bit 為 false
|
||||||
|
result := bitmap.IsTrue(tt.bitPos)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmap_Reset(t *testing.T) {
|
||||||
|
bitmap := MakeBitmapWithBitSize(128) // 初始化一個 128 位的 Bitmap
|
||||||
|
bitmap.SetTrue(0)
|
||||||
|
bitmap.SetTrue(64)
|
||||||
|
|
||||||
|
// 確認 bit 0 和 bit 64 是 true
|
||||||
|
assert.True(t, bitmap.IsTrue(0))
|
||||||
|
assert.True(t, bitmap.IsTrue(64))
|
||||||
|
|
||||||
|
bitmap.Reset() // 重置位圖
|
||||||
|
|
||||||
|
// 確認所有的位都已經重置為 false
|
||||||
|
assert.False(t, bitmap.IsTrue(0))
|
||||||
|
assert.False(t, bitmap.IsTrue(64))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmap_ByteSize(t *testing.T) {
|
||||||
|
bitmap := MakeBitmapWithBitSize(64) // 初始化一個 64 位的 Bitmap
|
||||||
|
assert.Equal(t, 8, bitmap.ByteSize()) // 64 位應該佔用 8 個 byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmap_BitSize(t *testing.T) {
|
||||||
|
bitmap := MakeBitmapWithBitSize(128) // 初始化一個 128 位的 Bitmap
|
||||||
|
assert.Equal(t, 128, bitmap.BitSize()) // 128 位應該有 128 個 bit
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmap_Resize(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initialSize int
|
||||||
|
newBitSize int
|
||||||
|
expectedLen int
|
||||||
|
}{
|
||||||
|
{"Resize to larger size", 64, 128, 16},
|
||||||
|
{"Resize to smaller size", 128, 64, 16}, // 大小應保持不變
|
||||||
|
{"Resize to equal size", 64, 64, 8}, // 大小應保持不變
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// 初始化一個 Bitmap
|
||||||
|
bitmap := MakeBitmapWithBitSize(tt.initialSize)
|
||||||
|
|
||||||
|
// 調用 Resize
|
||||||
|
resizedBitmap := bitmap.Resize(tt.newBitSize)
|
||||||
|
|
||||||
|
// 檢查結果的 byte 大小
|
||||||
|
assert.Equal(t, tt.expectedLen, len(resizedBitmap))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmap_UpdateFrom(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initialSize int
|
||||||
|
newBitmapSize int
|
||||||
|
expectedBitPos uint32
|
||||||
|
expectedResult bool
|
||||||
|
}{
|
||||||
|
{"Update to larger bitmap", 64, 128, 50, true},
|
||||||
|
{"Update to smaller bitmap", 128, 64, 50, true}, // 新的 Bitmap 將只更新前面部分
|
||||||
|
{"Update to equal size bitmap", 64, 64, 20, true}, // 將相同大小的 Bitmap 進行合併
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// 初始化一個初始大小的 Bitmap
|
||||||
|
bitmap := MakeBitmapWithBitSize(tt.initialSize)
|
||||||
|
bitmap.SetTrue(tt.expectedBitPos)
|
||||||
|
|
||||||
|
// 初始化一個新的 Bitmap
|
||||||
|
newBitmap := MakeBitmapWithBitSize(tt.newBitmapSize)
|
||||||
|
newBitmap.SetTrue(tt.expectedBitPos)
|
||||||
|
|
||||||
|
// 更新 Bitmap
|
||||||
|
updatedBitmap := bitmap.UpdateFrom(newBitmap)
|
||||||
|
|
||||||
|
// 檢查預期的位是否為 true
|
||||||
|
result := updatedBitmap.IsTrue(tt.expectedBitPos)
|
||||||
|
assert.Equal(t, tt.expectedResult, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmap_SetAllTrueAndSetAllFalse(t *testing.T) {
|
||||||
|
bitmap := MakeBitmapWithBitSize(128)
|
||||||
|
|
||||||
|
// 測試 SetAllTrue
|
||||||
|
bitmap.SetAllTrue()
|
||||||
|
for i := 0; i < bitmap.BitSize(); i++ {
|
||||||
|
if !bitmap.IsTrue(uint32(i)) {
|
||||||
|
t.Errorf("Expected bit %d to be true, but got false", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試 SetAllFalse
|
||||||
|
bitmap.SetAllFalse()
|
||||||
|
for i := 0; i < bitmap.BitSize(); i++ {
|
||||||
|
if bitmap.IsTrue(uint32(i)) {
|
||||||
|
t.Errorf("Expected bit %d to be false, but got true", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
module code.30cm.net/digimon/library-go/utils/bitmap
|
||||||
|
|
||||||
|
go 1.22.3
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.9.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,16 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
type BitMapUseCase interface {
|
||||||
|
// SetTrue 設定該 Bit 狀態為 true
|
||||||
|
SetTrue(bitPos uint32)
|
||||||
|
// SetFalse 設定該Bit 狀態為 false
|
||||||
|
SetFalse(bitPos uint32)
|
||||||
|
// IsTrue 確認是否為真
|
||||||
|
IsTrue(bitPos uint32) bool
|
||||||
|
// Reset 重設 BitMap
|
||||||
|
Reset()
|
||||||
|
// ByteSize 最大 Byte 數
|
||||||
|
ByteSize() int
|
||||||
|
// BitSize 最大 Byte * 8
|
||||||
|
BitSize() int
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package invited_code
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultCodeLen = 8
|
||||||
|
InitAutoId = 10000000
|
||||||
|
)
|
||||||
|
|
||||||
|
var ConvertTable = []string{
|
||||||
|
"O", "D", "W", "X", "Y",
|
||||||
|
"G", "B", "C", "H", "E",
|
||||||
|
"F", "A", "Q", "I", "J",
|
||||||
|
"L", "M", "N", "Z", "K",
|
||||||
|
"P", "V", "R", "S", "T",
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package invited_code
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Converter struct {
|
||||||
|
Base int
|
||||||
|
Length int
|
||||||
|
ConvertTable []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Converter) EncodeFromNum(id int64) (string, error) {
|
||||||
|
code, err := generateCode(id, c.Base, c.Length, c.ConvertTable)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return code, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Converter) DecodeFromCode(code string) (int64, error) {
|
||||||
|
var result int64 = 0
|
||||||
|
length := len(code)
|
||||||
|
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
char := string(code[i])
|
||||||
|
index := -1
|
||||||
|
for j, v := range c.ConvertTable {
|
||||||
|
if v == char {
|
||||||
|
index = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if index >= 0 {
|
||||||
|
result = result*int64(c.Base) + int64(index)
|
||||||
|
} else {
|
||||||
|
return 0, fmt.Errorf("character not found in convert table")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateCode 從 UID 生成 referralCode
|
||||||
|
func generateCode(id int64, base int, length int, convertTable []string) (string, error) {
|
||||||
|
maxReferralUIDBoundary := int64(math.Pow(float64(len(ConvertTable)), float64(DefaultCodeLen)))
|
||||||
|
if id > maxReferralUIDBoundary {
|
||||||
|
return "", fmt.Errorf("encode out of range")
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded := encodeToBase(id, base, length, convertTable)
|
||||||
|
|
||||||
|
return encoded, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeToBase(num int64, base int, length int, convertTable []string) string {
|
||||||
|
result := ""
|
||||||
|
for num > 0 {
|
||||||
|
index := num % int64(base)
|
||||||
|
result = convertTable[index] + result
|
||||||
|
num /= int64(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) < length {
|
||||||
|
result = strings.Repeat(convertTable[0], length-len(result)) + result
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustConverter(base int, length int, convertTable []string) ConvertUseCase {
|
||||||
|
return &Converter{
|
||||||
|
Base: base,
|
||||||
|
Length: length,
|
||||||
|
ConvertTable: convertTable,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
package invited_code
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 測試 ConvertUseCase 的功能
|
||||||
|
func TestConverter(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
id int64
|
||||||
|
base int
|
||||||
|
length int
|
||||||
|
wantCode string
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test Case 1: Valid ID 1000000",
|
||||||
|
id: 10000000,
|
||||||
|
base: 10,
|
||||||
|
length: 7,
|
||||||
|
wantCode: "DOOOOOOO",
|
||||||
|
wantError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test Case 2: Valid ID 12345678",
|
||||||
|
id: 12345678,
|
||||||
|
base: 10,
|
||||||
|
length: 8,
|
||||||
|
wantCode: "DWXYGBCH",
|
||||||
|
wantError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test Case 3: Valid ID 98765432",
|
||||||
|
id: 98765432,
|
||||||
|
base: 10,
|
||||||
|
length: 8,
|
||||||
|
wantCode: "EHCBGYXW",
|
||||||
|
wantError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test Case 4: ID too large",
|
||||||
|
id: 10000000000000001, // ID 超過界限
|
||||||
|
base: 10,
|
||||||
|
length: 8,
|
||||||
|
wantCode: "",
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
converter := MustConverter(10, 8, ConvertTable)
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
code, err := converter.EncodeFromNum(tt.id)
|
||||||
|
|
||||||
|
if tt.wantError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.wantCode, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantError {
|
||||||
|
// 測試解碼
|
||||||
|
decodedID, err := converter.DecodeFromCode(code)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.id, decodedID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeFromCode(t *testing.T) {
|
||||||
|
converter := MustConverter(10, 8, ConvertTable)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
code string
|
||||||
|
wantID int64
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Decode valid code 1",
|
||||||
|
code: "DOOOOOOO", // 對應於 id 10000000
|
||||||
|
wantID: 10000000,
|
||||||
|
wantError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Decode valid code 2",
|
||||||
|
code: "DWXYGBCH", // 對應於 id 12345678
|
||||||
|
wantID: 12345678,
|
||||||
|
wantError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Decode valid code 3",
|
||||||
|
code: "EHCBGYXW", // 對應於 id 98765432
|
||||||
|
wantID: 98765432,
|
||||||
|
wantError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Decode invalid code with character not in table",
|
||||||
|
code: "UWOXZZZ", // 包含 ZZZ,不在轉換表中
|
||||||
|
wantID: 0,
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := converter.DecodeFromCode(tt.code)
|
||||||
|
|
||||||
|
if tt.wantError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, int64(0), result)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.wantID, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
module code.30cm.net/digimon/library-go/utils/invited_code
|
||||||
|
|
||||||
|
go 1.22.3
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.9.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
package invited_code
|
||||||
|
|
||||||
|
type ConvertUseCase interface {
|
||||||
|
EncodeFromNum(id int64) (string, error)
|
||||||
|
DecodeFromCode(code string) (int64, error)
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package required
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
module code.30cm.net/digimon/library-go/worker_pool
|
||||||
|
|
||||||
|
go 1.22.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/panjf2000/ants/v2 v2.10.0
|
||||||
|
github.com/stretchr/testify v1.8.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
golang.org/x/sync v0.3.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,71 @@
|
||||||
|
package workerpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/panjf2000/ants/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultWorkerPoolSize = 2000
|
||||||
|
|
||||||
|
type WorkerPool interface {
|
||||||
|
Submit(task func()) error
|
||||||
|
SubmitAndWaitAll(tasks ...func() error) (taskErr chan error, submitErr error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type workerPool struct {
|
||||||
|
p *ants.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWorkerPool(size int) WorkerPool {
|
||||||
|
if size <= 0 {
|
||||||
|
size = defaultWorkerPoolSize
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := ants.NewPool(
|
||||||
|
size,
|
||||||
|
ants.WithDisablePurge(true),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return &workerPool{p: nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &workerPool{p: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *workerPool) Submit(task func()) error {
|
||||||
|
if p.p == nil {
|
||||||
|
return ants.Submit(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.p.Submit(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *workerPool) SubmitAndWaitAll(tasks ...func() error) (chan error, error) {
|
||||||
|
taskErrCh := make(chan error, len(tasks))
|
||||||
|
submitErrCh := make(chan error, len(tasks))
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(len(tasks))
|
||||||
|
|
||||||
|
for i := range tasks {
|
||||||
|
task := tasks[i]
|
||||||
|
err := p.Submit(func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := task(); err != nil {
|
||||||
|
taskErrCh <- err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
submitErrCh <- err
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if len(submitErrCh) != 0 {
|
||||||
|
return nil, <-submitErrCh
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskErrCh, nil
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package workerpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewWorkerPool(t *testing.T) {
|
||||||
|
t.Run("default size pool", func(t *testing.T) {
|
||||||
|
pool := NewWorkerPool(0)
|
||||||
|
assert.NotNil(t, pool)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("custom size pool", func(t *testing.T) {
|
||||||
|
size := 100
|
||||||
|
pool := NewWorkerPool(size)
|
||||||
|
assert.NotNil(t, pool)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubmit(t *testing.T) {
|
||||||
|
t.Run("submit task to worker pool", func(t *testing.T) {
|
||||||
|
pool := NewWorkerPool(10)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
err := pool.Submit(func() {
|
||||||
|
defer wg.Done()
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubmitAndWaitAll(t *testing.T) {
|
||||||
|
t.Run("submit and wait all tasks succeed", func(t *testing.T) {
|
||||||
|
pool := NewWorkerPool(10)
|
||||||
|
tasks := []func() error{
|
||||||
|
func() error {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
func() error {
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
taskErrCh, submitErr := pool.SubmitAndWaitAll(tasks...)
|
||||||
|
assert.NoError(t, submitErr)
|
||||||
|
close(taskErrCh)
|
||||||
|
for err := range taskErrCh {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("submit and wait all tasks with errors", func(t *testing.T) {
|
||||||
|
pool := NewWorkerPool(10)
|
||||||
|
expectedError := errors.New("task error")
|
||||||
|
tasks := []func() error{
|
||||||
|
func() error {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
func() error {
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
return expectedError
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
taskErrCh, submitErr := pool.SubmitAndWaitAll(tasks...)
|
||||||
|
assert.NoError(t, submitErr)
|
||||||
|
close(taskErrCh)
|
||||||
|
foundError := false
|
||||||
|
for err := range taskErrCh {
|
||||||
|
if err != nil {
|
||||||
|
foundError = true
|
||||||
|
assert.Equal(t, expectedError, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, foundError)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue