mirror of https://github.com/gofiber/fiber.git
🐛 bug: fix route constraints problems (#2033)
* 🐛 bug: fix route constraints problems
* escape support for data
* exactLen -> len
pull/2050/head^2
parent
ffb2d4cb1a
commit
aef7ea53b3
31
helpers.go
31
helpers.go
|
@ -698,18 +698,21 @@ const (
|
|||
|
||||
// Route Constraints
|
||||
const (
|
||||
ConstraintInt = "int"
|
||||
ConstraintBool = "bool"
|
||||
ConstraintFloat = "float"
|
||||
ConstraintAlpha = "alpha"
|
||||
ConstraintGuid = "guid"
|
||||
ConstraintMinLen = "minLen"
|
||||
ConstraintMaxLen = "maxLen"
|
||||
ConstraintExactLen = "exactLen"
|
||||
ConstraintBetweenLen = "betweenLen"
|
||||
ConstraintMin = "min"
|
||||
ConstraintMax = "max"
|
||||
ConstraintRange = "range"
|
||||
ConstraintDatetime = "datetime"
|
||||
ConstraintRegex = "regex"
|
||||
ConstraintInt = "int"
|
||||
ConstraintBool = "bool"
|
||||
ConstraintFloat = "float"
|
||||
ConstraintAlpha = "alpha"
|
||||
ConstraintGuid = "guid"
|
||||
ConstraintMinLen = "minLen"
|
||||
ConstraintMaxLen = "maxLen"
|
||||
ConstraintLen = "len"
|
||||
ConstraintBetweenLen = "betweenLen"
|
||||
ConstraintMinLenLower = "minlen"
|
||||
ConstraintMaxLenLower = "maxlen"
|
||||
ConstraintBetweenLenLower = "betweenlen"
|
||||
ConstraintMin = "min"
|
||||
ConstraintMax = "max"
|
||||
ConstraintRange = "range"
|
||||
ConstraintDatetime = "datetime"
|
||||
ConstraintRegex = "regex"
|
||||
)
|
||||
|
|
46
path.go
46
path.go
|
@ -7,6 +7,7 @@
|
|||
package fiber
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -79,7 +80,7 @@ const (
|
|||
guidConstraint
|
||||
minLenConstraint
|
||||
maxLenConstraint
|
||||
exactLenConstraint
|
||||
lenConstraint
|
||||
betweenLenConstraint
|
||||
minConstraint
|
||||
maxConstraint
|
||||
|
@ -261,7 +262,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r
|
|||
|
||||
if hasConstraint := (parameterConstraintStart != -1 && parameterConstraintEnd != -1); hasConstraint {
|
||||
constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd]
|
||||
userconstraints := strings.Split(constraintString, string(parameterConstraintSeparatorChars))
|
||||
userconstraints := splitNonEscaped(constraintString, string(parameterConstraintSeparatorChars))
|
||||
constraints = make([]*Constraint, 0, len(userconstraints))
|
||||
|
||||
for _, c := range userconstraints {
|
||||
|
@ -272,7 +273,15 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r
|
|||
if start != -1 && end != -1 {
|
||||
constraint := &Constraint{
|
||||
ID: getParamConstraintType(c[:start]),
|
||||
Data: strings.Split(RemoveEscapeChar(c[start+1:end]), string(parameterConstraintDataSeparatorChars)),
|
||||
Data: splitNonEscaped(c[start+1:end], string(parameterConstraintDataSeparatorChars)),
|
||||
}
|
||||
|
||||
// remove escapes from data
|
||||
if len(constraint.Data) == 1 {
|
||||
constraint.Data[0] = RemoveEscapeChar(constraint.Data[0])
|
||||
} else if len(constraint.Data) == 2 {
|
||||
constraint.Data[0] = RemoveEscapeChar(constraint.Data[0])
|
||||
constraint.Data[1] = RemoveEscapeChar(constraint.Data[1])
|
||||
}
|
||||
|
||||
// Precompile regex if has regex constraint
|
||||
|
@ -384,6 +393,21 @@ func findNextNonEscapedCharsetPosition(search string, charset []byte) int {
|
|||
return pos
|
||||
}
|
||||
|
||||
// splitNonEscaped slices s into all substrings separated by sep and returns a slice of the substrings between those separators
|
||||
// This function also takes a care of escape char when splitting.
|
||||
func splitNonEscaped(s, sep string) []string {
|
||||
var result []string
|
||||
i := findNextNonEscapedCharsetPosition(s, []byte(sep))
|
||||
|
||||
for i > -1 {
|
||||
result = append(result, s[:i])
|
||||
s = s[i+len(sep):]
|
||||
i = findNextNonEscapedCharsetPosition(s, []byte(sep))
|
||||
}
|
||||
|
||||
return append(result, s)
|
||||
}
|
||||
|
||||
// getMatch parses the passed url and tries to match it against the route segments and determine the parameter positions
|
||||
func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool {
|
||||
var i, paramsIterator, partLen int
|
||||
|
@ -526,13 +550,13 @@ func getParamConstraintType(constraintPart string) TypeConstraint {
|
|||
return alphaConstraint
|
||||
case ConstraintGuid:
|
||||
return guidConstraint
|
||||
case ConstraintMinLen:
|
||||
case ConstraintMinLen, ConstraintMinLenLower:
|
||||
return minLenConstraint
|
||||
case ConstraintMaxLen:
|
||||
case ConstraintMaxLen, ConstraintMaxLenLower:
|
||||
return maxLenConstraint
|
||||
case ConstraintExactLen:
|
||||
return exactLenConstraint
|
||||
case ConstraintBetweenLen:
|
||||
case ConstraintLen:
|
||||
return lenConstraint
|
||||
case ConstraintBetweenLen, ConstraintBetweenLenLower:
|
||||
return betweenLenConstraint
|
||||
case ConstraintMin:
|
||||
return minConstraint
|
||||
|
@ -555,7 +579,7 @@ func (c *Constraint) CheckConstraint(param string) bool {
|
|||
var num int
|
||||
|
||||
// check data exists
|
||||
needOneData := []TypeConstraint{minLenConstraint, maxLenConstraint, exactLenConstraint, minConstraint, maxConstraint, datetimeConstraint, regexConstraint}
|
||||
needOneData := []TypeConstraint{minLenConstraint, maxLenConstraint, lenConstraint, minConstraint, maxConstraint, datetimeConstraint, regexConstraint}
|
||||
needTwoData := []TypeConstraint{betweenLenConstraint, rangeConstraint}
|
||||
|
||||
for _, data := range needOneData {
|
||||
|
@ -570,6 +594,8 @@ func (c *Constraint) CheckConstraint(param string) bool {
|
|||
}
|
||||
}
|
||||
|
||||
fmt.Print(c.Data)
|
||||
|
||||
// check constraints
|
||||
switch c.ID {
|
||||
case intConstraint:
|
||||
|
@ -598,7 +624,7 @@ func (c *Constraint) CheckConstraint(param string) bool {
|
|||
if len(param) > data {
|
||||
return false
|
||||
}
|
||||
case exactLenConstraint:
|
||||
case lenConstraint:
|
||||
data, _ := strconv.Atoi(c.Data[0])
|
||||
|
||||
if len(param) != data {
|
||||
|
|
52
path_test.go
52
path_test.go
|
@ -475,7 +475,7 @@ func Test_Path_matchParams(t *testing.T) {
|
|||
{url: "/api/v1/123", params: []string{"123"}, match: true},
|
||||
{url: "/api/v1/12345", params: []string{"12345"}, match: true},
|
||||
})
|
||||
testCase("/api/v1/:param<exactLen(5)>", []testparams{
|
||||
testCase("/api/v1/:param<len(5)>", []testparams{
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
{url: "/api/v1/123", params: []string{"123"}, match: false},
|
||||
{url: "/api/v1/12345", params: []string{"12345"}, match: true},
|
||||
|
@ -549,6 +549,19 @@ func Test_Path_matchParams(t *testing.T) {
|
|||
{url: "/api/v1/25", params: []string{"25"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
testCase("/api/v1/:param<int\\;range(10,30)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: true},
|
||||
{url: "/api/v1/87283827683", params: []string{"87283827683"}, match: true},
|
||||
{url: "/api/v1/25", params: []string{"25"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: true},
|
||||
})
|
||||
testCase("/api/v1/:param<range(10\\,30,1500)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/87283827683", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/25", params: []string{"25"}, match: true},
|
||||
{url: "/api/v1/1200", params: []string{"1200"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Utils_GetTrimmedParam(t *testing.T) {
|
||||
|
@ -681,7 +694,7 @@ func Benchmark_Path_matchParams(t *testing.B) {
|
|||
{url: "/api/v1/123", params: []string{"123"}, match: true},
|
||||
{url: "/api/v1/12345", params: []string{"12345"}, match: true},
|
||||
})
|
||||
benchCase("/api/v1/:param<exactLen(5)>", []testparams{
|
||||
benchCase("/api/v1/:param<len(5)>", []testparams{
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
{url: "/api/v1/123", params: []string{"123"}, match: false},
|
||||
{url: "/api/v1/12345", params: []string{"12345"}, match: true},
|
||||
|
@ -755,28 +768,17 @@ func Benchmark_Path_matchParams(t *testing.B) {
|
|||
{url: "/api/v1/25", params: []string{"25"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Path_matchParams0(t *testing.T) {
|
||||
t.Parallel()
|
||||
type testparams struct {
|
||||
url string
|
||||
params []string
|
||||
match bool
|
||||
partialCheck bool
|
||||
}
|
||||
var ctxParams [maxParams]string
|
||||
testCase := func(r string, cases []testparams) {
|
||||
parser := parseRoute(r)
|
||||
for _, c := range cases {
|
||||
match := parser.getMatch(c.url, c.url, &ctxParams, c.partialCheck)
|
||||
utils.AssertEqual(t, c.match, match, fmt.Sprintf("route: '%s', url: '%s'", r, c.url))
|
||||
if match && len(c.params) > 0 {
|
||||
utils.AssertEqual(t, c.params[0:len(c.params)], ctxParams[0:len(c.params)], fmt.Sprintf("route: '%s', url: '%s'", r, c.url))
|
||||
}
|
||||
}
|
||||
}
|
||||
testCase("/api/v1/:param<datetime(2006-01-02)>", []testparams{
|
||||
{url: "/api/v1/2005-11-01", params: []string{"2005-11-01"}, match: true},
|
||||
benchCase("/api/v1/:param<int\\;range(10,30)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: true},
|
||||
{url: "/api/v1/87283827683", params: []string{"87283827683"}, match: true},
|
||||
{url: "/api/v1/25", params: []string{"25"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: true},
|
||||
})
|
||||
benchCase("/api/v1/:param<range(10\\,30,1500)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/87283827683", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/25", params: []string{"25"}, match: true},
|
||||
{url: "/api/v1/1200", params: []string{"1200"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue