diff --git a/app_test.go b/app_test.go index 84606e6b..c803dbfb 100644 --- a/app_test.go +++ b/app_test.go @@ -31,8 +31,34 @@ import ( "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/fasthttputil" + + "github.com/go-playground/validator/v10" ) +type UserStatsCreateDTO struct { + FirstName string `json:"first_name" validate:"required"` + Age int `json:"age" validate:"required,gt=5"` + BirthDate string `json:"birth_date" validate:"required,datetime=2006/01/02"` +} + +// test main func +func Test_NewValidatorError(t *testing.T) { + userDTO := UserStatsCreateDTO{ + FirstName: "John", + Age: 10, + BirthDate: "2024-01-01 12:00:00", + } + + validate := validator.New() + err := validate.Struct(userDTO) + + validationErrors := err.(validator.ValidationErrors) + + fmt.Println(validationErrors[0].Field()) + require.Equal(t, "BirthDate", validationErrors[0].Field()) + require.Equal(t, "datetime", validationErrors[0].Tag()) +} + func testEmptyHandler(_ Ctx) error { return nil } diff --git a/ctx_custom_interface.go b/ctx_custom_interface.go index 01d86c0b..b4b343a2 100644 --- a/ctx_custom_interface.go +++ b/ctx_custom_interface.go @@ -32,10 +32,14 @@ type CustomCtx[T any] interface { func NewDefaultCtx[TCtx *DefaultCtx](app *App[*DefaultCtx]) TCtx { // return ctx - return &DefaultCtx{ + ctx := &DefaultCtx{ // Set app reference app: app, } + ctx.req = &DefaultReq{ctx: ctx} + ctx.res = &DefaultRes{ctx: ctx} + + return ctx } func (app *App[TCtx]) newCtx() CtxGeneric[TCtx] { diff --git a/ctx_interface.go b/ctx_interface.go index a1397673..c2beaeaa 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -213,6 +213,7 @@ type CtxGeneric[T any] interface { Params(key string, defaultValue ...string) string // Path returns the path part of the request URL. // Optionally, you could override the path. + // Make copies or use the Immutable setting to use the value outside the Handler. Path(override ...string) string // Scheme contains the request protocol string: http or https for TLS requests. // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. @@ -258,15 +259,21 @@ type CtxGeneric[T any] interface { // Variables are read by the Render method and may be overwritten. ViewBind(vars Map) error // getLocationFromRoute get URL location from route using parameters - getLocationFromRoute(route Route[T], params Map) (string, error) + getLocationFromRoute(route Route, params Map) (string, error) // GetRouteURL generates URLs to named routes, with parameters. URLs are relative, for example: "/user/1831" GetRouteURL(routeName string, params Map) (string, error) // Render a template with data and sends a text/html response. // We support the following engines: https://github.com/gofiber/template Render(name string, bind any, layouts ...string) error renderExtensions(bind any) + // Req returns a convenience type whose API is limited to operations + // on the incoming request. + Req() Req + // Res returns a convenience type whose API is limited to operations + // on the outgoing response. + Res() Res // Route returns the matched Route struct. - Route() *Route[T] + Route() *Route // SaveFile saves any multipart file to disk. SaveFile(fileheader *multipart.FileHeader, path string) error // SaveFileToStorage saves any multipart file to an external storage system. @@ -349,47 +356,11 @@ type CtxGeneric[T any] interface { setIndexHandler(handler int) setIndexRoute(route int) setMatched(matched bool) - setRoute(route *Route) -} - -func NewDefaultCtx(app *App) *DefaultCtx { - // return ctx - ctx := &DefaultCtx{ - // Set app reference - app: app, - } - ctx.req = &DefaultReq{ctx: ctx} - ctx.res = &DefaultRes{ctx: ctx} - - return ctx -} - -func (app *App) newCtx() Ctx { - var c Ctx - - if app.newCtxFunc != nil { - c = app.newCtxFunc(app) - } else { - c = NewDefaultCtx(app) - } - - return c -} - -// AcquireCtx retrieves a new Ctx from the pool. -func (app *App) AcquireCtx(fctx *fasthttp.RequestCtx) Ctx { - ctx, ok := app.pool.Get().(Ctx) - - if !ok { - panic(errors.New("failed to type-assert to Ctx")) - } - ctx.Reset(fctx) - - return ctx -} - -// ReleaseCtx releases the ctx back into the pool. -func (app *App) ReleaseCtx(c Ctx) { - c.release() - app.pool.Put(c) + setRoute(route *Route[T]) + // Drop closes the underlying connection without sending any response headers or body. + // This can be useful for silently terminating client connections, such as in DDoS mitigation + // or when blocking access to sensitive endpoints. + Drop() error + // End immediately flushes the current response and closes the underlying connection. + End() error } diff --git a/go.mod b/go.mod index f4cd17c1..6c447ebb 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/gofiber/fiber/v3 go 1.23.0 require ( + github.com/go-playground/validator/v10 v10.25.0 github.com/gofiber/schema v1.3.0 github.com/gofiber/utils/v2 v2.0.0-beta.7 github.com/google/uuid v1.6.0 @@ -15,6 +16,13 @@ require ( golang.org/x/crypto v0.36.0 ) +require ( + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect +) + require ( github.com/andybalholm/brotli v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 71824581..85afa9f6 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= +github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= github.com/gofiber/schema v1.3.0 h1:K3F3wYzAY+aivfCCEHPufCthu5/13r/lzp1nuk6mr3Q= github.com/gofiber/schema v1.3.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c= github.com/gofiber/utils/v2 v2.0.0-beta.7 h1:NnHFrRHvhrufPABdWajcKZejz9HnCWmT/asoxRsiEbQ= @@ -12,6 +22,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -34,8 +46,6 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/router.go b/router.go index 2da54d81..0993d9ab 100644 --- a/router.go +++ b/router.go @@ -144,7 +144,7 @@ func (app *App[TCtx]) next(c TCtx) (bool, error) { //nolint:unparam // bool para } // If c.Next() does not match, return 404 - err := NewError(StatusNotFound, "Cannot "+c.Method()+" "+c.getPathOriginal()) + err := NewError(StatusNotFound, "Cannot "+c.Method()+" "+html.EscapeString(c.getPathOriginal())) // If no match, scan stack again if other methods match the request // Moved from app.handler because middleware may break the route chain @@ -154,61 +154,6 @@ func (app *App[TCtx]) next(c TCtx) (bool, error) { //nolint:unparam // bool para return false, err } -func (app *App) next2222(c *DefaultCtx) (bool, error) { - // Get stack length - tree, ok := app.treeStack[c.methodInt][c.treePathHash] - if !ok { - tree = app.treeStack[c.methodInt][0] - } - lenTree := len(tree) - 1 - - // Loop over the route stack starting from previous index - for c.indexRoute < lenTree { - // Increment route index - c.indexRoute++ - - // Get *Route - route := tree[c.indexRoute] - - var match bool - var err error - // skip for mounted apps - if route.mount { - continue - } - - // Check if it matches the request path - match = route.match(utils.UnsafeString(c.detectionPath), utils.UnsafeString(c.path), &c.values) - if !match { - // No match, next route - continue - } - // Pass route reference and param values - c.route = route - - // Non use handler matched - if !c.matched && !route.use { - c.matched = true - } - - // Execute first handler of route - c.indexHandler = 0 - if len(route.Handlers) > 0 { - err = route.Handlers[0](c) - } - return match, err // Stop scanning the stack - } - - // If c.Next() does not match, return 404 - err := NewError(StatusNotFound, "Cannot "+c.Method()+" "+html.EscapeString(c.pathOriginal)) - if !c.matched && app.methodExist(c) { - // If no match, scan stack again if other methods match the request - // Moved from app.handler because middleware may break the route chain - err = ErrMethodNotAllowed - } - return false, err -} - func (app *App[TCtx]) requestHandler(rctx *fasthttp.RequestCtx) { // Acquire DefaultCtx from the pool ctx := app.AcquireCtx(rctx) @@ -216,7 +161,7 @@ func (app *App[TCtx]) requestHandler(rctx *fasthttp.RequestCtx) { defer app.ReleaseCtx(ctx) // Check if the HTTP method is valid - if ctx.methodInt == -1 { + if app.methodInt(ctx.Method()) == -1 { _ = ctx.SendStatus(StatusNotImplemented) //nolint:errcheck // Always return nil return }