From aac1b97a4ca4be973c42078a85094c7961df6579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Tue, 28 Jan 2025 21:59:32 +0100 Subject: [PATCH 1/7] Implement Fluent Method Chaining for Status and Type Methods Using Generics #3221 --- app.go | 143 ++++++----- bind.go | 4 +- ctx.go | 21 +- ctx_custom_interface.go | 69 +++++ ctx_interface.go | 382 ++++++++++++++++++++++++---- ctx_interface_gen.go | 422 ++++++------------------------- ctx_test.go | 4 +- docs/api/app.md | 14 +- docs/api/fiber.md | 2 +- docs/partials/routing/handler.md | 32 +-- docs/whats_new.md | 6 +- group.go | 2 +- helpers.go | 10 +- hooks.go | 8 +- listen.go | 18 +- mount.go | 22 +- prefork.go | 2 +- register.go | 2 +- router.go | 20 +- router_test.go | 2 +- 20 files changed, 650 insertions(+), 535 deletions(-) create mode 100644 ctx_custom_interface.go diff --git a/app.go b/app.go index dca7efed..1c43497a 100644 --- a/app.go +++ b/app.go @@ -35,7 +35,9 @@ import ( const Version = "3.0.0-beta.4" // Handler defines a function to serve HTTP requests. -type Handler = func(Ctx) error +type Handler = func(Ctx[any]) error + +type customCtxFunc = func(app *App[Ctx[any]]) CustomCtx[Ctx[any]] // Map is a shortcut for map[string]any, useful for JSON returns type Map map[string]any @@ -78,7 +80,7 @@ type Storage interface { // return c.Status(code).SendString(err.Error()) // } // app := fiber.New(cfg) -type ErrorHandler = func(Ctx, error) error +type ErrorHandler = func(Ctx[any], error) error // Error represents an error that occurred while handling a request. type Error struct { @@ -87,7 +89,7 @@ type Error struct { } // App denotes the Fiber application. -type App struct { +type App[TCtx Ctx[TCtx]] struct { // Ctx pool pool sync.Pool // Fasthttp server @@ -101,7 +103,7 @@ type App struct { // Latest route & group latestRoute *Route // newCtxFunc - newCtxFunc func(app *App) CustomCtx + newCtxFunc customCtxFunc // TLS handler tlsHandler *TLSHandler // Mount fields @@ -470,7 +472,7 @@ var DefaultMethods = []string{ } // DefaultErrorHandler that process return errors from handlers -func DefaultErrorHandler(c Ctx, err error) error { +func DefaultErrorHandler(c Ctx[any], err error) error { code := StatusInternalServerError var e *Error if errors.As(err, &e) { @@ -490,9 +492,49 @@ func DefaultErrorHandler(c Ctx, err error) error { // Prefork: true, // ServerHeader: "Fiber", // }) -func New(config ...Config) *App { +func New(config ...Config) *App[DefaultCtx] { + app := newApp[DefaultCtx](config...) + + // Init app + app.init() + + return app +} + +// NewWithCustomCtx creates a new Fiber named instance with a custom context. +// +// app := fiber.NewWithCustomCtx(func(app *fiber.App) fiber.CustomCtx[MyCustomCtx] { +// return &MyCustomCtx{ +// DefaultCtx: *fiber.NewDefaultCtx(app), +// } +// }) +// +// You can pass optional configuration options by passing a Config struct: +// +// app := fiber.NewWithCustomCtx(func(app *fiber.App) fiber.CustomCtx[MyCustomCtx] { +// return &MyCustomCtx{ +// DefaultCtx: *fiber.NewDefaultCtx(app), +// } +// }, fiber.Config{ +// Prefork: true, +// ServerHeader: "Fiber", +// }) +func NewWithCustomCtx[TCtx Ctx[TCtx]](newCtxFunc customCtxFunc, config ...Config) *App[TCtx] { + app := newApp[TCtx](config...) + + // Set newCtxFunc + app.newCtxFunc = newCtxFunc + + // Init app + app.init() + + return app +} + +// newApp creates a new Fiber named instance. +func newApp[TCtx Ctx[TCtx]](config ...Config) *App[TCtx] { // Create a new app - app := &App{ + app := &App[TCtx]{ // Create config config: Config{}, getBytes: utils.UnsafeBytes, @@ -586,15 +628,12 @@ func New(config ...Config) *App { // Override colors app.config.ColorScheme = defaultColors(app.config.ColorScheme) - // Init app - app.init() - // Return app return app } // Adds an ip address to TrustProxyConfig.ranges or TrustProxyConfig.ips based on whether it is an IP range or not -func (app *App) handleTrustedProxy(ipAddress string) { +func (app *App[TCtx]) handleTrustedProxy(ipAddress string) { if strings.Contains(ipAddress, "/") { _, ipNet, err := net.ParseCIDR(ipAddress) if err != nil { @@ -612,29 +651,19 @@ func (app *App) handleTrustedProxy(ipAddress string) { } } -// NewCtxFunc allows to customize ctx methods as we want. -// Note: It doesn't allow adding new methods, only customizing exist methods. -func (app *App) NewCtxFunc(function func(app *App) CustomCtx) { - app.newCtxFunc = function - - if app.server != nil { - app.server.Handler = app.customRequestHandler - } -} - // RegisterCustomConstraint allows to register custom constraint. -func (app *App) RegisterCustomConstraint(constraint CustomConstraint) { +func (app *App[TCtx]) RegisterCustomConstraint(constraint CustomConstraint) { app.customConstraints = append(app.customConstraints, constraint) } // RegisterCustomBinder Allows to register custom binders to use as Bind().Custom("name"). // They should be compatible with CustomBinder interface. -func (app *App) RegisterCustomBinder(binder CustomBinder) { +func (app *App[TCtx]) RegisterCustomBinder(binder CustomBinder) { app.customBinders = append(app.customBinders, binder) } // SetTLSHandler Can be used to set ClientHelloInfo when using TLS with Listener. -func (app *App) SetTLSHandler(tlsHandler *TLSHandler) { +func (app *App[TCtx]) SetTLSHandler(tlsHandler *TLSHandler) { // Attach the tlsHandler to the config app.mutex.Lock() app.tlsHandler = tlsHandler @@ -642,7 +671,7 @@ func (app *App) SetTLSHandler(tlsHandler *TLSHandler) { } // Name Assign name to specific route. -func (app *App) Name(name string) Router { +func (app *App[TCtx]) Name(name string) Router { app.mutex.Lock() defer app.mutex.Unlock() @@ -668,7 +697,7 @@ func (app *App) Name(name string) Router { } // GetRoute Get route by name -func (app *App) GetRoute(name string) Route { +func (app *App[TCtx]) GetRoute(name string) Route { for _, routes := range app.stack { for _, route := range routes { if route.Name == name { @@ -681,7 +710,7 @@ func (app *App) GetRoute(name string) Route { } // GetRoutes Get all routes. When filterUseOption equal to true, it will filter the routes registered by the middleware. -func (app *App) GetRoutes(filterUseOption ...bool) []Route { +func (app *App[TCtx]) GetRoutes(filterUseOption ...bool) []Route { var rs []Route var filterUse bool if len(filterUseOption) != 0 { @@ -719,9 +748,9 @@ func (app *App) GetRoutes(filterUseOption ...bool) []Route { // app.Use("/mounted-path", subApp) // // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... -func (app *App) Use(args ...any) Router { +func (app *App[TCtx]) Use(args ...any) Router { var prefix string - var subApp *App + var subApp *App[TCtx] var prefixes []string var handlers []Handler @@ -758,66 +787,66 @@ func (app *App) 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) Get(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Get(path string, handler Handler, middleware ...Handler) Router { 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) Head(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Head(path string, handler Handler, middleware ...Handler) Router { 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) Post(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Post(path string, handler Handler, middleware ...Handler) Router { 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) Put(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Put(path string, handler Handler, middleware ...Handler) Router { return app.Add([]string{MethodPut}, path, handler, middleware...) } // Delete registers a route for DELETE methods that deletes the specified resource. -func (app *App) Delete(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Delete(path string, handler Handler, middleware ...Handler) Router { 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) Connect(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Connect(path string, handler Handler, middleware ...Handler) Router { 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) Options(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Options(path string, handler Handler, middleware ...Handler) Router { 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) Trace(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Trace(path string, handler Handler, middleware ...Handler) Router { 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) Patch(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Patch(path string, handler Handler, middleware ...Handler) Router { return app.Add([]string{MethodPatch}, path, handler, middleware...) } // Add allows you to specify multiple HTTP methods to register a route. -func (app *App) Add(methods []string, path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) Add(methods []string, path string, handler Handler, middleware ...Handler) Router { app.register(methods, path, nil, handler, middleware...) return app } // All will register the handler on all HTTP methods -func (app *App) All(path string, handler Handler, middleware ...Handler) Router { +func (app *App[TCtx]) All(path string, handler Handler, middleware ...Handler) Router { return app.Add(app.config.RequestMethods, path, handler, middleware...) } @@ -825,7 +854,7 @@ func (app *App) All(path string, handler Handler, middleware ...Handler) Router // // api := app.Group("/api") // api.Get("/users", handler) -func (app *App) Group(prefix string, handlers ...Handler) Router { +func (app *App[TCtx]) Group(prefix string, handlers ...Handler) Router { grp := &Group{Prefix: prefix, app: app} if len(handlers) > 0 { app.register([]string{methodUse}, prefix, grp, nil, handlers...) @@ -839,7 +868,7 @@ func (app *App) 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) Route(path string) Register { +func (app *App[TCtx]) Route(path string) Register { // Create new route route := &Registering{app: app, path: path} @@ -864,12 +893,12 @@ func NewError(code int, message ...string) *Error { } // Config returns the app config as value ( read-only ). -func (app *App) Config() Config { +func (app *App[TCtx]) Config() Config { return app.config } // Handler returns the server handler. -func (app *App) Handler() fasthttp.RequestHandler { //revive:disable-line:confusing-naming // Having both a Handler() (uppercase) and a handler() (lowercase) is fine. TODO: Use nolint:revive directive instead. See https://github.com/golangci/golangci-lint/issues/3476 +func (app *App[TCtx]) Handler() fasthttp.RequestHandler { //revive:disable-line:confusing-naming // Having both a Handler() (uppercase) and a handler() (lowercase) is fine. TODO: Use nolint:revive directive instead. See https://github.com/golangci/golangci-lint/issues/3476 // prepare the server for the start app.startupProcess() @@ -880,12 +909,12 @@ func (app *App) Handler() fasthttp.RequestHandler { //revive:disable-line:confus } // Stack returns the raw router stack. -func (app *App) Stack() [][]*Route { +func (app *App[TCtx]) Stack() [][]*Route { return app.stack } // HandlersCount returns the amount of registered handlers. -func (app *App) HandlersCount() uint32 { +func (app *App[TCtx]) HandlersCount() uint32 { return app.handlersCount } @@ -895,7 +924,7 @@ func (app *App) HandlersCount() uint32 { // Make sure the program doesn't exit and waits instead for Shutdown to return. // // Shutdown does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. -func (app *App) Shutdown() error { +func (app *App[TCtx]) Shutdown() error { return app.ShutdownWithContext(context.Background()) } @@ -906,7 +935,7 @@ func (app *App) Shutdown() error { // Make sure the program doesn't exit and waits instead for ShutdownWithTimeout to return. // // ShutdownWithTimeout does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. -func (app *App) ShutdownWithTimeout(timeout time.Duration) error { +func (app *App[TCtx]) ShutdownWithTimeout(timeout time.Duration) error { ctx, cancelFunc := context.WithTimeout(context.Background(), timeout) defer cancelFunc() return app.ShutdownWithContext(ctx) @@ -917,7 +946,7 @@ func (app *App) ShutdownWithTimeout(timeout time.Duration) error { // Make sure the program doesn't exit and waits instead for ShutdownWithTimeout to return. // // ShutdownWithContext does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. -func (app *App) ShutdownWithContext(ctx context.Context) error { +func (app *App[TCtx]) ShutdownWithContext(ctx context.Context) error { if app.hooks != nil { // TODO: check should be defered? app.hooks.executeOnShutdownHooks() @@ -932,12 +961,12 @@ func (app *App) ShutdownWithContext(ctx context.Context) error { } // Server returns the underlying fasthttp server -func (app *App) Server() *fasthttp.Server { +func (app *App[TCtx]) Server() *fasthttp.Server { return app.server } // Hooks returns the hook struct to register hooks. -func (app *App) Hooks() *Hooks { +func (app *App[TCtx]) Hooks() *Hooks { return app.hooks } @@ -960,7 +989,7 @@ type TestConfig struct { // Test is used for internal debugging by passing a *http.Request. // Config is optional and defaults to a 1s error on timeout, // 0 timeout will disable it completely. -func (app *App) Test(req *http.Request, config ...TestConfig) (*http.Response, error) { +func (app *App[TCtx]) Test(req *http.Request, config ...TestConfig) (*http.Response, error) { // Default config cfg := TestConfig{ Timeout: time.Second, @@ -1048,7 +1077,7 @@ type disableLogger struct{} func (*disableLogger) Printf(string, ...any) { } -func (app *App) init() *App { +func (app *App[TCtx]) init() *App[TCtx] { // lock application app.mutex.Lock() @@ -1100,7 +1129,7 @@ func (app *App) init() *App { // 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) ErrorHandler(ctx Ctx, err error) error { +func (app *App[TCtx]) ErrorHandler(ctx Ctx[TCtx], err error) error { var ( mountedErrHandler ErrorHandler mountedPrefixParts int @@ -1129,7 +1158,7 @@ func (app *App) ErrorHandler(ctx Ctx, err error) error { // serverErrorHandler is a wrapper around the application's error handler method // user for the fasthttp server configuration. It maps a set of fasthttp errors to fiber // errors before calling the application's error handler method. -func (app *App) serverErrorHandler(fctx *fasthttp.RequestCtx, err error) { +func (app *App[TCtx]) serverErrorHandler(fctx *fasthttp.RequestCtx, err error) { // Acquire Ctx with fasthttp request from pool c := app.AcquireCtx(fctx) defer app.ReleaseCtx(c) @@ -1164,7 +1193,7 @@ func (app *App) serverErrorHandler(fctx *fasthttp.RequestCtx, err error) { } // startupProcess Is the method which executes all the necessary processes just before the start of the server. -func (app *App) startupProcess() *App { +func (app *App[TCtx]) startupProcess() *App[TCtx] { app.mutex.Lock() defer app.mutex.Unlock() @@ -1177,7 +1206,7 @@ func (app *App) startupProcess() *App { } // Run onListen hooks. If they return an error, panic. -func (app *App) runOnListenHooks(listenData ListenData) { +func (app *App[TCtx]) runOnListenHooks(listenData ListenData) { if err := app.hooks.executeOnListenHooks(listenData); err != nil { panic(err) } diff --git a/bind.go b/bind.go index 13d9d367..eac6d5a5 100644 --- a/bind.go +++ b/bind.go @@ -9,7 +9,7 @@ import ( type CustomBinder interface { Name() string MIMETypes() []string - Parse(c Ctx, out any) error + Parse(c Ctx[any], out any) error } // StructValidator is an interface to register custom struct validator for binding. @@ -19,7 +19,7 @@ type StructValidator interface { // Bind struct type Bind struct { - ctx Ctx + ctx Ctx[any] dontHandleErrs bool } diff --git a/ctx.go b/ctx.go index 3af6b600..a963ecc6 100644 --- a/ctx.go +++ b/ctx.go @@ -47,9 +47,10 @@ const userContextKey contextKey = 0 // __local_user_context__ // generation tool `go install github.com/vburenin/ifacemaker@975a95966976eeb2d4365a7fb236e274c54da64c` // https://github.com/vburenin/ifacemaker/blob/975a95966976eeb2d4365a7fb236e274c54da64c/ifacemaker.go#L14-L30 // -//go:generate ifacemaker --file ctx.go --struct DefaultCtx --iface Ctx --pkg fiber --output ctx_interface_gen.go --not-exported true --iface-comment "Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on." +//go:generate ifacemaker --file ctx.go --struct DefaultCtx --iface Ctx --pkg fiber --output ctx_interface.go --not-exported true --iface-comment "Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on." +//go:generate go run ctx_interface_gen.go type DefaultCtx struct { - app *App // Reference to *App + app *App[Ctx[any]] // Reference to *App route *Route // Reference to *Route fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx bind *Bind // Default bind reference @@ -197,7 +198,7 @@ type Views interface { // ResFmt associates a Content Type to a fiber.Handler for c.Format type ResFmt struct { - Handler func(Ctx) error + Handler func(Ctx[any]) error MediaType string } @@ -222,7 +223,7 @@ func (c *DefaultCtx) AcceptsLanguages(offers ...string) string { } // App returns the *App reference to the instance of the Fiber application -func (c *DefaultCtx) App() *App { +func (c *DefaultCtx) App() *App[DefaultCtx] { return c.app } @@ -642,7 +643,7 @@ func (c *DefaultCtx) Get(key string, defaultValue ...string) string { // GetReqHeader returns the HTTP request header specified by filed. // This function is generic and can handle different headers type values. -func GetReqHeader[V GenericType](c Ctx, key string, defaultValue ...V) V { +func GetReqHeader[V GenericType](c Ctx[any], key string, defaultValue ...V) V { var v V return genericParseType[V](c.App().getString(c.Request().Header.Peek(key)), v, defaultValue...) } @@ -978,7 +979,7 @@ func (c *DefaultCtx) Locals(key any, value ...any) any { // All the values are removed from ctx after returning from the top // RequestHandler. Additionally, Close method is called on each value // implementing io.Closer before removing the value from ctx. -func Locals[V any](c Ctx, key any, value ...V) V { +func Locals[V any](c Ctx[any], key any, value ...V) V { var v V var ok bool if len(value) == 0 { @@ -1114,7 +1115,7 @@ func (c *DefaultCtx) Params(key string, defaultValue ...string) string { // // http://example.com/id/:number -> http://example.com/id/john // Params[int](c, "number", 0) -> returns 0 because can't parse 'john' as integer. -func Params[V GenericType](c Ctx, key string, defaultValue ...V) V { +func Params[V GenericType](c Ctx[any], key string, defaultValue ...V) V { var v V return genericParseType(c.Params(key), v, defaultValue...) } @@ -1236,7 +1237,7 @@ func (c *DefaultCtx) Queries() map[string]string { // name := Query[string](c, "search") // Returns "john" // 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 GenericType](c Ctx, key string, defaultValue ...V) V { +func Query[V GenericType](c Ctx[any], key string, defaultValue ...V) V { var v V q := c.App().getString(c.RequestCtx().QueryArgs().Peek(key)) @@ -1730,7 +1731,7 @@ func (c *DefaultCtx) Stale() bool { // Status sets the HTTP status for the response. // This method is chainable. -func (c *DefaultCtx) Status(status int) Ctx { +func (c *DefaultCtx) Status(status int) Ctx[DefaultCtx] { c.fasthttp.Response.SetStatusCode(status) return c } @@ -1775,7 +1776,7 @@ func (c *DefaultCtx) String() string { } // Type sets the Content-Type HTTP header to the MIME type specified by the file extension. -func (c *DefaultCtx) Type(extension string, charset ...string) Ctx { +func (c *DefaultCtx) Type(extension string, charset ...string) Ctx[DefaultCtx] { if len(charset) > 0 { c.fasthttp.Response.Header.SetContentType(utils.GetMIME(extension) + "; charset=" + charset[0]) } else { diff --git a/ctx_custom_interface.go b/ctx_custom_interface.go new file mode 100644 index 00000000..73a5a57e --- /dev/null +++ b/ctx_custom_interface.go @@ -0,0 +1,69 @@ +// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ +// 🤖 Github Repository: https://github.com/gofiber/fiber +// 📌 API Documentation: https://docs.gofiber.io + +package fiber + +import ( + "errors" + + "github.com/valyala/fasthttp" +) + +type CustomCtx[T any] interface { + Ctx[T] + + // Reset is a method to reset context fields by given request when to use server handlers. + Reset(fctx *fasthttp.RequestCtx) + + // Methods to use with next stack. + getMethodINT() int + getIndexRoute() int + getTreePath() string + getDetectionPath() string + getPathOriginal() string + getValues() *[maxParams]string + getMatched() bool + setIndexHandler(handler int) + setIndexRoute(route int) + setMatched(matched bool) + setRoute(route *Route) +} + +func NewDefaultCtx(app *App[DefaultCtx]) *DefaultCtx { + // return ctx + return &DefaultCtx{ + // Set app reference + app: app, + } +} + +func (app *App[TCtx]) newCtx() Ctx[TCtx] { + var c Ctx[TCtx] + + 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[TCtx]) AcquireCtx(fctx *fasthttp.RequestCtx) Ctx[TCtx] { + 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[TCtx]) ReleaseCtx(c Ctx[TCtx]) { + c.release() + app.pool.Put(c) +} diff --git a/ctx_interface.go b/ctx_interface.go index ca438d82..94e1b9fb 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -1,21 +1,343 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io +// Code generated by ifacemaker; DO NOT EDIT. package fiber import ( - "errors" + "bufio" + "context" + "crypto/tls" + "io" + "mime/multipart" "github.com/valyala/fasthttp" ) -type CustomCtx interface { - Ctx - +// T represents the Context which hold the HTTP request and response. +// It has methods for the request query string, parameters, body, HTTP headers and so on. +type Ctx[T any] interface { + // Accepts checks if the specified extensions or content types are acceptable. + Accepts(offers ...string) string + // AcceptsCharsets checks if the specified charset is acceptable. + AcceptsCharsets(offers ...string) string + // AcceptsEncodings checks if the specified encoding is acceptable. + AcceptsEncodings(offers ...string) string + // 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[Ctx[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) + // Attachment sets the HTTP response Content-Disposition header field to attachment. + Attachment(filename ...string) + // BaseURL returns (protocol + host + base path). + BaseURL() string + // BodyRaw contains the raw body submitted in a POST request. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting instead. + BodyRaw() []byte + tryDecodeBodyInOrder(originalBody *[]byte, encodings []string) ([]byte, uint8, error) + // Body contains the raw body submitted in a POST request. + // This method will decompress the body if the 'Content-Encoding' header is provided. + // It returns the original (or decompressed) body data which is valid only within the handler. + // Don't store direct references to the returned data. + // If you need to keep the body's data later, make a copy or use the Immutable option. + Body() []byte + // ClearCookie expires a specific cookie by key on the client side. + // If no key is provided it expires all cookies that came with the request. + ClearCookie(key ...string) + // RequestCtx returns *fasthttp.RequestCtx that carries a deadline + // a cancellation signal, and other values across API boundaries. + RequestCtx() *fasthttp.RequestCtx + // Context returns a context implementation that was set by + // user earlier or returns a non-nil, empty context,if it was not set earlier. + Context() context.Context + // SetContext sets a context implementation by user. + SetContext(ctx context.Context) + // Cookie sets a cookie by passing a cookie struct. + Cookie(cookie *Cookie) + // Cookies are used for getting a cookie value by key. + // Defaults to the empty string "" if the cookie doesn't exist. + // If a default value is given, it will return that value if the cookie doesn't exist. + // The returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting to use the value outside the Handler. + Cookies(key string, defaultValue ...string) string + // Download transfers the file from path as an attachment. + // Typically, browsers will prompt the user for download. + // By default, the Content-Disposition header filename= parameter is the filepath (this typically appears in the browser dialog). + // Override this default with the filename parameter. + Download(file string, filename ...string) error + // Request return the *fasthttp.Request object + // This allows you to use all fasthttp request methods + // https://godoc.org/github.com/valyala/fasthttp#Request + Request() *fasthttp.Request + // Response return the *fasthttp.Response object + // This allows you to use all fasthttp response methods + // https://godoc.org/github.com/valyala/fasthttp#Response + Response() *fasthttp.Response + // Format performs content-negotiation on the Accept HTTP header. + // It uses Accepts to select a proper format and calls the matching + // user-provided handler function. + // If no accepted format is found, and a format with MediaType "default" is given, + // that default handler is called. If no format is found and no default is given, + // StatusNotAcceptable is sent. + Format(handlers ...ResFmt) error + // AutoFormat performs content-negotiation on the Accept HTTP header. + // It uses Accepts to select a proper format. + // The supported content types are text/html, text/plain, application/json, and application/xml. + // For more flexible content negotiation, use Format. + // If the header is not specified or there is no proper format, text/plain is used. + AutoFormat(body any) error + // FormFile returns the first file by key from a MultipartForm. + FormFile(key string) (*multipart.FileHeader, error) + // FormValue returns the first value by key from a MultipartForm. + // Search is performed in QueryArgs, PostArgs, MultipartForm and FormFile in this particular order. + // Defaults to the empty string "" if the form value doesn't exist. + // If a default value is given, it will return that value if the form value does not exist. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting instead. + FormValue(key string, defaultValue ...string) string + // Fresh returns true when the response is still “fresh” in the client's cache, + // otherwise false is returned to indicate that the client cache is now stale + // and the full response should be sent. + // When a client sends the Cache-Control: no-cache request header to indicate an end-to-end + // reload request, this module will return false to make handling these requests transparent. + // https://github.com/jshttp/fresh/blob/10e0471669dbbfbfd8de65bc6efac2ddd0bfa057/index.js#L33 + Fresh() bool + // Get returns the HTTP request header specified by field. + // Field names are case-insensitive + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting instead. + Get(key string, defaultValue ...string) string + // GetRespHeader returns the HTTP response header specified by field. + // Field names are case-insensitive + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting instead. + GetRespHeader(key string, defaultValue ...string) string + // GetRespHeaders returns the HTTP response headers. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting instead. + GetRespHeaders() map[string][]string + // GetReqHeaders returns the HTTP request headers. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting instead. + GetReqHeaders() map[string][]string + // Host contains the host derived from the X-Forwarded-Host or Host HTTP header. + // Returned value is only valid within the handler. Do not store any references. + // In a network context, `Host` refers to the combination of a hostname and potentially a port number used for connecting, + // while `Hostname` refers specifically to the name assigned to a device on a network, excluding any port information. + // Example: URL: https://example.com:8080 -> Host: example.com:8080 + // Make copies or use the Immutable setting instead. + // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. + Host() string + // Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header using the c.Host() method. + // Returned value is only valid within the handler. Do not store any references. + // Example: URL: https://example.com:8080 -> Hostname: example.com + // Make copies or use the Immutable setting instead. + // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. + Hostname() string + // Port returns the remote port of the request. + Port() string + // IP returns the remote IP address of the request. + // If ProxyHeader and IP Validation is configured, it will parse that header and return the first valid IP address. + // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. + IP() string + // extractIPsFromHeader will return a slice of IPs it found given a header name in the order they appear. + // When IP validation is enabled, any invalid IPs will be omitted. + extractIPsFromHeader(header string) []string + // extractIPFromHeader will attempt to pull the real client IP from the given header when IP validation is enabled. + // currently, it will return the first valid IP address in header. + // when IP validation is disabled, it will simply return the value of the header without any inspection. + // Implementation is almost the same as in extractIPsFromHeader, but without allocation of []string. + extractIPFromHeader(header string) string + // IPs returns a string slice of IP addresses specified in the X-Forwarded-For request header. + // When IP validation is enabled, only valid IPs are returned. + IPs() []string + // Is returns the matching content type, + // if the incoming request's Content-Type HTTP header field matches the MIME type specified by the type parameter + Is(extension string) bool + // JSON converts any interface or string to JSON. + // Array and slice values encode as JSON arrays, + // except that []byte encodes as a base64-encoded string, + // and a nil slice encodes as the null JSON value. + // If the ctype parameter is given, this method will set the + // Content-Type header equal to ctype. If ctype is not given, + // The Content-Type header will be set to application/json. + JSON(data any, ctype ...string) error + // CBOR converts any interface or string to CBOR encoded bytes. + // If the ctype parameter is given, this method will set the + // Content-Type header equal to ctype. If ctype is not given, + // The Content-Type header will be set to application/cbor. + CBOR(data any, ctype ...string) error + // JSONP sends a JSON response with JSONP support. + // This method is identical to JSON, except that it opts-in to JSONP callback support. + // By default, the callback name is simply callback. + JSONP(data any, callback ...string) error + // XML converts any interface or string to XML. + // This method also sets the content header to application/xml. + XML(data any) error + // Links joins the links followed by the property to populate the response's Link HTTP header field. + Links(link ...string) + // Locals makes it possible to pass any values under keys scoped to the request + // and therefore available to all following routes that match the request. + // + // All the values are removed from ctx after returning from the top + // RequestHandler. Additionally, Close method is called on each value + // implementing io.Closer before removing the value from ctx. + Locals(key any, value ...any) any + // Location sets the response Location HTTP header to the specified path parameter. + Location(path string) + // Method returns the HTTP request method for the context, optionally overridden by the provided argument. + // If no override is given or if the provided override is not a valid HTTP method, it returns the current method from the context. + // Otherwise, it updates the context's method and returns the overridden method as a string. + Method(override ...string) string + // MultipartForm parse form entries from binary. + // This returns a map[string][]string, so given a key the value will be a string slice. + MultipartForm() (*multipart.Form, error) + // ClientHelloInfo return CHI from context + ClientHelloInfo() *tls.ClientHelloInfo + // Next executes the next method in the stack that matches the current route. + Next() error + // RestartRouting instead of going to the next handler. This may be useful after + // changing the request path. Note that handlers might be executed again. + RestartRouting() error + // OriginalURL contains the original request URL. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting to use the value outside the Handler. + OriginalURL() string + // Params is used to get the route parameters. + // Defaults to empty string "" if the param doesn't exist. + // If a default value is given, it will return that value if the param doesn't exist. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting to use the value outside the Handler. + Params(key string, defaultValue ...string) string + // Path returns the path part of the request URL. + // Optionally, you could override the path. + 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. + Scheme() string + // Protocol returns the HTTP protocol of request: HTTP/1.1 and HTTP/2. + Protocol() string + // Query returns the query string parameter in the url. + // Defaults to empty string "" if the query doesn't exist. + // If a default value is given, it will return that value if the query doesn't exist. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting to use the value outside the Handler. + Query(key string, defaultValue ...string) string + // Queries returns a map of query parameters and their values. + // + // GET /?name=alex&wanna_cake=2&id= + // Queries()["name"] == "alex" + // Queries()["wanna_cake"] == "2" + // Queries()["id"] == "" + // + // GET /?field1=value1&field1=value2&field2=value3 + // Queries()["field1"] == "value2" + // Queries()["field2"] == "value3" + // + // GET /?list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3 + // Queries()["list_a"] == "3" + // Queries()["list_b[]"] == "3" + // Queries()["list_c"] == "1,2,3" + // + // GET /api/search?filters.author.name=John&filters.category.name=Technology&filters[customer][name]=Alice&filters[status]=pending + // Queries()["filters.author.name"] == "John" + // Queries()["filters.category.name"] == "Technology" + // Queries()["filters[customer][name]"] == "Alice" + // Queries()["filters[status]"] == "pending" + Queries() map[string]string + // Range returns a struct containing the type and a slice of ranges. + Range(size int) (Range, error) + // Redirect returns the Redirect reference. + // Use Redirect().Status() to set custom redirection status code. + // If status is not specified, status defaults to 302 Found. + // You can use Redirect().To(), Redirect().Route() and Redirect().Back() for redirection. + Redirect() *Redirect + // ViewBind Add vars to default view var map binding to template engine. + // 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) + // 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) + // Route returns the matched Route struct. + 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. + SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error + // Secure returns whether a secure connection was established. + Secure() bool + // Send sets the HTTP response body without copying it. + // From this point onward the body argument must not be changed. + Send(body []byte) error + // SendFile transfers the file from the specified path. + // By default, the file is not compressed. To enable compression, set SendFile.Compress to true. + // The Content-Type response HTTP header field is set based on the file's extension. + // If the file extension is missing or invalid, the Content-Type is detected from the file's format. + SendFile(file string, config ...SendFile) error + // SendStatus sets the HTTP status code and if the response body is empty, + // it sets the correct status message in the body. + SendStatus(status int) error + // SendString sets the HTTP response body for string types. + // This means no type assertion, recommended for faster performance + SendString(body string) error + // SendStream sets response body stream and optional body size. + SendStream(stream io.Reader, size ...int) error + // SendStreamWriter sets response body stream writer + SendStreamWriter(streamWriter func(*bufio.Writer)) error + // Set sets the response's HTTP header field to the specified key, value. + Set(key, val string) + setCanonical(key, val string) + // Subdomains returns a string slice of subdomains in the domain name of the request. + // The subdomain offset, which defaults to 2, is used for determining the beginning of the subdomain segments. + Subdomains(offset ...int) []string + // Stale is not implemented yet, pull requests are welcome! + Stale() bool + // Status sets the HTTP status for the response. + // This method is chainable. + Status(status int) T + // String returns unique string representation of the ctx. + // + // The returned value may be useful for logging. + String() string + // Type sets the Content-Type HTTP header to the MIME type specified by the file extension. + Type(extension string, charset ...string) T + // Vary adds the given header field to the Vary response header. + // This will append the header, if not already listed, otherwise leaves it listed in the current location. + Vary(fields ...string) + // Write appends p into response body. + Write(p []byte) (int, error) + // Writef appends f & a into response body writer. + Writef(f string, a ...any) (int, error) + // WriteString appends s to response body. + WriteString(s string) (int, error) + // XHR returns a Boolean property, that is true, if the request's X-Requested-With header field is XMLHttpRequest, + // indicating that the request was issued by a client library (such as jQuery). + XHR() bool + // configDependentPaths set paths for route recognition and prepared paths for the user, + // here the features for caseSensitive, decoded paths, strict paths are evaluated + configDependentPaths() + // IsProxyTrusted checks trustworthiness of remote ip. + // If Config.TrustProxy false, it returns true + // IsProxyTrusted can check remote ip by proxy ranges and ip map. + IsProxyTrusted() bool + // IsFromLocal will return true if request came from local. + IsFromLocal() bool + // Bind You can bind body, cookie, headers etc. into the map, map slice, struct easily by using Binding method. + // It gives custom binding support, detailed binding options and more. + // Replacement of: BodyParser, ParamsParser, GetReqHeaders, GetRespHeaders, AllParams, QueryParser, ReqHeaderParser + Bind() *Bind // Reset is a method to reset context fields by given request when to use server handlers. Reset(fctx *fasthttp.RequestCtx) - + // Release is a method to reset context fields when to use ReleaseCtx() + release() + getBody() []byte // Methods to use with next stack. getMethodINT() int getIndexRoute() int @@ -28,42 +350,10 @@ type CustomCtx interface { setIndexRoute(route int) setMatched(matched bool) setRoute(route *Route) -} - -func NewDefaultCtx(app *App) *DefaultCtx { - // return ctx - return &DefaultCtx{ - // Set app reference - app: app, - } -} - -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) + // 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/ctx_interface_gen.go b/ctx_interface_gen.go index 101068a2..bc4317d4 100644 --- a/ctx_interface_gen.go +++ b/ctx_interface_gen.go @@ -1,359 +1,79 @@ -// Code generated by ifacemaker; DO NOT EDIT. +//go:build ignore -package fiber +package main import ( "bufio" - "context" - "crypto/tls" + "bytes" + "fmt" "io" - "mime/multipart" - - "github.com/valyala/fasthttp" + "os" + "regexp" + "strings" ) -// Ctx represents the Context which hold the HTTP request and response. -// It has methods for the request query string, parameters, body, HTTP headers and so on. -type Ctx interface { - // Accepts checks if the specified extensions or content types are acceptable. - Accepts(offers ...string) string - // AcceptsCharsets checks if the specified charset is acceptable. - AcceptsCharsets(offers ...string) string - // AcceptsEncodings checks if the specified encoding is acceptable. - AcceptsEncodings(offers ...string) string - // AcceptsLanguages checks if the specified language is acceptable. - AcceptsLanguages(offers ...string) string - // App returns the *App reference to the instance of the Fiber application - App() *App - // 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) - // Attachment sets the HTTP response Content-Disposition header field to attachment. - Attachment(filename ...string) - // BaseURL returns (protocol + host + base path). - BaseURL() string - // BodyRaw contains the raw body submitted in a POST request. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting instead. - BodyRaw() []byte - tryDecodeBodyInOrder(originalBody *[]byte, encodings []string) ([]byte, uint8, error) - // Body contains the raw body submitted in a POST request. - // This method will decompress the body if the 'Content-Encoding' header is provided. - // It returns the original (or decompressed) body data which is valid only within the handler. - // Don't store direct references to the returned data. - // If you need to keep the body's data later, make a copy or use the Immutable option. - Body() []byte - // ClearCookie expires a specific cookie by key on the client side. - // If no key is provided it expires all cookies that came with the request. - ClearCookie(key ...string) - // RequestCtx returns *fasthttp.RequestCtx that carries a deadline - // a cancellation signal, and other values across API boundaries. - RequestCtx() *fasthttp.RequestCtx - // Context returns a context implementation that was set by - // user earlier or returns a non-nil, empty context,if it was not set earlier. - Context() context.Context - // SetContext sets a context implementation by user. - SetContext(ctx context.Context) - // Cookie sets a cookie by passing a cookie struct. - Cookie(cookie *Cookie) - // Cookies are used for getting a cookie value by key. - // Defaults to the empty string "" if the cookie doesn't exist. - // If a default value is given, it will return that value if the cookie doesn't exist. - // The returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting to use the value outside the Handler. - Cookies(key string, defaultValue ...string) string - // Download transfers the file from path as an attachment. - // Typically, browsers will prompt the user for download. - // By default, the Content-Disposition header filename= parameter is the filepath (this typically appears in the browser dialog). - // Override this default with the filename parameter. - Download(file string, filename ...string) error - // Request return the *fasthttp.Request object - // This allows you to use all fasthttp request methods - // https://godoc.org/github.com/valyala/fasthttp#Request - Request() *fasthttp.Request - // Response return the *fasthttp.Response object - // This allows you to use all fasthttp response methods - // https://godoc.org/github.com/valyala/fasthttp#Response - Response() *fasthttp.Response - // Format performs content-negotiation on the Accept HTTP header. - // It uses Accepts to select a proper format and calls the matching - // user-provided handler function. - // If no accepted format is found, and a format with MediaType "default" is given, - // that default handler is called. If no format is found and no default is given, - // StatusNotAcceptable is sent. - Format(handlers ...ResFmt) error - // AutoFormat performs content-negotiation on the Accept HTTP header. - // It uses Accepts to select a proper format. - // The supported content types are text/html, text/plain, application/json, and application/xml. - // For more flexible content negotiation, use Format. - // If the header is not specified or there is no proper format, text/plain is used. - AutoFormat(body any) error - // FormFile returns the first file by key from a MultipartForm. - FormFile(key string) (*multipart.FileHeader, error) - // FormValue returns the first value by key from a MultipartForm. - // Search is performed in QueryArgs, PostArgs, MultipartForm and FormFile in this particular order. - // Defaults to the empty string "" if the form value doesn't exist. - // If a default value is given, it will return that value if the form value does not exist. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting instead. - FormValue(key string, defaultValue ...string) string - // Fresh returns true when the response is still “fresh” in the client's cache, - // otherwise false is returned to indicate that the client cache is now stale - // and the full response should be sent. - // When a client sends the Cache-Control: no-cache request header to indicate an end-to-end - // reload request, this module will return false to make handling these requests transparent. - // https://github.com/jshttp/fresh/blob/10e0471669dbbfbfd8de65bc6efac2ddd0bfa057/index.js#L33 - Fresh() bool - // Get returns the HTTP request header specified by field. - // Field names are case-insensitive - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting instead. - Get(key string, defaultValue ...string) string - // GetRespHeader returns the HTTP response header specified by field. - // Field names are case-insensitive - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting instead. - GetRespHeader(key string, defaultValue ...string) string - // GetRespHeaders returns the HTTP response headers. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting instead. - GetRespHeaders() map[string][]string - // GetReqHeaders returns the HTTP request headers. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting instead. - GetReqHeaders() map[string][]string - // Host contains the host derived from the X-Forwarded-Host or Host HTTP header. - // Returned value is only valid within the handler. Do not store any references. - // In a network context, `Host` refers to the combination of a hostname and potentially a port number used for connecting, - // while `Hostname` refers specifically to the name assigned to a device on a network, excluding any port information. - // Example: URL: https://example.com:8080 -> Host: example.com:8080 - // Make copies or use the Immutable setting instead. - // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. - Host() string - // Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header using the c.Host() method. - // Returned value is only valid within the handler. Do not store any references. - // Example: URL: https://example.com:8080 -> Hostname: example.com - // Make copies or use the Immutable setting instead. - // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. - Hostname() string - // Port returns the remote port of the request. - Port() string - // IP returns the remote IP address of the request. - // If ProxyHeader and IP Validation is configured, it will parse that header and return the first valid IP address. - // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. - IP() string - // extractIPsFromHeader will return a slice of IPs it found given a header name in the order they appear. - // When IP validation is enabled, any invalid IPs will be omitted. - extractIPsFromHeader(header string) []string - // extractIPFromHeader will attempt to pull the real client IP from the given header when IP validation is enabled. - // currently, it will return the first valid IP address in header. - // when IP validation is disabled, it will simply return the value of the header without any inspection. - // Implementation is almost the same as in extractIPsFromHeader, but without allocation of []string. - extractIPFromHeader(header string) string - // IPs returns a string slice of IP addresses specified in the X-Forwarded-For request header. - // When IP validation is enabled, only valid IPs are returned. - IPs() []string - // Is returns the matching content type, - // if the incoming request's Content-Type HTTP header field matches the MIME type specified by the type parameter - Is(extension string) bool - // JSON converts any interface or string to JSON. - // Array and slice values encode as JSON arrays, - // except that []byte encodes as a base64-encoded string, - // and a nil slice encodes as the null JSON value. - // If the ctype parameter is given, this method will set the - // Content-Type header equal to ctype. If ctype is not given, - // The Content-Type header will be set to application/json. - JSON(data any, ctype ...string) error - // CBOR converts any interface or string to CBOR encoded bytes. - // If the ctype parameter is given, this method will set the - // Content-Type header equal to ctype. If ctype is not given, - // The Content-Type header will be set to application/cbor. - CBOR(data any, ctype ...string) error - // JSONP sends a JSON response with JSONP support. - // This method is identical to JSON, except that it opts-in to JSONP callback support. - // By default, the callback name is simply callback. - JSONP(data any, callback ...string) error - // XML converts any interface or string to XML. - // This method also sets the content header to application/xml. - XML(data any) error - // Links joins the links followed by the property to populate the response's Link HTTP header field. - Links(link ...string) - // Locals makes it possible to pass any values under keys scoped to the request - // and therefore available to all following routes that match the request. - // - // All the values are removed from ctx after returning from the top - // RequestHandler. Additionally, Close method is called on each value - // implementing io.Closer before removing the value from ctx. - Locals(key any, value ...any) any - // Location sets the response Location HTTP header to the specified path parameter. - Location(path string) - // Method returns the HTTP request method for the context, optionally overridden by the provided argument. - // If no override is given or if the provided override is not a valid HTTP method, it returns the current method from the context. - // Otherwise, it updates the context's method and returns the overridden method as a string. - Method(override ...string) string - // MultipartForm parse form entries from binary. - // This returns a map[string][]string, so given a key the value will be a string slice. - MultipartForm() (*multipart.Form, error) - // ClientHelloInfo return CHI from context - ClientHelloInfo() *tls.ClientHelloInfo - // Next executes the next method in the stack that matches the current route. - Next() error - // RestartRouting instead of going to the next handler. This may be useful after - // changing the request path. Note that handlers might be executed again. - RestartRouting() error - // OriginalURL contains the original request URL. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting to use the value outside the Handler. - OriginalURL() string - // Params is used to get the route parameters. - // Defaults to empty string "" if the param doesn't exist. - // If a default value is given, it will return that value if the param doesn't exist. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting to use the value outside the Handler. - Params(key string, defaultValue ...string) string - // Path returns the path part of the request URL. - // Optionally, you could override the path. - 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. - Scheme() string - // Protocol returns the HTTP protocol of request: HTTP/1.1 and HTTP/2. - Protocol() string - // Query returns the query string parameter in the url. - // Defaults to empty string "" if the query doesn't exist. - // If a default value is given, it will return that value if the query doesn't exist. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting to use the value outside the Handler. - Query(key string, defaultValue ...string) string - // Queries returns a map of query parameters and their values. - // - // GET /?name=alex&wanna_cake=2&id= - // Queries()["name"] == "alex" - // Queries()["wanna_cake"] == "2" - // Queries()["id"] == "" - // - // GET /?field1=value1&field1=value2&field2=value3 - // Queries()["field1"] == "value2" - // Queries()["field2"] == "value3" - // - // GET /?list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3 - // Queries()["list_a"] == "3" - // Queries()["list_b[]"] == "3" - // Queries()["list_c"] == "1,2,3" - // - // GET /api/search?filters.author.name=John&filters.category.name=Technology&filters[customer][name]=Alice&filters[status]=pending - // Queries()["filters.author.name"] == "John" - // Queries()["filters.category.name"] == "Technology" - // Queries()["filters[customer][name]"] == "Alice" - // Queries()["filters[status]"] == "pending" - Queries() map[string]string - // Range returns a struct containing the type and a slice of ranges. - Range(size int) (Range, error) - // Redirect returns the Redirect reference. - // Use Redirect().Status() to set custom redirection status code. - // If status is not specified, status defaults to 302 Found. - // You can use Redirect().To(), Redirect().Route() and Redirect().Back() for redirection. - Redirect() *Redirect - // ViewBind Add vars to default view var map binding to template engine. - // 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) - // 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) - // Route returns the matched Route struct. - 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. - SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error - // Secure returns whether a secure connection was established. - Secure() bool - // Send sets the HTTP response body without copying it. - // From this point onward the body argument must not be changed. - Send(body []byte) error - // SendFile transfers the file from the specified path. - // By default, the file is not compressed. To enable compression, set SendFile.Compress to true. - // The Content-Type response HTTP header field is set based on the file's extension. - // If the file extension is missing or invalid, the Content-Type is detected from the file's format. - SendFile(file string, config ...SendFile) error - // SendStatus sets the HTTP status code and if the response body is empty, - // it sets the correct status message in the body. - SendStatus(status int) error - // SendString sets the HTTP response body for string types. - // This means no type assertion, recommended for faster performance - SendString(body string) error - // SendStream sets response body stream and optional body size. - SendStream(stream io.Reader, size ...int) error - // SendStreamWriter sets response body stream writer - SendStreamWriter(streamWriter func(*bufio.Writer)) error - // Set sets the response's HTTP header field to the specified key, value. - Set(key, val string) - setCanonical(key, val string) - // Subdomains returns a string slice of subdomains in the domain name of the request. - // The subdomain offset, which defaults to 2, is used for determining the beginning of the subdomain segments. - Subdomains(offset ...int) []string - // Stale is not implemented yet, pull requests are welcome! - Stale() bool - // Status sets the HTTP status for the response. - // This method is chainable. - Status(status int) Ctx - // String returns unique string representation of the ctx. - // - // The returned value may be useful for logging. - String() string - // Type sets the Content-Type HTTP header to the MIME type specified by the file extension. - Type(extension string, charset ...string) Ctx - // Vary adds the given header field to the Vary response header. - // This will append the header, if not already listed, otherwise leaves it listed in the current location. - Vary(fields ...string) - // Write appends p into response body. - Write(p []byte) (int, error) - // Writef appends f & a into response body writer. - Writef(f string, a ...any) (int, error) - // WriteString appends s to response body. - WriteString(s string) (int, error) - // XHR returns a Boolean property, that is true, if the request's X-Requested-With header field is XMLHttpRequest, - // indicating that the request was issued by a client library (such as jQuery). - XHR() bool - // configDependentPaths set paths for route recognition and prepared paths for the user, - // here the features for caseSensitive, decoded paths, strict paths are evaluated - configDependentPaths() - // IsProxyTrusted checks trustworthiness of remote ip. - // If Config.TrustProxy false, it returns true - // IsProxyTrusted can check remote ip by proxy ranges and ip map. - IsProxyTrusted() bool - // IsFromLocal will return true if request came from local. - IsFromLocal() bool - // Bind You can bind body, cookie, headers etc. into the map, map slice, struct easily by using Binding method. - // It gives custom binding support, detailed binding options and more. - // Replacement of: BodyParser, ParamsParser, GetReqHeaders, GetRespHeaders, AllParams, QueryParser, ReqHeaderParser - Bind() *Bind - // Reset is a method to reset context fields by given request when to use server handlers. - Reset(fctx *fasthttp.RequestCtx) - // Release is a method to reset context fields when to use ReleaseCtx() - release() - getBody() []byte - // Methods to use with next stack. - getMethodINT() int - getIndexRoute() int - getTreePath() string - getDetectionPath() string - getPathOriginal() string - getValues() *[maxParams]string - getMatched() bool - setIndexHandler(handler int) - setIndexRoute(route int) - setMatched(matched bool) - setRoute(route *Route) - // 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 +func main() { + const filename = "ctx_interface.go" + + // 1) read file + data, err := os.ReadFile(filename) + if err != nil { + panic(fmt.Errorf("failed to read file: %w", err)) + } + + // 2) patch interface + patched, err := patchCtxFile(data) + if err != nil { + panic(err) + } + + // 3) write patched file + if err := os.WriteFile(filename, patched, 0o644); err != nil { + panic(fmt.Errorf("failed to write patched file: %w", err)) + } +} + +// patchCtxFile adjust the Ctx interface in the given file +func patchCtxFile(input []byte) ([]byte, error) { + // process file line by line + in := bytes.NewReader(input) + scanner := bufio.NewScanner(in) + var outBuf bytes.Buffer + + regexCtx := regexp.MustCompile(`\bCtx\b`) + regexApp := regexp.MustCompile(`\*\bApp\b`) + + for scanner.Scan() { + line := scanner.Text() + + // A) change interface head definition + // => "type Ctx interface {" -> "type Ctx[T any] interface {" + if strings.HasPrefix(line, "type") { + line = strings.Replace(line, + "type Ctx interface {", + "type Ctx[T any] interface {", + 1, + ) + } else { + // B) replace every use of Ctx with T but only in the function definitions + // via regex and boundary word matching + // => "func (app *App[TCtx]) newCtx() Ctx {" -> "func (app *App[TCtx]) newCtx() T {" + if strings.Contains(line, "Ctx") { + line = regexCtx.ReplaceAllString(line, "T") + } + + // C) App with generic type + if strings.Contains(line, "App") { + // TODO: check this part + line = regexApp.ReplaceAllString(line, "*App[T]") + } + } + + outBuf.WriteString(line + "\n") + } + if err := scanner.Err(); err != nil && err != io.EOF { + return nil, fmt.Errorf("scanner error: %w", err) + } + + return outBuf.Bytes(), nil } diff --git a/ctx_test.go b/ctx_test.go index af308866..9b7fa355 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -111,7 +111,7 @@ func Test_Ctx_CustomCtx(t *testing.T) { app := New() - app.NewCtxFunc(func(app *App) CustomCtx { + app.NewCtxFunc(func(app *App[TCtx]) CustomCtx { return &customCtx{ DefaultCtx: *NewDefaultCtx(app), } @@ -138,7 +138,7 @@ func Test_Ctx_CustomCtx_and_Method(t *testing.T) { }) // Create custom context - app.NewCtxFunc(func(app *App) CustomCtx { + app.NewCtxFunc(func(app *App[TCtx]) CustomCtx { return &customCtx{ DefaultCtx: *NewDefaultCtx(app), } diff --git a/docs/api/app.md b/docs/api/app.md index 6582159c..ed9f7be0 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -48,7 +48,7 @@ func main() { The `MountPath` property contains one or more path patterns on which a sub-app was mounted. ```go title="Signature" -func (app *App) MountPath() string +func (app *App[TCtx]) MountPath() string ``` ```go title="Example" @@ -87,7 +87,7 @@ Mounting order is important for `MountPath`. To get mount paths properly, you sh You can group routes by creating a `*Group` struct. ```go title="Signature" -func (app *App) Group(prefix string, handlers ...Handler) Router +func (app *App[TCtx]) Group(prefix string, handlers ...Handler) Router ``` ```go title="Example" @@ -127,7 +127,7 @@ Returns an instance of a single route, which you can then use to handle HTTP ver Similar to [`Express`](https://expressjs.com/de/api.html#app.route). ```go title="Signature" -func (app *App) Route(path string) Register +func (app *App[TCtx]) Route(path string) Register ```
@@ -204,7 +204,7 @@ func main() { This method returns the number of registered handlers. ```go title="Signature" -func (app *App) HandlersCount() uint32 +func (app *App[TCtx]) HandlersCount() uint32 ``` ### Stack @@ -212,7 +212,7 @@ func (app *App) HandlersCount() uint32 This method returns the original router stack. ```go title="Signature" -func (app *App) Stack() [][]*Route +func (app *App[TCtx]) Stack() [][]*Route ``` ```go title="Example" @@ -280,7 +280,7 @@ func main() { This method assigns the name to the latest created route. ```go title="Signature" -func (app *App) Name(name string) Router +func (app *App[TCtx]) Name(name string) Router ``` ```go title="Example" @@ -514,6 +514,8 @@ func (app *App) ErrorHandler(ctx Ctx, err error) error ## NewCtxFunc +TODO: remove this section and replace with the new fiber.NewWithCustomCtx ... + `NewCtxFunc` allows you to customize the `ctx` struct as needed. ```go title="Signature" diff --git a/docs/api/fiber.md b/docs/api/fiber.md index 70320984..25aa10ac 100644 --- a/docs/api/fiber.md +++ b/docs/api/fiber.md @@ -103,7 +103,7 @@ app.Listen(":8080", fiber.ListenConfig{ | Property | Type | Description | Default | |-------------------------------------------------------------------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|---------| -| BeforeServeFunc | `func(app *App) error` | Allows customizing and accessing fiber app before serving the app. | `nil` | +| BeforeServeFunc | `func(app *App[TCtx]) error` | Allows customizing and accessing fiber app before serving the app. | `nil` | | CertClientFile | `string` | Path of the client certificate. If you want to use mTLS, you must enter this field. | `""` | | CertFile | `string` | Path of the certificate file. If you want to use TLS, you must enter this field. | `""` | | CertKeyFile | `string` | Path of the certificate's private key. If you want to use TLS, you must enter this field. | `""` | diff --git a/docs/partials/routing/handler.md b/docs/partials/routing/handler.md index 2b94a07c..9d6f06ed 100644 --- a/docs/partials/routing/handler.md +++ b/docs/partials/routing/handler.md @@ -9,22 +9,22 @@ Registers a route bound to a specific [HTTP method](https://developer.mozilla.or ```go title="Signatures" // HTTP methods -func (app *App) Get(path string, handler Handler, middlewares ...Handler) Router -func (app *App) Head(path string, handler Handler, middlewares ...Handler) Router -func (app *App) Post(path string, handler Handler, middlewares ...Handler) Router -func (app *App) Put(path string, handler Handler, middlewares ...Handler) Router -func (app *App) Delete(path string, handler Handler, middlewares ...Handler) Router -func (app *App) Connect(path string, handler Handler, middlewares ...Handler) Router -func (app *App) Options(path string, handler Handler, middlewares ...Handler) Router -func (app *App) Trace(path string, handler Handler, middlewares ...Handler) Router -func (app *App) Patch(path string, handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) Get(path string, handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) Head(path string, handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) Post(path string, handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) Put(path string, handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) Delete(path string, handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) Connect(path string, handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) Options(path string, handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) Trace(path string, handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) Patch(path string, handler Handler, middlewares ...Handler) Router // Add allows you to specify a method as value -func (app *App) Add(method, path string, handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) Add(method, path string, handler Handler, middlewares ...Handler) Router // All will register the route on all HTTP methods // Almost the same as app.Use but not bound to prefixes -func (app *App) All(path string, handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) All(path string, handler Handler, middlewares ...Handler) Router ``` ```go title="Examples" @@ -44,13 +44,13 @@ app.Post("/api/register", func(c fiber.Ctx) error { Can be used for middleware packages and prefix catchers. These routes will only match the beginning of each path i.e. `/john` will match `/john/doe`, `/johnnnnn` etc ```go title="Signature" -func (app *App) Use(args ...any) Router +func (app *App[TCtx]) Use(args ...any) Router // Different usage variations -func (app *App) Use(handler Handler, middlewares ...Handler) Router -func (app *App) Use(path string, handler Handler, middlewares ...Handler) Router -func (app *App) Use(paths []string, handler Handler, middlewares ...Handler) Router -func (app *App) Use(path string, app *App) Router +func (app *App[TCtx]) Use(handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) Use(path string, handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) Use(paths []string, handler Handler, middlewares ...Handler) Router +func (app *App[TCtx]) Use(path string, app *App) Router ``` ```go title="Examples" diff --git a/docs/whats_new.md b/docs/whats_new.md index 57e4bf0b..0d454dd8 100644 --- a/docs/whats_new.md +++ b/docs/whats_new.md @@ -73,6 +73,8 @@ We have made several changes to the Fiber app, including: ### Custom Ctx Interface in Fiber v3 +TODO - Add more details + Fiber v3 introduces a customizable `Ctx` interface, allowing developers to extend and modify the context to fit their needs. This feature provides greater flexibility and control over request handling. #### Idea Behind Custom Ctx Classes @@ -81,10 +83,12 @@ The idea behind custom `Ctx` classes is to give developers the ability to extend #### NewCtxFunc +TODO change example + The `NewCtxFunc` method allows you to customize the `Ctx` struct as needed. ```go title="Signature" -func (app *App) NewCtxFunc(function func(app *App) CustomCtx) +func (app *App[TCtx]) NewCtxFunc(function func(app *App) CustomCtx) ```
diff --git a/group.go b/group.go index 4142b0ba..389b7663 100644 --- a/group.go +++ b/group.go @@ -11,7 +11,7 @@ import ( // Group struct type Group struct { - app *App + app *App[Ctx[any]] parentGroup *Group name string diff --git a/helpers.go b/helpers.go index 04a1da69..5157b76e 100644 --- a/helpers.go +++ b/helpers.go @@ -93,7 +93,7 @@ func readContent(rf io.ReaderFrom, name string) (int64, error) { } // quoteString escape special characters in a given string -func (app *App) quoteString(raw string) string { +func (app *App[TCtx]) quoteString(raw string) string { bb := bytebufferpool.Get() quoted := app.getString(fasthttp.AppendQuotedArg(bb.B, app.getBytes(raw))) bytebufferpool.Put(bb) @@ -101,7 +101,7 @@ func (app *App) quoteString(raw string) string { } // Scan stack if other methods match the request -func (app *App) methodExist(c *DefaultCtx) bool { +func (app *App[TCtx]) methodExist(c *DefaultCtx) bool { var exists bool methods := app.config.RequestMethods @@ -146,7 +146,7 @@ func (app *App) methodExist(c *DefaultCtx) bool { } // Scan stack if other methods match the request -func (app *App) methodExistCustom(c CustomCtx) bool { +func (app *App[TCtx]) methodExistCustom(c CustomCtx[TCtx]) bool { var exists bool methods := app.config.RequestMethods for i := 0; i < len(methods); i++ { @@ -551,7 +551,7 @@ func matchEtag(s, etag string) bool { return false } -func (app *App) isEtagStale(etag string, noneMatchBytes []byte) bool { +func (app *App[TCtx]) isEtagStale(etag string, noneMatchBytes []byte) bool { var start, end int // Adapted from: @@ -661,7 +661,7 @@ func getBytesImmutable(s string) []byte { } // HTTP methods and their unique INTs -func (app *App) methodInt(s string) int { +func (app *App[TCtx]) methodInt(s string) int { // For better performance if len(app.configured.RequestMethods) == 0 { // TODO: Use iota instead diff --git a/hooks.go b/hooks.go index 3da5c671..03965e04 100644 --- a/hooks.go +++ b/hooks.go @@ -13,13 +13,13 @@ type ( OnListenHandler = func(ListenData) error OnShutdownHandler = func() error OnForkHandler = func(int) error - OnMountHandler = func(*App) error + OnMountHandler = func(*App[Ctx[any]]) error ) // Hooks is a struct to use it with App. type Hooks struct { // Embed app - app *App + app *App[Ctx[any]] // Hooks onRoute []OnRouteHandler @@ -39,7 +39,7 @@ type ListenData struct { TLS bool } -func newHooks(app *App) *Hooks { +func newHooks(app *App[Ctx[any]]) *Hooks { return &Hooks{ app: app, onRoute: make([]OnRouteHandler, 0), @@ -207,7 +207,7 @@ func (h *Hooks) executeOnForkHooks(pid int) { } } -func (h *Hooks) executeOnMountHooks(app *App) error { +func (h *Hooks) executeOnMountHooks(app *App[Ctx[any]]) error { for _, v := range h.onMount { if err := v(app); err != nil { return err diff --git a/listen.go b/listen.go index 793d36d2..561ce72c 100644 --- a/listen.go +++ b/listen.go @@ -58,7 +58,7 @@ type ListenConfig struct { // BeforeServeFunc allows customizing and accessing fiber app before serving the app. // // Default: nil - BeforeServeFunc func(app *App) error `json:"before_serve_func"` + BeforeServeFunc func(app *App[any]) error `json:"before_serve_func"` // OnShutdownError allows to customize error behavior when to graceful shutdown server by given signal. // @@ -171,7 +171,7 @@ func listenConfigDefault(config ...ListenConfig) ListenConfig { // app.Listen(":8080") // app.Listen("127.0.0.1:8080") // app.Listen(":8080", ListenConfig{EnablePrefork: true}) -func (app *App) Listen(addr string, config ...ListenConfig) error { +func (app *App[TCtx]) Listen(addr string, config ...ListenConfig) error { cfg := listenConfigDefault(config...) // Configure TLS @@ -258,7 +258,7 @@ func (app *App) 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) Listener(ln net.Listener, config ...ListenConfig) error { +func (app *App[TCtx]) Listener(ln net.Listener, config ...ListenConfig) error { cfg := listenConfigDefault(config...) // Graceful shutdown @@ -294,7 +294,7 @@ func (app *App) Listener(ln net.Listener, config ...ListenConfig) error { } // Create listener function. -func (*App) createListener(addr string, tlsConfig *tls.Config, cfg ListenConfig) (net.Listener, error) { +func (*App[TCtx]) createListener(addr string, tlsConfig *tls.Config, cfg ListenConfig) (net.Listener, error) { var listener net.Listener var err error @@ -317,7 +317,7 @@ func (*App) createListener(addr string, tlsConfig *tls.Config, cfg ListenConfig) return listener, nil } -func (app *App) printMessages(cfg ListenConfig, ln net.Listener) { +func (app *App[TCtx]) printMessages(cfg ListenConfig, 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) printMessages(cfg ListenConfig, ln net.Listener) { } // prepareListenData create an slice of ListenData -func (*App) 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) 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) prepareListenData(addr string, isTLS bool, cfg ListenConfig) ListenD } // startupMessage prepares the startup message with the handler number, port, address and other information -func (app *App) 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) { //nolint: revive // Accepting a bool param named isTLS if fine here // ignore child processes if IsChild() { return @@ -459,7 +459,7 @@ func (app *App) startupMessage(addr string, isTLS bool, pids string, cfg ListenC // method | path | name | handlers // GET | / | routeName | github.com/gofiber/fiber/v3.emptyHandler // HEAD | / | | github.com/gofiber/fiber/v3.emptyHandler -func (app *App) printRoutesMessage() { +func (app *App[TCtx]) printRoutesMessage() { // ignore child processes if IsChild() { return @@ -505,7 +505,7 @@ func (app *App) printRoutesMessage() { } // shutdown goroutine -func (app *App) gracefulShutdown(ctx context.Context, cfg ListenConfig) { +func (app *App[TCtx]) gracefulShutdown(ctx context.Context, cfg ListenConfig) { <-ctx.Done() var err error diff --git a/mount.go b/mount.go index d97b226d..2386b55b 100644 --- a/mount.go +++ b/mount.go @@ -15,7 +15,7 @@ import ( // Put fields related to mounting. type mountFields struct { // Mounted and main apps - appList map[string]*App + appList map[string]*App[Ctx[any]] // 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) *mountFields { +func newMountFields(app *App[any]) *mountFields { return &mountFields{ - appList: map[string]*App{"": app}, + appList: map[string]*App[any]{"": app}, appListKeys: make([]string, 0), } } @@ -39,7 +39,7 @@ func newMountFields(app *App) *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) mount(prefix string, subApp *App) Router { +func (app *App[TCtx]) mount(prefix string, subApp *App[TCtx]) Router { prefix = utils.TrimRight(prefix, '/') if prefix == "" { prefix = "/" @@ -68,7 +68,7 @@ func (app *App) mount(prefix string, subApp *App) 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) Router { +func (grp *Group) mount(prefix string, subApp *App[Ctx[any]]) Router { groupPath := getGroupPath(grp.Prefix, prefix) groupPath = utils.TrimRight(groupPath, '/') if groupPath == "" { @@ -96,17 +96,17 @@ func (grp *Group) mount(prefix string, subApp *App) Router { } // The MountPath property contains one or more path patterns on which a sub-app was mounted. -func (app *App) MountPath() string { +func (app *App[TCtx]) MountPath() string { return app.mountFields.mountPath } // hasMountedApps Checks if there are any mounted apps in the current application. -func (app *App) hasMountedApps() bool { +func (app *App[TCtx]) hasMountedApps() bool { return len(app.mountFields.appList) > 1 } // mountStartupProcess Handles the startup process of mounted apps by appending sub-app routes, generating app list keys, and processing sub-app routes. -func (app *App) mountStartupProcess() { +func (app *App[TCtx]) mountStartupProcess() { if app.hasMountedApps() { // add routes of sub-apps app.mountFields.subAppsProcessed.Do(func() { @@ -121,7 +121,7 @@ func (app *App) mountStartupProcess() { } // generateAppListKeys generates app list keys for Render, should work after appendSubAppLists -func (app *App) generateAppListKeys() { +func (app *App[TCtx]) generateAppListKeys() { for key := range app.mountFields.appList { app.mountFields.appListKeys = append(app.mountFields.appListKeys, key) } @@ -132,7 +132,7 @@ func (app *App) generateAppListKeys() { } // appendSubAppLists supports nested for sub apps -func (app *App) appendSubAppLists(appList map[string]*App, parent ...string) { +func (app *App[TCtx]) appendSubAppLists(appList map[string]*App[TCtx], parent ...string) { // Optimize: Cache parent prefix parentPrefix := "" if len(parent) > 0 { @@ -161,7 +161,7 @@ func (app *App) appendSubAppLists(appList map[string]*App, parent ...string) { } // processSubAppsRoutes adds routes of sub-apps recursively when the server is started -func (app *App) processSubAppsRoutes() { +func (app *App[TCtx]) processSubAppsRoutes() { for prefix, subApp := range app.mountFields.appList { // skip real app if prefix == "" { diff --git a/prefork.go b/prefork.go index 745ed306..7ebe0e3a 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) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig) error { +func (app *App[TCtx]) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig) error { var ln net.Listener var err error diff --git a/register.go b/register.go index ab67447c..913d983f 100644 --- a/register.go +++ b/register.go @@ -26,7 +26,7 @@ var _ (Register) = (*Registering)(nil) // Registering struct type Registering struct { - app *App + app *App[any] path string } diff --git a/router.go b/router.go index 9612da17..fba53cf9 100644 --- a/router.go +++ b/router.go @@ -108,7 +108,7 @@ func (r *Route) match(detectionPath, path string, params *[maxParams]string) boo return false } -func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint: unparam // bool param might be useful for testing +func (app *App[TCtx]) nextCustom(c CustomCtx[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,7 +156,7 @@ func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint: unparam // boo return false, err } -func (app *App) next(c *DefaultCtx) (bool, error) { +func (app *App[TCtx]) next(c *DefaultCtx) (bool, error) { // Get stack length tree, ok := app.treeStack[c.methodINT][c.treePath] if !ok { @@ -211,7 +211,7 @@ func (app *App) next(c *DefaultCtx) (bool, error) { return false, err } -func (app *App) defaultRequestHandler(rctx *fasthttp.RequestCtx) { +func (app *App[TCtx]) defaultRequestHandler(rctx *fasthttp.RequestCtx) { // Acquire DefaultCtx from the pool ctx, ok := app.AcquireCtx(rctx).(*DefaultCtx) if !ok { @@ -242,7 +242,7 @@ func (app *App) defaultRequestHandler(rctx *fasthttp.RequestCtx) { } } -func (app *App) customRequestHandler(rctx *fasthttp.RequestCtx) { +func (app *App[TCtx]) customRequestHandler(rctx *fasthttp.RequestCtx) { // Acquire CustomCtx from the pool ctx, ok := app.AcquireCtx(rctx).(CustomCtx) if !ok { @@ -273,7 +273,7 @@ func (app *App) customRequestHandler(rctx *fasthttp.RequestCtx) { } } -func (app *App) addPrefixToRoute(prefix string, route *Route) *Route { +func (app *App[TCtx]) addPrefixToRoute(prefix string, route *Route) *Route { prefixedPath := getGroupPath(prefix, route.Path) prettyPath := prefixedPath // Case-sensitive routing, all to lowercase @@ -294,7 +294,7 @@ func (app *App) addPrefixToRoute(prefix string, route *Route) *Route { return route } -func (*App) copyRoute(route *Route) *Route { +func (*App[TCtx]) copyRoute(route *Route) *Route { return &Route{ // Router booleans use: route.use, @@ -318,7 +318,7 @@ func (*App) copyRoute(route *Route) *Route { } } -func (app *App) register(methods []string, pathRaw string, group *Group, handler Handler, middleware ...Handler) { +func (app *App[TCtx]) register(methods []string, pathRaw string, group *Group, handler Handler, middleware ...Handler) { handlers := middleware if handler != nil { handlers = append(handlers, handler) @@ -392,7 +392,7 @@ func (app *App) register(methods []string, pathRaw string, group *Group, handler } } -func (app *App) addRoute(method string, route *Route, isMounted ...bool) { +func (app *App[TCtx]) addRoute(method string, route *Route, isMounted ...bool) { app.mutex.Lock() defer app.mutex.Unlock() @@ -436,7 +436,7 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) { // routeTree is being safely changed, as it would add a great deal of overhead in the request. // Latest benchmark results showed a degradation from 82.79 ns/op to 94.48 ns/op and can be found in: // https://github.com/gofiber/fiber/issues/2769#issuecomment-2227385283 -func (app *App) RebuildTree() *App { +func (app *App[TCtx]) RebuildTree() *App[TCtx] { app.mutex.Lock() defer app.mutex.Unlock() @@ -444,7 +444,7 @@ func (app *App) RebuildTree() *App { } // buildTree build the prefix tree from the previously registered routes -func (app *App) buildTree() *App { +func (app *App[TCtx]) buildTree() *App[TCtx] { if !app.routesRefreshed { return app } diff --git a/router_test.go b/router_test.go index fe5b3429..ff7429be 100644 --- a/router_test.go +++ b/router_test.go @@ -400,7 +400,7 @@ func Test_App_Rebuild_Tree(t *testing.T) { ///////////////// BENCHMARKS ///////////////// ////////////////////////////////////////////// -func registerDummyRoutes(app *App) { +func registerDummyRoutes(app *App[TCtx]) { h := func(_ Ctx) error { return nil } From 76271bbe983a7e2e4ac9a37c5f968465ef64bb27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Tue, 28 Jan 2025 22:08:56 +0100 Subject: [PATCH 2/7] Implement Fluent Method Chaining for Status and Type Methods Using Generics #3221 --- app.go | 6 +++--- ctx.go | 2 +- ctx_interface.go | 2 +- group.go | 2 +- hooks.go | 4 ++-- mount.go | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app.go b/app.go index 1c43497a..148b2f35 100644 --- a/app.go +++ b/app.go @@ -37,7 +37,7 @@ const Version = "3.0.0-beta.4" // Handler defines a function to serve HTTP requests. type Handler = func(Ctx[any]) error -type customCtxFunc = func(app *App[Ctx[any]]) CustomCtx[Ctx[any]] +type customCtxFunc = func(app *App[any]) CustomCtx[any] // Map is a shortcut for map[string]any, useful for JSON returns type Map map[string]any @@ -89,7 +89,7 @@ type Error struct { } // App denotes the Fiber application. -type App[TCtx Ctx[TCtx]] struct { +type App[TCtx any] struct { // Ctx pool pool sync.Pool // Fasthttp server @@ -493,7 +493,7 @@ func DefaultErrorHandler(c Ctx[any], err error) error { // ServerHeader: "Fiber", // }) func New(config ...Config) *App[DefaultCtx] { - app := newApp[DefaultCtx](config...) + app := newApp[any](config...) // Init app app.init() diff --git a/ctx.go b/ctx.go index a963ecc6..c9c0c340 100644 --- a/ctx.go +++ b/ctx.go @@ -50,7 +50,7 @@ const userContextKey contextKey = 0 // __local_user_context__ //go:generate ifacemaker --file ctx.go --struct DefaultCtx --iface Ctx --pkg fiber --output ctx_interface.go --not-exported true --iface-comment "Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on." //go:generate go run ctx_interface_gen.go type DefaultCtx struct { - app *App[Ctx[any]] // Reference to *App + app *App[any] // Reference to *App route *Route // Reference to *Route fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx bind *Bind // Default bind reference diff --git a/ctx_interface.go b/ctx_interface.go index 94e1b9fb..19f7e682 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -24,7 +24,7 @@ type Ctx[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[Ctx[T]] + 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) diff --git a/group.go b/group.go index 389b7663..04165ecb 100644 --- a/group.go +++ b/group.go @@ -11,7 +11,7 @@ import ( // Group struct type Group struct { - app *App[Ctx[any]] + app *App[any] parentGroup *Group name string diff --git a/hooks.go b/hooks.go index 03965e04..b6be6caa 100644 --- a/hooks.go +++ b/hooks.go @@ -13,13 +13,13 @@ type ( OnListenHandler = func(ListenData) error OnShutdownHandler = func() error OnForkHandler = func(int) error - OnMountHandler = func(*App[Ctx[any]]) error + OnMountHandler = func(*App[any]) error ) // Hooks is a struct to use it with App. type Hooks struct { // Embed app - app *App[Ctx[any]] + app *App[any] // Hooks onRoute []OnRouteHandler diff --git a/mount.go b/mount.go index 2386b55b..282f9f87 100644 --- a/mount.go +++ b/mount.go @@ -15,7 +15,7 @@ import ( // Put fields related to mounting. type mountFields struct { // Mounted and main apps - appList map[string]*App[Ctx[any]] + appList map[string]*App[any] // Prefix of app if it was mounted mountPath string // Ordered keys of apps (sorted by key length for Render) From b695df66f99df800083eb07a886b51c67741d8ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Mon, 3 Feb 2025 13:44:35 +0100 Subject: [PATCH 3/7] Implement Fluent Method Chaining for Status and Type Methods Using Generics #3221 --- app.go | 18 +++++++++--------- bind.go | 4 ++-- ctx.go | 22 ++++++++++++---------- ctx_custom_interface.go | 14 +++++++------- ctx_interface.go | 4 ++-- ctx_interface_gen.go | 9 ++++----- group.go | 2 +- hooks.go | 8 ++++---- mount.go | 4 ++-- 9 files changed, 43 insertions(+), 42 deletions(-) diff --git a/app.go b/app.go index 148b2f35..c686fb5f 100644 --- a/app.go +++ b/app.go @@ -35,9 +35,9 @@ import ( const Version = "3.0.0-beta.4" // Handler defines a function to serve HTTP requests. -type Handler = func(Ctx[any]) error +type Handler = func(ctx Ctx) error -type customCtxFunc = func(app *App[any]) CustomCtx[any] +type customCtxFunc = func(app *App[Ctx]) CustomCtx[Ctx] // Map is a shortcut for map[string]any, useful for JSON returns type Map map[string]any @@ -80,7 +80,7 @@ type Storage interface { // return c.Status(code).SendString(err.Error()) // } // app := fiber.New(cfg) -type ErrorHandler = func(Ctx[any], error) error +type ErrorHandler = func(Ctx, error) error // Error represents an error that occurred while handling a request. type Error struct { @@ -89,7 +89,7 @@ type Error struct { } // App denotes the Fiber application. -type App[TCtx any] struct { +type App[TCtx CtxGeneric[TCtx]] struct { // Ctx pool pool sync.Pool // Fasthttp server @@ -472,7 +472,7 @@ var DefaultMethods = []string{ } // DefaultErrorHandler that process return errors from handlers -func DefaultErrorHandler(c Ctx[any], err error) error { +func DefaultErrorHandler(c Ctx, err error) error { code := StatusInternalServerError var e *Error if errors.As(err, &e) { @@ -493,7 +493,7 @@ func DefaultErrorHandler(c Ctx[any], err error) error { // ServerHeader: "Fiber", // }) func New(config ...Config) *App[DefaultCtx] { - app := newApp[any](config...) + app := newApp[DefaultCtx](config...) // Init app app.init() @@ -519,7 +519,7 @@ func New(config ...Config) *App[DefaultCtx] { // Prefork: true, // ServerHeader: "Fiber", // }) -func NewWithCustomCtx[TCtx Ctx[TCtx]](newCtxFunc customCtxFunc, config ...Config) *App[TCtx] { +func NewWithCustomCtx[TCtx CtxGeneric[TCtx]](newCtxFunc customCtxFunc, config ...Config) *App[TCtx] { app := newApp[TCtx](config...) // Set newCtxFunc @@ -532,7 +532,7 @@ func NewWithCustomCtx[TCtx Ctx[TCtx]](newCtxFunc customCtxFunc, config ...Config } // newApp creates a new Fiber named instance. -func newApp[TCtx Ctx[TCtx]](config ...Config) *App[TCtx] { +func newApp[TCtx CtxGeneric[TCtx]](config ...Config) *App[TCtx] { // Create a new app app := &App[TCtx]{ // Create config @@ -1129,7 +1129,7 @@ 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 Ctx[TCtx], err error) error { +func (app *App[TCtx]) ErrorHandler(ctx CtxGeneric[TCtx], err error) error { var ( mountedErrHandler ErrorHandler mountedPrefixParts int diff --git a/bind.go b/bind.go index eac6d5a5..13d9d367 100644 --- a/bind.go +++ b/bind.go @@ -9,7 +9,7 @@ import ( type CustomBinder interface { Name() string MIMETypes() []string - Parse(c Ctx[any], out any) error + Parse(c Ctx, out any) error } // StructValidator is an interface to register custom struct validator for binding. @@ -19,7 +19,7 @@ type StructValidator interface { // Bind struct type Bind struct { - ctx Ctx[any] + ctx Ctx dontHandleErrs bool } diff --git a/ctx.go b/ctx.go index c9c0c340..952c3995 100644 --- a/ctx.go +++ b/ctx.go @@ -47,10 +47,10 @@ const userContextKey contextKey = 0 // __local_user_context__ // generation tool `go install github.com/vburenin/ifacemaker@975a95966976eeb2d4365a7fb236e274c54da64c` // https://github.com/vburenin/ifacemaker/blob/975a95966976eeb2d4365a7fb236e274c54da64c/ifacemaker.go#L14-L30 // -//go:generate ifacemaker --file ctx.go --struct DefaultCtx --iface Ctx --pkg fiber --output ctx_interface.go --not-exported true --iface-comment "Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on." +//go:generate ifacemaker --file ctx.go --struct DefaultCtx --iface CtxGeneric --pkg fiber --output ctx_interface.go --not-exported true --iface-comment "Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on." //go:generate go run ctx_interface_gen.go type DefaultCtx struct { - app *App[any] // Reference to *App + app *App[*DefaultCtx] // Reference to *App route *Route // Reference to *Route fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx bind *Bind // Default bind reference @@ -72,6 +72,8 @@ type DefaultCtx struct { matched bool // Non use route matched } +type Ctx = CtxGeneric[*DefaultCtx] + // SendFile defines configuration options when to transfer file with SendFile. type SendFile struct { // FS is the file system to serve the static files from. @@ -198,7 +200,7 @@ type Views interface { // ResFmt associates a Content Type to a fiber.Handler for c.Format type ResFmt struct { - Handler func(Ctx[any]) error + Handler func(Ctx) error MediaType string } @@ -223,7 +225,7 @@ func (c *DefaultCtx) AcceptsLanguages(offers ...string) string { } // App returns the *App reference to the instance of the Fiber application -func (c *DefaultCtx) App() *App[DefaultCtx] { +func (c *DefaultCtx) App() *App[*DefaultCtx] { return c.app } @@ -643,7 +645,7 @@ func (c *DefaultCtx) Get(key string, defaultValue ...string) string { // GetReqHeader returns the HTTP request header specified by filed. // This function is generic and can handle different headers type values. -func GetReqHeader[V GenericType](c Ctx[any], key string, defaultValue ...V) V { +func GetReqHeader[V GenericType](c Ctx, key string, defaultValue ...V) V { var v V return genericParseType[V](c.App().getString(c.Request().Header.Peek(key)), v, defaultValue...) } @@ -979,7 +981,7 @@ func (c *DefaultCtx) Locals(key any, value ...any) any { // All the values are removed from ctx after returning from the top // RequestHandler. Additionally, Close method is called on each value // implementing io.Closer before removing the value from ctx. -func Locals[V any](c Ctx[any], key any, value ...V) V { +func Locals[V any](c Ctx, key any, value ...V) V { var v V var ok bool if len(value) == 0 { @@ -1115,7 +1117,7 @@ func (c *DefaultCtx) Params(key string, defaultValue ...string) string { // // http://example.com/id/:number -> http://example.com/id/john // Params[int](c, "number", 0) -> returns 0 because can't parse 'john' as integer. -func Params[V GenericType](c Ctx[any], key string, defaultValue ...V) V { +func Params[V GenericType](c Ctx, key string, defaultValue ...V) V { var v V return genericParseType(c.Params(key), v, defaultValue...) } @@ -1237,7 +1239,7 @@ func (c *DefaultCtx) Queries() map[string]string { // name := Query[string](c, "search") // Returns "john" // 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 GenericType](c Ctx[any], key string, defaultValue ...V) V { +func Query[V GenericType](c Ctx, key string, defaultValue ...V) V { var v V q := c.App().getString(c.RequestCtx().QueryArgs().Peek(key)) @@ -1731,7 +1733,7 @@ func (c *DefaultCtx) Stale() bool { // Status sets the HTTP status for the response. // This method is chainable. -func (c *DefaultCtx) Status(status int) Ctx[DefaultCtx] { +func (c *DefaultCtx) Status(status int) *DefaultCtx { c.fasthttp.Response.SetStatusCode(status) return c } @@ -1776,7 +1778,7 @@ func (c *DefaultCtx) String() string { } // Type sets the Content-Type HTTP header to the MIME type specified by the file extension. -func (c *DefaultCtx) Type(extension string, charset ...string) Ctx[DefaultCtx] { +func (c *DefaultCtx) Type(extension string, charset ...string) *DefaultCtx { if len(charset) > 0 { c.fasthttp.Response.Header.SetContentType(utils.GetMIME(extension) + "; charset=" + charset[0]) } else { diff --git a/ctx_custom_interface.go b/ctx_custom_interface.go index 73a5a57e..263292b3 100644 --- a/ctx_custom_interface.go +++ b/ctx_custom_interface.go @@ -11,7 +11,7 @@ import ( ) type CustomCtx[T any] interface { - Ctx[T] + CtxGeneric[T] // Reset is a method to reset context fields by given request when to use server handlers. Reset(fctx *fasthttp.RequestCtx) @@ -30,7 +30,7 @@ type CustomCtx[T any] interface { setRoute(route *Route) } -func NewDefaultCtx(app *App[DefaultCtx]) *DefaultCtx { +func NewDefaultCtx(app *App[*DefaultCtx]) *DefaultCtx { // return ctx return &DefaultCtx{ // Set app reference @@ -38,8 +38,8 @@ func NewDefaultCtx(app *App[DefaultCtx]) *DefaultCtx { } } -func (app *App[TCtx]) newCtx() Ctx[TCtx] { - var c Ctx[TCtx] +func (app *App[TCtx]) newCtx() CtxGeneric[TCtx] { + var c CtxGeneric[TCtx] if app.newCtxFunc != nil { c = app.newCtxFunc(app) @@ -51,8 +51,8 @@ func (app *App[TCtx]) newCtx() Ctx[TCtx] { } // AcquireCtx retrieves a new Ctx from the pool. -func (app *App[TCtx]) AcquireCtx(fctx *fasthttp.RequestCtx) Ctx[TCtx] { - ctx, ok := app.pool.Get().(Ctx) +func (app *App[TCtx]) AcquireCtx(fctx *fasthttp.RequestCtx) CtxGeneric[TCtx] { + ctx, ok := app.pool.Get().(CtxGeneric[TCtx]) if !ok { panic(errors.New("failed to type-assert to Ctx")) @@ -63,7 +63,7 @@ func (app *App[TCtx]) AcquireCtx(fctx *fasthttp.RequestCtx) Ctx[TCtx] { } // ReleaseCtx releases the ctx back into the pool. -func (app *App[TCtx]) ReleaseCtx(c Ctx[TCtx]) { +func (app *App[TCtx]) ReleaseCtx(c CtxGeneric[TCtx]) { c.release() app.pool.Put(c) } diff --git a/ctx_interface.go b/ctx_interface.go index 19f7e682..7f324289 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -12,9 +12,9 @@ import ( "github.com/valyala/fasthttp" ) -// T represents the Context which hold the HTTP request and response. +// Ctx represents the Context which hold the HTTP request and response. // It has methods for the request query string, parameters, body, HTTP headers and so on. -type Ctx[T any] interface { +type CtxGeneric[T any] interface { // Accepts checks if the specified extensions or content types are acceptable. Accepts(offers ...string) string // AcceptsCharsets checks if the specified charset is acceptable. diff --git a/ctx_interface_gen.go b/ctx_interface_gen.go index bc4317d4..986f23cc 100644 --- a/ctx_interface_gen.go +++ b/ctx_interface_gen.go @@ -40,8 +40,8 @@ func patchCtxFile(input []byte) ([]byte, error) { scanner := bufio.NewScanner(in) var outBuf bytes.Buffer - regexCtx := regexp.MustCompile(`\bCtx\b`) - regexApp := regexp.MustCompile(`\*\bApp\b`) + regexCtx := regexp.MustCompile(`(\*Default)Ctx`) + regexApp := regexp.MustCompile(`\*App(\[\w+])?`) for scanner.Scan() { line := scanner.Text() @@ -50,8 +50,8 @@ func patchCtxFile(input []byte) ([]byte, error) { // => "type Ctx interface {" -> "type Ctx[T any] interface {" if strings.HasPrefix(line, "type") { line = strings.Replace(line, - "type Ctx interface {", - "type Ctx[T any] interface {", + "type CtxGeneric interface {", + "type CtxGeneric[T any] interface {", 1, ) } else { @@ -64,7 +64,6 @@ func patchCtxFile(input []byte) ([]byte, error) { // C) App with generic type if strings.Contains(line, "App") { - // TODO: check this part line = regexApp.ReplaceAllString(line, "*App[T]") } } diff --git a/group.go b/group.go index 04165ecb..fdf5118e 100644 --- a/group.go +++ b/group.go @@ -11,7 +11,7 @@ import ( // Group struct type Group struct { - app *App[any] + app *App[Ctx] parentGroup *Group name string diff --git a/hooks.go b/hooks.go index b6be6caa..b7c974f1 100644 --- a/hooks.go +++ b/hooks.go @@ -13,13 +13,13 @@ type ( OnListenHandler = func(ListenData) error OnShutdownHandler = func() error OnForkHandler = func(int) error - OnMountHandler = func(*App[any]) error + OnMountHandler = func(*App[Ctx]) error ) // Hooks is a struct to use it with App. type Hooks struct { // Embed app - app *App[any] + app *App[Ctx] // Hooks onRoute []OnRouteHandler @@ -39,7 +39,7 @@ type ListenData struct { TLS bool } -func newHooks(app *App[Ctx[any]]) *Hooks { +func newHooks(app *App[Ctx]) *Hooks { return &Hooks{ app: app, onRoute: make([]OnRouteHandler, 0), @@ -207,7 +207,7 @@ func (h *Hooks) executeOnForkHooks(pid int) { } } -func (h *Hooks) executeOnMountHooks(app *App[Ctx[any]]) error { +func (h *Hooks) executeOnMountHooks(app *App[Ctx]) error { for _, v := range h.onMount { if err := v(app); err != nil { return err diff --git a/mount.go b/mount.go index 282f9f87..8d32235c 100644 --- a/mount.go +++ b/mount.go @@ -15,7 +15,7 @@ import ( // Put fields related to mounting. type mountFields struct { // Mounted and main apps - appList map[string]*App[any] + appList map[string]*App[Ctx] // Prefix of app if it was mounted mountPath string // Ordered keys of apps (sorted by key length for Render) @@ -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[any]]) Router { +func (grp *Group) mount(prefix string, subApp *App[Ctx]) Router { groupPath := getGroupPath(grp.Prefix, prefix) groupPath = utils.TrimRight(groupPath, '/') if groupPath == "" { From fadbb0a50307d515bf4a062ed32ce3f2e4a834a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Mon, 3 Feb 2025 15:31:39 +0100 Subject: [PATCH 4/7] Implement Fluent Method Chaining for Status and Type Methods Using Generics #3221 --- app.go | 12 +++++------- ctx_interface.go | 2 +- group.go | 2 +- hooks.go | 6 +++--- listen.go | 2 +- mount.go | 6 +++--- register.go | 2 +- 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/app.go b/app.go index c686fb5f..50ec0bed 100644 --- a/app.go +++ b/app.go @@ -37,8 +37,6 @@ const Version = "3.0.0-beta.4" // Handler defines a function to serve HTTP requests. type Handler = func(ctx Ctx) error -type customCtxFunc = func(app *App[Ctx]) CustomCtx[Ctx] - // Map is a shortcut for map[string]any, useful for JSON returns type Map map[string]any @@ -103,7 +101,7 @@ type App[TCtx CtxGeneric[TCtx]] struct { // Latest route & group latestRoute *Route // newCtxFunc - newCtxFunc customCtxFunc + newCtxFunc func(app *App[TCtx]) CustomCtx[TCtx] // TLS handler tlsHandler *TLSHandler // Mount fields @@ -492,8 +490,8 @@ func DefaultErrorHandler(c Ctx, err error) error { // Prefork: true, // ServerHeader: "Fiber", // }) -func New(config ...Config) *App[DefaultCtx] { - app := newApp[DefaultCtx](config...) +func New(config ...Config) *App[*DefaultCtx] { + app := newApp[*DefaultCtx](config...) // Init app app.init() @@ -519,7 +517,7 @@ func New(config ...Config) *App[DefaultCtx] { // Prefork: true, // ServerHeader: "Fiber", // }) -func NewWithCustomCtx[TCtx CtxGeneric[TCtx]](newCtxFunc customCtxFunc, config ...Config) *App[TCtx] { +func NewWithCustomCtx[TCtx CtxGeneric[TCtx]](newCtxFunc func(app *App[TCtx]) CustomCtx[TCtx], config ...Config) *App[TCtx] { app := newApp[TCtx](config...) // Set newCtxFunc @@ -758,7 +756,7 @@ func (app *App[TCtx]) Use(args ...any) Router { switch arg := args[i].(type) { case string: prefix = arg - case *App: + case *App[TCtx]: subApp = arg case []string: prefixes = arg diff --git a/ctx_interface.go b/ctx_interface.go index 7f324289..be58c25f 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[T] + App() *App[*DefaultCtx] // 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) diff --git a/group.go b/group.go index fdf5118e..6101f34e 100644 --- a/group.go +++ b/group.go @@ -11,7 +11,7 @@ import ( // Group struct type Group struct { - app *App[Ctx] + app *App[*DefaultCtx] parentGroup *Group name string diff --git a/hooks.go b/hooks.go index b7c974f1..1b820280 100644 --- a/hooks.go +++ b/hooks.go @@ -13,13 +13,13 @@ type ( OnListenHandler = func(ListenData) error OnShutdownHandler = func() error OnForkHandler = func(int) error - OnMountHandler = func(*App[Ctx]) error + OnMountHandler = func(*App[*DefaultCtx]) error ) // Hooks is a struct to use it with App. type Hooks struct { // Embed app - app *App[Ctx] + app *App[*DefaultCtx] // Hooks onRoute []OnRouteHandler @@ -39,7 +39,7 @@ type ListenData struct { TLS bool } -func newHooks(app *App[Ctx]) *Hooks { +func newHooks(app *App[*DefaultCtx]) *Hooks { return &Hooks{ app: app, onRoute: make([]OnRouteHandler, 0), diff --git a/listen.go b/listen.go index 561ce72c..cd9470a0 100644 --- a/listen.go +++ b/listen.go @@ -58,7 +58,7 @@ type ListenConfig struct { // BeforeServeFunc allows customizing and accessing fiber app before serving the app. // // Default: nil - BeforeServeFunc func(app *App[any]) error `json:"before_serve_func"` + BeforeServeFunc func(app *App[*DefaultCtx]) error `json:"before_serve_func"` // OnShutdownError allows to customize error behavior when to graceful shutdown server by given signal. // diff --git a/mount.go b/mount.go index 8d32235c..76e152b3 100644 --- a/mount.go +++ b/mount.go @@ -15,7 +15,7 @@ import ( // Put fields related to mounting. type mountFields struct { // Mounted and main apps - appList map[string]*App[Ctx] + appList map[string]*App[*DefaultCtx] // 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[any]) *mountFields { +func newMountFields(app *App[*DefaultCtx]) *mountFields { return &mountFields{ - appList: map[string]*App[any]{"": app}, + appList: map[string]*App[*DefaultCtx]{"": app}, appListKeys: make([]string, 0), } } diff --git a/register.go b/register.go index 913d983f..66d4d434 100644 --- a/register.go +++ b/register.go @@ -26,7 +26,7 @@ var _ (Register) = (*Registering)(nil) // Registering struct type Registering struct { - app *App[any] + app *App[*DefaultCtx] path string } From 67965e6144c68cb25be94bddca27b8b1aa70ce3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Mon, 3 Feb 2025 20:51:18 +0100 Subject: [PATCH 5/7] Implement Fluent Method Chaining for Status and Type Methods Using Generics #3221 --- app.go | 109 +++++++++++++------------- ctx.go | 6 +- ctx_custom_interface.go | 11 +-- ctx_interface.go | 8 +- ctx_test.go | 2 +- group.go | 48 ++++++------ helpers.go | 15 ++-- hooks.go | 76 +++++++++--------- listen.go | 24 +++--- listen_test.go | 4 +- mount.go | 22 +++--- prefork.go | 2 +- register.go | 58 +++++++------- router.go | 165 +++++++++------------------------------- 14 files changed, 228 insertions(+), 322 deletions(-) diff --git a/app.go b/app.go index 50ec0bed..c5596b54 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 952c3995..f7e83bdc 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 263292b3..01d86c0b 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 be58c25f..20295c2d 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 9b7fa355..0f0b642c 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 6101f34e..fa5d207c 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 6d6b806a..9d1cbce4 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 1b820280..56fbb68a 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 cd9470a0..0fe4dcb7 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 032f7d32..e5cf4d9b 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 76e152b3..0b40b94b 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 7ebe0e3a..3e05352d 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 66d4d434..f281f62e 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 +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, middleware ...Handler) Register + Add(methods []string, handler Handler[TCtx], middleware ...Handler[TCtx]) Register[TCtx] - Route(path string) Register + 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 fba53cf9..1681b069 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] From b48acd937dcd05d77f8f26f9c358e8d0bd1dcad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Wed, 26 Mar 2025 14:38:58 +0100 Subject: [PATCH 6/7] fresh with main branch --- app_test.go | 26 ++++++++++++++++++ ctx_custom_interface.go | 6 +++- ctx_interface.go | 61 +++++++++++------------------------------ go.mod | 8 ++++++ go.sum | 14 ++++++++-- router.go | 59 ++------------------------------------- 6 files changed, 69 insertions(+), 105 deletions(-) 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 } From 1070d75d31b03244c7e917db85fb71f4bc53fc5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Wed, 26 Mar 2025 14:56:10 +0100 Subject: [PATCH 7/7] fresh with main branch --- app_test.go | 26 -------------------------- go.mod | 8 -------- go.sum | 12 ------------ 3 files changed, 46 deletions(-) diff --git a/app_test.go b/app_test.go index c803dbfb..84606e6b 100644 --- a/app_test.go +++ b/app_test.go @@ -31,34 +31,8 @@ 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/go.mod b/go.mod index 6c447ebb..f4cd17c1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ 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 @@ -16,13 +15,6 @@ 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 85afa9f6..d6968f7d 100644 --- a/go.sum +++ b/go.sum @@ -4,16 +4,6 @@ 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= @@ -22,8 +12,6 @@ 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=