From bb868e36f4a00c0e6513c5a83177ec55c887c303 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Tue, 17 Feb 2026 17:29:46 +0100 Subject: [PATCH 01/11] feat: enable modernize linter newexpr --- .golangci.yml | 23 ++++++++++++++++++++++- net/gotsrpc/decode.go | 9 --------- net/gotsrpc/errors.go | 39 --------------------------------------- 3 files changed, 22 insertions(+), 49 deletions(-) delete mode 100644 net/gotsrpc/decode.go delete mode 100644 net/gotsrpc/errors.go diff --git a/.golangci.yml b/.golangci.yml index de38795..ab834df 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -11,7 +11,6 @@ linters: disable: # Project specific linters - containedctx # containedctx is a linter that detects struct contained context.Context field [fast: false, auto-fix: false] - - modernize # Discouraged linters - noinlineerr # Disallows inline error handling (`if err := ...; err != nil {`). - embeddedstructfieldcheck # Embedded types should be at the top of the field list of a struct, and there must be an empty line separating embedded fields from regular fields. [fast] @@ -54,6 +53,28 @@ linters: # https://golangci-lint.run/docs/linters/ settings: + modernize: + # List of analyzers to disable. + # By default, all analyzers are enabled. + disable: + - any + - fmtappendf + - forvar + - mapsloop + - minmax + - omitzero + - plusbuild + - reflecttypefor + - slicescontains + - slicessort + - stditerators + - stringscut + - stringscutprefix + - stringsseq + - stringsbuilder + - testingcontext + - unsafefuncs + - waitgroup exhaustive: default-signifies-exhaustive: true gocritic: diff --git a/net/gotsrpc/decode.go b/net/gotsrpc/decode.go deleted file mode 100644 index 4eac447..0000000 --- a/net/gotsrpc/decode.go +++ /dev/null @@ -1,9 +0,0 @@ -package keelgotsrpc - -import "github.com/mitchellh/mapstructure" - -// Decode decodes the custom data into the given pointer to a map or struct. -// Deprecated: use mapstructure.Decode instead. -func Decode(data interface{}, v interface{}) error { - return mapstructure.Decode(data, v) -} diff --git a/net/gotsrpc/errors.go b/net/gotsrpc/errors.go deleted file mode 100644 index efa27f1..0000000 --- a/net/gotsrpc/errors.go +++ /dev/null @@ -1,39 +0,0 @@ -package keelgotsrpc - -// Error type -// Deprecated: use standard errors instead -type Error string - -// Common errors -const ( - // Deprecated: use standard errors instead - ErrorNotFound Error = "notFound" - // Deprecated: use standard errors instead - ErrorForbidden Error = "forbidden" - // Deprecated: use standard errors instead - ErrorPermissionDenied Error = "permissionDenied" -) - -// NewError returns a pointer error -// Deprecated: use standard errors instead -func NewError(e Error) *Error { - return &e -} - -// Is interface -// Deprecated: use standard errors instead -func (e *Error) Is(err error) bool { - if e == nil || err == nil { - return false - } else if v, ok := err.(*Error); ok && v != nil { - return e.Error() == v.Error() - } - - return false -} - -// Error interface -// Deprecated: use standard errors instead -func (e *Error) Error() string { - return string(*e) -} From f2b76848663627483b6af75f888a60c33b48f4a2 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Tue, 17 Feb 2026 17:31:44 +0100 Subject: [PATCH 02/11] feat: enable modernize linter reflecttypefor --- .golangci.yml | 7 ------- examples/persistence/mongo/store/codec.go | 2 +- server.go | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index ab834df..d4532eb 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -58,13 +58,6 @@ linters: # By default, all analyzers are enabled. disable: - any - - fmtappendf - - forvar - - mapsloop - - minmax - - omitzero - - plusbuild - - reflecttypefor - slicescontains - slicessort - stditerators diff --git a/examples/persistence/mongo/store/codec.go b/examples/persistence/mongo/store/codec.go index cfc8fe2..858ef55 100644 --- a/examples/persistence/mongo/store/codec.go +++ b/examples/persistence/mongo/store/codec.go @@ -12,7 +12,7 @@ import ( ) var ( - TDateTime = reflect.TypeOf(DateTime("")) + TDateTime = reflect.TypeFor[DateTime]() ) type DateTimeCodec struct{} diff --git a/server.go b/server.go index c1f5e7e..43a7b45 100644 --- a/server.go +++ b/server.go @@ -524,7 +524,7 @@ func (s *Server) readmeServices() string { for _, value := range s.initServices { if v, ok := value.(*service.HTTP); ok { - t := reflect.TypeOf(v) + t := reflect.TypeFor[*service.HTTP]() rows = append(rows, []string{ markdown.Code(v.Name()), markdown.Code(t.String()), From b21b75da2eec65fed682256a288925845e50f0ec Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Tue, 17 Feb 2026 17:33:10 +0100 Subject: [PATCH 03/11] feat: enable modernize linter slicescontains --- .golangci.yml | 1 - keeltest/server.go | 7 +++---- persistence/mongo/persistor.go | 7 +++---- server.go | 6 ++---- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index d4532eb..e94c6d6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -58,7 +58,6 @@ linters: # By default, all analyzers are enabled. disable: - any - - slicescontains - slicessort - stditerators - stringscut diff --git a/keeltest/server.go b/keeltest/server.go index 6f4803c..533f561 100644 --- a/keeltest/server.go +++ b/keeltest/server.go @@ -2,6 +2,7 @@ package keeltest import ( "context" + "slices" "github.com/spf13/viper" "go.opentelemetry.io/otel/metric" @@ -82,10 +83,8 @@ func (s *Server) AddServices(services ...Service) { // AddService add a single service func (s *Server) AddService(service Service) { - for _, value := range s.services { - if value == service { - return - } + if slices.Contains(s.services, service) { + return } s.services = append(s.services, service) diff --git a/persistence/mongo/persistor.go b/persistence/mongo/persistor.go index 7e1a6b9..d8a7b22 100644 --- a/persistence/mongo/persistor.go +++ b/persistence/mongo/persistor.go @@ -2,6 +2,7 @@ package keelmongo import ( "context" + "slices" "github.com/foomo/keel/env" "github.com/go-logr/zapr" @@ -188,10 +189,8 @@ func (p Persistor) HasCollection(ctx context.Context, name string) (bool, error) return false, err } - for i := range names { - if names[i] == name { - return true, nil - } + if slices.Contains(names, name) { + return true, nil } return false, nil diff --git a/server.go b/server.go index 43a7b45..dc5fe73 100644 --- a/server.go +++ b/server.go @@ -240,10 +240,8 @@ func (s *Server) AddServices(services ...Service) { // AddCloser adds a closer to be called on shutdown func (s *Server) AddCloser(closer interface{}) { - for _, value := range s.closers() { - if value == closer { - return - } + if slices.Contains(s.closers(), closer) { + return } if IsCloser(closer) { From 170554ebcb2eaf5cb915744494edffac474b84a1 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Tue, 17 Feb 2026 17:39:25 +0100 Subject: [PATCH 04/11] feat: enable modernize linter stringsseq --- .golangci.yml | 8 -------- config/config.go | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index e94c6d6..1c2ce1d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -58,15 +58,7 @@ linters: # By default, all analyzers are enabled. disable: - any - - slicessort - - stditerators - stringscut - - stringscutprefix - - stringsseq - - stringsbuilder - - testingcontext - - unsafefuncs - - waitgroup exhaustive: default-signifies-exhaustive: true gocritic: diff --git a/config/config.go b/config/config.go index ff1e6de..3808ecc 100644 --- a/config/config.go +++ b/config/config.go @@ -311,7 +311,7 @@ func GetStruct(c *viper.Viper, key string, fallback interface{}) (func(v interfa return err } - for _, keyPart := range strings.Split(key, ".") { + for keyPart := range strings.SplitSeq(key, ".") { if cfgPart, ok := cfg[keyPart]; ok { if o, ok := cfgPart.(map[string]interface{}); ok { cfg = o From 60c79fa94a283bac2c3a447c4f23f5e9b2d3cd50 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Tue, 17 Feb 2026 17:54:27 +0100 Subject: [PATCH 05/11] feat: enable modernize linter any --- .golangci.yml | 12 +++++------- config/config.go | 26 +++++++++++++------------- config/readme.go | 2 +- env/env.go | 10 +++++----- integration/temporal/error.go | 2 +- integration/temporal/log.go | 2 +- integration/temporal/logger.go | 12 ++++++------ internal/otel/logger.go | 8 ++++---- internal/pyroscope/logger.go | 6 +++--- keeltest/assert/assert.go | 4 ++-- keeltest/assert/assertions.go | 4 ++-- keeltest/client.go | 2 +- keeltest/inline.go | 4 ++-- keeltest/require/assertions.go | 4 ++-- keeltest/require/require.go | 4 ++-- log/fields.go | 4 ++-- log/logger.go | 2 +- net/http/middleware/cors.go | 24 ++++++++++++------------ net/http/middleware/gzip.go | 2 +- net/http/middleware/jwt.go | 4 ++-- net/http/roundtripware/gzip.go | 2 +- net/stream/jetstream/publisher.go | 10 +++++----- net/stream/jetstream/subscriber.go | 4 ++-- persistence/mongo/collection.go | 16 ++++++++-------- server.go | 30 +++++++++++++++--------------- service/httphealthz.go | 12 ++++++------ service/httpviper.go | 4 ++-- service/httpzap.go | 2 ++ 28 files changed, 109 insertions(+), 109 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 1c2ce1d..a36d926 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -53,24 +53,23 @@ linters: # https://golangci-lint.run/docs/linters/ settings: - modernize: - # List of analyzers to disable. - # By default, all analyzers are enabled. - disable: - - any - - stringscut + # https://golangci-lint.run/docs/linters/configuration/#exhaustive exhaustive: default-signifies-exhaustive: true + # https://golangci-lint.run/docs/linters/configuration/#gocritic gocritic: disabled-checks: - assignOp - ifElseChain - deprecatedComment + # https://golangci-lint.run/docs/linters/configuration/#gomoddirectives gomoddirectives: replace-local: true + # https://golangci-lint.run/docs/linters/configuration/#gosec gosec: severity: medium confidence: medium + # https://golangci-lint.run/docs/linters/configuration/#revive revive: rules: - name: exported @@ -78,7 +77,6 @@ linters: - name: package-comments disabled: true exclusions: - generated: lax presets: - comments - common-false-positives diff --git a/config/config.go b/config/config.go index 3808ecc..9939062 100644 --- a/config/config.go +++ b/config/config.go @@ -13,7 +13,7 @@ import ( var ( config *viper.Viper requiredKeys []string - defaults = map[string]interface{}{} + defaults = map[string]any{} types = map[string]string{} ) @@ -238,18 +238,18 @@ func MustGetStringSlice(c *viper.Viper, key string) func() []string { } } -func GetStringMap(c *viper.Viper, key string, fallback map[string]interface{}) func() map[string]interface{} { +func GetStringMap(c *viper.Viper, key string, fallback map[string]any) func() map[string]any { setDefault(c, key, "map[string]interface{}", fallback) - return func() map[string]interface{} { + return func() map[string]any { return c.GetStringMap(key) } } -func MustGetStringMap(c *viper.Viper, key string) func() map[string]interface{} { +func MustGetStringMap(c *viper.Viper, key string) func() map[string]any { must(c, key, "map[string]interface{}") - return func() map[string]interface{} { + return func() map[string]any { return c.GetStringMap(key) } } @@ -286,17 +286,17 @@ func MustGetStringMapStringSlice(c *viper.Viper, key string) func() map[string][ } } -func GetStruct(c *viper.Viper, key string, fallback interface{}) (func(v interface{}) error, error) { +func GetStruct(c *viper.Viper, key string, fallback any) (func(v any) error, error) { c = ensure(c) // decode default - var decoded map[string]interface{} + var decoded map[string]any if err := decode(fallback, &decoded); err != nil { return nil, err } // prefix key - configMap := make(map[string]interface{}, len(decoded)) + configMap := make(map[string]any, len(decoded)) for s, i := range decoded { configMap[key+"."+s] = i } @@ -305,15 +305,15 @@ func GetStruct(c *viper.Viper, key string, fallback interface{}) (func(v interfa return nil, err } - return func(v interface{}) error { - var cfg map[string]interface{} + return func(v any) error { + var cfg map[string]any if err := c.Unmarshal(&cfg); err != nil { return err } for keyPart := range strings.SplitSeq(key, ".") { if cfgPart, ok := cfg[keyPart]; ok { - if o, ok := cfgPart.(map[string]interface{}); ok { + if o, ok := cfgPart.(map[string]any); ok { cfg = o } } @@ -327,7 +327,7 @@ func RequiredKeys() []string { return requiredKeys } -func Defaults() map[string]interface{} { +func Defaults() map[string]any { return defaults } @@ -361,7 +361,7 @@ func must(c *viper.Viper, key, typeof string) { } } -func decode(input, output interface{}) error { +func decode(input, output any) error { decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ TagName: "yaml", Result: output, diff --git a/config/readme.go b/config/readme.go index 79290ca..bb557f5 100644 --- a/config/readme.go +++ b/config/readme.go @@ -18,7 +18,7 @@ func Readme() string { { keys := c.AllKeys() for _, key := range keys { - var fallback interface{} + var fallback any if v, ok := defaults[key]; ok { fallback = v } diff --git a/env/env.go b/env/env.go index 89c3566..20b7130 100644 --- a/env/env.go +++ b/env/env.go @@ -179,7 +179,7 @@ func MustGetGetIntSlice(key string) []int { func RequiredKeys() []string { var ret []string - requiredKeys.Range(func(key, value interface{}) bool { + requiredKeys.Range(func(key, value any) bool { if v, ok := key.(string); ok { ret = append(ret, v) } @@ -190,10 +190,10 @@ func RequiredKeys() []string { return ret } -func Defaults() map[string]interface{} { - ret := map[string]interface{}{} +func Defaults() map[string]any { + ret := map[string]any{} - defaults.Range(func(key, value interface{}) bool { + defaults.Range(func(key, value any) bool { if k, ok := key.(string); ok { ret[k] = value } @@ -207,7 +207,7 @@ func Defaults() map[string]interface{} { func Types() map[string]string { ret := map[string]string{} - types.Range(func(key, value interface{}) bool { + types.Range(func(key, value any) bool { if v, ok := value.(string); ok { if k, ok := key.(string); ok { ret[k] = v diff --git a/integration/temporal/error.go b/integration/temporal/error.go index 541ab8b..9b9a6f2 100644 --- a/integration/temporal/error.go +++ b/integration/temporal/error.go @@ -9,7 +9,7 @@ import ( const ActivityErrorType = "keeltemporal.ActivityError" -func NewActivityError(msg string, err error, details ...interface{}) error { +func NewActivityError(msg string, err error, details ...any) error { return temporal.NewNonRetryableApplicationError(msg, ActivityErrorType, err, details...) } diff --git a/integration/temporal/log.go b/integration/temporal/log.go index 4f34555..d118d97 100644 --- a/integration/temporal/log.go +++ b/integration/temporal/log.go @@ -36,7 +36,7 @@ func GetActivityLogger(ctx context.Context, fields ...zap.Field) tlog.Logger { } func LoggerWith(l tlog.Logger, fields ...zap.Field) tlog.Logger { - v := make([]interface{}, len(fields)) + v := make([]any, len(fields)) for i, field := range fields { v[i] = field } diff --git a/integration/temporal/logger.go b/integration/temporal/logger.go index 8370b05..30d10a8 100644 --- a/integration/temporal/logger.go +++ b/integration/temporal/logger.go @@ -17,11 +17,11 @@ func NewLogger(l *zap.Logger) *logger { } } -func (t *logger) Debug(msg string, keyvals ...interface{}) { +func (t *logger) Debug(msg string, keyvals ...any) { t.l.Debug(msg, t.fields(keyvals...)...) } -func (t *logger) Info(msg string, keyvals ...interface{}) { +func (t *logger) Info(msg string, keyvals ...any) { // TODO check with temporal why errors are being logged as info! for _, keyval := range keyvals { if keyval == "Error" { @@ -33,19 +33,19 @@ func (t *logger) Info(msg string, keyvals ...interface{}) { t.l.Info(msg, t.fields(keyvals...)...) } -func (t *logger) Warn(msg string, keyvals ...interface{}) { +func (t *logger) Warn(msg string, keyvals ...any) { t.l.Warn(msg, t.fields(keyvals...)...) } -func (t *logger) Error(msg string, keyvals ...interface{}) { +func (t *logger) Error(msg string, keyvals ...any) { t.l.Error(msg, t.fields(keyvals...)...) } -func (t *logger) With(keyvals ...interface{}) *logger { +func (t *logger) With(keyvals ...any) *logger { return NewLogger(t.l.With(t.fields(keyvals...)...)) } -func (t *logger) fields(keyvals ...interface{}) []zap.Field { +func (t *logger) fields(keyvals ...any) []zap.Field { var fields []zap.Field for i := 0; i < len(keyvals); i++ { diff --git a/internal/otel/logger.go b/internal/otel/logger.go index e2e948b..706cb33 100644 --- a/internal/otel/logger.go +++ b/internal/otel/logger.go @@ -25,15 +25,15 @@ func (l Logger) Enabled(level int) bool { return log.AtomicLevel().Enabled(zapcore.Level(-1 * level)) //nolint:gosec } -func (l Logger) Info(level int, msg string, keysAndValues ...interface{}) { +func (l Logger) Info(level int, msg string, keysAndValues ...any) { l.l.Info(msg, l.fields(keysAndValues)...) } -func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) { +func (l Logger) Error(err error, msg string, keysAndValues ...any) { l.l.Error(msg, l.fields(keysAndValues)...) } -func (l Logger) WithValues(keysAndValues ...interface{}) logr.LogSink { +func (l Logger) WithValues(keysAndValues ...any) logr.LogSink { return NewLogger(l.l.With(l.fields(keysAndValues)...)) } @@ -41,7 +41,7 @@ func (l Logger) WithName(name string) logr.LogSink { return NewLogger(l.l.Named(name)) } -func (l Logger) fields(keysAndValues []interface{}) []zap.Field { +func (l Logger) fields(keysAndValues []any) []zap.Field { ret := make([]zap.Field, 0, len(keysAndValues)/2) for i := 0; i < len(keysAndValues); i += 2 { ret = append(ret, zap.Any(fmt.Sprintf("%v", keysAndValues[i]), keysAndValues[i+1])) diff --git a/internal/pyroscope/logger.go b/internal/pyroscope/logger.go index 2063fbb..e85cc96 100644 --- a/internal/pyroscope/logger.go +++ b/internal/pyroscope/logger.go @@ -14,14 +14,14 @@ func NewLogger(l logr.Logger) *Logger { return &Logger{l: l} } -func (l *Logger) Infof(format string, a ...interface{}) { +func (l *Logger) Infof(format string, a ...any) { l.l.V(3).Info("[Info] " + fmt.Sprintf(format, a...)) } -func (l *Logger) Debugf(format string, a ...interface{}) { +func (l *Logger) Debugf(format string, a ...any) { l.l.V(4).Info("[Debug] " + fmt.Sprintf(format, a...)) } -func (l *Logger) Errorf(format string, a ...interface{}) { +func (l *Logger) Errorf(format string, a ...any) { l.l.V(0).Info("[Error] " + fmt.Sprintf(format, a...)) } diff --git a/keeltest/assert/assert.go b/keeltest/assert/assert.go index 08333b5..d4e7caf 100644 --- a/keeltest/assert/assert.go +++ b/keeltest/assert/assert.go @@ -11,7 +11,7 @@ import ( "github.com/tidwall/pretty" ) -func InlineEqual(t *testing.T, actual interface{}, msgAndArgs ...interface{}) bool { +func InlineEqual(t *testing.T, actual any, msgAndArgs ...any) bool { t.Helper() expected, ok := keeltest.Inline(t, 2, "%v", actual) @@ -22,7 +22,7 @@ func InlineEqual(t *testing.T, actual interface{}, msgAndArgs ...interface{}) bo } } -func InlineJSONEq(t *testing.T, actual interface{}, msgAndArgs ...interface{}) bool { +func InlineJSONEq(t *testing.T, actual any, msgAndArgs ...any) bool { t.Helper() // marshal value actualBytes, err := json.Marshal(actual) diff --git a/keeltest/assert/assertions.go b/keeltest/assert/assertions.go index 790574d..b918df0 100644 --- a/keeltest/assert/assertions.go +++ b/keeltest/assert/assertions.go @@ -24,7 +24,7 @@ func New(t *testing.T) *Assertions { //nolint:thelper } } -func (a *Assertions) InlineEqual(actual interface{}, msgAndArgs ...interface{}) bool { +func (a *Assertions) InlineEqual(actual any, msgAndArgs ...any) bool { a.t.Helper() expected, ok := keeltest.Inline(a.t, 2, "%v", actual) @@ -35,7 +35,7 @@ func (a *Assertions) InlineEqual(actual interface{}, msgAndArgs ...interface{}) } } -func (a *Assertions) InlineJSONEq(actual interface{}, msgAndArgs ...interface{}) bool { +func (a *Assertions) InlineJSONEq(actual any, msgAndArgs ...any) bool { a.t.Helper() // marshal value actualBytes, err := json.Marshal(actual) diff --git a/keeltest/client.go b/keeltest/client.go index 755c2b5..b27b306 100644 --- a/keeltest/client.go +++ b/keeltest/client.go @@ -54,7 +54,7 @@ func (c *HTTPClient) Get(ctx context.Context, path string) ([]byte, int, error) } } -func (c *HTTPClient) Post(ctx context.Context, path string, data interface{}) ([]byte, int, error) { +func (c *HTTPClient) Post(ctx context.Context, path string, data any) ([]byte, int, error) { var req *http.Request if v, err := json.Marshal(data); err != nil { diff --git a/keeltest/inline.go b/keeltest/inline.go index ce06a3f..3fe842b 100644 --- a/keeltest/inline.go +++ b/keeltest/inline.go @@ -12,7 +12,7 @@ import ( "github.com/foomo/keel/log" ) -func Inline(t *testing.T, skip int, msgAndArgs ...interface{}) (string, bool) { +func Inline(t *testing.T, skip int, msgAndArgs ...any) (string, bool) { t.Helper() // retrieve caller info @@ -83,7 +83,7 @@ func InlineFloat64(t *testing.T, skip int) (float64, bool) { } } -func InlineJSON(t *testing.T, skip int, target interface{}) { +func InlineJSON(t *testing.T, skip int, target any) { t.Helper() if inline, ok := Inline(t, skip+1); ok { diff --git a/keeltest/require/assertions.go b/keeltest/require/assertions.go index 7aabb20..e43698c 100644 --- a/keeltest/require/assertions.go +++ b/keeltest/require/assertions.go @@ -24,7 +24,7 @@ func New(t *testing.T) *Assertions { //nolint:thelper } } -func (a *Assertions) InlineEqual(actual interface{}, msgAndArgs ...interface{}) { +func (a *Assertions) InlineEqual(actual any, msgAndArgs ...any) { a.t.Helper() if expected, ok := keeltest.Inline(a.t, 2, "%v", actual); ok { @@ -32,7 +32,7 @@ func (a *Assertions) InlineEqual(actual interface{}, msgAndArgs ...interface{}) } } -func (a *Assertions) InlineJSONEq(actual interface{}, msgAndArgs ...interface{}) { +func (a *Assertions) InlineJSONEq(actual any, msgAndArgs ...any) { a.t.Helper() // marshal value actualBytes, err := json.Marshal(actual) diff --git a/keeltest/require/require.go b/keeltest/require/require.go index 334be83..1ee62c7 100644 --- a/keeltest/require/require.go +++ b/keeltest/require/require.go @@ -11,7 +11,7 @@ import ( "github.com/tidwall/pretty" ) -func InlineEqual(t *testing.T, actual interface{}, msgAndArgs ...interface{}) { +func InlineEqual(t *testing.T, actual any, msgAndArgs ...any) { t.Helper() if expected, ok := keeltest.Inline(t, 2, "%v", actual); ok { @@ -19,7 +19,7 @@ func InlineEqual(t *testing.T, actual interface{}, msgAndArgs ...interface{}) { } } -func InlineJSONEq(t *testing.T, actual interface{}, msgAndArgs ...interface{}) { +func InlineJSONEq(t *testing.T, actual any, msgAndArgs ...any) { t.Helper() // marshal value actualBytes, err := json.Marshal(actual) diff --git a/log/fields.go b/log/fields.go index 51f3353..c8afdaf 100644 --- a/log/fields.go +++ b/log/fields.go @@ -29,12 +29,12 @@ func FName(name string) zap.Field { } // FValue creates a zap.Field with a given value under the key "value". -func FValue(value interface{}) zap.Field { +func FValue(value any) zap.Field { return zap.String(ValueKey, fmt.Sprintf("%v", value)) } // FJSON creates a zap.Field with a given value under the key "json". -func FJSON(v interface{}) zap.Field { +func FJSON(v any) zap.Field { if out, err := json.Marshal(v); err != nil { return zap.String(JSONKey+"_error", err.Error()) } else { diff --git a/log/logger.go b/log/logger.go index dab8f1b..420501d 100644 --- a/log/logger.go +++ b/log/logger.go @@ -113,7 +113,7 @@ func SetDisableStacktrace(value bool) error { } // Must logs a fatal error if given -func Must(l *zap.Logger, err error, msgAndArgs ...interface{}) { +func Must(l *zap.Logger, err error, msgAndArgs ...any) { if err != nil { if l == nil { l = Logger() diff --git a/net/http/middleware/cors.go b/net/http/middleware/cors.go index 7db967d..63f1c5e 100644 --- a/net/http/middleware/cors.go +++ b/net/http/middleware/cors.go @@ -150,12 +150,12 @@ func CORSWithOptions(opts CORSOptions) Middleware { // Check allowed origin patterns for _, re := range allowOriginPatterns { if allowOrigin == "" { - index := strings.Index(origin, "://") - if index == -1 { + _, after, found := strings.Cut(origin, "://") + if !found { continue } - if len(origin[index+3:]) > 253 { + if len(after) > 253 { break } @@ -218,10 +218,10 @@ func CORSWithOptions(opts CORSOptions) Middleware { } func matchScheme(domain, pattern string) bool { - didx := strings.Index(domain, ":") - pidx := strings.Index(pattern, ":") + domScheme, _, domFound := strings.Cut(domain, ":") + patScheme, _, patFound := strings.Cut(pattern, ":") - return didx != -1 && pidx != -1 && domain[:didx] == pattern[:pidx] + return domFound && patFound && domScheme == patScheme } // matchSubdomain compares authority with wildcard @@ -230,21 +230,21 @@ func matchSubdomain(domain, pattern string) bool { return false } - didx := strings.Index(domain, "://") + _, domAuth, domFound := strings.Cut(domain, "://") + if !domFound { + return false + } - pidx := strings.Index(pattern, "://") - if didx == -1 || pidx == -1 { + _, patAuth, patFound := strings.Cut(pattern, "://") + if !patFound { return false } - domAuth := domain[didx+3:] // to avoid long loop by invalid long domain if len(domAuth) > 253 { return false } - patAuth := pattern[pidx+3:] - domComp := strings.Split(domAuth, ".") patComp := strings.Split(patAuth, ".") diff --git a/net/http/middleware/gzip.go b/net/http/middleware/gzip.go index 0b83c2e..072697a 100644 --- a/net/http/middleware/gzip.go +++ b/net/http/middleware/gzip.go @@ -58,7 +58,7 @@ func GZip(opts ...GZipOption) Middleware { func GZipWithOptions(opts GZipOptions) Middleware { return func(l *zap.Logger, name string, next http.Handler) http.Handler { pool := sync.Pool{ - New: func() interface{} { + New: func() any { return new(gzip.Reader) }, } diff --git a/net/http/middleware/jwt.go b/net/http/middleware/jwt.go index 2715a60..1ec3e89 100644 --- a/net/http/middleware/jwt.go +++ b/net/http/middleware/jwt.go @@ -126,7 +126,7 @@ func JWTWithSetContext(v bool) JWTOption { } // JWT middleware -func JWT(v *jwt.JWT, contextKey interface{}, opts ...JWTOption) Middleware { +func JWT(v *jwt.JWT, contextKey any, opts ...JWTOption) Middleware { options := GetDefaultJWTOptions() for _, opt := range opts { @@ -139,7 +139,7 @@ func JWT(v *jwt.JWT, contextKey interface{}, opts ...JWTOption) Middleware { } // JWTWithOptions middleware -func JWTWithOptions(v *jwt.JWT, contextKey interface{}, opts JWTOptions) Middleware { +func JWTWithOptions(v *jwt.JWT, contextKey any, opts JWTOptions) Middleware { return func(l *zap.Logger, name string, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { span := trace.SpanFromContext(r.Context()) diff --git a/net/http/roundtripware/gzip.go b/net/http/roundtripware/gzip.go index 980a014..951e994 100644 --- a/net/http/roundtripware/gzip.go +++ b/net/http/roundtripware/gzip.go @@ -55,7 +55,7 @@ func GZip(opts ...GZipOption) RoundTripware { return func(l *zap.Logger, next Handler) Handler { pool := sync.Pool{ - New: func() interface{} { + New: func() any { return gzip.NewWriter(nil) }, } diff --git a/net/stream/jetstream/publisher.go b/net/stream/jetstream/publisher.go index 081e75a..4ac1d7e 100644 --- a/net/stream/jetstream/publisher.go +++ b/net/stream/jetstream/publisher.go @@ -13,7 +13,7 @@ type ( marshal MarshalFn header nats.Header } - MarshalFn func(v interface{}) ([]byte, error) + MarshalFn func(v any) ([]byte, error) ) func (s *Publisher) JS() nats.JetStreamContext { @@ -28,7 +28,7 @@ func (s *Publisher) Subject() string { return s.subject } -func (s *Publisher) NewMsg(v interface{}) (*nats.Msg, error) { +func (s *Publisher) NewMsg(v any) (*nats.Msg, error) { data, err := s.Marshal(v) if err != nil { return nil, err @@ -47,7 +47,7 @@ func (s *Publisher) PubOpts(opts ...nats.PubOpt) []nats.PubOpt { return append(s.pubOpts, opts...) } -func (s *Publisher) PublishMsg(data interface{}, opts ...nats.PubOpt) (*nats.PubAck, error) { +func (s *Publisher) PublishMsg(data any, opts ...nats.PubOpt) (*nats.PubAck, error) { if msg, err := s.NewMsg(data); err != nil { return nil, err } else { @@ -55,7 +55,7 @@ func (s *Publisher) PublishMsg(data interface{}, opts ...nats.PubOpt) (*nats.Pub } } -func (s *Publisher) PublishMsgAsync(data interface{}, opts ...nats.PubOpt) (nats.PubAckFuture, error) { +func (s *Publisher) PublishMsgAsync(data any, opts ...nats.PubOpt) (nats.PubAckFuture, error) { if msg, err := s.NewMsg(data); err != nil { return nil, err } else { @@ -63,6 +63,6 @@ func (s *Publisher) PublishMsgAsync(data interface{}, opts ...nats.PubOpt) (nats } } -func (s *Publisher) Marshal(v interface{}) ([]byte, error) { +func (s *Publisher) Marshal(v any) ([]byte, error) { return s.marshal(v) } diff --git a/net/stream/jetstream/subscriber.go b/net/stream/jetstream/subscriber.go index 15740d1..1ac11f4 100644 --- a/net/stream/jetstream/subscriber.go +++ b/net/stream/jetstream/subscriber.go @@ -17,7 +17,7 @@ type ( unmarshal UnmarshalFn opts []nats.SubOpt } - UnmarshalFn func(data []byte, v interface{}) error + UnmarshalFn func(data []byte, v any) error ) func (s *Subscriber) JS() nats.JetStreamContext { @@ -58,7 +58,7 @@ func (s *Subscriber) QueueSubscribe(queue string, handler stream.MsgHandler, opt }, s.SubOpts(opts...)...) } -func (s *Subscriber) Unmarshal(msg *nats.Msg, v interface{}) error { +func (s *Subscriber) Unmarshal(msg *nats.Msg, v any) error { return s.unmarshal(msg.Data, v) } diff --git a/persistence/mongo/collection.go b/persistence/mongo/collection.go index 78490c8..e707b4d 100644 --- a/persistence/mongo/collection.go +++ b/persistence/mongo/collection.go @@ -18,7 +18,7 @@ import ( ) type ( - DecodeFn func(val interface{}) error + DecodeFn func(val any) error IterateHandlerFn func(decode DecodeFn) error ) @@ -162,7 +162,7 @@ func (c *Collection) Col() *mongo.Collection { // ~ Public methods // ------------------------------------------------------------------------------------------------ -func (c *Collection) Get(ctx context.Context, id string, result interface{}, opts ...*options.FindOneOptions) error { +func (c *Collection) Get(ctx context.Context, id string, result any, opts ...*options.FindOneOptions) error { if id == "" { return keelpersistence.ErrNotFound } @@ -329,7 +329,7 @@ func (c *Collection) Insert(ctx context.Context, entity Entity) error { } func (c *Collection) InsertMany(ctx context.Context, entities []Entity) error { - inserts := make([]interface{}, len(entities)) + inserts := make([]any, len(entities)) for i, entity := range entities { if entity == nil { return errors.New("entity must not be nil") @@ -375,7 +375,7 @@ func (c *Collection) Delete(ctx context.Context, id string) error { return nil } -func (c *Collection) Find(ctx context.Context, filter, results interface{}, opts ...*options.FindOptions) error { +func (c *Collection) Find(ctx context.Context, filter, results any, opts ...*options.FindOptions) error { cursor, err := c.collection.Find(ctx, filter, opts...) if errors.Is(err, mongo.ErrNoDocuments) { return keelerrors.NewWrappedError(keelpersistence.ErrNotFound, err) @@ -390,7 +390,7 @@ func (c *Collection) Find(ctx context.Context, filter, results interface{}, opts return cursor.Err() } -func (c *Collection) FindOne(ctx context.Context, filter, result interface{}, opts ...*options.FindOneOptions) error { +func (c *Collection) FindOne(ctx context.Context, filter, result any, opts ...*options.FindOneOptions) error { res := c.collection.FindOne(ctx, filter, opts...) if errors.Is(res.Err(), mongo.ErrNoDocuments) { return keelerrors.NewWrappedError(keelpersistence.ErrNotFound, res.Err()) @@ -401,7 +401,7 @@ func (c *Collection) FindOne(ctx context.Context, filter, result interface{}, op return res.Decode(result) } -func (c *Collection) FindIterate(ctx context.Context, filter interface{}, handler IterateHandlerFn, opts ...*options.FindOptions) error { +func (c *Collection) FindIterate(ctx context.Context, filter any, handler IterateHandlerFn, opts ...*options.FindOptions) error { cursor, err := c.collection.Find(ctx, filter, opts...) if errors.Is(err, mongo.ErrNoDocuments) { return keelerrors.NewWrappedError(keelpersistence.ErrNotFound, err) @@ -420,7 +420,7 @@ func (c *Collection) FindIterate(ctx context.Context, filter interface{}, handle return cursor.Err() } -func (c *Collection) Aggregate(ctx context.Context, pipeline mongo.Pipeline, results interface{}, opts ...*options.AggregateOptions) error { +func (c *Collection) Aggregate(ctx context.Context, pipeline mongo.Pipeline, results any, opts ...*options.AggregateOptions) error { cursor, err := c.collection.Aggregate(ctx, pipeline, opts...) if err != nil { return err @@ -451,7 +451,7 @@ func (c *Collection) AggregateIterate(ctx context.Context, pipeline mongo.Pipeli } // Count returns the count of documents -func (c *Collection) Count(ctx context.Context, filter interface{}, opts ...*options.CountOptions) (int64, error) { +func (c *Collection) Count(ctx context.Context, filter any, opts ...*options.CountOptions) (int64, error) { return c.collection.CountDocuments(ctx, filter, opts...) } diff --git a/server.go b/server.go index dc5fe73..af9a253 100644 --- a/server.go +++ b/server.go @@ -45,11 +45,11 @@ type Server struct { // gracefulPeriod should equal the terminationGracePeriodSeconds gracefulPeriod time.Duration running atomic.Bool - syncClosers []interface{} + syncClosers []any syncClosersLock sync.RWMutex syncReadmers []interfaces.Readmer syncReadmersLock sync.RWMutex - syncProbes map[healthz.Type][]interface{} + syncProbes map[healthz.Type][]any syncProbesLock sync.RWMutex ctx context.Context cancel context.CancelFunc @@ -66,7 +66,7 @@ func NewServer(opts ...Option) *Server { gracefulPeriod: time.Duration(env.GetInt("KEEL_GRACEFUL_PERIOD", 30)) * time.Second, shutdownSignals: []os.Signal{syscall.SIGINT, syscall.SIGTERM}, syncReadmers: []interfaces.Readmer{}, - syncProbes: map[healthz.Type][]interface{}{}, + syncProbes: map[healthz.Type][]any{}, ctx: context.Background(), c: config.Config(), l: log.Logger(), @@ -239,7 +239,7 @@ func (s *Server) AddServices(services ...Service) { } // AddCloser adds a closer to be called on shutdown -func (s *Server) AddCloser(closer interface{}) { +func (s *Server) AddCloser(closer any) { if slices.Contains(s.closers(), closer) { return } @@ -252,7 +252,7 @@ func (s *Server) AddCloser(closer interface{}) { } // AddClosers adds the given closers to be called on shutdown -func (s *Server) AddClosers(closers ...interface{}) { +func (s *Server) AddClosers(closers ...any) { for _, closer := range closers { s.AddCloser(closer) } @@ -271,7 +271,7 @@ func (s *Server) AddReadmers(readmers ...interfaces.Readmer) { } // AddHealthzer adds a probe to be called on healthz checks -func (s *Server) AddHealthzer(typ healthz.Type, probe interface{}) { +func (s *Server) AddHealthzer(typ healthz.Type, probe any) { if IsHealthz(probe) { s.addProbes(typ, probe) } else { @@ -280,29 +280,29 @@ func (s *Server) AddHealthzer(typ healthz.Type, probe interface{}) { } // AddHealthzers adds the given probes to be called on healthz checks -func (s *Server) AddHealthzers(typ healthz.Type, probes ...interface{}) { +func (s *Server) AddHealthzers(typ healthz.Type, probes ...any) { for _, probe := range probes { s.AddHealthzer(typ, probe) } } // AddAlwaysHealthzers adds the probes to be called on any healthz checks -func (s *Server) AddAlwaysHealthzers(probes ...interface{}) { +func (s *Server) AddAlwaysHealthzers(probes ...any) { s.AddHealthzers(healthz.TypeAlways, probes...) } // AddStartupHealthzers adds the startup probes to be called on healthz checks -func (s *Server) AddStartupHealthzers(probes ...interface{}) { +func (s *Server) AddStartupHealthzers(probes ...any) { s.AddHealthzers(healthz.TypeStartup, probes...) } // AddLivenessHealthzers adds the liveness probes to be called on healthz checks -func (s *Server) AddLivenessHealthzers(probes ...interface{}) { +func (s *Server) AddLivenessHealthzers(probes ...any) { s.AddHealthzers(healthz.TypeLiveness, probes...) } // AddReadinessHealthzers adds the readiness probes to be called on healthz checks -func (s *Server) AddReadinessHealthzers(probes ...interface{}) { +func (s *Server) AddReadinessHealthzers(probes ...any) { s.AddHealthzers(healthz.TypeReadiness, probes...) } @@ -352,14 +352,14 @@ func (s *Server) Run() { } } -func (s *Server) closers() []interface{} { +func (s *Server) closers() []any { s.syncClosersLock.RLock() defer s.syncClosersLock.RUnlock() return s.syncClosers } -func (s *Server) addClosers(v ...interface{}) { +func (s *Server) addClosers(v ...any) { s.syncClosersLock.Lock() defer s.syncClosersLock.Unlock() @@ -380,14 +380,14 @@ func (s *Server) addReadmers(v ...interfaces.Readmer) { s.syncReadmers = append(s.syncReadmers, v...) } -func (s *Server) probes() map[healthz.Type][]interface{} { +func (s *Server) probes() map[healthz.Type][]any { s.syncProbesLock.RLock() defer s.syncProbesLock.RUnlock() return s.syncProbes } -func (s *Server) addProbes(typ healthz.Type, v ...interface{}) { +func (s *Server) addProbes(typ healthz.Type, v ...any) { s.syncProbesLock.Lock() defer s.syncProbesLock.Unlock() diff --git a/service/httphealthz.go b/service/httphealthz.go index 3193f30..eba35fe 100644 --- a/service/httphealthz.go +++ b/service/httphealthz.go @@ -26,7 +26,7 @@ var ( ErrStartupProbeFailed = errors.New("startup probe failed") ) -func NewHealthz(l *zap.Logger, name, addr, path string, probes map[healthz.Type][]interface{}) *HTTP { +func NewHealthz(l *zap.Logger, name, addr, path string, probes map[healthz.Type][]any) *HTTP { handler := http.NewServeMux() unavailable := func(l *zap.Logger, w http.ResponseWriter, r *http.Request, err error) { @@ -36,7 +36,7 @@ func NewHealthz(l *zap.Logger, name, addr, path string, probes map[healthz.Type] } } - call := func(ctx context.Context, probe interface{}) (bool, error) { + call := func(ctx context.Context, probe any) (bool, error) { switch h := probe.(type) { case healthz.BoolHealthzer: return h.Healthz(), nil @@ -77,7 +77,7 @@ func NewHealthz(l *zap.Logger, name, addr, path string, probes map[healthz.Type] }) handler.HandleFunc(path+"/"+healthz.TypeLiveness.String(), func(w http.ResponseWriter, r *http.Request) { - var ps []interface{} + var ps []any if p, ok := probes[healthz.TypeAlways]; ok { ps = append(ps, p...) } @@ -101,7 +101,7 @@ func NewHealthz(l *zap.Logger, name, addr, path string, probes map[healthz.Type] }) handler.HandleFunc(path+"/"+healthz.TypeReadiness.String(), func(w http.ResponseWriter, r *http.Request) { - var ps []interface{} + var ps []any if p, ok := probes[healthz.TypeAlways]; ok { ps = append(ps, p...) } @@ -125,7 +125,7 @@ func NewHealthz(l *zap.Logger, name, addr, path string, probes map[healthz.Type] }) handler.HandleFunc(path+"/"+healthz.TypeStartup.String(), func(w http.ResponseWriter, r *http.Request) { - var ps []interface{} + var ps []any if p, ok := probes[healthz.TypeAlways]; ok { ps = append(ps, p...) } @@ -151,7 +151,7 @@ func NewHealthz(l *zap.Logger, name, addr, path string, probes map[healthz.Type] return NewHTTP(l, name, addr, handler) } -func NewDefaultHTTPProbes(l *zap.Logger, probes map[healthz.Type][]interface{}) *HTTP { +func NewDefaultHTTPProbes(l *zap.Logger, probes map[healthz.Type][]any) *HTTP { return NewHealthz( l, DefaultHTTPHealthzName, diff --git a/service/httpviper.go b/service/httpviper.go index 26b2c2d..b7bcacd 100644 --- a/service/httpviper.go +++ b/service/httpviper.go @@ -20,8 +20,8 @@ func NewHTTPViper(l *zap.Logger, c *viper.Viper, name, addr, path string) *HTTP handler := http.NewServeMux() handler.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { type payload struct { - Key string `json:"key"` - Value interface{} `json:"value"` + Key string `json:"key"` + Value any `json:"value"` } enc := json.NewEncoder(w) diff --git a/service/httpzap.go b/service/httpzap.go index ac77a5b..42b57c4 100644 --- a/service/httpzap.go +++ b/service/httpzap.go @@ -50,9 +50,11 @@ func NewHTTPZap(l *zap.Logger, name, addr, path string) *HTTP { if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return fmt.Sprintf("Request body must be well-formed JSON: %v", err) } + if req.Level == nil && req.DisableCaller == nil && req.DisableStacktrace == nil { return "Must specify a value." } + return "" }(); errmess != "" { w.WriteHeader(http.StatusBadRequest) From 19eaf66fa927f797f56dfce75050ee6ec89e4c1a Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Tue, 24 Feb 2026 13:57:01 +0100 Subject: [PATCH 06/11] chore: change key --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a7f5a44..e0367d5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,9 +31,9 @@ jobs: uses: actions/cache@v5 with: path: ~/.cache/golangci-lint - key: ${{ runner.os }}-golangci-lint-${{ hashFiles('**/go.mod') }} + key: golangci-lint-${{ runner.os }}-${{ hashFiles('**/go.mod') }} restore-keys: | - ${{ runner.os }}-golangci-lint- + golangci-lint-${{ runner.os }}-- - name: Run tidy run: make tidy && git diff --exit-code From 59f2cb045394bd646a031426b1da4ffd933eebc5 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Tue, 24 Feb 2026 13:57:27 +0100 Subject: [PATCH 07/11] feat: remove wrapped error in favor of errors.Join --- errors/wrappederror.go | 22 ------ errors/wrappederror_test.go | 88 ------------------------ examples/errors/main.go | 58 ---------------- examples/go.sum | 4 +- net/http/roundtripware/circuitbreaker.go | 3 +- net/stream/go.mod | 2 +- net/stream/go.sum | 4 +- persistence/mongo/collection.go | 13 ++-- 8 files changed, 12 insertions(+), 182 deletions(-) delete mode 100644 errors/wrappederror.go delete mode 100644 errors/wrappederror_test.go delete mode 100644 examples/errors/main.go diff --git a/errors/wrappederror.go b/errors/wrappederror.go deleted file mode 100644 index 9b1aa8d..0000000 --- a/errors/wrappederror.go +++ /dev/null @@ -1,22 +0,0 @@ -package keelerrors - -type wrappedError struct { - err error - cause error -} - -// NewWrappedError returns a new wrapped error -func NewWrappedError(err, cause error) error { - return &wrappedError{ - err: err, - cause: cause, - } -} - -func (e *wrappedError) Error() string { - return e.err.Error() + ": " + e.cause.Error() -} - -func (e *wrappedError) Unwrap() []error { - return []error{e.err, e.cause} -} diff --git a/errors/wrappederror_test.go b/errors/wrappederror_test.go deleted file mode 100644 index 8a30221..0000000 --- a/errors/wrappederror_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package keelerrors_test - -import ( - "errors" - "fmt" - "testing" - - keelerrors "github.com/foomo/keel/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func ExampleNewWrappedError() { - parentErr := errors.New("parent") - childErr := errors.New("child") - wrappedErr := keelerrors.NewWrappedError(parentErr, childErr) - fmt.Println(parentErr) - fmt.Println(childErr) - fmt.Println(wrappedErr) - // Output: - // parent - // child - // parent: child -} - -func TestNewWrappedError(t *testing.T) { - t.Parallel() - - parentErr := errors.New("parent") - childErr := errors.New("child") - assert.Error(t, keelerrors.NewWrappedError(parentErr, childErr)) -} - -func TestWrapped(t *testing.T) { - t.Parallel() - - parentErr := errors.New("parent") - childErr := errors.New("child") - assert.Error(t, keelerrors.NewWrappedError(parentErr, childErr)) -} - -func Test_wrappedError_As(t *testing.T) { - t.Parallel() - - type ( - Parent struct { - error - } - Child struct { - error - } - ) - - parentErr := &Parent{error: errors.New("parent")} - childErr := &Child{error: errors.New("parent")} - wrappedErr := keelerrors.NewWrappedError(parentErr, childErr) - - var ( - p *Parent - c *Child - ) - if assert.ErrorAs(t, wrappedErr, &p) { - assert.EqualError(t, p, parentErr.Error()) - } - - if assert.ErrorAs(t, wrappedErr, &c) { - assert.EqualError(t, c, childErr.Error()) - } -} - -func Test_wrappedError_Error(t *testing.T) { - t.Parallel() - - parentErr := errors.New("parent") - childErr := errors.New("child") - wrappedErr := keelerrors.NewWrappedError(parentErr, childErr) - assert.Equal(t, "parent: child", wrappedErr.Error()) -} - -func Test_wrappedError_Is(t *testing.T) { - t.Parallel() - - parentErr := errors.New("parent") - childErr := errors.New("child") - wrappedErr := keelerrors.NewWrappedError(parentErr, childErr) - require.ErrorIs(t, wrappedErr, parentErr) - require.ErrorIs(t, wrappedErr, childErr) -} diff --git a/examples/errors/main.go b/examples/errors/main.go deleted file mode 100644 index b966d62..0000000 --- a/examples/errors/main.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/pkg/errors" - - keelerrors "github.com/foomo/keel/errors" -) - -var ( - ErrOne = errors.New("one") - ErrTwo = errors.New("two") -) - -type ErrThree struct { - error -} - -func (e *ErrThree) Foo() string { - return e.Error() -} - -func main() { - err1 := ErrOne - err2 := keelerrors.NewWrappedError(err1, ErrTwo) - err3 := &ErrThree{error: errors.New("error three")} - err4 := keelerrors.NewWrappedError(err3, ErrTwo) - err5 := keelerrors.NewWrappedError(ErrTwo, err3) - - if errors.Is(err1, ErrOne) { - fmt.Println("err1 = ErrOne") //nolint:forbidigo - } - if errors.Is(err2, ErrTwo) { - fmt.Println("err2 = ErrTwo") //nolint:forbidigo - } - if errors.Is(err2, ErrOne) { - fmt.Println("err2 = ErrOne") //nolint:forbidigo - } - { - var foo *ErrThree - if errors.As(err3, &foo) { - fmt.Println("err3 = ErrThree (" + foo.Foo() + ")") //nolint:forbidigo - } - } - { - var foo *ErrThree - if errors.As(err4, &foo) { - fmt.Println("err4 = ErrThree (" + foo.Foo() + ")") //nolint:forbidigo - } - } - { - var foo *ErrThree - if errors.As(err5, &foo) { - fmt.Println("err5 = ErrThree (" + foo.Foo() + ")") //nolint:forbidigo - } - } -} diff --git a/examples/go.sum b/examples/go.sum index 7b0d278..d093de5 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -82,8 +82,8 @@ github.com/fbiville/markdown-table-formatter v0.3.0 h1:PIm1UNgJrFs8q1htGTw+wnnNY github.com/fbiville/markdown-table-formatter v0.3.0/go.mod h1:q89TDtSEVDdTaufgSbfHpNVdPU/bmfvqNkrC5HagmLY= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/foomo/go v0.0.3 h1:5pGzcPC78dImuBTT7nsZZnH+GIQUylbCtMkFEH26uZk= -github.com/foomo/go v0.0.3/go.mod h1:x6g64wiQusqaFElnh5rlk9unCgLKmfUWy0YFLejJxio= +github.com/foomo/go v0.1.1 h1:cGtB4ExN9qUcQxOu69fHYQj8dtOx32C2WrME+cn45Uk= +github.com/foomo/go v0.1.1/go.mod h1:M4/OAdVQc/JMgeR823v9JQXL+TJhmOa4bElpHJNMRgQ= github.com/foomo/gostandards v0.3.0 h1:Ev88Tdu0+52UH/d0l2KcAWx542A13o42d++LSTG0DC4= github.com/foomo/gostandards v0.3.0/go.mod h1:8hJ56tpyFAVwavs5FuUF3po7Bq7hOTkQ1+DzFi4GCWg= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/net/http/roundtripware/circuitbreaker.go b/net/http/roundtripware/circuitbreaker.go index 79d464f..88750d4 100644 --- a/net/http/roundtripware/circuitbreaker.go +++ b/net/http/roundtripware/circuitbreaker.go @@ -5,7 +5,6 @@ import ( "net/http" "time" - keelerrors "github.com/foomo/keel/errors" "github.com/foomo/keel/log" "github.com/sony/gobreaker" "go.opentelemetry.io/otel/attribute" @@ -183,7 +182,7 @@ func CircuitBreaker(set *CircuitBreakerSettings, opts ...CircuitBreakerOption) R // wrap the error in case it was produced because of the circuit breaker being (half-)open if errors.Is(err, gobreaker.ErrTooManyRequests) || errors.Is(err, gobreaker.ErrOpenState) { - return nil, keelerrors.NewWrappedError(ErrCircuitBreaker, err) + return nil, errors.Join(ErrCircuitBreaker, err) } else if err != nil { l.Error("unexpected error in circuit breaker", log.FError(err), diff --git a/net/stream/go.mod b/net/stream/go.mod index aad3831..319e3e5 100644 --- a/net/stream/go.mod +++ b/net/stream/go.mod @@ -5,7 +5,7 @@ go 1.26.0 replace github.com/foomo/keel => ../../ require ( - github.com/foomo/go v0.0.3 + github.com/foomo/go v0.1.1 github.com/foomo/keel v0.0.0-00010101000000-000000000000 github.com/nats-io/nats.go v1.48.0 github.com/pkg/errors v0.9.1 diff --git a/net/stream/go.sum b/net/stream/go.sum index 0725164..d7299ea 100644 --- a/net/stream/go.sum +++ b/net/stream/go.sum @@ -40,8 +40,8 @@ github.com/fbiville/markdown-table-formatter v0.3.0 h1:PIm1UNgJrFs8q1htGTw+wnnNY github.com/fbiville/markdown-table-formatter v0.3.0/go.mod h1:q89TDtSEVDdTaufgSbfHpNVdPU/bmfvqNkrC5HagmLY= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/foomo/go v0.0.3 h1:5pGzcPC78dImuBTT7nsZZnH+GIQUylbCtMkFEH26uZk= -github.com/foomo/go v0.0.3/go.mod h1:x6g64wiQusqaFElnh5rlk9unCgLKmfUWy0YFLejJxio= +github.com/foomo/go v0.1.1 h1:cGtB4ExN9qUcQxOu69fHYQj8dtOx32C2WrME+cn45Uk= +github.com/foomo/go v0.1.1/go.mod h1:M4/OAdVQc/JMgeR823v9JQXL+TJhmOa4bElpHJNMRgQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= diff --git a/persistence/mongo/collection.go b/persistence/mongo/collection.go index e707b4d..aec3994 100644 --- a/persistence/mongo/collection.go +++ b/persistence/mongo/collection.go @@ -2,12 +2,11 @@ package keelmongo import ( "context" + "errors" "slices" "time" - keelerrors "github.com/foomo/keel/errors" keelpersistence "github.com/foomo/keel/persistence" - "github.com/pkg/errors" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/bsoncodec" "go.mongodb.org/mongo-driver/mongo" @@ -210,7 +209,7 @@ func (c *Collection) Upsert(ctx context.Context, id string, entity Entity) error bson.D{bson.E{Key: "$set", Value: entity}}, options.FindOneAndUpdate().SetUpsert(false), ).Err(); errors.Is(err, mongo.ErrNoDocuments) { - return keelerrors.NewWrappedError(keelpersistence.ErrDirtyWrite, err) + return errors.Join(keelpersistence.ErrDirtyWrite, err) } else if err != nil { return err } @@ -367,7 +366,7 @@ func (c *Collection) Delete(ctx context.Context, id string) error { } if err := c.collection.FindOneAndDelete(ctx, bson.M{"id": id}).Err(); errors.Is(err, mongo.ErrNoDocuments) { - return keelerrors.NewWrappedError(keelpersistence.ErrNotFound, err) + return errors.Join(keelpersistence.ErrNotFound, err) } else if err != nil { return err } @@ -378,7 +377,7 @@ func (c *Collection) Delete(ctx context.Context, id string) error { func (c *Collection) Find(ctx context.Context, filter, results any, opts ...*options.FindOptions) error { cursor, err := c.collection.Find(ctx, filter, opts...) if errors.Is(err, mongo.ErrNoDocuments) { - return keelerrors.NewWrappedError(keelpersistence.ErrNotFound, err) + return errors.Join(keelpersistence.ErrNotFound, err) } else if err != nil { return err } @@ -393,7 +392,7 @@ func (c *Collection) Find(ctx context.Context, filter, results any, opts ...*opt func (c *Collection) FindOne(ctx context.Context, filter, result any, opts ...*options.FindOneOptions) error { res := c.collection.FindOne(ctx, filter, opts...) if errors.Is(res.Err(), mongo.ErrNoDocuments) { - return keelerrors.NewWrappedError(keelpersistence.ErrNotFound, res.Err()) + return errors.Join(keelpersistence.ErrNotFound, res.Err()) } else if res.Err() != nil { return res.Err() } @@ -404,7 +403,7 @@ func (c *Collection) FindOne(ctx context.Context, filter, result any, opts ...*o func (c *Collection) FindIterate(ctx context.Context, filter any, handler IterateHandlerFn, opts ...*options.FindOptions) error { cursor, err := c.collection.Find(ctx, filter, opts...) if errors.Is(err, mongo.ErrNoDocuments) { - return keelerrors.NewWrappedError(keelpersistence.ErrNotFound, err) + return errors.Join(keelpersistence.ErrNotFound, err) } else if err != nil { return err } From 8047da28e1ea6fb6747b964498d9ba79f06f4c6b Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Tue, 24 Feb 2026 13:59:01 +0100 Subject: [PATCH 08/11] chore: update tasks --- .lefthook.yaml | 2 +- .mise.toml | 4 ++-- Makefile | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.lefthook.yaml b/.lefthook.yaml index 436701d..3eff260 100644 --- a/.lefthook.yaml +++ b/.lefthook.yaml @@ -20,7 +20,7 @@ pre-commit: golangci-lint: glob: "*.go" run: golangci-lint run --new --fast-only - stage_fixed: true + stage_fixed: false commit-msg: skip: [ merge, rebase ] diff --git a/.mise.toml b/.mise.toml index 9e1fff3..b3013f6 100644 --- a/.mise.toml +++ b/.mise.toml @@ -1,5 +1,5 @@ [tools] # https://mise-tools.jdx.dev/tools/lefthook -lefthook = "2.1.0" +lefthook = "2.1.1" # https://mise-tools.jdx.dev/tools/golangci-lint -golangci-lint = "2.9.0" +golangci-lint = "2.10.1" diff --git a/Makefile b/Makefile index e9911be..3057fef 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ endif .PHONY: check ## Run lint & tests -check: tidy lint test +check: tidy generate lint test .PHONY: tidy ## Run go mod tidy @@ -75,7 +75,7 @@ test: ## Run go tests with `race` flag test.race: @echo "〉go test with -race" - @GO_TEST_TAGS=-skip,race go test -tags=safe -coverprofile=coverage.out -race work + @GO_TEST_TAGS=-skip go test -tags=safe -coverprofile=coverage.out -race work .PHONY: test.update ## Run go tests with `update` flag From 6e51aabc71831ad541eb44c31cbb1f33f046e1cb Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Tue, 24 Feb 2026 14:02:24 +0100 Subject: [PATCH 09/11] feat: add NewJWT helper --- keeltest/jwt.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 keeltest/jwt.go diff --git a/keeltest/jwt.go b/keeltest/jwt.go new file mode 100644 index 0000000..63f6ff6 --- /dev/null +++ b/keeltest/jwt.go @@ -0,0 +1,21 @@ +package keeltest + +import ( + "testing" + + testingx "github.com/foomo/go/testing" + keeljwt "github.com/foomo/keel/jwt" + "github.com/stretchr/testify/require" +) + +// NewJWT returns a new JWT instance with test keys for testing purposes. +func NewJWT(t *testing.T) *keeljwt.JWT { + t.Helper() + + publicPem, privatePem := testingx.NewRSAKeys(t) + + jwtKey, _, err := keeljwt.NewKeysFromFilenames(publicPem, privatePem, nil) + require.NoError(t, err) + + return keeljwt.New(jwtKey) +} From d55a95316417eba30682e4b60b660d79731a4e18 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Tue, 24 Feb 2026 14:22:09 +0100 Subject: [PATCH 10/11] fix: mod tidy --- go.mod | 1 + go.sum | 2 ++ integration/gotsrpc/go.sum | 4 ++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 3105db4..7134c1d 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.26.0 require ( github.com/avast/retry-go/v4 v4.7.0 github.com/fbiville/markdown-table-formatter v0.3.0 + github.com/foomo/go v0.1.1 github.com/foomo/gostandards v0.3.0 github.com/go-logr/logr v1.4.3 github.com/golang-jwt/jwt/v5 v5.3.1 diff --git a/go.sum b/go.sum index 4044c42..fa88e7a 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,8 @@ github.com/fbiville/markdown-table-formatter v0.3.0 h1:PIm1UNgJrFs8q1htGTw+wnnNY github.com/fbiville/markdown-table-formatter v0.3.0/go.mod h1:q89TDtSEVDdTaufgSbfHpNVdPU/bmfvqNkrC5HagmLY= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/foomo/go v0.1.1 h1:cGtB4ExN9qUcQxOu69fHYQj8dtOx32C2WrME+cn45Uk= +github.com/foomo/go v0.1.1/go.mod h1:M4/OAdVQc/JMgeR823v9JQXL+TJhmOa4bElpHJNMRgQ= github.com/foomo/gostandards v0.3.0 h1:Ev88Tdu0+52UH/d0l2KcAWx542A13o42d++LSTG0DC4= github.com/foomo/gostandards v0.3.0/go.mod h1:8hJ56tpyFAVwavs5FuUF3po7Bq7hOTkQ1+DzFi4GCWg= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/integration/gotsrpc/go.sum b/integration/gotsrpc/go.sum index 13fb41e..24b9062 100644 --- a/integration/gotsrpc/go.sum +++ b/integration/gotsrpc/go.sum @@ -40,8 +40,8 @@ github.com/fbiville/markdown-table-formatter v0.3.0 h1:PIm1UNgJrFs8q1htGTw+wnnNY github.com/fbiville/markdown-table-formatter v0.3.0/go.mod h1:q89TDtSEVDdTaufgSbfHpNVdPU/bmfvqNkrC5HagmLY= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/foomo/go v0.0.3 h1:5pGzcPC78dImuBTT7nsZZnH+GIQUylbCtMkFEH26uZk= -github.com/foomo/go v0.0.3/go.mod h1:x6g64wiQusqaFElnh5rlk9unCgLKmfUWy0YFLejJxio= +github.com/foomo/go v0.1.1 h1:cGtB4ExN9qUcQxOu69fHYQj8dtOx32C2WrME+cn45Uk= +github.com/foomo/go v0.1.1/go.mod h1:M4/OAdVQc/JMgeR823v9JQXL+TJhmOa4bElpHJNMRgQ= github.com/foomo/gostandards v0.3.0 h1:Ev88Tdu0+52UH/d0l2KcAWx542A13o42d++LSTG0DC4= github.com/foomo/gostandards v0.3.0/go.mod h1:8hJ56tpyFAVwavs5FuUF3po7Bq7hOTkQ1+DzFi4GCWg= github.com/foomo/gotsrpc/v2 v2.13.0 h1:ZfL2RqPKdFLLkU0Wdqu0NsQuku3Ung7susETyyoFAoI= From 5976359e00eb3840de5835c194b7eaf177a92c59 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Tue, 24 Feb 2026 14:54:08 +0100 Subject: [PATCH 11/11] style: format --- keeltest/client.go | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/keeltest/client.go b/keeltest/client.go index b27b306..1c546f7 100644 --- a/keeltest/client.go +++ b/keeltest/client.go @@ -43,15 +43,23 @@ func NewHTTPClient(opts ...HTTPClientOption) *HTTPClient { } func (c *HTTPClient) Get(ctx context.Context, path string) ([]byte, int, error) { - if req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.BaseURL+path, nil); err != nil { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.BaseURL+path, nil) + if err != nil { return nil, 0, err - } else if resp, err := c.Do(req); err != nil { + } + + resp, err := c.Do(req) //nolint:gosec // G704 + if err != nil { return nil, 0, err - } else if body, err := c.readBody(resp); err != nil { + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { return nil, 0, err - } else { - return body, resp.StatusCode, nil } + + return body, resp.StatusCode, nil } func (c *HTTPClient) Post(ctx context.Context, path string, data any) ([]byte, int, error) { @@ -67,21 +75,17 @@ func (c *HTTPClient) Post(ctx context.Context, path string, data any) ([]byte, i req.Header.Set("Content-Type", "application/json") - if resp, err := c.Do(req); err != nil { + resp, err := c.Do(req) //nolint:gosec // G704 + if err != nil { return nil, 0, err - } else if body, err := c.readBody(resp); err != nil { - return nil, 0, err - } else { - return body, resp.StatusCode, nil } -} -func (c *HTTPClient) readBody(resp *http.Response) ([]byte, error) { defer resp.Body.Close() - if body, err := io.ReadAll(resp.Body); err != nil { - return nil, err - } else { - return body, nil + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, 0, err } + + return body, resp.StatusCode, nil }