diff --git a/app.go b/app.go index 50ec0bed4e..c5596b54a5 100644 --- a/app.go +++ b/app.go @@ -35,7 +35,7 @@ import ( const Version = "3.0.0-beta.4" // Handler defines a function to serve HTTP requests. -type Handler = func(ctx Ctx) error +type Handler[TCtx CtxGeneric[TCtx]] = func(ctx TCtx) error // Map is a shortcut for map[string]any, useful for JSON returns type Map map[string]any @@ -78,7 +78,7 @@ type Storage interface { // return c.Status(code).SendString(err.Error()) // } // app := fiber.New(cfg) -type ErrorHandler = func(Ctx, error) error +type ErrorHandler[TCtx CtxGeneric[TCtx]] = func(TCtx, error) error // Error represents an error that occurred while handling a request. type Error struct { @@ -97,19 +97,19 @@ type App[TCtx CtxGeneric[TCtx]] struct { // Converts byte slice to a string getString func(b []byte) string // Hooks - hooks *Hooks + hooks *Hooks[TCtx] // Latest route & group - latestRoute *Route + latestRoute *Route[TCtx] // newCtxFunc newCtxFunc func(app *App[TCtx]) CustomCtx[TCtx] // TLS handler tlsHandler *TLSHandler // Mount fields - mountFields *mountFields + mountFields *mountFields[TCtx] // Route stack divided by HTTP methods - stack [][]*Route + stack [][]*Route[TCtx] // Route stack divided by HTTP methods and route prefixes - treeStack []map[string][]*Route + treeStack []map[string][]*Route[TCtx] // custom binders customBinders []CustomBinder // customConstraints is a list of external constraints @@ -117,9 +117,9 @@ type App[TCtx CtxGeneric[TCtx]] struct { // sendfiles stores configurations for handling ctx.SendFile operations sendfiles []*sendFileStore // App config - config Config + config Config[TCtx] // Indicates if the value was explicitly configured - configured Config + configured Config[TCtx] // sendfilesMutex is a mutex used for sendfile operations sendfilesMutex sync.RWMutex mutex sync.Mutex @@ -132,7 +132,7 @@ type App[TCtx CtxGeneric[TCtx]] struct { } // Config is a struct holding the server settings. -type Config struct { //nolint:govet // Aligning the struct fields is not necessary. betteralign:ignore +type Config[TCtx CtxGeneric[TCtx]] struct { //nolint:govet // Aligning the struct fields is not necessary. betteralign:ignore // Enables the "Server: value" HTTP header. // // Default: "" @@ -250,7 +250,7 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa // ErrorHandler is executed when an error is returned from fiber.Handler. // // Default: DefaultErrorHandler - ErrorHandler ErrorHandler `json:"-"` + ErrorHandler ErrorHandler[TCtx] `json:"-"` // When set to true, disables keep-alive connections. // The server will close incoming connections after sending the first response to client. @@ -470,7 +470,7 @@ var DefaultMethods = []string{ } // DefaultErrorHandler that process return errors from handlers -func DefaultErrorHandler(c Ctx, err error) error { +func DefaultErrorHandler[TCtx CtxGeneric[TCtx]](c TCtx, err error) error { code := StatusInternalServerError var e *Error if errors.As(err, &e) { @@ -490,7 +490,7 @@ func DefaultErrorHandler(c Ctx, err error) error { // Prefork: true, // ServerHeader: "Fiber", // }) -func New(config ...Config) *App[*DefaultCtx] { +func New(config ...Config[*DefaultCtx]) *App[*DefaultCtx] { app := newApp[*DefaultCtx](config...) // Init app @@ -517,7 +517,7 @@ func New(config ...Config) *App[*DefaultCtx] { // Prefork: true, // ServerHeader: "Fiber", // }) -func NewWithCustomCtx[TCtx CtxGeneric[TCtx]](newCtxFunc func(app *App[TCtx]) CustomCtx[TCtx], config ...Config) *App[TCtx] { +func NewWithCustomCtx[TCtx CtxGeneric[TCtx]](newCtxFunc func(app *App[TCtx]) CustomCtx[TCtx], config ...Config[TCtx]) *App[TCtx] { app := newApp[TCtx](config...) // Set newCtxFunc @@ -530,14 +530,14 @@ func NewWithCustomCtx[TCtx CtxGeneric[TCtx]](newCtxFunc func(app *App[TCtx]) Cus } // newApp creates a new Fiber named instance. -func newApp[TCtx CtxGeneric[TCtx]](config ...Config) *App[TCtx] { +func newApp[TCtx CtxGeneric[TCtx]](config ...Config[TCtx]) *App[TCtx] { // Create a new app app := &App[TCtx]{ // Create config - config: Config{}, + config: Config[TCtx]{}, getBytes: utils.UnsafeBytes, getString: utils.UnsafeString, - latestRoute: &Route{}, + latestRoute: &Route[TCtx]{}, customBinders: []CustomBinder{}, sendfiles: []*sendFileStore{}, } @@ -550,7 +550,7 @@ func newApp[TCtx CtxGeneric[TCtx]](config ...Config) *App[TCtx] { } // Define hooks - app.hooks = newHooks(app) + app.hooks = newHooks[TCtx](app) // Define mountFields app.mountFields = newMountFields(app) @@ -589,7 +589,7 @@ func newApp[TCtx CtxGeneric[TCtx]](config ...Config) *App[TCtx] { } if app.config.ErrorHandler == nil { - app.config.ErrorHandler = DefaultErrorHandler + app.config.ErrorHandler = DefaultErrorHandler[TCtx] } if app.config.JSONEncoder == nil { @@ -620,8 +620,8 @@ func newApp[TCtx CtxGeneric[TCtx]](config ...Config) *App[TCtx] { } // Create router stack - app.stack = make([][]*Route, len(app.config.RequestMethods)) - app.treeStack = make([]map[string][]*Route, len(app.config.RequestMethods)) + app.stack = make([][]*Route[TCtx], len(app.config.RequestMethods)) + app.treeStack = make([]map[string][]*Route[TCtx], len(app.config.RequestMethods)) // Override colors app.config.ColorScheme = defaultColors(app.config.ColorScheme) @@ -669,7 +669,7 @@ func (app *App[TCtx]) SetTLSHandler(tlsHandler *TLSHandler) { } // Name Assign name to specific route. -func (app *App[TCtx]) Name(name string) Router { +func (app *App[TCtx]) Name(name string) Router[TCtx] { app.mutex.Lock() defer app.mutex.Unlock() @@ -695,7 +695,7 @@ func (app *App[TCtx]) Name(name string) Router { } // GetRoute Get route by name -func (app *App[TCtx]) GetRoute(name string) Route { +func (app *App[TCtx]) GetRoute(name string) Route[TCtx] { for _, routes := range app.stack { for _, route := range routes { if route.Name == name { @@ -704,12 +704,12 @@ func (app *App[TCtx]) GetRoute(name string) Route { } } - return Route{} + return Route[TCtx]{} } // GetRoutes Get all routes. When filterUseOption equal to true, it will filter the routes registered by the middleware. -func (app *App[TCtx]) GetRoutes(filterUseOption ...bool) []Route { - var rs []Route +func (app *App[TCtx]) GetRoutes(filterUseOption ...bool) []Route[TCtx] { + var rs []Route[TCtx] var filterUse bool if len(filterUseOption) != 0 { filterUse = filterUseOption[0] @@ -746,11 +746,11 @@ func (app *App[TCtx]) GetRoutes(filterUseOption ...bool) []Route { // app.Use("/mounted-path", subApp) // // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... -func (app *App[TCtx]) Use(args ...any) Router { +func (app *App[TCtx]) Use(args ...any) Router[TCtx] { var prefix string var subApp *App[TCtx] var prefixes []string - var handlers []Handler + var handlers []Handler[TCtx] for i := 0; i < len(args); i++ { switch arg := args[i].(type) { @@ -760,7 +760,7 @@ func (app *App[TCtx]) Use(args ...any) Router { subApp = arg case []string: prefixes = arg - case Handler: + case Handler[TCtx]: handlers = append(handlers, arg) default: panic(fmt.Sprintf("use: invalid handler %v\n", reflect.TypeOf(arg))) @@ -785,66 +785,66 @@ func (app *App[TCtx]) Use(args ...any) Router { // Get registers a route for GET methods that requests a representation // of the specified resource. Requests using GET should only retrieve data. -func (app *App[TCtx]) Get(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Get(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodGet}, path, handler, middleware...) } // Head registers a route for HEAD methods that asks for a response identical // to that of a GET request, but without the response body. -func (app *App[TCtx]) Head(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Head(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodHead}, path, handler, middleware...) } // Post registers a route for POST methods that is used to submit an entity to the // specified resource, often causing a change in state or side effects on the server. -func (app *App[TCtx]) Post(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Post(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodPost}, path, handler, middleware...) } // Put registers a route for PUT methods that replaces all current representations // of the target resource with the request payload. -func (app *App[TCtx]) Put(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Put(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodPut}, path, handler, middleware...) } // Delete registers a route for DELETE methods that deletes the specified resource. -func (app *App[TCtx]) Delete(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Delete(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodDelete}, path, handler, middleware...) } // Connect registers a route for CONNECT methods that establishes a tunnel to the // server identified by the target resource. -func (app *App[TCtx]) Connect(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Connect(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodConnect}, path, handler, middleware...) } // Options registers a route for OPTIONS methods that is used to describe the // communication options for the target resource. -func (app *App[TCtx]) Options(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Options(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodOptions}, path, handler, middleware...) } // Trace registers a route for TRACE methods that performs a message loop-back // test along the path to the target resource. -func (app *App[TCtx]) Trace(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Trace(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodTrace}, path, handler, middleware...) } // Patch registers a route for PATCH methods that is used to apply partial // modifications to a resource. -func (app *App[TCtx]) Patch(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Patch(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodPatch}, path, handler, middleware...) } // Add allows you to specify multiple HTTP methods to register a route. -func (app *App[TCtx]) Add(methods []string, path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Add(methods []string, path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { app.register(methods, path, nil, handler, middleware...) return app } // All will register the handler on all HTTP methods -func (app *App[TCtx]) All(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) All(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return app.Add(app.config.RequestMethods, path, handler, middleware...) } @@ -852,8 +852,8 @@ func (app *App[TCtx]) All(path string, handler Handler, middleware ...Handler) R // // api := app.Group("/api") // api.Get("/users", handler) -func (app *App[TCtx]) Group(prefix string, handlers ...Handler) Router { - grp := &Group{Prefix: prefix, app: app} +func (app *App[TCtx]) Group(prefix string, handlers ...Handler[TCtx]) Router[TCtx] { + grp := &Group[TCtx]{Prefix: prefix, app: app} if len(handlers) > 0 { app.register([]string{methodUse}, prefix, grp, nil, handlers...) } @@ -866,9 +866,9 @@ func (app *App[TCtx]) Group(prefix string, handlers ...Handler) Router { // Route is used to define routes with a common prefix inside the common function. // Uses Group method to define new sub-router. -func (app *App[TCtx]) Route(path string) Register { +func (app *App[TCtx]) Route(path string) Register[TCtx] { // Create new route - route := &Registering{app: app, path: path} + route := &Registering[TCtx]{app: app, path: path} return route } @@ -891,7 +891,7 @@ func NewError(code int, message ...string) *Error { } // Config returns the app config as value ( read-only ). -func (app *App[TCtx]) Config() Config { +func (app *App[TCtx]) Config() Config[TCtx] { return app.config } @@ -900,14 +900,11 @@ func (app *App[TCtx]) Handler() fasthttp.RequestHandler { //revive:disable-line: // prepare the server for the start app.startupProcess() - if app.newCtxFunc != nil { - return app.customRequestHandler - } - return app.defaultRequestHandler + return app.requestHandler } // Stack returns the raw router stack. -func (app *App[TCtx]) Stack() [][]*Route { +func (app *App[TCtx]) Stack() [][]*Route[TCtx] { return app.stack } @@ -964,7 +961,7 @@ func (app *App[TCtx]) Server() *fasthttp.Server { } // Hooks returns the hook struct to register hooks. -func (app *App[TCtx]) Hooks() *Hooks { +func (app *App[TCtx]) Hooks() *Hooks[TCtx] { return app.hooks } @@ -1094,11 +1091,7 @@ func (app *App[TCtx]) init() *App[TCtx] { } // fasthttp server settings - if app.newCtxFunc != nil { - app.server.Handler = app.customRequestHandler - } else { - app.server.Handler = app.defaultRequestHandler - } + app.server.Handler = app.requestHandler app.server.Name = app.config.ServerHeader app.server.Concurrency = app.config.Concurrency app.server.NoDefaultDate = app.config.DisableDefaultDate @@ -1127,9 +1120,9 @@ func (app *App[TCtx]) init() *App[TCtx] { // sub fibers by their prefixes and if it finds a match, it uses that // error handler. Otherwise it uses the configured error handler for // the app, which if not set is the DefaultErrorHandler. -func (app *App[TCtx]) ErrorHandler(ctx CtxGeneric[TCtx], err error) error { +func (app *App[TCtx]) ErrorHandler(ctx TCtx, err error) error { var ( - mountedErrHandler ErrorHandler + mountedErrHandler ErrorHandler[TCtx] mountedPrefixParts int ) diff --git a/ctx.go b/ctx.go index 952c39952e..f7e83bdc17 100644 --- a/ctx.go +++ b/ctx.go @@ -51,7 +51,7 @@ const userContextKey contextKey = 0 // __local_user_context__ //go:generate go run ctx_interface_gen.go type DefaultCtx struct { app *App[*DefaultCtx] // Reference to *App - route *Route // Reference to *Route + route *Route[*DefaultCtx] // Reference to *Route fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx bind *Bind // Default bind reference redirect *Redirect // Default redirect reference @@ -1048,6 +1048,7 @@ func (c *DefaultCtx) Next() error { } // Continue handler stack + // TODO: reduce this with generics if c.app.newCtxFunc != nil { _, err := c.app.nextCustom(c) return err @@ -1063,6 +1064,7 @@ func (c *DefaultCtx) RestartRouting() error { var err error c.indexRoute = -1 + // TODO: reduce this with generics if c.app.newCtxFunc != nil { _, err = c.app.nextCustom(c) } else { @@ -1978,7 +1980,7 @@ func (c *DefaultCtx) setMatched(matched bool) { c.matched = matched } -func (c *DefaultCtx) setRoute(route *Route) { +func (c *DefaultCtx) setRoute(route *Route[*DefaultCtx]) { c.route = route } diff --git a/ctx_custom_interface.go b/ctx_custom_interface.go index 263292b340..01d86c0b2f 100644 --- a/ctx_custom_interface.go +++ b/ctx_custom_interface.go @@ -27,10 +27,10 @@ type CustomCtx[T any] interface { setIndexHandler(handler int) setIndexRoute(route int) setMatched(matched bool) - setRoute(route *Route) + setRoute(route *Route[T]) } -func NewDefaultCtx(app *App[*DefaultCtx]) *DefaultCtx { +func NewDefaultCtx[TCtx *DefaultCtx](app *App[*DefaultCtx]) TCtx { // return ctx return &DefaultCtx{ // Set app reference @@ -41,6 +41,7 @@ func NewDefaultCtx(app *App[*DefaultCtx]) *DefaultCtx { func (app *App[TCtx]) newCtx() CtxGeneric[TCtx] { var c CtxGeneric[TCtx] + // TODO: fix this with generics ? if app.newCtxFunc != nil { c = app.newCtxFunc(app) } else { @@ -51,8 +52,8 @@ func (app *App[TCtx]) newCtx() CtxGeneric[TCtx] { } // AcquireCtx retrieves a new Ctx from the pool. -func (app *App[TCtx]) AcquireCtx(fctx *fasthttp.RequestCtx) CtxGeneric[TCtx] { - ctx, ok := app.pool.Get().(CtxGeneric[TCtx]) +func (app *App[TCtx]) AcquireCtx(fctx *fasthttp.RequestCtx) TCtx { + ctx, ok := app.pool.Get().(TCtx) if !ok { panic(errors.New("failed to type-assert to Ctx")) @@ -63,7 +64,7 @@ func (app *App[TCtx]) AcquireCtx(fctx *fasthttp.RequestCtx) CtxGeneric[TCtx] { } // ReleaseCtx releases the ctx back into the pool. -func (app *App[TCtx]) ReleaseCtx(c CtxGeneric[TCtx]) { +func (app *App[TCtx]) ReleaseCtx(c TCtx) { c.release() app.pool.Put(c) } diff --git a/ctx_interface.go b/ctx_interface.go index be58c25f04..20295c2df0 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -24,7 +24,7 @@ type CtxGeneric[T any] interface { // AcceptsLanguages checks if the specified language is acceptable. AcceptsLanguages(offers ...string) string // App returns the *App[T] reference to the instance of the Fiber application - App() *App[*DefaultCtx] + App() *App[T] // Append the specified value to the HTTP response header field. // If the header is not already set, it creates the header with the specified value. Append(field string, values ...string) @@ -258,7 +258,7 @@ 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, params Map) (string, error) + getLocationFromRoute(route Route[T], 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. @@ -266,7 +266,7 @@ type CtxGeneric[T any] interface { Render(name string, bind any, layouts ...string) error renderExtensions(bind any) // Route returns the matched Route struct. - Route() *Route + Route() *Route[T] // 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,7 +349,7 @@ type CtxGeneric[T any] interface { setIndexHandler(handler int) setIndexRoute(route int) setMatched(matched bool) - setRoute(route *Route) + 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. diff --git a/ctx_test.go b/ctx_test.go index 9b7fa355d8..0f0b642c7d 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -262,7 +262,7 @@ func Test_Ctx_App(t *testing.T) { app.config.BodyLimit = 1000 c := app.AcquireCtx(&fasthttp.RequestCtx{}) - require.Equal(t, 1000, c.App().config.BodyLimit) + require.Equal(t, 1000, app.config.BodyLimit) } // go test -run Test_Ctx_Append diff --git a/group.go b/group.go index 6101f34e00..fa5d207cd6 100644 --- a/group.go +++ b/group.go @@ -10,9 +10,9 @@ import ( ) // Group struct -type Group struct { - app *App[*DefaultCtx] - parentGroup *Group +type Group[TCtx CtxGeneric[TCtx]] struct { + app *App[TCtx] + parentGroup *Group[TCtx] name string Prefix string @@ -23,7 +23,7 @@ type Group struct { // // If this method is used before any route added to group, it'll set group name and OnGroupNameHook will be used. // Otherwise, it'll set route name and OnName hook will be used. -func (grp *Group) Name(name string) Router { +func (grp *Group[TCtx]) Name(name string) Router[TCtx] { if grp.anyRouteDefined { grp.app.Name(name) @@ -66,21 +66,21 @@ func (grp *Group) Name(name string) Router { // app.Use("/mounted-path", subApp) // // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... -func (grp *Group) Use(args ...any) Router { - var subApp *App +func (grp *Group[TCtx]) Use(args ...any) Router[TCtx] { + var subApp *App[TCtx] var prefix string var prefixes []string - var handlers []Handler + var handlers []Handler[TCtx] for i := 0; i < len(args); i++ { switch arg := args[i].(type) { case string: prefix = arg - case *App: + case *App[TCtx]: subApp = arg case []string: prefixes = arg - case Handler: + case Handler[TCtx]: handlers = append(handlers, arg) default: panic(fmt.Sprintf("use: invalid handler %v\n", reflect.TypeOf(arg))) @@ -109,59 +109,59 @@ func (grp *Group) Use(args ...any) Router { // Get registers a route for GET methods that requests a representation // of the specified resource. Requests using GET should only retrieve data. -func (grp *Group) Get(path string, handler Handler, middleware ...Handler) Router { +func (grp *Group[TCtx]) Get(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodGet}, path, handler, middleware...) } // Head registers a route for HEAD methods that asks for a response identical // to that of a GET request, but without the response body. -func (grp *Group) Head(path string, handler Handler, middleware ...Handler) Router { +func (grp *Group[TCtx]) Head(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodHead}, path, handler, middleware...) } // Post registers a route for POST methods that is used to submit an entity to the // specified resource, often causing a change in state or side effects on the server. -func (grp *Group) Post(path string, handler Handler, middleware ...Handler) Router { +func (grp *Group[TCtx]) Post(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodPost}, path, handler, middleware...) } // Put registers a route for PUT methods that replaces all current representations // of the target resource with the request payload. -func (grp *Group) Put(path string, handler Handler, middleware ...Handler) Router { +func (grp *Group[TCtx]) Put(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodPut}, path, handler, middleware...) } // Delete registers a route for DELETE methods that deletes the specified resource. -func (grp *Group) Delete(path string, handler Handler, middleware ...Handler) Router { +func (grp *Group[TCtx]) Delete(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodDelete}, path, handler, middleware...) } // Connect registers a route for CONNECT methods that establishes a tunnel to the // server identified by the target resource. -func (grp *Group) Connect(path string, handler Handler, middleware ...Handler) Router { +func (grp *Group[TCtx]) Connect(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodConnect}, path, handler, middleware...) } // Options registers a route for OPTIONS methods that is used to describe the // communication options for the target resource. -func (grp *Group) Options(path string, handler Handler, middleware ...Handler) Router { +func (grp *Group[TCtx]) Options(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodOptions}, path, handler, middleware...) } // Trace registers a route for TRACE methods that performs a message loop-back // test along the path to the target resource. -func (grp *Group) Trace(path string, handler Handler, middleware ...Handler) Router { +func (grp *Group[TCtx]) Trace(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodTrace}, path, handler, middleware...) } // Patch registers a route for PATCH methods that is used to apply partial // modifications to a resource. -func (grp *Group) Patch(path string, handler Handler, middleware ...Handler) Router { +func (grp *Group[TCtx]) Patch(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodPatch}, path, handler, middleware...) } // Add allows you to specify multiple HTTP methods to register a route. -func (grp *Group) Add(methods []string, path string, handler Handler, middleware ...Handler) Router { +func (grp *Group[TCtx]) Add(methods []string, path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { grp.app.register(methods, getGroupPath(grp.Prefix, path), grp, handler, middleware...) if !grp.anyRouteDefined { grp.anyRouteDefined = true @@ -171,7 +171,7 @@ func (grp *Group) Add(methods []string, path string, handler Handler, middleware } // All will register the handler on all HTTP methods -func (grp *Group) All(path string, handler Handler, middleware ...Handler) Router { +func (grp *Group[TCtx]) All(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] { _ = grp.Add(grp.app.config.RequestMethods, path, handler, middleware...) return grp } @@ -180,14 +180,14 @@ func (grp *Group) All(path string, handler Handler, middleware ...Handler) Route // // api := app.Group("/api") // api.Get("/users", handler) -func (grp *Group) Group(prefix string, handlers ...Handler) Router { +func (grp *Group[TCtx]) Group(prefix string, handlers ...Handler[TCtx]) Router[TCtx] { prefix = getGroupPath(grp.Prefix, prefix) if len(handlers) > 0 { grp.app.register([]string{methodUse}, prefix, grp, nil, handlers...) } // Create new group - newGrp := &Group{Prefix: prefix, app: grp.app, parentGroup: grp} + newGrp := &Group[TCtx]{Prefix: prefix, app: grp.app, parentGroup: grp} if err := grp.app.hooks.executeOnGroupHooks(*newGrp); err != nil { panic(err) } @@ -197,9 +197,9 @@ func (grp *Group) Group(prefix string, handlers ...Handler) Router { // Route is used to define routes with a common prefix inside the common function. // Uses Group method to define new sub-router. -func (grp *Group) Route(path string) Register { +func (grp *Group[TCtx]) Route(path string) Register[TCtx] { // Create new group - register := &Registering{app: grp.app, path: getGroupPath(grp.Prefix, path)} + register := &Registering[TCtx]{app: grp.app, path: getGroupPath(grp.Prefix, path)} return register } diff --git a/helpers.go b/helpers.go index 6d6b806a22..9d1cbce434 100644 --- a/helpers.go +++ b/helpers.go @@ -113,9 +113,9 @@ func (app *App[TCtx]) methodExist(c *DefaultCtx) bool { // Reset stack index c.setIndexRoute(-1) - tree, ok := c.App().treeStack[i][c.getTreePath()] + tree, ok := app.treeStack[i][c.getTreePath()] if !ok { - tree = c.App().treeStack[i][""] + tree = app.treeStack[i][""] } // Get stack length lenr := len(tree) - 1 @@ -148,6 +148,7 @@ func (app *App[TCtx]) methodExist(c *DefaultCtx) bool { // Scan stack if other methods match the request func (app *App[TCtx]) methodExistCustom(c CustomCtx[TCtx]) bool { var exists bool + methods := app.config.RequestMethods for i := 0; i < len(methods); i++ { // Skip original method @@ -157,9 +158,9 @@ func (app *App[TCtx]) methodExistCustom(c CustomCtx[TCtx]) bool { // Reset stack index c.setIndexRoute(-1) - tree, ok := c.App().treeStack[i][c.getTreePath()] + tree, ok := app.treeStack[i][c.getTreePath()] if !ok { - tree = c.App().treeStack[i][""] + tree = app.treeStack[i][""] } // Get stack length lenr := len(tree) - 1 @@ -190,9 +191,9 @@ func (app *App[TCtx]) methodExistCustom(c CustomCtx[TCtx]) bool { } // uniqueRouteStack drop all not unique routes from the slice -func uniqueRouteStack(stack []*Route) []*Route { - var unique []*Route - m := make(map[*Route]int) +func uniqueRouteStack[TCtx CtxGeneric[TCtx]](stack []*Route[TCtx]) []*Route[TCtx] { + var unique []*Route[TCtx] + m := make(map[*Route[TCtx]]int) for _, v := range stack { if _, ok := m[v]; !ok { // Unique key found. Record position and collect diff --git a/hooks.go b/hooks.go index 1b820280a3..56fbb68ad9 100644 --- a/hooks.go +++ b/hooks.go @@ -6,30 +6,30 @@ import ( // OnRouteHandler Handlers define a function to create hooks for Fiber. type ( - OnRouteHandler = func(Route) error - OnNameHandler = OnRouteHandler - OnGroupHandler = func(Group) error - OnGroupNameHandler = OnGroupHandler - OnListenHandler = func(ListenData) error - OnShutdownHandler = func() error - OnForkHandler = func(int) error - OnMountHandler = func(*App[*DefaultCtx]) error + OnRouteHandler[TCtx CtxGeneric[TCtx]] = func(Route[TCtx]) error + OnNameHandler[TCtx CtxGeneric[TCtx]] = OnRouteHandler[TCtx] + OnGroupHandler[TCtx CtxGeneric[TCtx]] = func(Group[TCtx]) error + OnGroupNameHandler[TCtx CtxGeneric[TCtx]] = OnGroupHandler[TCtx] + OnListenHandler = func(ListenData) error + OnShutdownHandler = func() error + OnForkHandler = func(int) error + OnMountHandler[TCtx CtxGeneric[TCtx]] = func(*App[TCtx]) error ) // Hooks is a struct to use it with App. -type Hooks struct { +type Hooks[TCtx CtxGeneric[TCtx]] struct { // Embed app - app *App[*DefaultCtx] + app *App[TCtx] // Hooks - onRoute []OnRouteHandler - onName []OnNameHandler - onGroup []OnGroupHandler - onGroupName []OnGroupNameHandler + onRoute []OnRouteHandler[TCtx] + onName []OnNameHandler[TCtx] + onGroup []OnGroupHandler[TCtx] + onGroupName []OnGroupNameHandler[TCtx] onListen []OnListenHandler onShutdown []OnShutdownHandler onFork []OnForkHandler - onMount []OnMountHandler + onMount []OnMountHandler[TCtx] } // ListenData is a struct to use it with OnListenHandler @@ -39,23 +39,23 @@ type ListenData struct { TLS bool } -func newHooks(app *App[*DefaultCtx]) *Hooks { - return &Hooks{ +func newHooks[TCtx CtxGeneric[TCtx]](app *App[TCtx]) *Hooks[TCtx] { + return &Hooks[TCtx]{ app: app, - onRoute: make([]OnRouteHandler, 0), - onGroup: make([]OnGroupHandler, 0), - onGroupName: make([]OnGroupNameHandler, 0), - onName: make([]OnNameHandler, 0), + onRoute: make([]OnRouteHandler[TCtx], 0), + onGroup: make([]OnGroupHandler[TCtx], 0), + onGroupName: make([]OnGroupNameHandler[TCtx], 0), + onName: make([]OnNameHandler[TCtx], 0), onListen: make([]OnListenHandler, 0), onShutdown: make([]OnShutdownHandler, 0), onFork: make([]OnForkHandler, 0), - onMount: make([]OnMountHandler, 0), + onMount: make([]OnMountHandler[TCtx], 0), } } // OnRoute is a hook to execute user functions on each route registration. // Also you can get route properties by route parameter. -func (h *Hooks) OnRoute(handler ...OnRouteHandler) { +func (h *Hooks[TCtx]) OnRoute(handler ...OnRouteHandler[TCtx]) { h.app.mutex.Lock() h.onRoute = append(h.onRoute, handler...) h.app.mutex.Unlock() @@ -65,7 +65,7 @@ func (h *Hooks) OnRoute(handler ...OnRouteHandler) { // Also you can get route properties by route parameter. // // WARN: OnName only works with naming routes, not groups. -func (h *Hooks) OnName(handler ...OnNameHandler) { +func (h *Hooks[TCtx]) OnName(handler ...OnNameHandler[TCtx]) { h.app.mutex.Lock() h.onName = append(h.onName, handler...) h.app.mutex.Unlock() @@ -73,7 +73,7 @@ func (h *Hooks) OnName(handler ...OnNameHandler) { // OnGroup is a hook to execute user functions on each group registration. // Also you can get group properties by group parameter. -func (h *Hooks) OnGroup(handler ...OnGroupHandler) { +func (h *Hooks[TCtx]) OnGroup(handler ...OnGroupHandler[TCtx]) { h.app.mutex.Lock() h.onGroup = append(h.onGroup, handler...) h.app.mutex.Unlock() @@ -83,28 +83,28 @@ func (h *Hooks) OnGroup(handler ...OnGroupHandler) { // Also you can get group properties by group parameter. // // WARN: OnGroupName only works with naming groups, not routes. -func (h *Hooks) OnGroupName(handler ...OnGroupNameHandler) { +func (h *Hooks[TCtx]) OnGroupName(handler ...OnGroupNameHandler[TCtx]) { h.app.mutex.Lock() h.onGroupName = append(h.onGroupName, handler...) h.app.mutex.Unlock() } // OnListen is a hook to execute user functions on Listen, ListenTLS, Listener. -func (h *Hooks) OnListen(handler ...OnListenHandler) { +func (h *Hooks[TCtx]) OnListen(handler ...OnListenHandler) { h.app.mutex.Lock() h.onListen = append(h.onListen, handler...) h.app.mutex.Unlock() } // OnShutdown is a hook to execute user functions after Shutdown. -func (h *Hooks) OnShutdown(handler ...OnShutdownHandler) { +func (h *Hooks[TCtx]) OnShutdown(handler ...OnShutdownHandler) { h.app.mutex.Lock() h.onShutdown = append(h.onShutdown, handler...) h.app.mutex.Unlock() } // OnFork is a hook to execute user function after fork process. -func (h *Hooks) OnFork(handler ...OnForkHandler) { +func (h *Hooks[TCtx]) OnFork(handler ...OnForkHandler) { h.app.mutex.Lock() h.onFork = append(h.onFork, handler...) h.app.mutex.Unlock() @@ -113,13 +113,13 @@ func (h *Hooks) OnFork(handler ...OnForkHandler) { // OnMount is a hook to execute user function after mounting process. // The mount event is fired when sub-app is mounted on a parent app. The parent app is passed as a parameter. // It works for app and group mounting. -func (h *Hooks) OnMount(handler ...OnMountHandler) { +func (h *Hooks[TCtx]) OnMount(handler ...OnMountHandler[TCtx]) { h.app.mutex.Lock() h.onMount = append(h.onMount, handler...) h.app.mutex.Unlock() } -func (h *Hooks) executeOnRouteHooks(route Route) error { +func (h *Hooks[TCtx]) executeOnRouteHooks(route Route[TCtx]) error { // Check mounting if h.app.mountFields.mountPath != "" { route.path = h.app.mountFields.mountPath + route.path @@ -135,7 +135,7 @@ func (h *Hooks) executeOnRouteHooks(route Route) error { return nil } -func (h *Hooks) executeOnNameHooks(route Route) error { +func (h *Hooks[TCtx]) executeOnNameHooks(route Route[TCtx]) error { // Check mounting if h.app.mountFields.mountPath != "" { route.path = h.app.mountFields.mountPath + route.path @@ -151,7 +151,7 @@ func (h *Hooks) executeOnNameHooks(route Route) error { return nil } -func (h *Hooks) executeOnGroupHooks(group Group) error { +func (h *Hooks[TCtx]) executeOnGroupHooks(group Group[TCtx]) error { // Check mounting if h.app.mountFields.mountPath != "" { group.Prefix = h.app.mountFields.mountPath + group.Prefix @@ -166,7 +166,7 @@ func (h *Hooks) executeOnGroupHooks(group Group) error { return nil } -func (h *Hooks) executeOnGroupNameHooks(group Group) error { +func (h *Hooks[TCtx]) executeOnGroupNameHooks(group Group[TCtx]) error { // Check mounting if h.app.mountFields.mountPath != "" { group.Prefix = h.app.mountFields.mountPath + group.Prefix @@ -181,7 +181,7 @@ func (h *Hooks) executeOnGroupNameHooks(group Group) error { return nil } -func (h *Hooks) executeOnListenHooks(listenData ListenData) error { +func (h *Hooks[TCtx]) executeOnListenHooks(listenData ListenData) error { for _, v := range h.onListen { if err := v(listenData); err != nil { return err @@ -191,7 +191,7 @@ func (h *Hooks) executeOnListenHooks(listenData ListenData) error { return nil } -func (h *Hooks) executeOnShutdownHooks() { +func (h *Hooks[TCtx]) executeOnShutdownHooks() { for _, v := range h.onShutdown { if err := v(); err != nil { log.Errorf("failed to call shutdown hook: %v", err) @@ -199,7 +199,7 @@ func (h *Hooks) executeOnShutdownHooks() { } } -func (h *Hooks) executeOnForkHooks(pid int) { +func (h *Hooks[TCtx]) executeOnForkHooks(pid int) { for _, v := range h.onFork { if err := v(pid); err != nil { log.Errorf("failed to call fork hook: %v", err) @@ -207,7 +207,7 @@ func (h *Hooks) executeOnForkHooks(pid int) { } } -func (h *Hooks) executeOnMountHooks(app *App[Ctx]) error { +func (h *Hooks[TCtx]) executeOnMountHooks(app *App[TCtx]) error { for _, v := range h.onMount { if err := v(app); err != nil { return err diff --git a/listen.go b/listen.go index cd9470a03e..0fe4dcb703 100644 --- a/listen.go +++ b/listen.go @@ -39,7 +39,7 @@ const ( ) // ListenConfig is a struct to customize startup of Fiber. -type ListenConfig struct { +type ListenConfig[TCtx CtxGeneric[TCtx]] struct { // GracefulContext is a field to shutdown Fiber by given context gracefully. // // Default: nil @@ -58,7 +58,7 @@ type ListenConfig struct { // BeforeServeFunc allows customizing and accessing fiber app before serving the app. // // Default: nil - BeforeServeFunc func(app *App[*DefaultCtx]) error `json:"before_serve_func"` + BeforeServeFunc func(app *App[TCtx]) error `json:"before_serve_func"` // OnShutdownError allows to customize error behavior when to graceful shutdown server by given signal. // @@ -131,9 +131,9 @@ type ListenConfig struct { } // listenConfigDefault is a function to set default values of ListenConfig. -func listenConfigDefault(config ...ListenConfig) ListenConfig { +func listenConfigDefault[TCtx CtxGeneric[TCtx]](config ...ListenConfig[TCtx]) ListenConfig[TCtx] { if len(config) < 1 { - return ListenConfig{ + return ListenConfig[TCtx]{ TLSMinVersion: tls.VersionTLS12, ListenerNetwork: NetworkTCP4, OnShutdownError: func(err error) { @@ -171,8 +171,8 @@ func listenConfigDefault(config ...ListenConfig) ListenConfig { // app.Listen(":8080") // app.Listen("127.0.0.1:8080") // app.Listen(":8080", ListenConfig{EnablePrefork: true}) -func (app *App[TCtx]) Listen(addr string, config ...ListenConfig) error { - cfg := listenConfigDefault(config...) +func (app *App[TCtx]) Listen(addr string, config ...ListenConfig[TCtx]) error { + cfg := listenConfigDefault[TCtx](config...) // Configure TLS var tlsConfig *tls.Config @@ -258,7 +258,7 @@ func (app *App[TCtx]) Listen(addr string, config ...ListenConfig) error { // Listener serves HTTP requests from the given listener. // You should enter custom ListenConfig to customize startup. (prefork, startup message, graceful shutdown...) -func (app *App[TCtx]) Listener(ln net.Listener, config ...ListenConfig) error { +func (app *App[TCtx]) Listener(ln net.Listener, config ...ListenConfig[TCtx]) error { cfg := listenConfigDefault(config...) // Graceful shutdown @@ -294,7 +294,7 @@ func (app *App[TCtx]) Listener(ln net.Listener, config ...ListenConfig) error { } // Create listener function. -func (*App[TCtx]) createListener(addr string, tlsConfig *tls.Config, cfg ListenConfig) (net.Listener, error) { +func (*App[TCtx]) createListener(addr string, tlsConfig *tls.Config, cfg ListenConfig[TCtx]) (net.Listener, error) { var listener net.Listener var err error @@ -317,7 +317,7 @@ func (*App[TCtx]) createListener(addr string, tlsConfig *tls.Config, cfg ListenC return listener, nil } -func (app *App[TCtx]) printMessages(cfg ListenConfig, ln net.Listener) { +func (app *App[TCtx]) printMessages(cfg ListenConfig[TCtx], ln net.Listener) { // Print startup message if !cfg.DisableStartupMessage { app.startupMessage(ln.Addr().String(), getTLSConfig(ln) != nil, "", cfg) @@ -330,7 +330,7 @@ func (app *App[TCtx]) printMessages(cfg ListenConfig, ln net.Listener) { } // prepareListenData create an slice of ListenData -func (*App[TCtx]) prepareListenData(addr string, isTLS bool, cfg ListenConfig) ListenData { //revive:disable-line:flag-parameter // Accepting a bool param named isTLS if fine here +func (*App[TCtx]) prepareListenData(addr string, isTLS bool, cfg ListenConfig[TCtx]) ListenData { //revive:disable-line:flag-parameter // Accepting a bool param named isTLS if fine here host, port := parseAddr(addr) if host == "" { if cfg.ListenerNetwork == NetworkTCP6 { @@ -348,7 +348,7 @@ func (*App[TCtx]) prepareListenData(addr string, isTLS bool, cfg ListenConfig) L } // startupMessage prepares the startup message with the handler number, port, address and other information -func (app *App[TCtx]) startupMessage(addr string, isTLS bool, pids string, cfg ListenConfig) { //nolint: revive // Accepting a bool param named isTLS if fine here +func (app *App[TCtx]) startupMessage(addr string, isTLS bool, pids string, cfg ListenConfig[TCtx]) { //nolint: revive // Accepting a bool param named isTLS if fine here // ignore child processes if IsChild() { return @@ -505,7 +505,7 @@ func (app *App[TCtx]) printRoutesMessage() { } // shutdown goroutine -func (app *App[TCtx]) gracefulShutdown(ctx context.Context, cfg ListenConfig) { +func (app *App[TCtx]) gracefulShutdown(ctx context.Context, cfg ListenConfig[TCtx]) { <-ctx.Done() var err error diff --git a/listen_test.go b/listen_test.go index 032f7d32c4..e5cf4d9bfe 100644 --- a/listen_test.go +++ b/listen_test.go @@ -464,9 +464,9 @@ func Test_Listen_BeforeServeFunc(t *testing.T) { }() wantErr := errors.New("test") - require.ErrorIs(t, app.Listen(":0", ListenConfig{ + require.ErrorIs(t, app.Listen(":0", ListenConfig[*DefaultCtx]{ DisableStartupMessage: true, - BeforeServeFunc: func(fiber *App) error { + BeforeServeFunc: func(fiber *App[*DefaultCtx]) error { handlers = fiber.HandlersCount() return wantErr diff --git a/mount.go b/mount.go index 76e152b323..0b40b94b80 100644 --- a/mount.go +++ b/mount.go @@ -13,9 +13,9 @@ import ( ) // Put fields related to mounting. -type mountFields struct { +type mountFields[TCtx CtxGeneric[TCtx]] struct { // Mounted and main apps - appList map[string]*App[*DefaultCtx] + appList map[string]*App[TCtx] // Prefix of app if it was mounted mountPath string // Ordered keys of apps (sorted by key length for Render) @@ -27,9 +27,9 @@ type mountFields struct { } // Create empty mountFields instance -func newMountFields(app *App[*DefaultCtx]) *mountFields { - return &mountFields{ - appList: map[string]*App[*DefaultCtx]{"": app}, +func newMountFields[TCtx CtxGeneric[TCtx]](app *App[TCtx]) *mountFields[TCtx] { + return &mountFields[TCtx]{ + appList: map[string]*App[TCtx]{"": app}, appListKeys: make([]string, 0), } } @@ -39,7 +39,7 @@ func newMountFields(app *App[*DefaultCtx]) *mountFields { // compose them as a single service using Mount. The fiber's error handler and // any of the fiber's sub apps are added to the application's error handlers // to be invoked on errors that happen within the prefix route. -func (app *App[TCtx]) mount(prefix string, subApp *App[TCtx]) Router { +func (app *App[TCtx]) mount(prefix string, subApp *App[TCtx]) Router[TCtx] { prefix = utils.TrimRight(prefix, '/') if prefix == "" { prefix = "/" @@ -54,7 +54,7 @@ func (app *App[TCtx]) mount(prefix string, subApp *App[TCtx]) Router { } // register mounted group - mountGroup := &Group{Prefix: prefix, app: subApp} + mountGroup := &Group[TCtx]{Prefix: prefix, app: subApp} app.register([]string{methodUse}, prefix, mountGroup, nil) // Execute onMount hooks @@ -68,7 +68,7 @@ func (app *App[TCtx]) mount(prefix string, subApp *App[TCtx]) Router { // Mount attaches another app instance as a sub-router along a routing path. // It's very useful to split up a large API as many independent routers and // compose them as a single service using Mount. -func (grp *Group) mount(prefix string, subApp *App[Ctx]) Router { +func (grp *Group[TCtx]) mount(prefix string, subApp *App[TCtx]) Router[TCtx] { groupPath := getGroupPath(grp.Prefix, prefix) groupPath = utils.TrimRight(groupPath, '/') if groupPath == "" { @@ -84,7 +84,7 @@ func (grp *Group) mount(prefix string, subApp *App[Ctx]) Router { } // register mounted group - mountGroup := &Group{Prefix: groupPath, app: subApp} + mountGroup := &Group[TCtx]{Prefix: groupPath, app: subApp} grp.app.register([]string{methodUse}, groupPath, mountGroup, nil) // Execute onMount hooks @@ -194,7 +194,7 @@ func (app *App[TCtx]) processSubAppsRoutes() { } // Create a slice to hold the sub-app's routes - subRoutes := make([]*Route, len(route.group.app.stack[m])) + subRoutes := make([]*Route[TCtx], len(route.group.app.stack[m])) // Iterate over the sub-app's routes for j, subAppRoute := range route.group.app.stack[m] { @@ -209,7 +209,7 @@ func (app *App[TCtx]) processSubAppsRoutes() { } // Insert the sub-app's routes into the parent app's stack - newStack := make([]*Route, len(app.stack[m])+len(subRoutes)-1) + newStack := make([]*Route[TCtx], len(app.stack[m])+len(subRoutes)-1) copy(newStack[:i], app.stack[m][:i]) copy(newStack[i:i+len(subRoutes)], subRoutes) copy(newStack[i+len(subRoutes):], app.stack[m][i+1:]) diff --git a/prefork.go b/prefork.go index 7ebe0e3a5e..3e05352d6c 100644 --- a/prefork.go +++ b/prefork.go @@ -35,7 +35,7 @@ func IsChild() bool { } // prefork manages child processes to make use of the OS REUSEPORT or REUSEADDR feature -func (app *App[TCtx]) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig) error { +func (app *App[TCtx]) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig[TCtx]) error { var ln net.Listener var err error diff --git a/register.go b/register.go index 66d4d43475..f281f62ee1 100644 --- a/register.go +++ b/register.go @@ -5,28 +5,28 @@ package fiber // Register defines all router handle interface generate by Route(). -type Register interface { - All(handler Handler, middleware ...Handler) Register - Get(handler Handler, middleware ...Handler) Register - Head(handler Handler, middleware ...Handler) Register - Post(handler Handler, middleware ...Handler) Register - Put(handler Handler, middleware ...Handler) Register - Delete(handler Handler, middleware ...Handler) Register - Connect(handler Handler, middleware ...Handler) Register - Options(handler Handler, middleware ...Handler) Register - Trace(handler Handler, middleware ...Handler) Register - Patch(handler Handler, middleware ...Handler) Register - - Add(methods []string, handler Handler, middleware ...Handler) Register - - Route(path string) Register +type Register[TCtx CtxGeneric[TCtx]] interface { + All(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] + Get(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] + Head(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] + Post(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] + Put(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] + Delete(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] + Connect(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] + Options(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] + Trace(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] + Patch(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] + + Add(methods []string, handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] + + Route(path string) Register[TCtx] } -var _ (Register) = (*Registering)(nil) +var _ Register[*DefaultCtx] = (*Registering[*DefaultCtx])(nil) // Registering struct -type Registering struct { - app *App[*DefaultCtx] +type Registering[TCtx CtxGeneric[TCtx]] struct { + app *App[TCtx] path string } @@ -45,76 +45,76 @@ type Registering struct { // }) // // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... -func (r *Registering) All(handler Handler, middleware ...Handler) Register { +func (r *Registering[TCtx]) All(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] { r.app.register([]string{methodUse}, r.path, nil, handler, middleware...) return r } // Get registers a route for GET methods that requests a representation // of the specified resource. Requests using GET should only retrieve data. -func (r *Registering) Get(handler Handler, middleware ...Handler) Register { +func (r *Registering[TCtx]) Get(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] { r.app.Add([]string{MethodGet}, r.path, handler, middleware...) return r } // Head registers a route for HEAD methods that asks for a response identical // to that of a GET request, but without the response body. -func (r *Registering) Head(handler Handler, middleware ...Handler) Register { +func (r *Registering[TCtx]) Head(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodHead}, handler, middleware...) } // Post registers a route for POST methods that is used to submit an entity to the // specified resource, often causing a change in state or side effects on the server. -func (r *Registering) Post(handler Handler, middleware ...Handler) Register { +func (r *Registering[TCtx]) Post(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodPost}, handler, middleware...) } // Put registers a route for PUT methods that replaces all current representations // of the target resource with the request payload. -func (r *Registering) Put(handler Handler, middleware ...Handler) Register { +func (r *Registering[TCtx]) Put(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodPut}, handler, middleware...) } // Delete registers a route for DELETE methods that deletes the specified resource. -func (r *Registering) Delete(handler Handler, middleware ...Handler) Register { +func (r *Registering[TCtx]) Delete(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodDelete}, handler, middleware...) } // Connect registers a route for CONNECT methods that establishes a tunnel to the // server identified by the target resource. -func (r *Registering) Connect(handler Handler, middleware ...Handler) Register { +func (r *Registering[TCtx]) Connect(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodConnect}, handler, middleware...) } // Options registers a route for OPTIONS methods that is used to describe the // communication options for the target resource. -func (r *Registering) Options(handler Handler, middleware ...Handler) Register { +func (r *Registering[TCtx]) Options(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodOptions}, handler, middleware...) } // Trace registers a route for TRACE methods that performs a message loop-back // test along the r.Path to the target resource. -func (r *Registering) Trace(handler Handler, middleware ...Handler) Register { +func (r *Registering[TCtx]) Trace(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodTrace}, handler, middleware...) } // Patch registers a route for PATCH methods that is used to apply partial // modifications to a resource. -func (r *Registering) Patch(handler Handler, middleware ...Handler) Register { +func (r *Registering[TCtx]) Patch(handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodPatch}, handler, middleware...) } // Add allows you to specify multiple HTTP methods to register a route. -func (r *Registering) Add(methods []string, handler Handler, middleware ...Handler) Register { +func (r *Registering[TCtx]) Add(methods []string, handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] { r.app.register(methods, r.path, nil, handler, middleware...) return r } // Route returns a new Register instance whose route path takes // the path in the current instance as its prefix. -func (r *Registering) Route(path string) Register { +func (r *Registering[TCtx]) Route(path string) Register[TCtx] { // Create new group - route := &Registering{app: r.app, path: getGroupPath(r.path, path)} + route := &Registering[TCtx]{app: r.app, path: getGroupPath(r.path, path)} return route } diff --git a/router.go b/router.go index fba53cf912..1681b0697c 100644 --- a/router.go +++ b/router.go @@ -6,9 +6,7 @@ package fiber import ( "bytes" - "errors" "fmt" - "html" "sort" "sync/atomic" @@ -17,33 +15,33 @@ import ( ) // Router defines all router handle interface, including app and group router. -type Router interface { - Use(args ...any) Router +type Router[TCtx CtxGeneric[TCtx]] interface { + Use(args ...any) Router[TCtx] - Get(path string, handler Handler, middleware ...Handler) Router - Head(path string, handler Handler, middleware ...Handler) Router - Post(path string, handler Handler, middleware ...Handler) Router - Put(path string, handler Handler, middleware ...Handler) Router - Delete(path string, handler Handler, middleware ...Handler) Router - Connect(path string, handler Handler, middleware ...Handler) Router - Options(path string, handler Handler, middleware ...Handler) Router - Trace(path string, handler Handler, middleware ...Handler) Router - Patch(path string, handler Handler, middleware ...Handler) Router + Get(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] + Head(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] + Post(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] + Put(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] + Delete(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] + Connect(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] + Options(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] + Trace(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] + Patch(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] - Add(methods []string, path string, handler Handler, middleware ...Handler) Router - All(path string, handler Handler, middleware ...Handler) Router + Add(methods []string, path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] + All(path string, handler Handler[TCtx], middleware ...Handler[TCtx]) Router[TCtx] - Group(prefix string, handlers ...Handler) Router + Group(prefix string, handlers ...Handler[TCtx]) Router[TCtx] - Route(path string) Register + Route(path string) Register[TCtx] - Name(name string) Router + Name(name string) Router[TCtx] } // Route is a struct that holds all metadata for each registered handler. -type Route struct { +type Route[TCtx CtxGeneric[TCtx]] struct { // ### important: always keep in sync with the copy method "app.copyRoute" ### - group *Group // Group instance. used for routes in groups + group *Group[TCtx] // Group instance. used for routes in groups path string // Prettified path @@ -51,10 +49,10 @@ type Route struct { Method string `json:"method"` // HTTP method Name string `json:"name"` // Route's name //nolint:revive // Having both a Path (uppercase) and a path (lowercase) is fine - Path string `json:"path"` // Original registered route path - Params []string `json:"params"` // Case-sensitive param keys - Handlers []Handler `json:"-"` // Ctx handlers - routeParser routeParser // Parameter parser + Path string `json:"path"` // Original registered route path + Params []string `json:"params"` // Case-sensitive param keys + Handlers []Handler[TCtx] `json:"-"` // Ctx handlers + routeParser routeParser // Parameter parser // Data for routing pos uint32 // Position in stack -> important for the sort of the matched routes use bool // USE matches path prefixes @@ -63,7 +61,7 @@ type Route struct { root bool // Path equals '/' } -func (r *Route) match(detectionPath, path string, params *[maxParams]string) bool { +func (r *Route[TCtx]) match(detectionPath, path string, params *[maxParams]string) bool { // root detectionPath check if r.root && len(detectionPath) == 1 && detectionPath[0] == '/' { return true @@ -108,7 +106,7 @@ func (r *Route) match(detectionPath, path string, params *[maxParams]string) boo return false } -func (app *App[TCtx]) nextCustom(c CustomCtx[TCtx]) (bool, error) { //nolint: unparam // bool param might be useful for testing +func (app *App[TCtx]) next(c TCtx) (bool, error) { //nolint: unparam // bool param might be useful for testing // Get stack length tree, ok := app.treeStack[c.getMethodINT()][c.getTreePath()] if !ok { @@ -156,98 +154,9 @@ func (app *App[TCtx]) nextCustom(c CustomCtx[TCtx]) (bool, error) { //nolint: un return false, err } -func (app *App[TCtx]) next(c *DefaultCtx) (bool, error) { - // Get stack length - tree, ok := app.treeStack[c.methodINT][c.treePath] - if !ok { - tree = app.treeStack[c.methodINT][""] - } - 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(c.detectionPath, 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]) defaultRequestHandler(rctx *fasthttp.RequestCtx) { +func (app *App[TCtx]) requestHandler(rctx *fasthttp.RequestCtx) { // Acquire DefaultCtx from the pool - ctx, ok := app.AcquireCtx(rctx).(*DefaultCtx) - if !ok { - panic(errors.New("requestHandler: failed to type-assert to *DefaultCtx")) - } - - defer app.ReleaseCtx(ctx) - - // Check if the HTTP method is valid - if ctx.methodINT == -1 { - _ = ctx.SendStatus(StatusNotImplemented) //nolint:errcheck // Always return nil - return - } - - // Optional: Check flash messages - rawHeaders := ctx.Request().Header.RawHeaders() - if len(rawHeaders) > 0 && bytes.Contains(rawHeaders, []byte(FlashCookieName)) { - ctx.Redirect().parseAndClearFlashMessages() - } - - // Attempt to match a route and execute the chain - _, err := app.next(ctx) - if err != nil { - if catch := ctx.App().ErrorHandler(ctx, err); catch != nil { - _ = ctx.SendStatus(StatusInternalServerError) //nolint:errcheck // Always return nil - } - // TODO: Do we need to return here? - } -} - -func (app *App[TCtx]) customRequestHandler(rctx *fasthttp.RequestCtx) { - // Acquire CustomCtx from the pool - ctx, ok := app.AcquireCtx(rctx).(CustomCtx) - if !ok { - panic(errors.New("requestHandler: failed to type-assert to CustomCtx")) - } + ctx := app.AcquireCtx(rctx) defer app.ReleaseCtx(ctx) @@ -264,16 +173,16 @@ func (app *App[TCtx]) customRequestHandler(rctx *fasthttp.RequestCtx) { } // Attempt to match a route and execute the chain - _, err := app.nextCustom(ctx) + _, err := app.next(ctx) if err != nil { - if catch := ctx.App().ErrorHandler(ctx, err); catch != nil { + if catch := app.ErrorHandler(ctx, err); catch != nil { _ = ctx.SendStatus(StatusInternalServerError) //nolint:errcheck // Always return nil } // TODO: Do we need to return here? } } -func (app *App[TCtx]) addPrefixToRoute(prefix string, route *Route) *Route { +func (app *App[TCtx]) addPrefixToRoute(prefix string, route *Route[TCtx]) *Route[TCtx] { prefixedPath := getGroupPath(prefix, route.Path) prettyPath := prefixedPath // Case-sensitive routing, all to lowercase @@ -294,8 +203,8 @@ func (app *App[TCtx]) addPrefixToRoute(prefix string, route *Route) *Route { return route } -func (*App[TCtx]) copyRoute(route *Route) *Route { - return &Route{ +func (*App[TCtx]) copyRoute(route *Route[TCtx]) *Route[TCtx] { + return &Route[TCtx]{ // Router booleans use: route.use, mount: route.mount, @@ -318,7 +227,7 @@ func (*App[TCtx]) copyRoute(route *Route) *Route { } } -func (app *App[TCtx]) register(methods []string, pathRaw string, group *Group, handler Handler, middleware ...Handler) { +func (app *App[TCtx]) register(methods []string, pathRaw string, group *Group[TCtx], handler Handler[TCtx], middleware ...Handler[TCtx]) { handlers := middleware if handler != nil { handlers = append(handlers, handler) @@ -358,7 +267,7 @@ func (app *App[TCtx]) register(methods []string, pathRaw string, group *Group, h isStar := pathClean == "/*" isRoot := pathClean == "/" - route := Route{ + route := Route[TCtx]{ use: isUse, mount: isMount, star: isStar, @@ -392,7 +301,7 @@ func (app *App[TCtx]) register(methods []string, pathRaw string, group *Group, h } } -func (app *App[TCtx]) addRoute(method string, route *Route, isMounted ...bool) { +func (app *App[TCtx]) addRoute(method string, route *Route[TCtx], isMounted ...bool) { app.mutex.Lock() defer app.mutex.Unlock() @@ -428,7 +337,7 @@ func (app *App[TCtx]) addRoute(method string, route *Route, isMounted ...bool) { } } -// BuildTree rebuilds the prefix tree from the previously registered routes. +// RebuildTree BuildTree rebuilds the prefix tree from the previously registered routes. // This method is useful when you want to register routes dynamically after the app has started. // It is not recommended to use this method on production environments because rebuilding // the tree is performance-intensive and not thread-safe in runtime. Since building the tree @@ -451,7 +360,7 @@ func (app *App[TCtx]) buildTree() *App[TCtx] { // loop all the methods and stacks and create the prefix tree for m := range app.config.RequestMethods { - tsMap := make(map[string][]*Route) + tsMap := make(map[string][]*Route[TCtx]) for _, route := range app.stack[m] { treePath := "" if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 { @@ -469,7 +378,7 @@ func (app *App[TCtx]) buildTree() *App[TCtx] { for treePart := range tsMap { if treePart != "" { // merge global tree routes in current tree stack - tsMap[treePart] = uniqueRouteStack(append(tsMap[treePart], tsMap[""]...)) + tsMap[treePart] = uniqueRouteStack[TCtx](append(tsMap[treePart], tsMap[""]...)) } // sort tree slices with the positions slc := tsMap[treePart]