fiber/router.go
2024-02-04 19:21:54 +01:00

312 lines
10 KiB
Go

package fiber
import (
"fmt"
"github.com/gofiber/fiber/v3/routing"
"reflect"
)
type Router interface {
FindNextHandler(method string, path string) Handler
ConfigureRoute(config Config, prefix string, route Route) Route
CopyRoute(route Route) Route
GetAllRoutes() []any // TODO: specific routes ?
// TODO: Add contrains function ? or just in expressjs router
// TODO: add mount function for the merge of the routers
}
// CommonRouterI defines all router handle interface, including app and group router.
type CommonRouterI interface {
Use(args ...any) CommonRouterI
Get(path string, handler Handler, middleware ...Handler) CommonRouterI
Head(path string, handler Handler, middleware ...Handler) CommonRouterI
Post(path string, handler Handler, middleware ...Handler) CommonRouterI
Put(path string, handler Handler, middleware ...Handler) CommonRouterI
Delete(path string, handler Handler, middleware ...Handler) CommonRouterI
Connect(path string, handler Handler, middleware ...Handler) CommonRouterI
Options(path string, handler Handler, middleware ...Handler) CommonRouterI
Trace(path string, handler Handler, middleware ...Handler) CommonRouterI
Patch(path string, handler Handler, middleware ...Handler) CommonRouterI
Add(methods []string, path string, handler Handler, middleware ...Handler) CommonRouterI
Static(prefix, root string, config ...Static) CommonRouterI
All(path string, handler Handler, middleware ...Handler) CommonRouterI
Group(prefix string, handlers ...Handler) CommonRouterI
Route(path string) routing.Register
Name(name string) CommonRouterI
}
type Route struct {
// Public fields
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
}
// TODO: add Route getters
type IGroup interface {
GetPrefix() string
}
// Group struct
type Group struct {
Prefix string
IGroup
}
// Name Assign name to specific route.
func (app *App[TRouter]) Name(name string) CommonRouterI {
app.checkDefaultRouter()
app.mutex.Lock()
defer app.mutex.Unlock()
for _, routes := range app.stack {
for _, route := range routes {
isMethodValid := route.Method == app.latestRoute.Method || app.latestRoute.use ||
(app.latestRoute.Method == MethodGet && route.Method == MethodHead)
if route.Path == app.latestRoute.Path && isMethodValid {
route.Name = name
if route.group != nil {
route.Name = route.group.name + route.Name
}
}
}
}
if err := app.hooks.executeOnNameHooks(*app.latestRoute); err != nil {
panic(err)
}
return app
}
// GetRoute Get route by name
func (app *App[TRouter]) GetRoute(name string) routing.Route {
app.checkDefaultRouter()
for _, routes := range app.stack {
for _, route := range routes {
if route.Name == name {
return *route
}
}
}
return routing.Route{}
}
// TODO: part of the router api for the interchangeable class
// GetRoutes Get all routes. When filterUseOption equal to true, it will filter the routes registered by the middleware.
func (app *App[TRouter]) GetRoutes(filterUseOption ...bool) []routing.Route {
app.checkDefaultRouter()
var rs []routing.Route
var filterUse bool
if len(filterUseOption) != 0 {
filterUse = filterUseOption[0]
}
for _, routes := range app.stack {
for _, route := range routes {
if filterUse && route.use {
continue
}
rs = append(rs, *route)
}
}
return rs
}
// Use registers a middleware route that will match requests
// with the provided prefix (which is optional and defaults to "/").
// Also, you can pass 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 Use. 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.
//
// app.Use(func(c fiber.Ctx) error {
// return c.Next()
// })
// app.Use("/api", func(c fiber.Ctx) error {
// return c.Next()
// })
// app.Use("/api", handler, func(c fiber.Ctx) error {
// return c.Next()
// })
// subApp := fiber.New()
// app.Use("/mounted-path", subApp)
//
// This method will match all HTTP verbs: GET, POST, PUT, HEAD etc...
func (app *App[TRouter]) Use(args ...any) CommonRouterI {
app.checkDefaultRouter()
var prefix string
var subApp *App[TRouter]
var prefixes []string
var handlers []Handler
for i := 0; i < len(args); i++ {
switch arg := args[i].(type) {
case string:
prefix = arg
case *App[TRouter]:
subApp = arg
case []string:
prefixes = arg
case Handler:
handlers = append(handlers, arg)
default:
panic(fmt.Sprintf("use: invalid handler %v\n", reflect.TypeOf(arg)))
}
}
if len(prefixes) == 0 {
prefixes = append(prefixes, prefix)
}
for _, prefix := range prefixes {
if subApp != nil {
app.mount(prefix, subApp)
return app
}
app.register([]string{methodUse}, prefix, nil, nil, handlers...)
}
return app
}
// 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[TRouter]) Get(path string, handler Handler, middleware ...Handler) CommonRouterI {
app.checkDefaultRouter()
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[TRouter]) Head(path string, handler Handler, middleware ...Handler) CommonRouterI {
app.checkDefaultRouter()
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[TRouter]) Post(path string, handler Handler, middleware ...Handler) CommonRouterI {
app.checkDefaultRouter()
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[TRouter]) Put(path string, handler Handler, middleware ...Handler) CommonRouterI {
app.checkDefaultRouter()
return app.Add([]string{MethodPut}, path, handler, middleware...)
}
// Delete registers a route for DELETE methods that deletes the specified resource.
func (app *App[TRouter]) Delete(path string, handler Handler, middleware ...Handler) CommonRouterI {
app.checkDefaultRouter()
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[TRouter]) Connect(path string, handler Handler, middleware ...Handler) CommonRouterI {
app.checkDefaultRouter()
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[TRouter]) Options(path string, handler Handler, middleware ...Handler) CommonRouterI {
app.checkDefaultRouter()
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[TRouter]) Trace(path string, handler Handler, middleware ...Handler) CommonRouterI {
app.checkDefaultRouter()
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[TRouter]) Patch(path string, handler Handler, middleware ...Handler) CommonRouterI {
app.checkDefaultRouter()
return app.Add([]string{MethodPatch}, path, handler, middleware...)
}
// Add allows you to specify multiple HTTP methods to register a route.
func (app *App[TRouter]) Add(methods []string, path string, handler Handler, middleware ...Handler) CommonRouterI {
app.checkDefaultRouter()
app.register(methods, path, nil, handler, middleware...)
return app
}
// Static will create a file server serving static files
func (app *App[TRouter]) Static(prefix, root string, config ...Static) CommonRouterI {
app.registerStatic(prefix, root, config...)
return app
}
// All will register the handler on all HTTP methods
func (app *App[TRouter]) All(path string, handler Handler, middleware ...Handler) CommonRouterI {
app.checkDefaultRouter()
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[TRouter]) Group(prefix string, handlers ...Handler) CommonRouterI {
app.checkDefaultRouter()
grp := &routing.Group{Prefix: prefix, app: app}
if len(handlers) > 0 {
app.register([]string{methodUse}, prefix, grp, nil, handlers...)
}
if err := app.hooks.executeOnGroupHooks(*grp); err != nil {
panic(err)
}
return grp
}
// 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[TRouter]) Route(path string) routing.Register {
// Create new route
route := &routing.Registering{app: app, path: path}
return route
}
// TODO: move to router
// Stack returns the raw router stack.
func (app *App[TRouter]) Stack() [][]*routing.Route {
return app.stack
}
// TODO: move to router
// HandlersCount returns the amount of registered handlers.
func (app *App[TRouter]) HandlersCount() uint32 {
return app.handlersCount
}
func (app *App[TRouter]) checkDefaultRouter() {
_, ok := any(app.router).(CommonRouterI)
if !ok {
panic("Router is not the default router, pls use the app.Router() method for interactions with other routers")
}
}