Skip to content

Commit

Permalink
Implement Fluent Method Chaining for Status and Type Methods Using Ge…
Browse files Browse the repository at this point in the history
…nerics #3221
  • Loading branch information
ReneWerner87 committed Feb 3, 2025
1 parent fadbb0a commit 67965e6
Show file tree
Hide file tree
Showing 14 changed files with 230 additions and 324 deletions.
109 changes: 51 additions & 58 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -97,29 +97,29 @@ 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
customConstraints []CustomConstraint
// 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
Expand All @@ -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: ""
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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{},
}
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()

Expand All @@ -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 {
Expand All @@ -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]
Expand Down Expand Up @@ -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) {
Expand All @@ -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)))
Expand All @@ -785,75 +785,75 @@ 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...)
}

// Group is used for Routes with common prefix to define a new sub-router with optional middleware.
//
// 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...)
}
Expand All @@ -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
}
Expand All @@ -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
}

Expand All @@ -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
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
)

Expand Down
6 changes: 4 additions & 2 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const userContextKey contextKey = 0 // __local_user_context__
//go:generate go run ctx_interface_gen.go
type DefaultCtx struct {

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / govulncheck-check

DefaultCtx refers to

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

invalid recursive type DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

DefaultCtx refers to

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

invalid recursive type DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

DefaultCtx refers to

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

invalid recursive type DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

DefaultCtx refers to

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

invalid recursive type DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

DefaultCtx refers to

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / lint

invalid recursive type DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / lint

DefaultCtx refers to

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / lint

DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / lint

invalid recursive type DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / lint

DefaultCtx refers to

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / repeated

invalid recursive type DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / repeated

DefaultCtx refers to

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / repeated

DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / repeated

invalid recursive type DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / repeated

DefaultCtx refers to

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / Compare

invalid recursive type DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / Compare

DefaultCtx refers to

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / Compare

DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / Compare

invalid recursive type DefaultCtx

Check failure on line 52 in ctx.go

View workflow job for this annotation

GitHub Actions / Compare

DefaultCtx refers to
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
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down
Loading

0 comments on commit 67965e6

Please sign in to comment.