From d778e2b864925463633bbe69e1693883b73d3ac8 Mon Sep 17 00:00:00 2001 From: Marc Bir Date: Fri, 24 Jul 2026 18:32:21 -0700 Subject: [PATCH 1/4] bump to go 1.26 bump to latest linter rules minor lint cleanup dependency update no functional changes --- .golangci.yaml | 88 +++++++++++++------------ cache/basic_test.go | 37 +++++++---- cache/noop.go | 2 +- cache/noop_test.go | 31 ++++----- chain/chain_test.go | 6 +- config/config.go | 4 +- config/config_test.go | 47 +++++++------ config/pg.go | 1 + dates/dates.go | 15 +++-- errs/cause_test.go | 8 +-- errs/stack.go | 1 + errs/stack_test.go | 6 ++ forms/forms.go | 4 +- forms/forms_test.go | 108 +++++++++++++++--------------- go.mod | 23 ++++--- go.sum | 51 ++++++-------- httplog/request.go | 1 + httplog/request_test.go | 2 +- httputil/auth.go | 1 + httputil/body_test.go | 27 ++++---- httputil/dump.go | 6 +- httputil/dump_test.go | 105 +++++++++++++++-------------- httputil/errors_test.go | 2 +- httputil/wrap_writer_test.go | 12 ++-- params/params_test.go | 124 ++++++++++++++++++----------------- validation/error_test.go | 1 - validation/errors_test.go | 6 +- worker/fanout.go | 10 +-- worker/fanout_test.go | 6 +- worker/hashedfanout_test.go | 4 +- 30 files changed, 384 insertions(+), 355 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index a9bff0b..8e93b72 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,60 +1,58 @@ version: "2" run: - go: "1.23" - tests: false + go: "1.26" linters: default: all disable: - depguard + - dupl - exhaustruct + - funlen - gochecknoglobals - - gocognit - - intrange + - gomodguard - ireturn - - wsl # in favor of wsl_v5 + - lll + - mnd + - tagalign + - tagliatelle + - testpackage + - varnamelen + - wsl settings: - mnd: - ignored-numbers: - - "2" cyclop: - max-complexity: 18 - varnamelen: - max-distance: 10 - ignore-type-assert-ok: true - ignore-map-index-ok: true - ignore-decls: - - ok bool - - i int - - n int - - e error - - h http.Handler - - t time.Time - - v reflect.Value - - w io.Writer - - w http.ResponseWriter - - rw http.ResponseWriter - - r *http.Request - - wg sync.WaitGroup - - c chan - - op string - - l zerolog.Logger - - r io.Reader - - l zerolog.Context - - s string - - l string - wsl_v5: - disable: - - decl + max-complexity: 15 + goconst: + ignore-string-values: + - 'Host' + paralleltest: + ignore-missing: true exclusions: - generated: lax presets: - comments - - common-false-positives - - legacy - - std-error-handling - paths: - - .*_gen.go$ - - temp + rules: + - path: (.+)_test.go + linters: + - canonicalheader + - containedctx + - err113 + - errcheck + - errchkjson + - forbidigo + - goconst + - godot + - gosec + - govet + - maintidx + - nilnil + - nlreturn + - noctx + - nonamedreturns + - perfsprint + - revive + - staticcheck + - testifylint + - wrapcheck + - wsl_v5 formatters: enable: - goimports @@ -69,3 +67,7 @@ formatters: custom-order: true exclusions: generated: disable + +output: + sort-order: + - file diff --git a/cache/basic_test.go b/cache/basic_test.go index 6e4873f..3619f51 100644 --- a/cache/basic_test.go +++ b/cache/basic_test.go @@ -7,6 +7,7 @@ import ( "math/rand" "sort" "sync" + "sync/atomic" "testing" "github.com/stretchr/testify/assert" @@ -38,6 +39,7 @@ func ExampleBasic() { func randString(length int) string { randBytes := make([]byte, length) + _, err := crand.Read(randBytes) if err != nil { panic(err) @@ -53,48 +55,48 @@ func TestCache(t *testing.T) { c := cache.NewBasic[string, int]() // Empty v, ok := c.Get("a") - assert.Equal(t, 0, v) - assert.Equal(t, false, ok) + assert.Zero(t, v) + assert.False(t, ok) kk := c.Keys() - assert.Equal(t, 0, len(kk)) + assert.Empty(t, kk) // New Value c.Set("a", 1) v, ok = c.Get("a") assert.Equal(t, 1, v) - assert.Equal(t, true, ok) + assert.True(t, ok) kk = c.Keys() - assert.Equal(t, 1, len(kk)) + assert.Len(t, kk, 1) assert.Equal(t, []string{"a"}, kk) // Override c.Set("a", 2) v, ok = c.Get("a") assert.Equal(t, 2, v) - assert.Equal(t, true, ok) + assert.True(t, ok) // New Value v, ok = c.Get("b") - assert.Equal(t, 0, v) - assert.Equal(t, false, ok) + assert.Zero(t, v) + assert.False(t, ok) c.Set("b", 2) v, ok = c.Get("b") assert.Equal(t, 2, v) - assert.Equal(t, true, ok) + assert.True(t, ok) kk = c.Keys() sort.Strings(kk) - assert.Equal(t, 2, len(kk)) + assert.Len(t, kk, 2) assert.Equal(t, []string{"a", "b"}, kk) // Delete c.Delete("a") kk = c.Keys() - assert.Equal(t, 1, len(kk)) + assert.Len(t, kk, 1) assert.Equal(t, []string{"b"}, kk) } @@ -103,23 +105,32 @@ type foo struct { } func TestMultiThread(t *testing.T) { + const workers = int64(1000) + const iterations = 1000 f := foo{} + count := atomic.Int64{} f.c = cache.NewBasic[int, string]() var wg sync.WaitGroup - for i := int64(0); i < 1000; i++ { + for i := range workers { wg.Add(1) go func(i int64) { defer wg.Done() f.c.Clear() m := rand.New(rand.NewSource(i)) - for n := 0; n < 1000; n++ { + for range iterations { key := m.Intn(100) value := randString(10) f.c.Set(key, value) f.c.Get(key) + count.Add(1) } }(i) } wg.Wait() + + expected := workers * iterations + if count.Load() != expected { + t.Errorf("Expected %d, got %d", expected, count.Load()) + } } diff --git a/cache/noop.go b/cache/noop.go index 481cb91..2e428ad 100644 --- a/cache/noop.go +++ b/cache/noop.go @@ -15,7 +15,7 @@ func (c *NoOp[K, V]) Set(_ K, _ V) { // Get always returns !ok. func (c *NoOp[K, V]) Get(_ K) (out V, ok bool) { //nolint: ireturn,nonamedreturns - return + return out, ok } // Keys always returns nil array. diff --git a/cache/noop_test.go b/cache/noop_test.go index 4e0bf6f..09cd58d 100644 --- a/cache/noop_test.go +++ b/cache/noop_test.go @@ -15,45 +15,42 @@ func TestNoOpCache(t *testing.T) { c := cache.NewNoOp[string, int]() // Empty v, ok := c.Get("a") - assert.Equal(t, 0, v) - assert.Equal(t, false, ok) - + assert.Zero(t, v) + assert.False(t, ok) kk := c.Keys() - assert.Equal(t, 0, len(kk)) + assert.Empty(t, kk) // New Value c.Set("a", 1) v, ok = c.Get("a") - assert.Equal(t, 0, v) - assert.Equal(t, false, ok) - + assert.Zero(t, v) + assert.False(t, ok) kk = c.Keys() - assert.Equal(t, 0, len(kk)) + assert.Empty(t, kk) // Override c.Set("a", 2) v, ok = c.Get("a") - assert.Equal(t, 0, v) - assert.Equal(t, false, ok) + assert.Zero(t, v) + assert.False(t, ok) // New Value v, ok = c.Get("b") - assert.Equal(t, 0, v) - assert.Equal(t, false, ok) + assert.Zero(t, v) + assert.False(t, ok) c.Set("b", 2) v, ok = c.Get("b") - assert.Equal(t, 0, v) - assert.Equal(t, false, ok) - + assert.Zero(t, v) + assert.False(t, ok) kk = c.Keys() - assert.Equal(t, 0, len(kk)) + assert.Empty(t, kk) // Delete c.Delete("a") kk = c.Keys() - assert.Equal(t, 0, len(kk)) + assert.Empty(t, kk) c.Clear() } diff --git a/chain/chain_test.go b/chain/chain_test.go index 93f7ab1..fa20983 100644 --- a/chain/chain_test.go +++ b/chain/chain_test.go @@ -12,7 +12,11 @@ import ( func prefixLetter(letter string) chain.Constructor { return func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(letter)) + _, err := w.Write([]byte(letter)) + if err != nil { + panic(err) + } + h.ServeHTTP(w, r) }) } diff --git a/config/config.go b/config/config.go index 1215660..75536c3 100644 --- a/config/config.go +++ b/config/config.go @@ -100,7 +100,7 @@ func parseTag(tag string) error { // object must be a pointer to a struct. See ExampleLoad for simple example. func Load(cfg any) error { v := reflect.ValueOf(cfg) - if v.Kind() != reflect.Ptr || v.IsZero() { + if v.Kind() != reflect.Pointer || v.IsZero() { return ErrInvalidConfigObject } @@ -117,7 +117,7 @@ func Load(cfg any) error { v = reflect.Indirect(v) - for i := 0; i < v.NumField(); i++ { + for i := range v.NumField() { f := v.Type().Field(i) tag := f.Tag.Get(TagName) diff --git a/config/config_test.go b/config/config_test.go index aa2ba6b..0d9ba5d 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -17,13 +17,13 @@ import ( ) type Config struct { - LocalDebug bool `env:"DEBUG, false"` - Port int `env:"PORT, 3000"` - Interval time.Duration `env:"INTERVAL"` - TimeZone *time.Location `env:"TIMEZONE, America/Los_Angeles"` - DB string `env:"DB,,pg"` - MyUrl *url.URL `env:"MY_URL"` - Ignore string `env:"-" json:"-"` + LocalDebug bool `json:"LocalDebug" env:"DEBUG, false"` + Port int `json:"Port" env:"PORT, 3000"` + Interval time.Duration `json:"Interval" env:"INTERVAL"` + TimeZone *time.Location `json:"TimeZone" env:"TIMEZONE, America/Los_Angeles"` + DB string `json:"DB" env:"DB,,pg"` + MyURL *url.URL `json:"MyURL" env:"MY_URL"` + Ignore string `json:"-" env:"-"` } type InvalidConfig struct { @@ -41,7 +41,7 @@ func TestSetup(t *testing.T) { defaultConfigFile := config.File b, _ := json.Marshal(Config{}) defaultConfig := string(b) - configWithService := fmt.Sprintf(`{"LocalDebug":true,"Port":1234,"Interval":0,"TimeZone":{},"DB":"host=1.2.3.4 user=user password=pass dbname=dbname application_name=%s","MyUrl":{"Scheme":"https","Opaque":"","User":null,"Host":"www.google.com","Path":"","RawPath":"","OmitHost":false,"ForceQuery":false,"RawQuery":"a=b","Fragment":"","RawFragment":""}}`, filepath.Base(os.Args[0])) + configWithService := fmt.Sprintf(`{"LocalDebug":true,"Port":1234,"Interval":0,"TimeZone":{},"DB":"host=1.2.3.4 user=user password=pass dbname=dbname application_name=%s","MyURL":{"Scheme":"https","Opaque":"","User":null,"Host":"www.google.com","Path":"","Fragment":"","RawQuery":"a=b","RawPath":"","RawFragment":"","ForceQuery":false,"OmitHost":false}}`, filepath.Base(os.Args[0])) tests := []struct { name string @@ -51,11 +51,11 @@ func TestSetup(t *testing.T) { json string wantErr bool }{ - {"defaults", nil, &Config{}, nil, `{"LocalDebug":true,"Port":1234,"Interval":0,"TimeZone":{},"DB":"host=1.2.3.4 user=user password=pass dbname=dbname application_name=test","MyUrl":{"Scheme":"https","Opaque":"","User":null,"Host":"www.google.com","Path":"","RawPath":"","OmitHost":false,"ForceQuery":false,"RawQuery":"a=b","Fragment":"","RawFragment":""}}`, false}, - {"envOverride", nil, &Config{}, map[string]string{"INTERVAL": "15s", "DB_PORT": "1", "DB_MAX_CONN": "99", "DB_SSLMODE": "funky"}, `{"LocalDebug":true,"Port":1234,"Interval":15000000000,"TimeZone":{},"DB":"host=1.2.3.4 port=1 user=user password=pass dbname=dbname sslmode=funky pool_max_conns=99 application_name=test","MyUrl":{"Scheme":"https","Opaque":"","User":null,"Host":"www.google.com","Path":"","RawPath":"","OmitHost":false,"ForceQuery":false,"RawQuery":"a=b","Fragment":"","RawFragment":""}}`, false}, - {"EmptyEnv", func() { config.File = ".envEMPTY" }, &Config{}, nil, `{"LocalDebug":false,"Port":3000,"Interval":0,"TimeZone":{},"DB":"","MyUrl":null}`, false}, - {"InvalidTZ", nil, &Config{}, map[string]string{"TIMEZONE": "FOO"}, `{"LocalDebug":true,"Port":1234,"Interval":0,"TimeZone":null,"DB":"host=1.2.3.4 user=user password=pass dbname=dbname application_name=test","MyUrl":{"Scheme":"https","Opaque":"","User":null,"Host":"www.google.com","Path":"","RawPath":"","OmitHost":false,"ForceQuery":false,"RawQuery":"a=b","Fragment":"","RawFragment":""}}`, true}, - {"InvalidURL", nil, &Config{}, map[string]string{"MY_URL": "%"}, `{"LocalDebug":true,"Port":1234,"Interval":0,"TimeZone":{},"DB":"host=1.2.3.4 user=user password=pass dbname=dbname application_name=test","MyUrl":null}`, true}, + {"defaults", nil, &Config{}, nil, `{"LocalDebug":true,"Port":1234,"Interval":0,"TimeZone":{},"DB":"host=1.2.3.4 user=user password=pass dbname=dbname application_name=test","MyURL":{"Scheme":"https","Opaque":"","User":null,"Host":"www.google.com","Path":"","Fragment":"","RawQuery":"a=b","RawPath":"","RawFragment":"","ForceQuery":false,"OmitHost":false}}`, false}, + {"envOverride", nil, &Config{}, map[string]string{"INTERVAL": "15s", "DB_PORT": "1", "DB_MAX_CONN": "99", "DB_SSLMODE": "funky"}, `{"LocalDebug":true,"Port":1234,"Interval":15000000000,"TimeZone":{},"DB":"host=1.2.3.4 port=1 user=user password=pass dbname=dbname sslmode=funky pool_max_conns=99 application_name=test","MyURL":{"Scheme":"https","Opaque":"","User":null,"Host":"www.google.com","Path":"","Fragment":"","RawQuery":"a=b","RawPath":"","RawFragment":"","ForceQuery":false,"OmitHost":false}}`, false}, + {"EmptyEnv", func() { config.File = ".envEMPTY" }, &Config{}, nil, `{"LocalDebug":false,"Port":3000,"Interval":0,"TimeZone":{},"DB":"","MyURL":null}`, false}, + {"InvalidTZ", nil, &Config{}, map[string]string{"TIMEZONE": "FOO"}, `{"LocalDebug":true,"Port":1234,"Interval":0,"TimeZone":null,"DB":"host=1.2.3.4 user=user password=pass dbname=dbname application_name=test","MyURL":{"Scheme":"https","Opaque":"","User":null,"Host":"www.google.com","Path":"","Fragment":"","RawQuery":"a=b","RawPath":"","RawFragment":"","ForceQuery":false,"OmitHost":false}}`, true}, + {"InvalidURL", nil, &Config{}, map[string]string{"MY_URL": "%"}, `{"LocalDebug":true,"Port":1234,"Interval":0,"TimeZone":{},"DB":"host=1.2.3.4 user=user password=pass dbname=dbname application_name=test","MyURL":null}`, true}, {"BadConfig", nil, nil, nil, `null`, true}, {"BadFile", func() { config.File = ".envBAD" }, &Config{}, nil, defaultConfig, true}, {"BadResolver", func() { config.Resolvers = config.ResolverMap{} }, &Config{}, nil, defaultConfig, true}, @@ -79,7 +79,8 @@ func TestSetup(t *testing.T) { t.Setenv(k, v) } - if err := config.Load(tt.cfg); (err != nil) != tt.wantErr { + err := config.Load(tt.cfg) + if (err != nil) != tt.wantErr { t.Errorf("Load() error = %v, wantErr %v", err, tt.wantErr) } @@ -136,7 +137,8 @@ func TestComplex(t *testing.T) { t.Setenv(k, v) } - if err := config.Load(tt.cfg); (err != nil) != tt.wantErr { + err := config.Load(tt.cfg) + if (err != nil) != tt.wantErr { t.Errorf("Load() error = %v, wantErr %v", err, tt.wantErr) } @@ -155,7 +157,7 @@ func TestComplex(t *testing.T) { type ExampleConfig struct { DebugMode bool `env:"DEBUG, false"` Port int `env:"PORT, 3000"` - DB string `env:"DB,localhost,pg"` + DB string `env:"DB,,pg"` Test []string `env:"TEST_ARRAY"` } @@ -165,6 +167,11 @@ func ExampleLoad() { fmt.Printf("DebugMode=%v\n", cfg.DebugMode) fmt.Printf("Port=%v\n", cfg.Port) fmt.Printf("DB=%v\n", cfg.DB) + + // Output: + // DebugMode=false + // Port=3000 + // DB= } func TestFoo(t *testing.T) { @@ -173,8 +180,10 @@ func TestFoo(t *testing.T) { cfg := ExampleConfig{} - _ = config.Load(&cfg) - test := viper.GetStringSlice("TEST_ARRAY") + err := config.Load(&cfg) + if err != nil { + t.Fatal(err) + } + fmt.Printf("TEST=%#v\n", cfg.Test) - fmt.Printf("test=%#v\n", test) } diff --git a/config/pg.go b/config/pg.go index c0df53f..52b19e1 100644 --- a/config/pg.go +++ b/config/pg.go @@ -31,6 +31,7 @@ func appendIf(aa []string, key, value string) []string { // Set ApplicationName to override the value. func GetPgDBString(base string) string { var pairs []string + pairs = appendIf(pairs, "host", viper.GetString(base+"_HOST")) pairs = appendIf(pairs, "port", viper.GetString(base+"_PORT")) pairs = appendIf(pairs, "user", viper.GetString(base+"_USER")) diff --git a/dates/dates.go b/dates/dates.go index 35112b7..f737df4 100644 --- a/dates/dates.go +++ b/dates/dates.go @@ -1,12 +1,19 @@ package dates import ( + "fmt" "strings" "time" ) var nowFunc = time.Now +const ( + EOD = "EOD" + EOM = "EOM" + EOY = "EOY" +) + func EndOfDay(t time.Time) time.Time { year, month, day := t.Date() @@ -67,13 +74,13 @@ func TimeToTime(t time.Time, duration string, location *time.Location) (time.Tim } switch anchor { - case "EOD": + case EOD: t = EndOfDay(t) duration = duration[3:] - case "EOM": + case EOM: t = EndOfMonth(t) duration = duration[3:] - case "EOY": + case EOY: t = EndOfYear(t) duration = duration[3:] } @@ -84,7 +91,7 @@ func TimeToTime(t time.Time, duration string, location *time.Location) (time.Tim d, err := time.ParseDuration(duration) if err != nil { - return time.Time{}, err //nolint + return time.Time{}, fmt.Errorf("TimeToTime:%w", err) } return t.Add(d), nil diff --git a/errs/cause_test.go b/errs/cause_test.go index 08edbbf..645499c 100644 --- a/errs/cause_test.go +++ b/errs/cause_test.go @@ -14,19 +14,19 @@ type CauseTest struct { isNil bool } -type NilErr struct{} +type NilError struct{} -func (t NilErr) Cause() error { +func (t NilError) Cause() error { return nil } -func (t NilErr) Error() string { +func (t NilError) Error() string { return "" } func TestCause(t *testing.T) { err1 := errors.New("1") - nilErr := NilErr{} + nilErr := NilError{} tests := []CauseTest{ { diff --git a/errs/stack.go b/errs/stack.go index 323eb63..8597db6 100644 --- a/errs/stack.go +++ b/errs/stack.go @@ -69,6 +69,7 @@ func WithStack(e any, skip int) error { } var pcs [maxDepth]uintptr + n := runtime.Callers(skip+stackOffset, pcs[:]) var st stack = pcs[0:n] diff --git a/errs/stack_test.go b/errs/stack_test.go index d629ca4..3ecee25 100644 --- a/errs/stack_test.go +++ b/errs/stack_test.go @@ -108,6 +108,8 @@ type StackTest struct { } func (test StackTest) testErr(t *testing.T) bool { + t.Helper() + if test.err == nil { if test.isNil { return false @@ -124,6 +126,8 @@ func (test StackTest) testErr(t *testing.T) bool { } func (test StackTest) testFrameString(t *testing.T, ff []errs.Frame) { + t.Helper() + if len(ff) != len(test.stack) { t.Errorf("len(stack) expected %#v, got %#v", len(test.stack), len(ff)) return @@ -137,6 +141,8 @@ func (test StackTest) testFrameString(t *testing.T, ff []errs.Frame) { } func (test StackTest) testFramesMap(t *testing.T, ff []map[string]string) { + t.Helper() + if len(ff) != len(test.stack) { t.Errorf("len(stack) expected %#v, got %#v", len(test.stack), len(ff)) return diff --git a/forms/forms.go b/forms/forms.go index 2c2f448..d490257 100644 --- a/forms/forms.go +++ b/forms/forms.go @@ -46,10 +46,12 @@ func GetFile(r *http.Request, name string, required bool) (File, bool, error) { type LookupString func(key string) string +const Null = "null" + func GetString(lookup LookupString, name string, required bool) (string, bool, error) { s := lookup(name) - if s == "null" { + if s == Null { s = "" } diff --git a/forms/forms_test.go b/forms/forms_test.go index 819a039..7c00138 100644 --- a/forms/forms_test.go +++ b/forms/forms_test.go @@ -23,7 +23,7 @@ func TestGetFile(t *testing.T) { newRequest := func(notMultipart bool) (*http.Request, error) { if notMultipart { - return http.NewRequest("GET", "/ping", nil) + return http.NewRequest(http.MethodGet, "/ping", nil) } var data bytes.Buffer @@ -44,7 +44,7 @@ func TestGetFile(t *testing.T) { return nil, fmt.Errorf("error closing writer: for field: %w", err) } - req := httptest.NewRequest("POST", "/ping", &data) + req := httptest.NewRequest(http.MethodPost, "/ping", &data) req.Header.Set("Content-Type", w.FormDataContentType()) return req, nil @@ -109,7 +109,7 @@ func TestGetString(t *testing.T) { data = url.Values{key: []string{value}}.Encode() } - req := httptest.NewRequest("POST", "/ping", strings.NewReader(data)) + req := httptest.NewRequest(http.MethodPost, "/ping", strings.NewReader(data)) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") return req @@ -140,7 +140,7 @@ func TestGetString(t *testing.T) { contentType = w.FormDataContentType() } - req := httptest.NewRequest("POST", "/ping", &data) + req := httptest.NewRequest(http.MethodPost, "/ping", &data) req.Header.Set("Content-Type", contentType) return req, nil @@ -174,7 +174,7 @@ func TestGetString(t *testing.T) { params = fmt.Sprintf("?%s=%s", tt.key, tt.value) } - r := httptest.NewRequest("POST", "/BAR"+params, nil) + r := httptest.NewRequest(http.MethodPost, "/BAR"+params, nil) got, ok, err := GetString(r.FormValue, tt.form, tt.required) @@ -212,10 +212,10 @@ func TestGetInt32(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=123", nil), "foo", true, 123, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, 0, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, 0, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, 0, true, false}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=123", nil), "foo", true, 123, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, 0, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, 0, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, 0, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -238,12 +238,12 @@ func TestGetInt(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=123", nil), "foo", true, 123, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, 0, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, 0, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, 0, true, false}, - {"max", httptest.NewRequest("GET", "/BAR?foo=9223372036854775807", nil), "foo", true, 9223372036854775807, false, true}, - {"over max", httptest.NewRequest("GET", "/BAR?foo=19223372036854775807", nil), "foo", true, 0, true, false}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=123", nil), "foo", true, 123, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, 0, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, 0, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, 0, true, false}, + {"max", httptest.NewRequest(http.MethodGet, "/BAR?foo=9223372036854775807", nil), "foo", true, 9223372036854775807, false, true}, + {"over max", httptest.NewRequest(http.MethodGet, "/BAR?foo=19223372036854775807", nil), "foo", true, 0, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -266,12 +266,12 @@ func TestGetInt64(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=123", nil), "foo", true, 123, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, 0, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, 0, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, 0, true, false}, - {"max", httptest.NewRequest("GET", "/BAR?foo=9223372036854775807", nil), "foo", true, 9223372036854775807, false, true}, - {"over max", httptest.NewRequest("GET", "/BAR?foo=19223372036854775807", nil), "foo", true, 0, true, false}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=123", nil), "foo", true, 123, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, 0, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, 0, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, 0, true, false}, + {"max", httptest.NewRequest(http.MethodGet, "/BAR?foo=9223372036854775807", nil), "foo", true, 9223372036854775807, false, true}, + {"over max", httptest.NewRequest(http.MethodGet, "/BAR?foo=19223372036854775807", nil), "foo", true, 0, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -294,12 +294,12 @@ func TestGetBool(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=true", nil), "foo", true, true, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, false, true, false}, - {"required null", httptest.NewRequest("GET", "/BAR?foo=null", nil), "foo", true, false, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, false, false, false}, - {"not required null", httptest.NewRequest("GET", "/BAR?foo=null", nil), "foo", false, false, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, false, true, false}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=true", nil), "foo", true, true, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, false, true, false}, + {"required null", httptest.NewRequest(http.MethodGet, "/BAR?foo=null", nil), "foo", true, false, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, false, false, false}, + {"not required null", httptest.NewRequest(http.MethodGet, "/BAR?foo=null", nil), "foo", false, false, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, false, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -322,11 +322,11 @@ func TestGetInt32Array(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=123", nil), "foo", true, []int32{123}, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, nil, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, nil, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, nil, true, false}, - {"large", httptest.NewRequest("GET", "/BAR?foo=1,2,3,4", nil), "foo", true, []int32{1, 2, 3, 4}, false, true}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=123", nil), "foo", true, []int32{123}, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, nil, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, nil, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, nil, true, false}, + {"large", httptest.NewRequest(http.MethodGet, "/BAR?foo=1,2,3,4", nil), "foo", true, []int32{1, 2, 3, 4}, false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -349,11 +349,11 @@ func TestGetUUIDArray(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=123", nil), "foo", true, []int32{123}, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, nil, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, nil, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, nil, true, false}, - {"large", httptest.NewRequest("GET", "/BAR?foo=1,2,3,4", nil), "foo", true, []int32{1, 2, 3, 4}, false, true}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=123", nil), "foo", true, []int32{123}, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, nil, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, nil, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, nil, true, false}, + {"large", httptest.NewRequest(http.MethodGet, "/BAR?foo=1,2,3,4", nil), "foo", true, []int32{1, 2, 3, 4}, false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -376,10 +376,10 @@ func TestGetTime(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=2006-01-02T15:04:05Z", nil), "foo", true, time.Date(2006, 0o1, 0o2, 15, 4, 5, 0, time.UTC), false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, time.Time{}, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, time.Time{}, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=200601021504050700", nil), "foo", true, time.Time{}, true, false}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=2006-01-02T15:04:05Z", nil), "foo", true, time.Date(2006, 0o1, 0o2, 15, 4, 5, 0, time.UTC), false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, time.Time{}, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, time.Time{}, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=200601021504050700", nil), "foo", true, time.Time{}, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -404,10 +404,10 @@ func TestGetUUID(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=48ab873f-d4fc-4e2b-bf92-9440e431ff54", nil), "foo", true, testUUID, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, uuid.UUID{}, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, uuid.UUID{}, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, uuid.UUID{}, true, false}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=48ab873f-d4fc-4e2b-bf92-9440e431ff54", nil), "foo", true, testUUID, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, uuid.UUID{}, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, uuid.UUID{}, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, uuid.UUID{}, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -452,10 +452,10 @@ func TestGetEnum(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=bbb", nil), "foo", true, testEnumB, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, testEnumUnknown, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, testEnumUnknown, false, false}, - {"bad value", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, testEnumUnknown, false, true}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=bbb", nil), "foo", true, testEnumB, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, testEnumUnknown, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, testEnumUnknown, false, false}, + {"bad value", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, testEnumUnknown, false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -478,11 +478,11 @@ func TestGetEnumArray(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=bbb", nil), "foo", true, []TestEnum{testEnumB}, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, nil, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, nil, false, false}, - {"bad value", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, []TestEnum{testEnumUnknown}, false, true}, - {"all", httptest.NewRequest("GET", "/BAR?foo=aaa,bbb,ccc", nil), "foo", true, []TestEnum{testEnumA, testEnumB, testEnumC}, false, true}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=bbb", nil), "foo", true, []TestEnum{testEnumB}, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, nil, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, nil, false, false}, + {"bad value", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, []TestEnum{testEnumUnknown}, false, true}, + {"all", httptest.NewRequest(http.MethodGet, "/BAR?foo=aaa,bbb,ccc", nil), "foo", true, []TestEnum{testEnumA, testEnumB, testEnumC}, false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/go.mod b/go.mod index d8fc8b6..842f0b8 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/bir/iken -go 1.24.0 +go 1.26.0 require ( - github.com/go-viper/mapstructure/v2 v2.4.0 + github.com/go-viper/mapstructure/v2 v2.5.0 github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.7.6 + github.com/jackc/pgx/v5 v5.10.0 github.com/pkg/errors v0.9.1 - github.com/rs/zerolog v1.34.0 + github.com/rs/zerolog v1.35.1 github.com/spf13/cast v1.10.0 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 @@ -15,22 +15,21 @@ require ( require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect + github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4b129d6..23955a8 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,12 @@ -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -17,23 +15,20 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= -github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -41,9 +36,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= -github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= +github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= @@ -63,17 +57,12 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/httplog/request.go b/httplog/request.go index 843193f..7b8ae2f 100644 --- a/httplog/request.go +++ b/httplog/request.go @@ -75,6 +75,7 @@ func RequestLogger(shouldLog FnShouldLog) func(http.Handler) http.Handler { //no start := now() var logRequest, logRequestBody, logResponse bool + logRequest = true toLogLevel := StatusToLogLevel diff --git a/httplog/request_test.go b/httplog/request_test.go index e526297..3483fe9 100644 --- a/httplog/request_test.go +++ b/httplog/request_test.go @@ -75,7 +75,7 @@ func TestRequestLogger(t *testing.T) { got := logOutput.String() if len(got) < 1 { - assert.True(t, len(tt.want) < 1, "got empty data, expected logs") + assert.Empty(t, tt.want, "got empty data, expected logs") return } diff --git a/httputil/auth.go b/httputil/auth.go index 170eaf3..1c4538e 100644 --- a/httputil/auth.go +++ b/httputil/auth.go @@ -336,6 +336,7 @@ type ClientSecurityGroup[T any] []ClientAuthenticateFunc[T] // Auth authenticates a client request with all the authhenticate functions or returns the first failure. func (s ClientSecurityGroup[T]) Auth(r *http.Request, innerClient *http.Client, u T) (*http.Client, error) { var err error + outerClient := innerClient modifiedReq := r.Clone(r.Context()) diff --git a/httputil/body_test.go b/httputil/body_test.go index 0b6c6c7..784080c 100644 --- a/httputil/body_test.go +++ b/httputil/body_test.go @@ -14,27 +14,30 @@ import ( ) type TestObject struct { - ID string - Count int + ID string `json:"id"` + Count int `json:"count"` } func (p *TestObject) UnmarshalJSON(b []byte) error { var requiredCheck map[string]any - if err := json.Unmarshal(b, &requiredCheck); err != nil { + err := json.Unmarshal(b, &requiredCheck) + if err != nil { return validation.Error{err.Error(), fmt.Errorf("TestObject.UnmarshalJSON Required: `%v`: %w", string(b), err)} } var validationErrors validation.Errors - if _, ok := requiredCheck["ID"]; !ok { + _, ok := requiredCheck["ID"] + if !ok { return validationErrors.Add("message_id", "missing required field") } type TestObjectJSON TestObject var parseObject TestObjectJSON - if err := json.Unmarshal(b, &parseObject); err != nil { + err = json.Unmarshal(b, &parseObject) + if err != nil { return validation.Error{err.Error(), fmt.Errorf("Message.UnmarshalJSON: `%v`: %w", string(b), err)} } @@ -43,10 +46,6 @@ func (p *TestObject) UnmarshalJSON(b []byte) error { return nil } -func strP(s string) *string { - return &s -} - type BadIOReader struct { err error } @@ -64,11 +63,11 @@ func TestGetJSONBody(t *testing.T) { wantErr bool }{ {"no body", nil, nil, nil, true}, - {"string", bytes.NewBufferString(`"foo"`), strP(""), strP("foo"), false}, - {"invalid json", bytes.NewBufferString(`{"foo"`), strP(""), strP(""), true}, - {"empty", bytes.NewBufferString(``), strP(""), strP(""), true}, - {"EOF", &BadIOReader{io.EOF}, strP(""), strP(""), true}, - {"read error", &BadIOReader{io.ErrClosedPipe}, strP(""), strP(""), true}, + {"string", bytes.NewBufferString(`"foo"`), new(""), new("foo"), false}, + {"invalid json", bytes.NewBufferString(`{"foo"`), new(""), new(""), true}, + {"empty", bytes.NewBufferString(``), new(""), new(""), true}, + {"EOF", &BadIOReader{io.EOF}, new(""), new(""), true}, + {"read error", &BadIOReader{io.ErrClosedPipe}, new(""), new(""), true}, {"null body", bytes.NewBufferString(`null`), &TestObject{}, &TestObject{}, true}, {"validation error - bad ID type", bytes.NewBufferString(`{"ID":1}`), &TestObject{}, &TestObject{}, true}, {"validations error - no ID", bytes.NewBufferString(`{}`), &TestObject{}, &TestObject{}, true}, diff --git a/httputil/dump.go b/httputil/dump.go index 0238a72..3da5ed2 100644 --- a/httputil/dump.go +++ b/httputil/dump.go @@ -9,6 +9,8 @@ import ( "strings" ) +const ChunkedEncoding = "chunked" + // Ported from go stdlib httputil.dump. Tweaked to split the header and body into separate functions for more // flexible logging. Header is returned as a map[string]string for ease of handling. Strictly a logging utility for // inbound requests. @@ -22,7 +24,7 @@ func DumpHeader(req *http.Request) map[string]string { reqURI = req.URL.RequestURI() } - out[valueOrDefault(req.Method, "GET")] = fmt.Sprintf("%s HTTP/%d.%d", reqURI, req.ProtoMajor, req.ProtoMinor) + out[valueOrDefault(req.Method, http.MethodGet)] = fmt.Sprintf("%s HTTP/%d.%d", reqURI, req.ProtoMajor, req.ProtoMinor) absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://") if !absRequestURI { @@ -66,7 +68,7 @@ func DumpBody(req *http.Request) ([]byte, error) { return nil, err } - chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" + chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == ChunkedEncoding var b bytes.Buffer diff --git a/httputil/dump_test.go b/httputil/dump_test.go index 8b961af..1bdcf95 100644 --- a/httputil/dump_test.go +++ b/httputil/dump_test.go @@ -14,12 +14,6 @@ import ( "time" ) -type eofReader struct{} - -func (n eofReader) Close() error { return nil } - -func (n eofReader) Read([]byte) (int, error) { return 0, io.EOF } - type dumpTest struct { Name string // Either Req or GetReq can be set/nil but not both. @@ -49,7 +43,7 @@ var dumpTests = []dumpTest{ { Name: "HTTP/1.1 => chunked coding; body; empty trailer", Req: &http.Request{ - Method: "GET", + Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "www.google.com", @@ -57,22 +51,22 @@ var dumpTests = []dumpTest{ }, ProtoMajor: 1, ProtoMinor: 1, - TransferEncoding: []string{"chunked"}, + TransferEncoding: []string{ChunkedEncoding}, }, Body: []byte("abcdef"), WantHeader: map[string]string{ - "GET": "/search HTTP/1.1", + http.MethodGet: "/search HTTP/1.1", "Host": "www.google.com", - "Transfer-Encoding": "chunked", + "Transfer-Encoding": ChunkedEncoding, }, WantBody: chunk("abcdef") + chunk(""), }, { Name: "Verify that DumpRequest preserves the HTTP version number, doesn't add a Host", Req: &http.Request{ - Method: "GET", + Method: http.MethodGet, URL: mustParseURL("/foo"), ProtoMajor: 1, ProtoMinor: 0, @@ -82,8 +76,8 @@ var dumpTests = []dumpTest{ }, WantHeader: map[string]string{ - "GET": "/foo HTTP/1.0", - "X-Foo": "X-Bar", + http.MethodGet: "/foo HTTP/1.0", + "X-Foo": "X-Bar", }, }, { @@ -100,8 +94,8 @@ var dumpTests = []dumpTest{ }, WantHeader: map[string]string{ - "GET": "/search HTTP/1.1", - "Host": "www.google.com", + http.MethodGet: "/search HTTP/1.1", + "Host": "www.google.com", }, MustError: true, }, @@ -119,15 +113,15 @@ var dumpTests = []dumpTest{ }, WantHeader: map[string]string{ - "GET": "/search HTTP/1.1", - "Host": "www.google.com", + http.MethodGet: "/search HTTP/1.1", + "Host": "www.google.com", }, MustError: true, }, { Name: "Request with Body > 8196 (default buffer size)", Req: &http.Request{ - Method: "POST", + Method: http.MethodPost, URL: &url.URL{ Scheme: "http", Host: "post.tld", @@ -144,7 +138,7 @@ var dumpTests = []dumpTest{ Body: bytes.Repeat([]byte("a"), 8193), WantHeader: map[string]string{ - "POST": "/ HTTP/1.1", + http.MethodPost: "/ HTTP/1.1", "Host": "post.tld", "Content-Length": "8193", }, @@ -159,8 +153,8 @@ var dumpTests = []dumpTest{ "User-Agent: blah\r\n\r\n") }, WantHeader: map[string]string{ - "GET": "http://foo.com/ HTTP/1.1", - "User-Agent": "blah", + http.MethodGet: "http://foo.com/ HTTP/1.1", + "User-Agent": "blah", }, }, @@ -173,7 +167,7 @@ var dumpTests = []dumpTest{ "\r\nkey1=name1&key2=name2") }, WantHeader: map[string]string{ - "POST": "/v2/api/?login HTTP/1.1", + http.MethodPost: "/v2/api/?login HTTP/1.1", "Host": "passport.myhost.com", "Content-Length": "3", }, @@ -188,7 +182,7 @@ var dumpTests = []dumpTest{ "\r\nkey1=name1&key2=name2") }, WantHeader: map[string]string{ - "POST": "/v2/api/?login HTTP/1.1", + http.MethodPost: "/v2/api/?login HTTP/1.1", "Host": "passport.myhost.com", "Content-Length": "0", }, @@ -203,8 +197,8 @@ var dumpTests = []dumpTest{ "\r\nkey1=name1&key2=name2") }, WantHeader: map[string]string{ - "POST": "/v2/api/?login HTTP/1.1", - "Host": "passport.myhost.com", + http.MethodPost: "/v2/api/?login HTTP/1.1", + "Host": "passport.myhost.com", }, }, { @@ -215,8 +209,8 @@ var dumpTests = []dumpTest{ "\r\nkey1=name1&key2=name2") }, WantHeader: map[string]string{ - "POST": "/v2/api/?login HTTP/1.1", - "Host": "passport.myhost.com", + http.MethodPost: "/v2/api/?login HTTP/1.1", + "Host": "passport.myhost.com", }, }, } @@ -229,29 +223,7 @@ func TestDumpRequest(t *testing.T) { continue } - freshReq := func(ti dumpTest) *http.Request { - req := ti.Req - if req == nil { - req = ti.GetReq() - } - - if req.Header == nil { - req.Header = make(http.Header) - } - - if ti.Body == nil { - return req - } - switch b := ti.Body.(type) { - case []byte: - req.Body = io.NopCloser(bytes.NewReader(b)) - case func() io.ReadCloser: - req.Body = b() - default: - t.Fatalf("Test %q: unsupported Body of %T", tt.Name, ti.Body) - } - return req - } + freshReq := requestMaker(t, tt) req := freshReq(tt) got := DumpHeader(req) @@ -279,7 +251,6 @@ func TestDumpRequest(t *testing.T) { } continue } - } // Validate we haven't leaked any goroutines. @@ -300,10 +271,42 @@ func TestDumpRequest(t *testing.T) { t.Errorf("Unexpectedly large number of new goroutines: %d new: %s", dg, buf) } +func requestMaker(t *testing.T, tt dumpTest) func(ti dumpTest) *http.Request { + t.Helper() + + return func(ti dumpTest) *http.Request { + t.Helper() + + req := ti.Req + if req == nil { + req = ti.GetReq() + } + + if req.Header == nil { + req.Header = make(http.Header) + } + + if ti.Body == nil { + return req + } + switch b := ti.Body.(type) { + case []byte: + req.Body = io.NopCloser(bytes.NewReader(b)) + case func() io.ReadCloser: + req.Body = b() + default: + t.Fatalf("Test %q: unsupported Body of %T", tt.Name, ti.Body) + } + return req + } +} + // deadline returns the time which is needed before t.Deadline() // if one is configured, and it is s greater than needed in the future, // otherwise defaultDelay from the current time. func deadline(t *testing.T, defaultDelay, needed time.Duration) time.Time { + t.Helper() + if dl, ok := t.Deadline(); ok { if dl = dl.Add(-needed); dl.After(time.Now()) { // Allow an arbitrarily long delay. diff --git a/httputil/errors_test.go b/httputil/errors_test.go index 81a9794..2e8bcbe 100644 --- a/httputil/errors_test.go +++ b/httputil/errors_test.go @@ -83,7 +83,7 @@ func TestErrorHandler(t *testing.T) { var log errorLog err := json.Unmarshal(logOutput.Bytes(), &log) - assert.Nil(t, err) + assert.NoError(t, err) assert.Equal(t, test.logMessage, log.Msg, logOutput.String()) }) } diff --git a/httputil/wrap_writer_test.go b/httputil/wrap_writer_test.go index 97258d5..1fe565f 100644 --- a/httputil/wrap_writer_test.go +++ b/httputil/wrap_writer_test.go @@ -3,7 +3,7 @@ package httputil_test import ( "bufio" "bytes" - "fmt" + "errors" "io" "net" "net/http" @@ -79,18 +79,18 @@ func TestNewWrapResponse(t *testing.T) { assert.Error(t, err, "Hijack Not Implemented") } -func NewFancy() fancyWriter { - return fancyWriter{ResponseRecorder: httptest.NewRecorder()} -} - type fancyWriter struct { *httptest.ResponseRecorder } +func NewFancy() fancyWriter { + return fancyWriter{ResponseRecorder: httptest.NewRecorder()} +} + func (_ fancyWriter) Flush() {} func (_ fancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return nil, nil, fmt.Errorf("not implemented") + return nil, nil, errors.ErrUnsupported } func (w fancyWriter) ReadFrom(r io.Reader) (n int64, err error) { diff --git a/params/params_test.go b/params/params_test.go index 4ddb52f..d36420f 100644 --- a/params/params_test.go +++ b/params/params_test.go @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func NewQueryRequest(method, target, key, value string) *http.Request { @@ -44,7 +45,7 @@ func NewMultiSourceRequest(method, target, key string, values [4]string) *http.R func TestGetString(t *testing.T) { newHeaderRequest := func(key, value string) *http.Request { - r := httptest.NewRequest("GET", "/ping", nil) + r := httptest.NewRequest(http.MethodGet, "/ping", nil) if key != "" { r.Header.Set(key, value) } @@ -87,10 +88,10 @@ func TestGetInt32(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=123", nil), "foo", true, 123, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, 0, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, 0, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, 0, true, false}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=123", nil), "foo", true, 123, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, 0, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, 0, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, 0, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -113,12 +114,12 @@ func TestGetInt(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=123", nil), "foo", true, 123, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, 0, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, 0, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, 0, true, false}, - {"max", httptest.NewRequest("GET", "/BAR?foo=9223372036854775807", nil), "foo", true, 9223372036854775807, false, true}, - {"over max", httptest.NewRequest("GET", "/BAR?foo=19223372036854775807", nil), "foo", true, 0, true, false}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=123", nil), "foo", true, 123, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, 0, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, 0, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, 0, true, false}, + {"max", httptest.NewRequest(http.MethodGet, "/BAR?foo=9223372036854775807", nil), "foo", true, 9223372036854775807, false, true}, + {"over max", httptest.NewRequest(http.MethodGet, "/BAR?foo=19223372036854775807", nil), "foo", true, 0, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -141,12 +142,12 @@ func TestGetInt64(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=123", nil), "foo", true, 123, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, 0, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, 0, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, 0, true, false}, - {"max", httptest.NewRequest("GET", "/BAR?foo=9223372036854775807", nil), "foo", true, 9223372036854775807, false, true}, - {"over max", httptest.NewRequest("GET", "/BAR?foo=19223372036854775807", nil), "foo", true, 0, true, false}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=123", nil), "foo", true, 123, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, 0, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, 0, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, 0, true, false}, + {"max", httptest.NewRequest(http.MethodGet, "/BAR?foo=9223372036854775807", nil), "foo", true, 9223372036854775807, false, true}, + {"over max", httptest.NewRequest(http.MethodGet, "/BAR?foo=19223372036854775807", nil), "foo", true, 0, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -169,10 +170,10 @@ func TestGetBool(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=true", nil), "foo", true, true, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, false, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, false, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, false, true, false}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=true", nil), "foo", true, true, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, false, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, false, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, false, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -195,11 +196,11 @@ func TestGetInt32Array(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=123", nil), "foo", true, []int32{123}, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, nil, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, nil, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, nil, true, false}, - {"large", httptest.NewRequest("GET", "/BAR?foo=1,2,3,4", nil), "foo", true, []int32{1, 2, 3, 4}, false, true}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=123", nil), "foo", true, []int32{123}, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, nil, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, nil, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, nil, true, false}, + {"large", httptest.NewRequest(http.MethodGet, "/BAR?foo=1,2,3,4", nil), "foo", true, []int32{1, 2, 3, 4}, false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -224,12 +225,12 @@ func TestGetUUIDArray(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo="+id1.String(), nil), "foo", true, []uuid.UUID{id1}, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, nil, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, nil, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, nil, true, false}, - {"large", httptest.NewRequest("GET", fmt.Sprintf("/BAR?foo=%s,%s,%s", id1.String(), id2.String(), id3.String()), nil), "foo", true, []uuid.UUID{id1, id2, id3}, false, true}, - {"large repeated", httptest.NewRequest("GET", fmt.Sprintf("/BAR?foo=%s&foo=%s,,,%s", id1.String(), id2.String(), id3.String()), nil), "foo", true, []uuid.UUID{id1, id2, id3}, false, true}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo="+id1.String(), nil), "foo", true, []uuid.UUID{id1}, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, nil, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, nil, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, nil, true, false}, + {"large", httptest.NewRequest(http.MethodGet, fmt.Sprintf("/BAR?foo=%s,%s,%s", id1.String(), id2.String(), id3.String()), nil), "foo", true, []uuid.UUID{id1, id2, id3}, false, true}, + {"large repeated", httptest.NewRequest(http.MethodGet, fmt.Sprintf("/BAR?foo=%s&foo=%s,,,%s", id1.String(), id2.String(), id3.String()), nil), "foo", true, []uuid.UUID{id1, id2, id3}, false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -252,10 +253,10 @@ func TestGetTime(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=2006-01-02T15:04:05Z", nil), "foo", true, time.Date(2006, 0o1, 0o2, 15, 4, 5, 0, time.UTC), false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, time.Time{}, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, time.Time{}, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=200601021504050700", nil), "foo", true, time.Time{}, true, false}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=2006-01-02T15:04:05Z", nil), "foo", true, time.Date(2006, 0o1, 0o2, 15, 4, 5, 0, time.UTC), false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, time.Time{}, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, time.Time{}, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=200601021504050700", nil), "foo", true, time.Time{}, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -280,10 +281,10 @@ func TestGetUUID(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=48ab873f-d4fc-4e2b-bf92-9440e431ff54", nil), "foo", true, testUUID, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, uuid.UUID{}, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, uuid.UUID{}, false, false}, - {"bad format", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, uuid.UUID{}, true, false}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=48ab873f-d4fc-4e2b-bf92-9440e431ff54", nil), "foo", true, testUUID, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, uuid.UUID{}, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, uuid.UUID{}, false, false}, + {"bad format", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, uuid.UUID{}, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -297,15 +298,15 @@ func TestGetUUID(t *testing.T) { } func TestURLParam(t *testing.T) { - r, _ := http.NewRequest("GET", "/", nil) + r, _ := http.NewRequest(http.MethodGet, "/", nil) r.SetPathValue("id", "12345") got, ok, err := GetInt(r, "id", true) - assert.Nil(t, err) + assert.NoError(t, err) assert.NotEmpty(t, got) assert.True(t, ok) - assert.Equal(t, got, 12345) + assert.Equal(t, 12345, got) } type TestEnum int8 @@ -340,10 +341,10 @@ func TestGetEnum(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=bbb", nil), "foo", true, testEnumB, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, testEnumUnknown, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, testEnumUnknown, false, false}, - {"bad value", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, testEnumUnknown, false, true}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=bbb", nil), "foo", true, testEnumB, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, testEnumUnknown, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, testEnumUnknown, false, false}, + {"bad value", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, testEnumUnknown, false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -366,11 +367,11 @@ func TestGetEnumArray(t *testing.T) { wantErr bool wantOk bool }{ - {"simple", httptest.NewRequest("GET", "/BAR?foo=bbb", nil), "foo", true, []TestEnum{testEnumB}, false, true}, - {"required missing", httptest.NewRequest("GET", "/BAR", nil), "foo", true, nil, true, false}, - {"not required missing", httptest.NewRequest("GET", "/BAR?", nil), "foo", false, nil, false, false}, - {"bad value", httptest.NewRequest("GET", "/BAR?foo=a123", nil), "foo", true, []TestEnum{testEnumUnknown}, false, true}, - {"all", httptest.NewRequest("GET", "/BAR?foo=aaa,bbb,ccc", nil), "foo", true, []TestEnum{testEnumA, testEnumB, testEnumC}, false, true}, + {"simple", httptest.NewRequest(http.MethodGet, "/BAR?foo=bbb", nil), "foo", true, []TestEnum{testEnumB}, false, true}, + {"required missing", httptest.NewRequest(http.MethodGet, "/BAR", nil), "foo", true, nil, true, false}, + {"not required missing", httptest.NewRequest(http.MethodGet, "/BAR?", nil), "foo", false, nil, false, false}, + {"bad value", httptest.NewRequest(http.MethodGet, "/BAR?foo=a123", nil), "foo", true, []TestEnum{testEnumUnknown}, false, true}, + {"all", httptest.NewRequest(http.MethodGet, "/BAR?foo=aaa,bbb,ccc", nil), "foo", true, []TestEnum{testEnumA, testEnumB, testEnumC}, false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -663,7 +664,7 @@ func TestMatrix(t *testing.T) { }{ { Name: "required present", - Request: RequestFunctions[source]("GET", "/BAR", "foo", typeInfo.TestValueAsString), + Request: RequestFunctions[source](http.MethodGet, "/BAR", "foo", typeInfo.TestValueAsString), Method: typeInfo.Methods[source], Required: true, Want: typeInfo.TestValue, @@ -671,21 +672,21 @@ func TestMatrix(t *testing.T) { }, { Name: "required missing", - Request: RequestFunctions[source]("GET", "/BAR", "", ""), + Request: RequestFunctions[source](http.MethodGet, "/BAR", "", ""), Method: typeInfo.Methods[source], Required: true, WantErr: true, }, { Name: "optional missing", - Request: RequestFunctions[source]("GET", "/BAR", "", ""), + Request: RequestFunctions[source](http.MethodGet, "/BAR", "", ""), Method: typeInfo.Methods[source], Required: false, WantOk: false, }, { Name: "ignore other sources", - Request: NewMultiSourceRequest("GET", "/BAR", "foo", multiSourceValues), + Request: NewMultiSourceRequest(http.MethodGet, "/BAR", "foo", multiSourceValues), Method: typeInfo.Methods[source], Want: typeInfo.TestValue, WantOk: true, @@ -693,17 +694,18 @@ func TestMatrix(t *testing.T) { } for _, tt := range perTypeTests { - t.Run("Get"+typeInfo.Name+ParamSourceNames[source]+"_"+tt.Name, func(t *testing.T) { + t.Run(http.MethodGet+typeInfo.Name+ParamSourceNames[source]+"_"+tt.Name, func(t *testing.T) { got, ok, err := tt.Method(tt.Request, "foo", tt.Required) - if tt.WantErr { + switch { + case tt.WantErr: assert.Error(t, err) - } else if tt.WantOk { + case tt.WantOk: assert.NoError(t, err) assert.True(t, ok) assert.Equal(t, tt.Want, got) - } else { - assert.NoError(t, err) + default: + require.NoError(t, err) assert.False(t, ok) } }) diff --git a/validation/error_test.go b/validation/error_test.go index e41f1e4..a1d4570 100644 --- a/validation/error_test.go +++ b/validation/error_test.go @@ -8,7 +8,6 @@ import ( ) func TestError_Error(t *testing.T) { - type fields struct{} tests := []struct { name string Message string diff --git a/validation/errors_test.go b/validation/errors_test.go index f7ac5f6..b55d525 100644 --- a/validation/errors_test.go +++ b/validation/errors_test.go @@ -43,7 +43,7 @@ func TestErrors_Add(t *testing.T) { assert.Equal(t, tt.want, got.Error()) b, err := json.Marshal(got.Fields()) - assert.Nil(t, err) + assert.NoError(t, err) assert.Equal(t, tt.wantJson, string(b)) }) @@ -74,14 +74,14 @@ func TestErrors_GetErr(t *testing.T) { _ = ee.Add("a", errB) - assert.NotNil(t, ee.GetErr()) + assert.Error(t, ee.GetErr()) assert.Equal(t, "a: b.", ee.GetErr().Error()) assert.ErrorIs(t, ee.GetErr(), errB) } func TestErrors_New(t *testing.T) { err := validation.New("a", "b") - assert.NotEmpty(t, err) + assert.Error(t, err) assert.Equal(t, "a: b.", err.Error()) } diff --git a/worker/fanout.go b/worker/fanout.go index 41213cb..36ba12a 100644 --- a/worker/fanout.go +++ b/worker/fanout.go @@ -28,16 +28,12 @@ func (f *FanOut[I]) Invoke(input I) { func (f *FanOut[I]) Process(p ProcessorFunc[I]) { wg := sync.WaitGroup{} - for i := uint(0); i < f.workerCount; i++ { - wg.Add(1) - - go func() { + for range f.workerCount { + wg.Go(func() { for i := range f.inputs { p(i) } - - wg.Done() - }() + }) } wg.Wait() diff --git a/worker/fanout_test.go b/worker/fanout_test.go index 7a219d7..237cae5 100644 --- a/worker/fanout_test.go +++ b/worker/fanout_test.go @@ -13,7 +13,7 @@ import ( func testInts(ct int) []int { out := make([]int, ct) - for i := 0; i < ct; i++ { + for i := range ct { out[i] = i + 1 } @@ -21,6 +21,8 @@ func testInts(ct int) []int { } func TestNewFanOut(t *testing.T) { + t.Parallel() + tests := []struct { name string workerCount uint @@ -63,8 +65,6 @@ func TestNewFanOut(t *testing.T) { testInts(16), 136, }, - - // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/worker/hashedfanout_test.go b/worker/hashedfanout_test.go index 064505c..49e7374 100644 --- a/worker/hashedfanout_test.go +++ b/worker/hashedfanout_test.go @@ -55,9 +55,7 @@ func TestNewHashedFanOut(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - hasher := worker.StringHasher(func(i int) string { - return strconv.Itoa(i) - }) + hasher := worker.StringHasher(strconv.Itoa) w := worker.NewHashedFanOut[int](tt.workerCount, tt.bufferSize, hasher) go func() { From 2c1c14485730a0b3e7e42c61c1e51ee682096919 Mon Sep 17 00:00:00 2001 From: Marc Bir Date: Fri, 24 Jul 2026 18:35:22 -0700 Subject: [PATCH 2/4] update GHA versions --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 26e7f07..372f86f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,12 +7,12 @@ jobs: build-test: strategy: matrix: - go-version: [ 1.23, 1.24 ] + go-version: [ 1.25, 1.26 ] os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: go-version: ${{ matrix.go-version }} @@ -21,8 +21,8 @@ jobs: - name: Test run: go test -v ./... -coverprofile=coverage.txt -covermode=count - name: Coverage Report - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v7 with: files: ./coverage.txt - name: lint - uses: golangci/golangci-lint-action@v7 + uses: golangci/golangci-lint-action@v9 From 1512a45401132c728ef9fea37fdfa434a71e4033 Mon Sep 17 00:00:00 2001 From: Marc Bir Date: Fri, 24 Jul 2026 18:37:31 -0700 Subject: [PATCH 3/4] only supporting go 1.26 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 372f86f..854b8d4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ jobs: build-test: strategy: matrix: - go-version: [ 1.25, 1.26 ] + go-version: [ 1.26 ] os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} steps: From 190c48f7ef4f664dadf9e04feaebe7e68cc09093 Mon Sep 17 00:00:00 2001 From: Marc Bir Date: Fri, 24 Jul 2026 18:46:26 -0700 Subject: [PATCH 4/4] use explicit result on cache NoOp --- cache/noop.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cache/noop.go b/cache/noop.go index 2e428ad..d4d5a71 100644 --- a/cache/noop.go +++ b/cache/noop.go @@ -14,8 +14,8 @@ func (c *NoOp[K, V]) Set(_ K, _ V) { } // Get always returns !ok. -func (c *NoOp[K, V]) Get(_ K) (out V, ok bool) { //nolint: ireturn,nonamedreturns - return out, ok +func (c *NoOp[K, V]) Get(_ K) (V, bool) { + return *new(V), false } // Keys always returns nil array.