From de15dbf40593a937dcfe6fb53c9ec09e084134a5 Mon Sep 17 00:00:00 2001 From: yusing Date: Thu, 4 Dec 2025 12:17:33 +0800 Subject: [PATCH] refactor: move some utility functions to goutils and update references --- goutils | 2 +- internal/acl/config.go | 5 +- internal/api/v1/file/list.go | 6 +- internal/autocert/config.go | 3 +- internal/docker/container.go | 3 +- internal/homepage/route.go | 2 +- .../logging/accesslog/access_logger_test.go | 4 +- internal/logging/accesslog/formatter.go | 8 +- internal/logging/accesslog/rotate.go | 6 +- internal/logging/accesslog/rotate_test.go | 22 ++-- .../net/gphttp/loadbalancer/loadbalancer.go | 2 +- .../net/gphttp/middleware/captcha/session.go | 5 +- .../gphttp/middleware/errorpage/error_page.go | 4 +- internal/net/gphttp/middleware/middlewares.go | 6 +- internal/net/types/url.go | 2 - internal/proxmox/node.go | 2 +- internal/route/routes/routes.go | 2 +- internal/serialization/serialization.go | 3 +- internal/types/docker.go | 3 - internal/types/routes.go | 2 +- internal/utils/fs.go | 36 ----- internal/utils/nearest_field.go | 50 ------- internal/utils/pool/pool.go | 123 ------------------ internal/utils/pool/pool_debug.go | 15 --- internal/utils/pool/pool_prod.go | 7 - internal/utils/time_now.go | 44 ------- internal/utils/time_now_test.go | 104 --------------- 27 files changed, 41 insertions(+), 430 deletions(-) delete mode 100644 internal/utils/fs.go delete mode 100644 internal/utils/nearest_field.go delete mode 100644 internal/utils/pool/pool.go delete mode 100644 internal/utils/pool/pool_debug.go delete mode 100644 internal/utils/pool/pool_prod.go delete mode 100644 internal/utils/time_now.go delete mode 100644 internal/utils/time_now_test.go diff --git a/goutils b/goutils index cfb8d74b..dc10bf40 160000 --- a/goutils +++ b/goutils @@ -1 +1 @@ -Subproject commit cfb8d74b17025a1da5cf9a6f9c9e73c5c558b344 +Subproject commit dc10bf40f9a637da1745c615747c53347c339af5 diff --git a/internal/acl/config.go b/internal/acl/config.go index 16ede527..244bc451 100644 --- a/internal/acl/config.go +++ b/internal/acl/config.go @@ -14,7 +14,6 @@ import ( "github.com/yusing/godoxy/internal/logging/accesslog" "github.com/yusing/godoxy/internal/maxmind" "github.com/yusing/godoxy/internal/notif" - "github.com/yusing/godoxy/internal/utils" gperr "github.com/yusing/goutils/errs" strutils "github.com/yusing/goutils/strings" "github.com/yusing/goutils/task" @@ -82,7 +81,7 @@ var ActiveConfig atomic.Pointer[Config] const cacheTTL = 1 * time.Minute func (c *checkCache) Expired() bool { - return c.created.Add(cacheTTL).Before(utils.TimeNow()) + return c.created.Add(cacheTTL).Before(time.Now()) } // TODO: add stats @@ -180,7 +179,7 @@ func (c *Config) cacheRecord(info *maxmind.IPInfo, allow bool) { c.ipCache.Store(info.Str, &checkCache{ IPInfo: info, allow: allow, - created: utils.TimeNow(), + created: time.Now(), }) } diff --git a/internal/api/v1/file/list.go b/internal/api/v1/file/list.go index 1af12746..86930cfe 100644 --- a/internal/api/v1/file/list.go +++ b/internal/api/v1/file/list.go @@ -6,8 +6,8 @@ import ( "github.com/gin-gonic/gin" "github.com/yusing/godoxy/internal/common" - "github.com/yusing/godoxy/internal/utils" apitypes "github.com/yusing/goutils/apitypes" + "github.com/yusing/goutils/fs" ) type ListFilesResponse struct { @@ -35,7 +35,7 @@ func List(c *gin.Context) { } // config/ - files, err := utils.ListFiles(common.ConfigBasePath, 0, true) + files, err := fs.ListFiles(common.ConfigBasePath, 0, true) if err != nil { c.Error(apitypes.InternalServerError(err, "failed to list files")) return @@ -48,7 +48,7 @@ func List(c *gin.Context) { } // config/middlewares/ - mids, err := utils.ListFiles(common.MiddlewareComposeBasePath, 0, true) + mids, err := fs.ListFiles(common.MiddlewareComposeBasePath, 0, true) if err != nil { c.Error(apitypes.InternalServerError(err, "failed to list files")) return diff --git a/internal/autocert/config.go b/internal/autocert/config.go index c6eafc8d..cad88d37 100644 --- a/internal/autocert/config.go +++ b/internal/autocert/config.go @@ -15,7 +15,6 @@ import ( "github.com/go-acme/lego/v4/lego" "github.com/rs/zerolog/log" "github.com/yusing/godoxy/internal/common" - "github.com/yusing/godoxy/internal/utils" gperr "github.com/yusing/goutils/errs" ) @@ -96,7 +95,7 @@ func (cfg *Config) Validate() gperr.Error { if cfg.Provider != ProviderCustom { b.Add(ErrUnknownProvider. Subject(cfg.Provider). - With(gperr.DoYouMean(utils.NearestField(cfg.Provider, Providers)))) + With(gperr.DoYouMeanField(cfg.Provider, Providers))) } } else { provider, err := providerConstructor(cfg.Options) diff --git a/internal/docker/container.go b/internal/docker/container.go index d0c187ef..6c7fe2ca 100644 --- a/internal/docker/container.go +++ b/internal/docker/container.go @@ -16,7 +16,6 @@ import ( "github.com/yusing/godoxy/agent/pkg/agent" "github.com/yusing/godoxy/internal/serialization" "github.com/yusing/godoxy/internal/types" - "github.com/yusing/godoxy/internal/utils" gperr "github.com/yusing/goutils/errs" ) @@ -223,7 +222,7 @@ func setPrivateHostname(c *types.Container, helper containerHelper) { } } } - nearest := gperr.DoYouMean(utils.NearestField(c.Network, helper.NetworkSettings.Networks)) + nearest := gperr.DoYouMeanField(c.Network, helper.NetworkSettings.Networks) addError(c, fmt.Errorf("network %q not found, %w", c.Network, nearest)) return } diff --git a/internal/homepage/route.go b/internal/homepage/route.go index b4b4a855..9e77759d 100644 --- a/internal/homepage/route.go +++ b/internal/homepage/route.go @@ -4,7 +4,7 @@ import ( "net/http" nettypes "github.com/yusing/godoxy/internal/net/types" - "github.com/yusing/godoxy/internal/utils/pool" + "github.com/yusing/goutils/pool" ) type route interface { diff --git a/internal/logging/accesslog/access_logger_test.go b/internal/logging/accesslog/access_logger_test.go index a4c94a8c..f5ab3dd6 100644 --- a/internal/logging/accesslog/access_logger_test.go +++ b/internal/logging/accesslog/access_logger_test.go @@ -9,7 +9,7 @@ import ( "time" . "github.com/yusing/godoxy/internal/logging/accesslog" - "github.com/yusing/godoxy/internal/utils" + "github.com/yusing/goutils/mockable" "github.com/yusing/goutils/task" expect "github.com/yusing/goutils/testing" ) @@ -57,7 +57,7 @@ func fmtLog(cfg *RequestLoggerConfig) (ts string, line string) { t := time.Now() logger := NewMockAccessLogger(testTask, cfg) - utils.MockTimeNow(t) + mockable.MockTimeNow(t) buf = logger.(RequestFormatter).AppendRequestLog(buf, req, resp) return t.Format(LogTimeFormat), string(buf) } diff --git a/internal/logging/accesslog/formatter.go b/internal/logging/accesslog/formatter.go index 3ca8f7fc..752c6d92 100644 --- a/internal/logging/accesslog/formatter.go +++ b/internal/logging/accesslog/formatter.go @@ -9,7 +9,7 @@ import ( "github.com/rs/zerolog" maxmind "github.com/yusing/godoxy/internal/maxmind/types" - "github.com/yusing/godoxy/internal/utils" + "github.com/yusing/goutils/mockable" ) type ( @@ -67,7 +67,7 @@ func (f *CommonFormatter) AppendRequestLog(line []byte, req *http.Request, res * line = append(line, clientIP(req)...) line = append(line, " - - ["...) - line = utils.TimeNow().AppendFormat(line, LogTimeFormat) + line = mockable.TimeNow().AppendFormat(line, LogTimeFormat) line = append(line, `] "`...) line = append(line, req.Method...) @@ -103,7 +103,7 @@ func (f *JSONFormatter) AppendRequestLog(line []byte, req *http.Request, res *ht writer := bytes.NewBuffer(line) logger := zerolog.New(writer) event := logger.Info(). - Str("time", utils.TimeNow().Format(LogTimeFormat)). + Str("time", mockable.TimeNow().Format(LogTimeFormat)). Str("ip", clientIP(req)). Str("method", req.Method). Str("scheme", scheme(req)). @@ -136,7 +136,7 @@ func (f ACLLogFormatter) AppendACLLog(line []byte, info *maxmind.IPInfo, blocked writer := bytes.NewBuffer(line) logger := zerolog.New(writer) event := logger.Info(). - Str("time", utils.TimeNow().Format(LogTimeFormat)). + Str("time", mockable.TimeNow().Format(LogTimeFormat)). Str("ip", info.Str) if blocked { event.Str("action", "block") diff --git a/internal/logging/accesslog/rotate.go b/internal/logging/accesslog/rotate.go index e3dd4bb1..083443d9 100644 --- a/internal/logging/accesslog/rotate.go +++ b/internal/logging/accesslog/rotate.go @@ -8,8 +8,8 @@ import ( "time" "github.com/rs/zerolog" - "github.com/yusing/godoxy/internal/utils" gperr "github.com/yusing/goutils/errs" + "github.com/yusing/goutils/mockable" strutils "github.com/yusing/goutils/strings" ) @@ -81,14 +81,14 @@ func rotateLogFile(file supportRotate, config *Retention, result *RotateResult) func rotateLogFileByPolicy(file supportRotate, config *Retention, result *RotateResult) (rotated bool, err error) { var shouldStop func() bool - t := utils.TimeNow() + t := mockable.TimeNow() switch { case config.Last > 0: shouldStop = func() bool { return result.NumLinesKeep-result.NumLinesInvalid == int(config.Last) } // not needed to parse time for last N lines case config.Days > 0: - cutoff := utils.TimeNow().AddDate(0, 0, -int(config.Days)+1) + cutoff := mockable.TimeNow().AddDate(0, 0, -int(config.Days)+1) shouldStop = func() bool { return t.Before(cutoff) } default: return false, nil // should not happen diff --git a/internal/logging/accesslog/rotate_test.go b/internal/logging/accesslog/rotate_test.go index 3a56eec1..99dcfca8 100644 --- a/internal/logging/accesslog/rotate_test.go +++ b/internal/logging/accesslog/rotate_test.go @@ -7,7 +7,7 @@ import ( "time" . "github.com/yusing/godoxy/internal/logging/accesslog" - "github.com/yusing/godoxy/internal/utils" + "github.com/yusing/goutils/mockable" strutils "github.com/yusing/goutils/strings" "github.com/yusing/goutils/task" expect "github.com/yusing/goutils/testing" @@ -56,7 +56,7 @@ func TestRotateKeepLast(t *testing.T) { for _, format := range ReqLoggerFormats { t.Run(string(format)+" keep last", func(t *testing.T) { file := NewMockFile(true) - utils.MockTimeNow(testTime) + mockable.MockTimeNow(testTime) logger := NewAccessLoggerWithIO(task.RootTask("test", false), file, &RequestLoggerConfig{ Format: format, }) @@ -93,7 +93,7 @@ func TestRotateKeepLast(t *testing.T) { expect.Nil(t, logger.Config().Retention) nLines := 10 for i := range nLines { - utils.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1)) + mockable.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1)) logger.Log(req, resp) } logger.Flush() @@ -105,7 +105,7 @@ func TestRotateKeepLast(t *testing.T) { expect.Equal(t, retention.KeepSize, 0) logger.Config().Retention = retention - utils.MockTimeNow(testTime) + mockable.MockTimeNow(testTime) var result RotateResult rotated, err := logger.(AccessLogRotater).Rotate(&result) expect.NoError(t, err) @@ -139,7 +139,7 @@ func TestRotateKeepFileSize(t *testing.T) { expect.Nil(t, logger.Config().Retention) nLines := 10 for i := range nLines { - utils.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1)) + mockable.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1)) logger.Log(req, resp) } logger.Flush() @@ -151,7 +151,7 @@ func TestRotateKeepFileSize(t *testing.T) { expect.Equal(t, retention.Last, 0) logger.Config().Retention = retention - utils.MockTimeNow(testTime) + mockable.MockTimeNow(testTime) var result RotateResult rotated, err := logger.(AccessLogRotater).Rotate(&result) expect.NoError(t, err) @@ -171,7 +171,7 @@ func TestRotateKeepFileSize(t *testing.T) { expect.Nil(t, logger.Config().Retention) nLines := 100 for i := range nLines { - utils.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1)) + mockable.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1)) logger.Log(req, resp) } logger.Flush() @@ -183,7 +183,7 @@ func TestRotateKeepFileSize(t *testing.T) { expect.Equal(t, retention.Last, 0) logger.Config().Retention = retention - utils.MockTimeNow(testTime) + mockable.MockTimeNow(testTime) var result RotateResult rotated, err := logger.(AccessLogRotater).Rotate(&result) expect.NoError(t, err) @@ -205,7 +205,7 @@ func TestRotateSkipInvalidTime(t *testing.T) { expect.Nil(t, logger.Config().Retention) nLines := 10 for i := range nLines { - utils.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1)) + mockable.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1)) logger.Log(req, resp) logger.Flush() @@ -248,7 +248,7 @@ func BenchmarkRotate(b *testing.B) { Format: FormatJSON, }) for i := range 100 { - utils.MockTimeNow(testTime.AddDate(0, 0, -100+i+1)) + mockable.MockTimeNow(testTime.AddDate(0, 0, -100+i+1)) logger.Log(req, resp) } logger.Flush() @@ -282,7 +282,7 @@ func BenchmarkRotateWithInvalidTime(b *testing.B) { Format: FormatJSON, }) for i := range 10000 { - utils.MockTimeNow(testTime.AddDate(0, 0, -10000+i+1)) + mockable.MockTimeNow(testTime.AddDate(0, 0, -10000+i+1)) logger.Log(req, resp) if i%10 == 0 { _, _ = file.Write([]byte("invalid time\n")) diff --git a/internal/net/gphttp/loadbalancer/loadbalancer.go b/internal/net/gphttp/loadbalancer/loadbalancer.go index da398e32..688d906a 100644 --- a/internal/net/gphttp/loadbalancer/loadbalancer.go +++ b/internal/net/gphttp/loadbalancer/loadbalancer.go @@ -10,8 +10,8 @@ import ( "github.com/rs/zerolog/log" idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types" "github.com/yusing/godoxy/internal/types" - "github.com/yusing/godoxy/internal/utils/pool" gperr "github.com/yusing/goutils/errs" + "github.com/yusing/goutils/pool" "github.com/yusing/goutils/task" "golang.org/x/sync/errgroup" ) diff --git a/internal/net/gphttp/middleware/captcha/session.go b/internal/net/gphttp/middleware/captcha/session.go index 0a61e23b..e0763903 100644 --- a/internal/net/gphttp/middleware/captcha/session.go +++ b/internal/net/gphttp/middleware/captcha/session.go @@ -8,7 +8,6 @@ import ( _ "embed" "github.com/yusing/godoxy/internal/jsonstore" - "github.com/yusing/godoxy/internal/utils" ) type CaptchaSession struct { @@ -22,7 +21,7 @@ var CaptchaSessions = jsonstore.Store[*CaptchaSession]("captcha_sessions") func newCaptchaSession(p Provider) *CaptchaSession { buf := make([]byte, 32) _, _ = rand.Read(buf) - now := utils.TimeNow() + now := time.Now() return &CaptchaSession{ ID: hex.EncodeToString(buf), Expiry: now.Add(p.SessionExpiry()), @@ -30,5 +29,5 @@ func newCaptchaSession(p Provider) *CaptchaSession { } func (s *CaptchaSession) expired() bool { - return utils.TimeNow().After(s.Expiry) + return time.Now().After(s.Expiry) } diff --git a/internal/net/gphttp/middleware/errorpage/error_page.go b/internal/net/gphttp/middleware/errorpage/error_page.go index 6a576afa..ed4377eb 100644 --- a/internal/net/gphttp/middleware/errorpage/error_page.go +++ b/internal/net/gphttp/middleware/errorpage/error_page.go @@ -9,10 +9,10 @@ import ( "github.com/puzpuzpuz/xsync/v4" "github.com/rs/zerolog/log" "github.com/yusing/godoxy/internal/common" - "github.com/yusing/godoxy/internal/utils" "github.com/yusing/godoxy/internal/watcher" "github.com/yusing/godoxy/internal/watcher/events" gperr "github.com/yusing/goutils/errs" + "github.com/yusing/goutils/fs" "github.com/yusing/goutils/task" ) @@ -46,7 +46,7 @@ func GetErrorPageByStatus(statusCode int) (content []byte, ok bool) { } func loadContent() { - files, err := utils.ListFiles(errPagesBasePath, 0) + files, err := fs.ListFiles(errPagesBasePath, 0) if err != nil { log.Err(err).Msg("failed to list error page resources") return diff --git a/internal/net/gphttp/middleware/middlewares.go b/internal/net/gphttp/middleware/middlewares.go index 571d5e1d..bf0699e4 100644 --- a/internal/net/gphttp/middleware/middlewares.go +++ b/internal/net/gphttp/middleware/middlewares.go @@ -7,8 +7,8 @@ import ( "github.com/rs/zerolog/log" "github.com/yusing/godoxy/internal/common" - "github.com/yusing/godoxy/internal/utils" gperr "github.com/yusing/goutils/errs" + fsutils "github.com/yusing/goutils/fs" strutils "github.com/yusing/goutils/strings" ) @@ -52,7 +52,7 @@ func Get(name string) (*Middleware, Error) { if !ok { return nil, ErrUnknownMiddleware. Subject(name). - With(gperr.DoYouMean(utils.NearestField(name, allMiddlewares))) + With(gperr.DoYouMeanField(name, allMiddlewares)) } return middleware, nil } @@ -63,7 +63,7 @@ func All() map[string]*Middleware { func LoadComposeFiles() { errs := gperr.NewBuilder("middleware compile errors") - middlewareDefs, err := utils.ListFiles(common.MiddlewareComposeBasePath, 0) + middlewareDefs, err := fsutils.ListFiles(common.MiddlewareComposeBasePath, 0) if err != nil { if errors.Is(err, fs.ErrNotExist) { return diff --git a/internal/net/types/url.go b/internal/net/types/url.go index dcc1ebaf..bddc6b07 100644 --- a/internal/net/types/url.go +++ b/internal/net/types/url.go @@ -4,11 +4,9 @@ import ( urlPkg "net/url" "github.com/bytedance/sonic" - "github.com/yusing/godoxy/internal/utils" ) type URL struct { - _ utils.NoCopy urlPkg.URL } diff --git a/internal/proxmox/node.go b/internal/proxmox/node.go index 03948e88..1772c836 100644 --- a/internal/proxmox/node.go +++ b/internal/proxmox/node.go @@ -7,7 +7,7 @@ import ( "github.com/bytedance/sonic" "github.com/luthermonson/go-proxmox" - "github.com/yusing/godoxy/internal/utils/pool" + "github.com/yusing/goutils/pool" ) type Node struct { diff --git a/internal/route/routes/routes.go b/internal/route/routes/routes.go index 51edd7f6..4c325124 100644 --- a/internal/route/routes/routes.go +++ b/internal/route/routes/routes.go @@ -2,7 +2,7 @@ package routes import ( "github.com/yusing/godoxy/internal/types" - "github.com/yusing/godoxy/internal/utils/pool" + "github.com/yusing/goutils/pool" ) var ( diff --git a/internal/serialization/serialization.go b/internal/serialization/serialization.go index cdcbe82a..4b1835f6 100644 --- a/internal/serialization/serialization.go +++ b/internal/serialization/serialization.go @@ -13,7 +13,6 @@ import ( "github.com/go-playground/validator/v10" "github.com/goccy/go-yaml" "github.com/puzpuzpuz/xsync/v4" - "github.com/yusing/godoxy/internal/utils" gi "github.com/yusing/gointernals" "github.com/yusing/goutils/env" gperr "github.com/yusing/goutils/errs" @@ -300,7 +299,7 @@ func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidat errs.Add(err.Subject(k)) } } else { - errs.Add(ErrUnknownField.Subject(k).With(gperr.DoYouMean(utils.NearestField(k, info.fieldNames)))) + errs.Add(ErrUnknownField.Subject(k).With(gperr.DoYouMeanField(k, info.fieldNames))) } } if info.hasValidateTag && checkValidateTag { diff --git a/internal/types/docker.go b/internal/types/docker.go index 89b32e11..54cfe543 100644 --- a/internal/types/docker.go +++ b/internal/types/docker.go @@ -5,7 +5,6 @@ import ( "github.com/docker/docker/api/types/container" "github.com/yusing/ds/ordered" "github.com/yusing/godoxy/agent/pkg/agent" - "github.com/yusing/godoxy/internal/utils" gperr "github.com/yusing/goutils/errs" ) @@ -14,8 +13,6 @@ type ( PortMapping = map[int]container.Port Container struct { - _ utils.NoCopy - DockerHost string `json:"docker_host"` Image *ContainerImage `json:"image"` ContainerName string `json:"container_name"` diff --git a/internal/types/routes.go b/internal/types/routes.go index 7dfa680d..1609c490 100644 --- a/internal/types/routes.go +++ b/internal/types/routes.go @@ -7,9 +7,9 @@ import ( "github.com/yusing/godoxy/internal/homepage" nettypes "github.com/yusing/godoxy/internal/net/types" provider "github.com/yusing/godoxy/internal/route/provider/types" - "github.com/yusing/godoxy/internal/utils/pool" gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/http/reverseproxy" + "github.com/yusing/goutils/pool" "github.com/yusing/goutils/task" ) diff --git a/internal/utils/fs.go b/internal/utils/fs.go deleted file mode 100644 index 41029014..00000000 --- a/internal/utils/fs.go +++ /dev/null @@ -1,36 +0,0 @@ -package utils - -import ( - "fmt" - "os" - "path" -) - -// Recursively lists all files in a directory until `maxDepth` is reached -// Returns a slice of file paths relative to `dir`. -func ListFiles(dir string, maxDepth int, hideHidden ...bool) ([]string, error) { - entries, err := os.ReadDir(dir) - if err != nil { - return nil, fmt.Errorf("error listing directory %s: %w", dir, err) - } - hideHiddenFiles := len(hideHidden) > 0 && hideHidden[0] - files := make([]string, 0) - for _, entry := range entries { - if hideHiddenFiles && entry.Name()[0] == '.' { - continue - } - if entry.IsDir() { - if maxDepth <= 0 { - continue - } - subEntries, err := ListFiles(path.Join(dir, entry.Name()), maxDepth-1) - if err != nil { - return nil, err - } - files = append(files, subEntries...) - } else { - files = append(files, path.Join(dir, entry.Name())) - } - } - return files, nil -} diff --git a/internal/utils/nearest_field.go b/internal/utils/nearest_field.go deleted file mode 100644 index a44e7ec2..00000000 --- a/internal/utils/nearest_field.go +++ /dev/null @@ -1,50 +0,0 @@ -package utils - -import ( - "reflect" - - strutils "github.com/yusing/goutils/strings" -) - -func NearestField(input string, s any) string { - minDistance := -1 - nearestField := "" - var fields []string - switch s := s.(type) { - case []string: - fields = s - default: - t := reflect.TypeOf(s) - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - switch t.Kind() { - case reflect.Struct: - fields = make([]string, 0) - for i := range t.NumField() { - jsonTag, ok := t.Field(i).Tag.Lookup("json") - if ok { - fields = append(fields, jsonTag) - } else { - fields = append(fields, t.Field(i).Name) - } - } - case reflect.Map: - keys := reflect.ValueOf(s).MapKeys() - fields = make([]string, len(keys)) - for i, key := range keys { - fields[i] = key.String() - } - default: - panic("NearestField unsupported type: " + t.String()) - } - } - for _, field := range fields { - distance := strutils.LevenshteinDistance(input, field) - if minDistance == -1 || distance < minDistance { - minDistance = distance - nearestField = field - } - } - return nearestField -} diff --git a/internal/utils/pool/pool.go b/internal/utils/pool/pool.go deleted file mode 100644 index 51cbb467..00000000 --- a/internal/utils/pool/pool.go +++ /dev/null @@ -1,123 +0,0 @@ -package pool - -import ( - "sort" - "sync/atomic" - - "github.com/puzpuzpuz/xsync/v4" - "github.com/rs/zerolog/log" -) - -type ( - Pool[T Object] struct { - m *xsync.Map[string, T] - name string - disableLog atomic.Bool - } - // Preferable allows an object to express deterministic replacement preference - // when multiple objects with the same key are added to the pool. - // If new.PreferOver(old) returns true, the new object replaces the old one. - Preferable interface { - PreferOver(other any) bool - } - Object interface { - Key() string - Name() string - } - ObjectWithDisplayName interface { - Object - DisplayName() string - } -) - -func New[T Object](name string) Pool[T] { - return Pool[T]{m: xsync.NewMap[string, T](), name: name} -} - -func (p *Pool[T]) ToggleLog(v bool) { - p.disableLog.Store(v) -} - -func (p *Pool[T]) Name() string { - return p.name -} - -func (p *Pool[T]) Add(obj T) { - p.AddKey(obj.Key(), obj) -} - -func (p *Pool[T]) AddKey(key string, obj T) { - if cur, exists := p.m.Load(key); exists { - if newPref, ok := any(obj).(Preferable); ok { - if !newPref.PreferOver(cur) { - // keep existing - return - } - } - } - p.checkExists(key) - p.m.Store(key, obj) - p.logAction("added", obj) -} - -func (p *Pool[T]) AddIfNotExists(obj T) (actual T, added bool) { - actual, loaded := p.m.LoadOrStore(obj.Key(), obj) - if !loaded { - p.logAction("added", obj) - } - return actual, !loaded -} - -func (p *Pool[T]) Del(obj T) { - p.m.Delete(obj.Key()) - p.logAction("removed", obj) -} - -func (p *Pool[T]) DelKey(key string) { - if v, exists := p.m.LoadAndDelete(key); exists { - p.logAction("removed", v) - } -} - -func (p *Pool[T]) Get(key string) (T, bool) { - return p.m.Load(key) -} - -func (p *Pool[T]) Size() int { - return p.m.Size() -} - -func (p *Pool[T]) Clear() { - p.m.Clear() -} - -func (p *Pool[T]) Iter(fn func(k string, v T) bool) { - p.m.Range(fn) -} - -func (p *Pool[T]) Slice() []T { - slice := make([]T, 0, p.m.Size()) - for _, v := range p.m.Range { - slice = append(slice, v) - } - sort.Slice(slice, func(i, j int) bool { - return slice[i].Name() < slice[j].Name() - }) - return slice -} - -func (p *Pool[T]) logAction(action string, obj T) { - if p.disableLog.Load() { - return - } - if withName, ok := any(obj).(ObjectWithDisplayName); ok { - disp, name := withName.DisplayName(), withName.Name() - if disp != name { - log.Info().Msgf("%s: %s %s (%s)", p.name, action, disp, name) - } else { - log.Info().Msgf("%s: %s %s", p.name, action, name) - } - } else { - log.Info().Msgf("%s: %s %s", p.name, action, obj.Name()) - } -} diff --git a/internal/utils/pool/pool_debug.go b/internal/utils/pool/pool_debug.go deleted file mode 100644 index bb444dbc..00000000 --- a/internal/utils/pool/pool_debug.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build debug - -package pool - -import ( - "runtime/debug" - - "github.com/rs/zerolog/log" -) - -func (p Pool[T]) checkExists(key string) { - if _, ok := p.m.Load(key); ok { - log.Warn().Msgf("%s: key %s already exists\nstacktrace: %s", p.name, key, string(debug.Stack())) - } -} diff --git a/internal/utils/pool/pool_prod.go b/internal/utils/pool/pool_prod.go deleted file mode 100644 index 5a5108ae..00000000 --- a/internal/utils/pool/pool_prod.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !debug - -package pool - -func (p Pool[T]) checkExists(key string) { - // no-op in production -} diff --git a/internal/utils/time_now.go b/internal/utils/time_now.go deleted file mode 100644 index d33aa91d..00000000 --- a/internal/utils/time_now.go +++ /dev/null @@ -1,44 +0,0 @@ -package utils - -import ( - "time" - - "go.uber.org/atomic" -) - -var ( - TimeNow = DefaultTimeNow - shouldCallTimeNow atomic.Bool - timeNowTicker = time.NewTicker(shouldCallTimeNowInterval) - lastTimeNow = atomic.NewTime(time.Now()) -) - -const shouldCallTimeNowInterval = 100 * time.Millisecond - -func MockTimeNow(t time.Time) { - TimeNow = func() time.Time { - return t - } -} - -// DefaultTimeNow is a time.Now wrapper that reduces the number of calls to time.Now -// by caching the result and only allow calling time.Now when the ticker fires. -// -// Returned value may have +-100ms error. -func DefaultTimeNow() time.Time { - swapped := shouldCallTimeNow.CompareAndSwap(false, true) - if swapped { // first call - now := time.Now() - lastTimeNow.Store(now) - return now - } - return lastTimeNow.Load() -} - -func init() { - go func() { - for range timeNowTicker.C { - shouldCallTimeNow.Store(true) - } - }() -} diff --git a/internal/utils/time_now_test.go b/internal/utils/time_now_test.go deleted file mode 100644 index 3607c16f..00000000 --- a/internal/utils/time_now_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package utils - -import ( - "testing" - "time" -) - -var sink time.Time - -func BenchmarkTimeNow(b *testing.B) { - b.Run("default", func(b *testing.B) { - for b.Loop() { - sink = time.Now() - } - }) - - b.Run("reduced_call", func(b *testing.B) { - for b.Loop() { - sink = DefaultTimeNow() - } - }) -} - -func TestDefaultTimeNow(t *testing.T) { - // Get initial time - t1 := DefaultTimeNow() - - // Second call should return the same time without calling time.Now - t2 := DefaultTimeNow() - - if !t1.Equal(t2) { - t.Errorf("Expected t1 == t2, got t1 = %v, t2 = %v", t1, t2) - } - - // Set shouldCallTimeNow to true - shouldCallTimeNow.Store(true) - - // This should update the lastTimeNow - t3 := DefaultTimeNow() - - // The time should have changed - if t2.Equal(t3) { - t.Errorf("Expected t2 != t3, got t2 = %v, t3 = %v", t2, t3) - } - - // Fourth call should return the same time as third call - t4 := DefaultTimeNow() - - if !t3.Equal(t4) { - t.Errorf("Expected t3 == t4, got t3 = %v, t4 = %v", t3, t4) - } -} - -func TestMockTimeNow(t *testing.T) { - // Save the original TimeNow function to restore later - originalTimeNow := TimeNow - defer func() { - TimeNow = originalTimeNow - }() - - // Create a fixed time - fixedTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) - - // Mock the time - MockTimeNow(fixedTime) - - // TimeNow should return the fixed time - result := TimeNow() - - if !result.Equal(fixedTime) { - t.Errorf("Expected %v, got %v", fixedTime, result) - } -} - -func TestTimeNowTicker(t *testing.T) { - // This test verifies that the ticker properly updates shouldCallTimeNow - - // Reset the flag - shouldCallTimeNow.Store(false) - - // Wait for the ticker to tick (slightly more than the interval) - time.Sleep(shouldCallTimeNowInterval + 10*time.Millisecond) - - // The ticker should have set shouldCallTimeNow to true - if !shouldCallTimeNow.Load() { - t.Error("Expected shouldCallTimeNow to be true after ticker interval") - } - - // Call DefaultTimeNow which should reset the flag - DefaultTimeNow() - - // Check that the flag is reset - if shouldCallTimeNow.Load() { - t.Error("Expected shouldCallTimeNow to be false after calling DefaultTimeNow") - } -} - -/* -BenchmarkTimeNow -BenchmarkTimeNow/default -BenchmarkTimeNow/default-20 48158628 24.86 ns/op 0 B/op 0 allocs/op -BenchmarkTimeNow/reduced_call -BenchmarkTimeNow/reduced_call-20 1000000000 1.000 ns/op 0 B/op 0 allocs/op -*/