diff --git a/.golangci.yml b/.golangci.yml index 37a32744..de70317d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -164,7 +164,8 @@ issues: linters: disable: - - spancheck + - spancheck # opentelemetry, irrelevant + - tagalign # requires awkward manual formatting of struct tags enable: - asasalint - asciicheck @@ -199,7 +200,7 @@ linters: - grouper - inamedparam - loggercheck - # - mirror # TODO https://github.com/gofiber/fiber/issues/2816 + - mirror - misspell - nakedret - nilerr @@ -208,7 +209,7 @@ linters: - nolintlint - nonamedreturns - nosprintfhostport - # - perfsprint # TODO https://github.com/gofiber/fiber/issues/2816 + - perfsprint - predeclared - promlinter - reassign @@ -217,7 +218,6 @@ linters: - sqlclosecheck - staticcheck - stylecheck - # - tagalign # TODO https://github.com/gofiber/fiber/issues/2816 - tagliatelle - testifylint # - testpackage # TODO: Enable once https://github.com/gofiber/fiber/issues/2252 is implemented diff --git a/addon/retry/exponential_backoff_test.go b/addon/retry/exponential_backoff_test.go index 6c9d0fdc..370f90a0 100644 --- a/addon/retry/exponential_backoff_test.go +++ b/addon/retry/exponential_backoff_test.go @@ -1,7 +1,7 @@ package retry import ( - "fmt" + "errors" "testing" "time" @@ -44,9 +44,9 @@ func TestExponentialBackoff_Retry(t *testing.T) { MaxRetryCount: 5, }, f: func() error { - return fmt.Errorf("failed function") + return errors.New("failed function") }, - expErr: fmt.Errorf("failed function"), + expErr: errors.New("failed function"), }, } diff --git a/app.go b/app.go index d7bb6abd..f681ae2c 100644 --- a/app.go +++ b/app.go @@ -897,7 +897,7 @@ func (app *App) ShutdownWithContext(ctx context.Context) error { app.mutex.Lock() defer app.mutex.Unlock() if app.server == nil { - return fmt.Errorf("shutdown: server is not running") + return ErrNotRunning } return app.server.ShutdownWithContext(ctx) } @@ -948,7 +948,7 @@ func (app *App) Test(req *http.Request, msTimeout ...int) (*http.Response, error var returned bool defer func() { if !returned { - channel <- fmt.Errorf("runtime.Goexit() called in handler or server panic") + channel <- ErrHandlerExited } }() @@ -1071,10 +1071,7 @@ func (app *App) ErrorHandler(ctx Ctx, err error) error { // errors before calling the application's error handler method. func (app *App) serverErrorHandler(fctx *fasthttp.RequestCtx, err error) { // Acquire Ctx with fasthttp request from pool - c, ok := app.AcquireCtx().(*DefaultCtx) - if !ok { - panic(fmt.Errorf("failed to type-assert to *DefaultCtx")) - } + c := app.AcquireCtx() c.Reset(fctx) defer app.ReleaseCtx(c) diff --git a/app_test.go b/app_test.go index 7cfaa589..81d7650e 100644 --- a/app_test.go +++ b/app_test.go @@ -1672,7 +1672,7 @@ func Test_App_ReadBodyStream(t *testing.T) { require.NoError(t, err, "app.Test(req)") body, err := io.ReadAll(resp.Body) require.NoError(t, err, "io.ReadAll(resp.Body)") - require.Equal(t, fmt.Sprintf("true %s", testString), string(body)) + require.Equal(t, "true "+testString, string(body)) } func Test_App_DisablePreParseMultipartForm(t *testing.T) { @@ -1688,7 +1688,7 @@ func Test_App_DisablePreParseMultipartForm(t *testing.T) { return err } if !req.IsBodyStream() { - return fmt.Errorf("not a body stream") + return errors.New("not a body stream") } file, err := mpf.File["test"][0].Open() if err != nil { @@ -1700,7 +1700,7 @@ func Test_App_DisablePreParseMultipartForm(t *testing.T) { return fmt.Errorf("failed to read: %w", err) } if n != len(testString) { - return fmt.Errorf("bad read length") + return errors.New("bad read length") } return c.Send(buffer) }) diff --git a/bind_test.go b/bind_test.go index 7080c311..d8b3efe6 100644 --- a/bind_test.go +++ b/bind_test.go @@ -1556,7 +1556,7 @@ func (*structValidator) ValidateStruct(out any) error { out = reflect.ValueOf(out).Elem().Interface() sq, ok := out.(simpleQuery) if !ok { - return fmt.Errorf("failed to type-assert to simpleQuery") + return errors.New("failed to type-assert to simpleQuery") } if sq.Name != "john" { diff --git a/client.go b/client.go index 04957693..8825f9d8 100644 --- a/client.go +++ b/client.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/json" "encoding/xml" + "errors" "fmt" "io" "mime/multipart" @@ -885,7 +886,7 @@ func AcquireClient() *Client { } c, ok := v.(*Client) if !ok { - panic(fmt.Errorf("failed to type-assert to *Client")) + panic(errors.New("failed to type-assert to *Client")) } return c } @@ -911,7 +912,7 @@ func ReleaseClient(c *Client) { func AcquireAgent() *Agent { a, ok := agentPool.Get().(*Agent) if !ok { - panic(fmt.Errorf("failed to type-assert to *Agent")) + panic(errors.New("failed to type-assert to *Agent")) } return a } @@ -938,7 +939,7 @@ func AcquireResponse() *Response { } r, ok := v.(*Response) if !ok { - panic(fmt.Errorf("failed to type-assert to *Response")) + panic(errors.New("failed to type-assert to *Response")) } return r } @@ -965,7 +966,7 @@ func AcquireArgs() *Args { } a, ok := v.(*Args) if !ok { - panic(fmt.Errorf("failed to type-assert to *Args")) + panic(errors.New("failed to type-assert to *Args")) } return a } @@ -990,7 +991,7 @@ func AcquireFormFile() *FormFile { } ff, ok := v.(*FormFile) if !ok { - panic(fmt.Errorf("failed to type-assert to *FormFile")) + panic(errors.New("failed to type-assert to *FormFile")) } return ff } diff --git a/client_test.go b/client_test.go index dba90a22..57cc4e4d 100644 --- a/client_test.go +++ b/client_test.go @@ -8,7 +8,6 @@ import ( "encoding/json" "encoding/xml" "errors" - "fmt" "io" "mime/multipart" "net" @@ -630,7 +629,7 @@ type readErrorConn struct { } func (*readErrorConn) Read(_ []byte) (int, error) { - return 0, fmt.Errorf("error") + return 0, errors.New("error") } func (*readErrorConn) Write(p []byte) (int, error) { diff --git a/ctx.go b/ctx.go index 93e8948f..52787a55 100644 --- a/ctx.go +++ b/ctx.go @@ -8,6 +8,7 @@ import ( "bytes" "context" "crypto/tls" + "errors" "fmt" "io" "mime/multipart" @@ -615,7 +616,7 @@ func (c *DefaultCtx) Hostname() string { func (c *DefaultCtx) Port() string { tcpaddr, ok := c.fasthttp.RemoteAddr().(*net.TCPAddr) if !ok { - panic(fmt.Errorf("failed to type-assert to *net.TCPAddr")) + panic(errors.New("failed to type-assert to *net.TCPAddr")) } return strconv.Itoa(tcpaddr.Port) } @@ -1108,12 +1109,8 @@ func (c *DefaultCtx) Queries() map[string]string { // age := Query[int](c, "age") // Returns 8 // unknown := Query[string](c, "unknown", "default") // Returns "default" since the query parameter "unknown" is not found func Query[V QueryType](c Ctx, key string, defaultValue ...V) V { - ctx, ok := c.(*DefaultCtx) - if !ok { - panic(fmt.Errorf("failed to type-assert to *DefaultCtx")) - } var v V - q := ctx.app.getString(ctx.fasthttp.QueryArgs().Peek(key)) + q := c.App().getString(c.Context().QueryArgs().Peek(key)) switch any(v).(type) { case int: @@ -1151,7 +1148,7 @@ func Query[V QueryType](c Ctx, key string, defaultValue ...V) V { if q == "" && len(defaultValue) > 0 { return defaultValue[0] } - return assertValueType[V, []byte](ctx.app.getBytes(q)) + return assertValueType[V, []byte](c.App().getBytes(q)) default: if len(defaultValue) > 0 { return defaultValue[0] diff --git a/ctx_interface.go b/ctx_interface.go index 799be560..3eef6a69 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -7,7 +7,7 @@ package fiber import ( "context" "crypto/tls" - "fmt" + "errors" "io" "mime/multipart" "sync" @@ -446,7 +446,7 @@ func (app *App) NewCtx(fctx *fasthttp.RequestCtx) Ctx { func (app *App) AcquireCtx() Ctx { ctx, ok := app.pool.Get().(Ctx) if !ok { - panic(fmt.Errorf("failed to type-assert to Ctx")) + panic(errors.New("failed to type-assert to Ctx")) } return ctx } diff --git a/ctx_test.go b/ctx_test.go index b91c75d7..2c99c0a5 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -1271,7 +1271,7 @@ func Test_Ctx_FormValue(t *testing.T) { require.NoError(t, writer.Close()) req := httptest.NewRequest(MethodPost, "/test", body) - req.Header.Set("Content-Type", fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary())) + req.Header.Set("Content-Type", "multipart/form-data; boundary="+writer.Boundary()) req.Header.Set("Content-Length", strconv.Itoa(len(body.Bytes()))) resp, err := app.Test(req) @@ -2155,7 +2155,7 @@ func Test_Ctx_MultipartForm(t *testing.T) { require.NoError(t, writer.Close()) req := httptest.NewRequest(MethodPost, "/test", body) - req.Header.Set(HeaderContentType, fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary())) + req.Header.Set(HeaderContentType, "multipart/form-data; boundary="+writer.Boundary()) req.Header.Set(HeaderContentLength, strconv.Itoa(len(body.Bytes()))) resp, err := app.Test(req) @@ -4181,7 +4181,7 @@ func Test_Ctx_Render_Go_Template(t *testing.T) { require.NoError(t, err) }() - _, err = file.Write([]byte("template")) + _, err = file.WriteString("template") require.NoError(t, err) err = file.Close() diff --git a/error.go b/error.go index 13b7e58f..2e44c277 100644 --- a/error.go +++ b/error.go @@ -11,9 +11,13 @@ import ( // Unexported because users will hopefully never need to see it. var errUnreachable = errors.New("fiber: unreachable code, please create an issue at github.com/gofiber/fiber") -// Graceful shutdown errors +// General errors var ( ErrGracefulTimeout = errors.New("shutdown: graceful timeout has been reached, exiting") + // ErrNotRunning indicates that a Shutdown method was called when the server was not running. + ErrNotRunning = errors.New("shutdown: server is not running") + // ErrHandlerExited is returned by App.Test if a handler panics or calls runtime.Goexit(). + ErrHandlerExited = errors.New("runtime.Goexit() called in handler or server panic") ) // Fiber redirection errors diff --git a/helpers.go b/helpers.go index fd542c55..1e1bc408 100644 --- a/helpers.go +++ b/helpers.go @@ -7,6 +7,7 @@ package fiber import ( "bytes" "crypto/tls" + "errors" "fmt" "io" "net" @@ -41,25 +42,27 @@ func getTLSConfig(ln net.Listener) *tls.Config { pointer := reflect.ValueOf(ln) // Is it a tls.listener? - if pointer.String() == "<*tls.listener Value>" { - // Copy value from pointer - if val := reflect.Indirect(pointer); val.Type() != nil { - // Get private field from value - if field := val.FieldByName("config"); field.Type() != nil { - // Copy value from pointer field (unsafe) - newval := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())) //nolint:gosec // Probably the only way to extract the *tls.Config from a net.Listener. TODO: Verify there really is no easier way without using unsafe. - if newval.Type() != nil { - // Get element from pointer - if elem := newval.Elem(); elem.Type() != nil { - // Cast value to *tls.Config - c, ok := elem.Interface().(*tls.Config) - //nolint:revive // We need to check if the type assertion was successful - if !ok { - panic(fmt.Errorf("failed to type-assert to *tls.Config")) - } - return c - } + if pointer.String() != "<*tls.listener Value>" { + return nil + } + + // Copy value from pointer + if val := reflect.Indirect(pointer); val.Type() != nil { + // Get private field from value + if field := val.FieldByName("config"); field.Type() != nil { + // Copy value from pointer field (unsafe) + newval := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())) //nolint:gosec // Probably the only way to extract the *tls.Config from a net.Listener. TODO: Verify there really is no easier way without using unsafe. + if newval.Type() == nil { + return nil + } + // Get element from pointer + if elem := newval.Elem(); elem.Type() != nil { + // Cast value to *tls.Config + c, ok := elem.Interface().(*tls.Config) + if !ok { + panic(errors.New("failed to type-assert to *tls.Config")) } + return c } } } diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index 1652c0d6..1beee76d 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -50,7 +50,7 @@ func Test_Cache_Expired(t *testing.T) { app.Use(New(Config{Expiration: 2 * time.Second})) app.Get("/", func(c fiber.Ctx) error { - return c.SendString(fmt.Sprintf("%d", time.Now().UnixNano())) + return c.SendString(strconv.FormatInt(time.Now().UnixNano(), 10)) }) resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) @@ -88,7 +88,7 @@ func Test_Cache(t *testing.T) { app.Use(New()) app.Get("/", func(c fiber.Ctx) error { - now := fmt.Sprintf("%d", time.Now().UnixNano()) + now := strconv.FormatInt(time.Now().UnixNano(), 10) return c.SendString(now) }) @@ -307,7 +307,7 @@ func Test_Cache_Invalid_Expiration(t *testing.T) { app.Use(cache) app.Get("/", func(c fiber.Ctx) error { - now := fmt.Sprintf("%d", time.Now().UnixNano()) + now := strconv.FormatInt(time.Now().UnixNano(), 10) return c.SendString(now) }) @@ -513,8 +513,7 @@ func Test_CustomExpiration(t *testing.T) { app.Get("/", func(c fiber.Ctx) error { c.Response().Header.Add("Cache-Time", "1") - now := fmt.Sprintf("%d", time.Now().UnixNano()) - return c.SendString(now) + return c.SendString(strconv.FormatInt(time.Now().UnixNano(), 10)) }) resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) @@ -620,7 +619,7 @@ func Test_Cache_WithHead(t *testing.T) { app.Use(New()) handler := func(c fiber.Ctx) error { - now := fmt.Sprintf("%d", time.Now().UnixNano()) + now := strconv.FormatInt(time.Now().UnixNano(), 10) return c.SendString(now) } app.Route("/").Get(handler).Head(handler) diff --git a/middleware/earlydata/earlydata_test.go b/middleware/earlydata/earlydata_test.go index 7e3c6d39..9ec76d6b 100644 --- a/middleware/earlydata/earlydata_test.go +++ b/middleware/earlydata/earlydata_test.go @@ -70,7 +70,7 @@ func appWithConfig(t *testing.T, c *fiber.Config) *fiber.App { }, "/", func(c fiber.Ctx) error { valid, ok := c.Locals(localsKeyTestValid).(bool) if !ok { - panic(fmt.Errorf("failed to type-assert to bool")) + panic(errors.New("failed to type-assert to bool")) } if !valid { return errors.New("handler called even though validation failed") diff --git a/middleware/filesystem/utils.go b/middleware/filesystem/utils.go index 3cf7fddc..3c11acf1 100644 --- a/middleware/filesystem/utils.go +++ b/middleware/filesystem/utils.go @@ -1,6 +1,7 @@ package filesystem import ( + "errors" "fmt" "html" "io/fs" @@ -12,6 +13,11 @@ import ( "github.com/gofiber/fiber/v3" ) +// ErrDirListingNotSupported is returned from the filesystem middleware handler if +// the given fs.FS does not support directory listing. This is uncommon and may +// indicate an issue with the FS implementation. +var ErrDirListingNotSupported = errors.New("failed to type-assert to fs.ReadDirFile") + func getFileExtension(p string) string { n := strings.LastIndexByte(p, '.') if n < 0 { @@ -23,7 +29,7 @@ func getFileExtension(p string) string { func dirList(c fiber.Ctx, f fs.File) error { ff, ok := f.(fs.ReadDirFile) if !ok { - return fmt.Errorf("failed to type-assert to fs.ReadDirFile") + return ErrDirListingNotSupported } fileinfos, err := ff.ReadDir(-1) if err != nil { diff --git a/middleware/helmet/helmet.go b/middleware/helmet/helmet.go index 6548d9be..30344654 100644 --- a/middleware/helmet/helmet.go +++ b/middleware/helmet/helmet.go @@ -70,7 +70,7 @@ func New(config ...Config) fiber.Handler { subdomains = "; includeSubDomains" } if cfg.HSTSPreloadEnabled { - subdomains = fmt.Sprintf("%s; preload", subdomains) + subdomains += "; preload" } c.Set(fiber.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", cfg.HSTSMaxAge, subdomains)) } diff --git a/middleware/keyauth/keyauth_test.go b/middleware/keyauth/keyauth_test.go index ba795aad..2d955ac2 100644 --- a/middleware/keyauth/keyauth_test.go +++ b/middleware/keyauth/keyauth_test.go @@ -3,7 +3,6 @@ package keyauth import ( "context" - "fmt" "io" "net/http" "net/http/httptest" @@ -300,7 +299,7 @@ func TestCustomSuccessAndFailureHandlers(t *testing.T) { // Create a request with a valid API key in the Authorization header req := httptest.NewRequest(fiber.MethodGet, "/", nil) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", CorrectKey)) + req.Header.Add("Authorization", "Bearer "+CorrectKey) // Send the request to the app res, err = app.Test(req) @@ -363,7 +362,7 @@ func TestCustomNextFunc(t *testing.T) { // Create a request with a different path and send it to the app with correct key req = httptest.NewRequest(fiber.MethodGet, "/not-allowed", nil) - req.Header.Add("Authorization", fmt.Sprintf("Basic %s", CorrectKey)) + req.Header.Add("Authorization", "Basic "+CorrectKey) res, err = app.Test(req) require.NoError(t, err) @@ -397,7 +396,7 @@ func TestAuthSchemeToken(t *testing.T) { // Create a request with a valid API key in the "Token" Authorization header req := httptest.NewRequest(fiber.MethodGet, "/", nil) - req.Header.Add("Authorization", fmt.Sprintf("Token %s", CorrectKey)) + req.Header.Add("Authorization", "Token "+CorrectKey) // Send the request to the app res, err := app.Test(req) @@ -445,7 +444,7 @@ func TestAuthSchemeBasic(t *testing.T) { // Create a request with a valid API key in the "Authorization" header using the "Basic" scheme req := httptest.NewRequest(fiber.MethodGet, "/", nil) - req.Header.Add("Authorization", fmt.Sprintf("Basic %s", CorrectKey)) + req.Header.Add("Authorization", "Basic "+CorrectKey) // Send the request to the app res, err = app.Test(req) diff --git a/redirect.go b/redirect.go index 74b6ad30..98053db8 100644 --- a/redirect.go +++ b/redirect.go @@ -5,7 +5,7 @@ package fiber import ( - "fmt" + "errors" "strings" "sync" @@ -53,7 +53,7 @@ type RedirectConfig struct { func AcquireRedirect() *Redirect { redirect, ok := redirectPool.Get().(*Redirect) if !ok { - panic(fmt.Errorf("failed to type-assert to *Redirect")) + panic(errors.New("failed to type-assert to *Redirect")) } return redirect diff --git a/router.go b/router.go index b210b5a5..42d5a176 100644 --- a/router.go +++ b/router.go @@ -5,6 +5,7 @@ package fiber import ( + "errors" "fmt" "html" "sort" @@ -209,12 +210,12 @@ func (app *App) requestHandler(rctx *fasthttp.RequestCtx) { if app.newCtxFunc != nil { c, ok = app.AcquireCtx().(CustomCtx) if !ok { - panic(fmt.Errorf("failed to type-assert to CustomCtx")) + panic(errors.New("failed to type-assert to CustomCtx")) } } else { c, ok = app.AcquireCtx().(*DefaultCtx) if !ok { - panic(fmt.Errorf("failed to type-assert to *DefaultCtx")) + panic(errors.New("failed to type-assert to *DefaultCtx")) } } c.Reset(rctx)