mirror of https://github.com/gofiber/fiber.git
Merge branch 'main' into feature/3098-allow-removing-registered-route
commit
dd3430bbea
4
app.go
4
app.go
|
@ -109,7 +109,7 @@ type App struct {
|
|||
// Route stack divided by HTTP methods
|
||||
stack [][]*Route
|
||||
// Route stack divided by HTTP methods and route prefixes
|
||||
treeStack []map[string][]*Route
|
||||
treeStack []map[int][]*Route
|
||||
// custom binders
|
||||
customBinders []CustomBinder
|
||||
// customConstraints is a list of external constraints
|
||||
|
@ -581,7 +581,7 @@ func New(config ...Config) *App {
|
|||
|
||||
// Create router stack
|
||||
app.stack = make([][]*Route, len(app.config.RequestMethods))
|
||||
app.treeStack = make([]map[string][]*Route, len(app.config.RequestMethods))
|
||||
app.treeStack = make([]map[int][]*Route, len(app.config.RequestMethods))
|
||||
|
||||
// Override colors
|
||||
app.config.ColorScheme = defaultColors(app.config.ColorScheme)
|
||||
|
|
81
ctx.go
81
ctx.go
|
@ -33,8 +33,11 @@ const (
|
|||
schemeHTTPS = "https"
|
||||
)
|
||||
|
||||
// maxParams defines the maximum number of parameters per route.
|
||||
const maxParams = 30
|
||||
const (
|
||||
// maxParams defines the maximum number of parameters per route.
|
||||
maxParams = 30
|
||||
maxDetectionPaths = 3
|
||||
)
|
||||
|
||||
// The contextKey type is unexported to prevent collisions with context keys defined in
|
||||
// other packages.
|
||||
|
@ -49,28 +52,26 @@ const userContextKey contextKey = 0 // __local_user_context__
|
|||
//
|
||||
//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."
|
||||
type DefaultCtx struct {
|
||||
app *App // Reference to *App
|
||||
route *Route // Reference to *Route
|
||||
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
|
||||
bind *Bind // Default bind reference
|
||||
redirect *Redirect // Default redirect reference
|
||||
req *DefaultReq // Default request api reference
|
||||
res *DefaultRes // Default response api reference
|
||||
values [maxParams]string // Route parameter values
|
||||
viewBindMap sync.Map // Default view map to bind template engine
|
||||
method string // HTTP method
|
||||
baseURI string // HTTP base uri
|
||||
path string // HTTP path with the modifications by the configuration -> string copy from pathBuffer
|
||||
detectionPath string // Route detection path -> string copy from detectionPathBuffer
|
||||
treePath string // Path for the search in the tree
|
||||
pathOriginal string // Original HTTP path
|
||||
pathBuffer []byte // HTTP path buffer
|
||||
detectionPathBuffer []byte // HTTP detectionPath buffer
|
||||
flashMessages redirectionMsgs // Flash messages
|
||||
indexRoute int // Index of the current route
|
||||
indexHandler int // Index of the current handler
|
||||
methodINT int // HTTP method INT equivalent
|
||||
matched bool // Non use route matched
|
||||
app *App // Reference to *App
|
||||
route *Route // Reference to *Route
|
||||
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
|
||||
bind *Bind // Default bind reference
|
||||
redirect *Redirect // Default redirect reference
|
||||
req *DefaultReq // Default request api reference
|
||||
res *DefaultRes // Default response api reference
|
||||
values [maxParams]string // Route parameter values
|
||||
viewBindMap sync.Map // Default view map to bind template engine
|
||||
method string // HTTP method
|
||||
baseURI string // HTTP base uri
|
||||
pathOriginal string // Original HTTP path
|
||||
flashMessages redirectionMsgs // Flash messages
|
||||
path []byte // HTTP path with the modifications by the configuration
|
||||
detectionPath []byte // Route detection path
|
||||
treePathHash int // Hash of the path for the search in the tree
|
||||
indexRoute int // Index of the current route
|
||||
indexHandler int // Index of the current handler
|
||||
methodINT int // HTTP method INT equivalent
|
||||
matched bool // Non use route matched
|
||||
}
|
||||
|
||||
// SendFile defines configuration options when to transfer file with SendFile.
|
||||
|
@ -1123,8 +1124,9 @@ func Params[V GenericType](c Ctx, key string, defaultValue ...V) V {
|
|||
|
||||
// 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.
|
||||
func (c *DefaultCtx) Path(override ...string) string {
|
||||
if len(override) != 0 && c.path != override[0] {
|
||||
if len(override) != 0 && string(c.path) != override[0] {
|
||||
// Set new path to context
|
||||
c.pathOriginal = override[0]
|
||||
|
||||
|
@ -1133,7 +1135,7 @@ func (c *DefaultCtx) Path(override ...string) string {
|
|||
// Prettify path
|
||||
c.configDependentPaths()
|
||||
}
|
||||
return c.path
|
||||
return c.app.getString(c.path)
|
||||
}
|
||||
|
||||
// Scheme contains the request protocol string: http or https for TLS requests.
|
||||
|
@ -1832,32 +1834,31 @@ func (c *DefaultCtx) 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
|
||||
func (c *DefaultCtx) configDependentPaths() {
|
||||
c.pathBuffer = append(c.pathBuffer[0:0], c.pathOriginal...)
|
||||
c.path = append(c.path[:0], c.pathOriginal...)
|
||||
// If UnescapePath enabled, we decode the path and save it for the framework user
|
||||
if c.app.config.UnescapePath {
|
||||
c.pathBuffer = fasthttp.AppendUnquotedArg(c.pathBuffer[:0], c.pathBuffer)
|
||||
c.path = fasthttp.AppendUnquotedArg(c.path[:0], c.path)
|
||||
}
|
||||
c.path = c.app.getString(c.pathBuffer)
|
||||
|
||||
// another path is specified which is for routing recognition only
|
||||
// use the path that was changed by the previous configuration flags
|
||||
c.detectionPathBuffer = append(c.detectionPathBuffer[0:0], c.pathBuffer...)
|
||||
c.detectionPath = append(c.detectionPath[:0], c.path...)
|
||||
// If CaseSensitive is disabled, we lowercase the original path
|
||||
if !c.app.config.CaseSensitive {
|
||||
c.detectionPathBuffer = utils.ToLowerBytes(c.detectionPathBuffer)
|
||||
c.detectionPath = utils.ToLowerBytes(c.detectionPath)
|
||||
}
|
||||
// If StrictRouting is disabled, we strip all trailing slashes
|
||||
if !c.app.config.StrictRouting && len(c.detectionPathBuffer) > 1 && c.detectionPathBuffer[len(c.detectionPathBuffer)-1] == '/' {
|
||||
c.detectionPathBuffer = utils.TrimRight(c.detectionPathBuffer, '/')
|
||||
if !c.app.config.StrictRouting && len(c.detectionPath) > 1 && c.detectionPath[len(c.detectionPath)-1] == '/' {
|
||||
c.detectionPath = utils.TrimRight(c.detectionPath, '/')
|
||||
}
|
||||
c.detectionPath = c.app.getString(c.detectionPathBuffer)
|
||||
|
||||
// Define the path for dividing routes into areas for fast tree detection, so that fewer routes need to be traversed,
|
||||
// since the first three characters area select a list of routes
|
||||
c.treePath = c.treePath[0:0]
|
||||
const maxDetectionPaths = 3
|
||||
c.treePathHash = 0
|
||||
if len(c.detectionPath) >= maxDetectionPaths {
|
||||
c.treePath = c.detectionPath[:maxDetectionPaths]
|
||||
c.treePathHash = int(c.detectionPath[0])<<16 |
|
||||
int(c.detectionPath[1])<<8 |
|
||||
int(c.detectionPath[2])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1958,12 +1959,12 @@ func (c *DefaultCtx) getIndexRoute() int {
|
|||
return c.indexRoute
|
||||
}
|
||||
|
||||
func (c *DefaultCtx) getTreePath() string {
|
||||
return c.treePath
|
||||
func (c *DefaultCtx) getTreePathHash() int {
|
||||
return c.treePathHash
|
||||
}
|
||||
|
||||
func (c *DefaultCtx) getDetectionPath() string {
|
||||
return c.detectionPath
|
||||
return c.app.getString(c.detectionPath)
|
||||
}
|
||||
|
||||
func (c *DefaultCtx) getPathOriginal() string {
|
||||
|
|
|
@ -19,7 +19,7 @@ type CustomCtx interface {
|
|||
// Methods to use with next stack.
|
||||
getMethodINT() int
|
||||
getIndexRoute() int
|
||||
getTreePath() string
|
||||
getTreePathHash() int
|
||||
getDetectionPath() string
|
||||
getPathOriginal() string
|
||||
getValues() *[maxParams]string
|
||||
|
|
|
@ -347,7 +347,7 @@ type Ctx interface {
|
|||
// Methods to use with next stack.
|
||||
getMethodINT() int
|
||||
getIndexRoute() int
|
||||
getTreePath() string
|
||||
getTreePathHash() int
|
||||
getDetectionPath() string
|
||||
getPathOriginal() string
|
||||
getValues() *[maxParams]string
|
||||
|
|
|
@ -8,8 +8,7 @@ sidebar_position: 5
|
|||
|
||||
Fiber provides the [Bind](../api/bind.md#validation) function to validate and bind [request data](../api/bind.md#binders) to a struct.
|
||||
|
||||
```go title="Example"
|
||||
|
||||
```go title="Basic Example"
|
||||
import "github.com/go-playground/validator/v10"
|
||||
|
||||
type structValidator struct {
|
||||
|
@ -42,3 +41,71 @@ app.Post("/", func(c fiber.Ctx) error {
|
|||
return c.JSON(user)
|
||||
})
|
||||
```
|
||||
|
||||
```go title="Advanced Validation Example"
|
||||
type User struct {
|
||||
Name string `json:"name" validate:"required,min=3,max=32"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Age int `json:"age" validate:"gte=0,lte=100"`
|
||||
Password string `json:"password" validate:"required,min=8"`
|
||||
Website string `json:"website" validate:"url"`
|
||||
}
|
||||
|
||||
// Custom validation error messages
|
||||
type UserWithCustomMessages struct {
|
||||
Name string `json:"name" validate:"required,min=3,max=32" message:"Name is required and must be between 3 and 32 characters"`
|
||||
Email string `json:"email" validate:"required,email" message:"Valid email is required"`
|
||||
Age int `json:"age" validate:"gte=0,lte=100" message:"Age must be between 0 and 100"`
|
||||
}
|
||||
|
||||
app.Post("/user", func(c fiber.Ctx) error {
|
||||
user := new(User)
|
||||
|
||||
if err := c.Bind().Body(user); err != nil {
|
||||
// Handle validation errors
|
||||
if validationErrors, ok := err.(validator.ValidationErrors); ok {
|
||||
for _, e := range validationErrors {
|
||||
// e.Field() - field name
|
||||
// e.Tag() - validation tag
|
||||
// e.Value() - invalid value
|
||||
// e.Param() - validation parameter
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"field": e.Field(),
|
||||
"error": e.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(user)
|
||||
})
|
||||
```
|
||||
|
||||
```go title="Custom Validator Example"
|
||||
// Custom validator for password strength
|
||||
type PasswordValidator struct {
|
||||
validate *validator.Validate
|
||||
}
|
||||
|
||||
func (v *PasswordValidator) Validate(out any) error {
|
||||
if err := v.validate.Struct(out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Custom password validation logic
|
||||
if user, ok := out.(*User); ok {
|
||||
if len(user.Password) < 8 {
|
||||
return errors.New("password must be at least 8 characters")
|
||||
}
|
||||
// Add more password validation rules here
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Usage
|
||||
app := fiber.New(fiber.Config{
|
||||
StructValidator: &PasswordValidator{validate: validator.New()},
|
||||
})
|
||||
```
|
||||
|
|
|
@ -113,9 +113,9 @@ func (app *App) methodExist(c *DefaultCtx) bool {
|
|||
// Reset stack index
|
||||
c.setIndexRoute(-1)
|
||||
|
||||
tree, ok := c.App().treeStack[i][c.getTreePath()]
|
||||
tree, ok := c.App().treeStack[i][c.treePathHash]
|
||||
if !ok {
|
||||
tree = c.App().treeStack[i][""]
|
||||
tree = c.App().treeStack[i][0]
|
||||
}
|
||||
// Get stack length
|
||||
lenr := len(tree) - 1
|
||||
|
@ -157,9 +157,9 @@ func (app *App) methodExistCustom(c CustomCtx) bool {
|
|||
// Reset stack index
|
||||
c.setIndexRoute(-1)
|
||||
|
||||
tree, ok := c.App().treeStack[i][c.getTreePath()]
|
||||
tree, ok := c.App().treeStack[i][c.getTreePathHash()]
|
||||
if !ok {
|
||||
tree = c.App().treeStack[i][""]
|
||||
tree = c.App().treeStack[i][0]
|
||||
}
|
||||
// Get stack length
|
||||
lenr := len(tree) - 1
|
||||
|
|
32
router.go
32
router.go
|
@ -110,9 +110,9 @@ func (r *Route) match(detectionPath, path string, params *[maxParams]string) boo
|
|||
|
||||
func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint:unparam // bool param might be useful for testing
|
||||
// Get stack length
|
||||
tree, ok := app.treeStack[c.getMethodINT()][c.getTreePath()]
|
||||
tree, ok := app.treeStack[c.getMethodINT()][c.getTreePathHash()]
|
||||
if !ok {
|
||||
tree = app.treeStack[c.getMethodINT()][""]
|
||||
tree = app.treeStack[c.getMethodINT()][0]
|
||||
}
|
||||
lenr := len(tree) - 1
|
||||
|
||||
|
@ -158,9 +158,9 @@ func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint:unparam // bool
|
|||
|
||||
func (app *App) next(c *DefaultCtx) (bool, error) {
|
||||
// Get stack length
|
||||
tree, ok := app.treeStack[c.methodINT][c.treePath]
|
||||
tree, ok := app.treeStack[c.methodINT][c.treePathHash]
|
||||
if !ok {
|
||||
tree = app.treeStack[c.methodINT][""]
|
||||
tree = app.treeStack[c.methodINT][0]
|
||||
}
|
||||
lenTree := len(tree) - 1
|
||||
|
||||
|
@ -180,7 +180,7 @@ func (app *App) next(c *DefaultCtx) (bool, error) {
|
|||
}
|
||||
|
||||
// Check if it matches the request path
|
||||
match = route.match(c.detectionPath, c.path, &c.values)
|
||||
match = route.match(utils.UnsafeString(c.detectionPath), utils.UnsafeString(c.path), &c.values)
|
||||
if !match {
|
||||
// No match, next route
|
||||
continue
|
||||
|
@ -552,30 +552,28 @@ func (app *App) buildTree() *App {
|
|||
|
||||
// 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[int][]*Route)
|
||||
for _, route := range app.stack[m] {
|
||||
treePath := ""
|
||||
if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 {
|
||||
treePath = route.routeParser.segs[0].Const[:3]
|
||||
treePathHash := 0
|
||||
if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= maxDetectionPaths {
|
||||
treePathHash = int(route.routeParser.segs[0].Const[0])<<16 |
|
||||
int(route.routeParser.segs[0].Const[1])<<8 |
|
||||
int(route.routeParser.segs[0].Const[2])
|
||||
}
|
||||
// create tree stack
|
||||
tsMap[treePath] = append(tsMap[treePath], route)
|
||||
tsMap[treePathHash] = append(tsMap[treePathHash], route)
|
||||
}
|
||||
app.treeStack[m] = tsMap
|
||||
}
|
||||
|
||||
// loop the methods and tree stacks and add global stack and sort everything
|
||||
for m := range app.config.RequestMethods {
|
||||
tsMap := app.treeStack[m]
|
||||
for treePart := range tsMap {
|
||||
if treePart != "" {
|
||||
if treePart != 0 {
|
||||
// merge global tree routes in current tree stack
|
||||
tsMap[treePart] = uniqueRouteStack(append(tsMap[treePart], tsMap[""]...))
|
||||
tsMap[treePart] = uniqueRouteStack(append(tsMap[treePart], tsMap[0]...))
|
||||
}
|
||||
// sort tree slices with the positions
|
||||
slc := tsMap[treePart]
|
||||
sort.Slice(slc, func(i, j int) bool { return slc[i].pos < slc[j].pos })
|
||||
}
|
||||
app.treeStack[m] = tsMap
|
||||
}
|
||||
app.routesRefreshed = false
|
||||
|
||||
|
|
Loading…
Reference in New Issue