mirror of https://github.com/gofiber/fiber.git
✨ feature: route constraints (#1998)
* Segment parameters constraints and determining it's type * add parsing for constraints. * fix tests * add tests, benchs & some fixes. * fix regex & datetime tests. * clean up constraint parser, multiple constraint support. * update * regex customization. * constants, remove variadic methods. * add some benchs, refactor constraint check funtion. * more readable conditions * fix tests * precompile regex * precompile regex when parsing the route * update comments Co-authored-by: wernerr <rene@gofiber.io> Co-authored-by: Mohab Abd El-Dayem <mohab.m.mohamed@gmail.com> Co-authored-by: RW <rene@gofiber.io>pull/2017/head
parent
95abdacba0
commit
2517944c80
18
helpers.go
18
helpers.go
|
@ -695,3 +695,21 @@ const (
|
|||
CookieSameSiteStrictMode = "strict"
|
||||
CookieSameSiteNoneMode = "none"
|
||||
)
|
||||
|
||||
// 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"
|
||||
)
|
||||
|
|
297
path.go
297
path.go
|
@ -7,9 +7,13 @@
|
|||
package fiber
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/gofiber/fiber/v2/internal/uuid"
|
||||
"github.com/gofiber/fiber/v2/utils"
|
||||
)
|
||||
|
||||
|
@ -33,20 +37,54 @@ type routeSegment struct {
|
|||
IsGreedy bool // indicates whether the parameter is greedy or not, is used with wildcard and plus
|
||||
IsOptional bool // indicates whether the parameter is optional or not
|
||||
// common information
|
||||
IsLast bool // shows if the segment is the last one for the route
|
||||
HasOptionalSlash bool // segment has the possibility of an optional slash
|
||||
Length int // length of the parameter for segment, when its 0 then the length is undetermined
|
||||
IsLast bool // shows if the segment is the last one for the route
|
||||
HasOptionalSlash bool // segment has the possibility of an optional slash
|
||||
Constraints []*Constraint // Constraint type if segment is a parameter, if not it will be set to noConstraint by default
|
||||
Length int // length of the parameter for segment, when its 0 then the length is undetermined
|
||||
// future TODO: add support for optional groups "/abc(/def)?"
|
||||
}
|
||||
|
||||
// different special routing signs
|
||||
const (
|
||||
wildcardParam byte = '*' // indicates a optional greedy parameter
|
||||
plusParam byte = '+' // indicates a required greedy parameter
|
||||
optionalParam byte = '?' // concludes a parameter by name and makes it optional
|
||||
paramStarterChar byte = ':' // start character for a parameter with name
|
||||
slashDelimiter byte = '/' // separator for the route, unlike the other delimiters this character at the end can be optional
|
||||
escapeChar byte = '\\' // escape character
|
||||
wildcardParam byte = '*' // indicates a optional greedy parameter
|
||||
plusParam byte = '+' // indicates a required greedy parameter
|
||||
optionalParam byte = '?' // concludes a parameter by name and makes it optional
|
||||
paramStarterChar byte = ':' // start character for a parameter with name
|
||||
slashDelimiter byte = '/' // separator for the route, unlike the other delimiters this character at the end can be optional
|
||||
escapeChar byte = '\\' // escape character
|
||||
paramConstraintStart byte = '<' // start of type constraint for a parameter
|
||||
paramConstraintEnd byte = '>' // end of type constraint for a parameter
|
||||
paramConstraintSeparator byte = ';' // separator of type constraints for a parameter
|
||||
paramConstraintDataStart byte = '(' // start of data of type constraint for a parameter
|
||||
paramConstraintDataEnd byte = ')' // end of data of type constraint for a parameter
|
||||
paramConstraintDataSeparator byte = ',' // separator of datas of type constraint for a parameter
|
||||
)
|
||||
|
||||
// parameter constraint types
|
||||
type TypeConstraint int16
|
||||
|
||||
type Constraint struct {
|
||||
ID TypeConstraint
|
||||
RegexCompiler *regexp.Regexp
|
||||
Data []string
|
||||
}
|
||||
|
||||
const (
|
||||
noConstraint TypeConstraint = iota + 1
|
||||
intConstraint
|
||||
boolConstraint
|
||||
floatConstraint
|
||||
alphaConstraint
|
||||
datetimeConstraint
|
||||
guidConstraint
|
||||
minLenConstraint
|
||||
maxLenConstraint
|
||||
exactLenConstraint
|
||||
betweenLenConstraint
|
||||
minConstraint
|
||||
maxConstraint
|
||||
rangeConstraint
|
||||
regexConstraint
|
||||
)
|
||||
|
||||
// list of possible parameter and segment delimiter
|
||||
|
@ -61,6 +99,18 @@ var (
|
|||
parameterDelimiterChars = append([]byte{paramStarterChar, escapeChar}, routeDelimiter...)
|
||||
// list of chars to find the end of a parameter
|
||||
parameterEndChars = append([]byte{optionalParam}, parameterDelimiterChars...)
|
||||
// list of parameter constraint start
|
||||
parameterConstraintStartChars = []byte{paramConstraintStart}
|
||||
// list of parameter constraint end
|
||||
parameterConstraintEndChars = []byte{paramConstraintEnd}
|
||||
// list of parameter separator
|
||||
parameterConstraintSeparatorChars = []byte{paramConstraintSeparator}
|
||||
// list of parameter constraint data start
|
||||
parameterConstraintDataStartChars = []byte{paramConstraintDataStart}
|
||||
// list of parameter constraint data end
|
||||
parameterConstraintDataEndChars = []byte{paramConstraintDataEnd}
|
||||
// list of parameter constraint data separator
|
||||
parameterConstraintDataSeparatorChars = []byte{paramConstraintDataSeparator}
|
||||
)
|
||||
|
||||
// parseRoute analyzes the route and divides it into segments for constant areas and parameters,
|
||||
|
@ -177,8 +227,16 @@ func (routeParser *routeParser) analyseConstantPart(pattern string, nextParamPos
|
|||
func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *routeSegment) {
|
||||
isWildCard := pattern[0] == wildcardParam
|
||||
isPlusParam := pattern[0] == plusParam
|
||||
parameterEndPosition := findNextNonEscapedCharsetPosition(pattern[1:], parameterEndChars)
|
||||
|
||||
var parameterEndPosition int
|
||||
if strings.ContainsRune(pattern, rune(paramConstraintStart)) && strings.ContainsRune(pattern, rune(paramConstraintEnd)) {
|
||||
parameterEndPosition = findNextCharsetPositionConstraint(pattern[1:], parameterEndChars)
|
||||
} else {
|
||||
parameterEndPosition = findNextNonEscapedCharsetPosition(pattern[1:], parameterEndChars)
|
||||
}
|
||||
|
||||
parameterConstraintStart := -1
|
||||
parameterConstraintEnd := -1
|
||||
// handle wildcard end
|
||||
if isWildCard || isPlusParam {
|
||||
parameterEndPosition = 0
|
||||
|
@ -187,10 +245,53 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r
|
|||
} else if !isInCharset(pattern[parameterEndPosition+1], parameterDelimiterChars) {
|
||||
parameterEndPosition++
|
||||
}
|
||||
|
||||
// find constraint part if exists in the parameter part and remove it
|
||||
if parameterEndPosition > 0 {
|
||||
parameterConstraintStart = findNextNonEscapedCharsetPosition(pattern[0:parameterEndPosition], parameterConstraintStartChars)
|
||||
parameterConstraintEnd = findNextNonEscapedCharsetPosition(pattern[0:parameterEndPosition+1], parameterConstraintEndChars)
|
||||
}
|
||||
|
||||
// cut params part
|
||||
processedPart := pattern[0 : parameterEndPosition+1]
|
||||
|
||||
paramName := RemoveEscapeChar(GetTrimmedParam(processedPart))
|
||||
|
||||
// Check has constraint
|
||||
var constraints []*Constraint
|
||||
|
||||
if hasConstraint := (parameterConstraintStart != -1 && parameterConstraintEnd != -1); hasConstraint {
|
||||
constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd]
|
||||
userconstraints := strings.Split(constraintString, string(parameterConstraintSeparatorChars))
|
||||
constraints = make([]*Constraint, 0, len(userconstraints))
|
||||
|
||||
for _, c := range userconstraints {
|
||||
start := findNextNonEscapedCharsetPosition(c, parameterConstraintDataStartChars)
|
||||
end := findNextNonEscapedCharsetPosition(c, parameterConstraintDataEndChars)
|
||||
|
||||
// Assign constraint
|
||||
if start != -1 && end != -1 {
|
||||
constraint := &Constraint{
|
||||
ID: getParamConstraintType(c[:start]),
|
||||
Data: strings.Split(RemoveEscapeChar(c[start+1:end]), string(parameterConstraintDataSeparatorChars)),
|
||||
}
|
||||
|
||||
// Precompile regex if has regex constraint
|
||||
if constraint.ID == regexConstraint {
|
||||
constraint.RegexCompiler = regexp.MustCompile(constraint.Data[0])
|
||||
}
|
||||
|
||||
constraints = append(constraints, constraint)
|
||||
} else {
|
||||
constraints = append(constraints, &Constraint{
|
||||
ID: getParamConstraintType(c),
|
||||
Data: []string{},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
paramName = RemoveEscapeChar(GetTrimmedParam(pattern[0:parameterConstraintStart]))
|
||||
}
|
||||
|
||||
// add access iterator to wildcard and plus
|
||||
if isWildCard {
|
||||
routeParser.wildCardCount++
|
||||
|
@ -200,12 +301,18 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r
|
|||
paramName += strconv.Itoa(routeParser.plusCount)
|
||||
}
|
||||
|
||||
return processedPart, &routeSegment{
|
||||
segment := &routeSegment{
|
||||
ParamName: paramName,
|
||||
IsParam: true,
|
||||
IsOptional: isWildCard || pattern[parameterEndPosition] == optionalParam,
|
||||
IsGreedy: isWildCard || isPlusParam,
|
||||
}
|
||||
|
||||
if len(constraints) > 0 {
|
||||
segment.Constraints = constraints
|
||||
}
|
||||
|
||||
return processedPart, segment
|
||||
}
|
||||
|
||||
// isInCharset check is the given character in the charset list
|
||||
|
@ -230,6 +337,34 @@ func findNextCharsetPosition(search string, charset []byte) int {
|
|||
return nextPosition
|
||||
}
|
||||
|
||||
// findNextCharsetPositionConstraint search the next char position from the charset
|
||||
// unlike findNextCharsetPosition, it takes care of constraint start-end chars to parse route pattern
|
||||
func findNextCharsetPositionConstraint(search string, charset []byte) int {
|
||||
nextPosition := -1
|
||||
constraintStart := -1
|
||||
constraintEnd := -1
|
||||
|
||||
for _, char := range charset {
|
||||
pos := strings.IndexByte(search, char)
|
||||
|
||||
if char == paramConstraintStart {
|
||||
constraintStart = pos
|
||||
}
|
||||
|
||||
if char == paramConstraintEnd {
|
||||
constraintEnd = pos
|
||||
}
|
||||
//fmt.Println(string(char))
|
||||
if pos != -1 && (pos < nextPosition || nextPosition == -1) {
|
||||
if pos > constraintStart && pos < constraintEnd {
|
||||
nextPosition = pos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nextPosition
|
||||
}
|
||||
|
||||
// findNextNonEscapedCharsetPosition search the next char position from the charset and skip the escaped characters
|
||||
func findNextNonEscapedCharsetPosition(search string, charset []byte) int {
|
||||
pos := findNextCharsetPosition(search, charset)
|
||||
|
@ -272,6 +407,14 @@ func (routeParser *routeParser) getMatch(detectionPath, path string, params *[ma
|
|||
}
|
||||
// take over the params positions
|
||||
params[paramsIterator] = path[:i]
|
||||
|
||||
// check constraint
|
||||
for _, c := range segment.Constraints {
|
||||
if matched := c.CheckConstraint(params[paramsIterator]); !matched {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
paramsIterator++
|
||||
}
|
||||
|
||||
|
@ -370,3 +513,133 @@ func RemoveEscapeChar(word string) string {
|
|||
}
|
||||
return word
|
||||
}
|
||||
|
||||
func getParamConstraintType(constraintPart string) TypeConstraint {
|
||||
switch constraintPart {
|
||||
case ConstraintInt:
|
||||
return intConstraint
|
||||
case ConstraintBool:
|
||||
return boolConstraint
|
||||
case ConstraintFloat:
|
||||
return floatConstraint
|
||||
case ConstraintAlpha:
|
||||
return alphaConstraint
|
||||
case ConstraintGuid:
|
||||
return guidConstraint
|
||||
case ConstraintMinLen:
|
||||
return minLenConstraint
|
||||
case ConstraintMaxLen:
|
||||
return maxLenConstraint
|
||||
case ConstraintExactLen:
|
||||
return exactLenConstraint
|
||||
case ConstraintBetweenLen:
|
||||
return betweenLenConstraint
|
||||
case ConstraintMin:
|
||||
return minConstraint
|
||||
case ConstraintMax:
|
||||
return maxConstraint
|
||||
case ConstraintRange:
|
||||
return rangeConstraint
|
||||
case ConstraintDatetime:
|
||||
return datetimeConstraint
|
||||
case ConstraintRegex:
|
||||
return regexConstraint
|
||||
default:
|
||||
return noConstraint
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *Constraint) CheckConstraint(param string) bool {
|
||||
var err error
|
||||
var num int
|
||||
|
||||
// check data exists
|
||||
needOneData := []TypeConstraint{minLenConstraint, maxLenConstraint, exactLenConstraint, minConstraint, maxConstraint, datetimeConstraint, regexConstraint}
|
||||
needTwoData := []TypeConstraint{betweenLenConstraint, rangeConstraint}
|
||||
|
||||
for _, data := range needOneData {
|
||||
if c.ID == data && len(c.Data) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, data := range needTwoData {
|
||||
if c.ID == data && len(c.Data) < 2 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// check constraints
|
||||
switch c.ID {
|
||||
case intConstraint:
|
||||
_, err = strconv.Atoi(param)
|
||||
case boolConstraint:
|
||||
_, err = strconv.ParseBool(param)
|
||||
case floatConstraint:
|
||||
_, err = strconv.ParseFloat(param, 32)
|
||||
case alphaConstraint:
|
||||
for _, r := range param {
|
||||
if !unicode.IsLetter(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
case guidConstraint:
|
||||
_, err = uuid.Parse(param)
|
||||
case minLenConstraint:
|
||||
data, _ := strconv.Atoi(c.Data[0])
|
||||
|
||||
if len(param) < data {
|
||||
return false
|
||||
}
|
||||
case maxLenConstraint:
|
||||
data, _ := strconv.Atoi(c.Data[0])
|
||||
|
||||
if len(param) > data {
|
||||
return false
|
||||
}
|
||||
case exactLenConstraint:
|
||||
data, _ := strconv.Atoi(c.Data[0])
|
||||
|
||||
if len(param) != data {
|
||||
return false
|
||||
}
|
||||
case betweenLenConstraint:
|
||||
data, _ := strconv.Atoi(c.Data[0])
|
||||
data2, _ := strconv.Atoi(c.Data[1])
|
||||
length := len(param)
|
||||
if length < data || length > data2 {
|
||||
return false
|
||||
}
|
||||
case minConstraint:
|
||||
data, _ := strconv.Atoi(c.Data[0])
|
||||
num, err = strconv.Atoi(param)
|
||||
|
||||
if num < data {
|
||||
return false
|
||||
}
|
||||
case maxConstraint:
|
||||
data, _ := strconv.Atoi(c.Data[0])
|
||||
num, err = strconv.Atoi(param)
|
||||
|
||||
if num > data {
|
||||
return false
|
||||
}
|
||||
case rangeConstraint:
|
||||
data, _ := strconv.Atoi(c.Data[0])
|
||||
data2, _ := strconv.Atoi(c.Data[1])
|
||||
num, err = strconv.Atoi(param)
|
||||
|
||||
if num < data || num > data2 {
|
||||
return false
|
||||
}
|
||||
case datetimeConstraint:
|
||||
_, err = time.Parse(c.Data[0], param)
|
||||
case regexConstraint:
|
||||
if match := c.RegexCompiler.MatchString(param); !match {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
|
260
path_test.go
260
path_test.go
|
@ -431,6 +431,124 @@ func Test_Path_matchParams(t *testing.T) {
|
|||
{url: "/api", params: nil, match: false},
|
||||
{url: "/api/:test", params: nil, match: false},
|
||||
})
|
||||
testCase("/api/v1/:param<int>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
testCase("/api/v1/:param<bool>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: true},
|
||||
})
|
||||
testCase("/api/v1/:param<float>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: true},
|
||||
{url: "/api/v1/8728382.5", params: []string{"8728382.5"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
testCase("/api/v1/:param<alpha>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: true},
|
||||
{url: "/api/v1/#!?", params: []string{"#!?"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
})
|
||||
testCase("/api/v1/:param<guid>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/f0fa66cc-d22e-445b-866d-1d76e776371d", params: []string{"f0fa66cc-d22e-445b-866d-1d76e776371d"}, match: true},
|
||||
})
|
||||
testCase("/api/v1/:param<minLen>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
})
|
||||
testCase("/api/v1/:param<minLen(5)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: true},
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: true},
|
||||
{url: "/api/v1/123", params: []string{"123"}, match: false},
|
||||
{url: "/api/v1/12345", params: []string{"12345"}, match: true},
|
||||
})
|
||||
testCase("/api/v1/:param<maxLen(5)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: true},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{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{
|
||||
{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},
|
||||
})
|
||||
testCase("/api/v1/:param<betweenLen(1)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
})
|
||||
testCase("/api/v1/:param<betweenLen(2,5)>", []testparams{
|
||||
{url: "/api/v1/e", params: []string{"e"}, match: false},
|
||||
{url: "/api/v1/en", params: []string{"en"}, match: true},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/123", params: []string{"123"}, match: true},
|
||||
{url: "/api/v1/12345", params: []string{"12345"}, match: true},
|
||||
})
|
||||
testCase("/api/v1/:param<betweenLen(2,5)>", []testparams{
|
||||
{url: "/api/v1/e", params: []string{"e"}, match: false},
|
||||
{url: "/api/v1/en", params: []string{"en"}, match: true},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/123", params: []string{"123"}, match: true},
|
||||
{url: "/api/v1/12345", params: []string{"12345"}, match: true},
|
||||
})
|
||||
testCase("/api/v1/:param<min(5)>", []testparams{
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
{url: "/api/v1/1", params: []string{"1"}, match: false},
|
||||
{url: "/api/v1/5", params: []string{"5"}, match: true},
|
||||
})
|
||||
testCase("/api/v1/:param<max(5)>", []testparams{
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
{url: "/api/v1/1", params: []string{"1"}, match: true},
|
||||
{url: "/api/v1/5", params: []string{"5"}, match: true},
|
||||
{url: "/api/v1/15", params: []string{"15"}, match: false},
|
||||
})
|
||||
testCase("/api/v1/:param<range(5,10)>", []testparams{
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
{url: "/api/v1/9", params: []string{"9"}, match: true},
|
||||
{url: "/api/v1/5", params: []string{"5"}, match: true},
|
||||
{url: "/api/v1/15", params: []string{"15"}, match: false},
|
||||
})
|
||||
testCase("/api/v1/:param<datetime(2006\\-01\\-02)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/2005-11-01", params: []string{"2005-11-01"}, match: true},
|
||||
})
|
||||
testCase("/api/v1/:param<regex(p\\([a\\-z]\\+\\)ch)>", []testparams{
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
{url: "/api/v1/15", params: []string{"15"}, match: false},
|
||||
{url: "/api/v1/peach", params: []string{"peach"}, match: true},
|
||||
{url: "/api/v1/p34ch", params: []string{"p34ch"}, match: false},
|
||||
})
|
||||
testCase("/api/v1/:param<int;bool((>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
testCase("/api/v1/:param<int;max(3000)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/123", params: []string{"123"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
testCase("/api/v1/:param<int;maxLen(10)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/87283827683", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/123", params: []string{"123"}, 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: false},
|
||||
{url: "/api/v1/87283827683", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/25", params: []string{"25"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Utils_GetTrimmedParam(t *testing.T) {
|
||||
|
@ -519,4 +637,146 @@ func Benchmark_Path_matchParams(t *testing.B) {
|
|||
{url: "/api/v2", params: nil, match: false},
|
||||
{url: "/api/v1/", params: nil, match: false},
|
||||
})
|
||||
benchCase("/api/v1/:param<int>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
benchCase("/api/v1/:param<bool>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: true},
|
||||
})
|
||||
benchCase("/api/v1/:param<float>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: true},
|
||||
{url: "/api/v1/8728382.5", params: []string{"8728382.5"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
benchCase("/api/v1/:param<alpha>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: true},
|
||||
{url: "/api/v1/#!?", params: []string{"#!?"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
})
|
||||
benchCase("/api/v1/:param<guid>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/f0fa66cc-d22e-445b-866d-1d76e776371d", params: []string{"f0fa66cc-d22e-445b-866d-1d76e776371d"}, match: true},
|
||||
})
|
||||
benchCase("/api/v1/:param<minLen>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
})
|
||||
benchCase("/api/v1/:param<minLen(5)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: true},
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: true},
|
||||
{url: "/api/v1/123", params: []string{"123"}, match: false},
|
||||
{url: "/api/v1/12345", params: []string{"12345"}, match: true},
|
||||
})
|
||||
benchCase("/api/v1/:param<maxLen(5)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: true},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{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{
|
||||
{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},
|
||||
})
|
||||
benchCase("/api/v1/:param<betweenLen(1)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
})
|
||||
benchCase("/api/v1/:param<betweenLen(2,5)>", []testparams{
|
||||
{url: "/api/v1/e", params: []string{"e"}, match: false},
|
||||
{url: "/api/v1/en", params: []string{"en"}, match: true},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/123", params: []string{"123"}, match: true},
|
||||
{url: "/api/v1/12345", params: []string{"12345"}, match: true},
|
||||
})
|
||||
benchCase("/api/v1/:param<betweenLen(2,5)>", []testparams{
|
||||
{url: "/api/v1/e", params: []string{"e"}, match: false},
|
||||
{url: "/api/v1/en", params: []string{"en"}, match: true},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/123", params: []string{"123"}, match: true},
|
||||
{url: "/api/v1/12345", params: []string{"12345"}, match: true},
|
||||
})
|
||||
benchCase("/api/v1/:param<min(5)>", []testparams{
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
{url: "/api/v1/1", params: []string{"1"}, match: false},
|
||||
{url: "/api/v1/5", params: []string{"5"}, match: true},
|
||||
})
|
||||
benchCase("/api/v1/:param<max(5)>", []testparams{
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
{url: "/api/v1/1", params: []string{"1"}, match: true},
|
||||
{url: "/api/v1/5", params: []string{"5"}, match: true},
|
||||
{url: "/api/v1/15", params: []string{"15"}, match: false},
|
||||
})
|
||||
benchCase("/api/v1/:param<range(5,10)>", []testparams{
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
{url: "/api/v1/9", params: []string{"9"}, match: true},
|
||||
{url: "/api/v1/5", params: []string{"5"}, match: true},
|
||||
{url: "/api/v1/15", params: []string{"15"}, match: false},
|
||||
})
|
||||
benchCase("/api/v1/:param<datetime(2006\\-01\\-02)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/2005-11-01", params: []string{"2005-11-01"}, match: true},
|
||||
})
|
||||
benchCase("/api/v1/:param<regex(p\\([a\\-z]\\+\\)ch)>", []testparams{
|
||||
{url: "/api/v1/ent", params: []string{"ent"}, match: false},
|
||||
{url: "/api/v1/15", params: []string{"15"}, match: false},
|
||||
{url: "/api/v1/peach", params: []string{"peach"}, match: true},
|
||||
{url: "/api/v1/p34ch", params: []string{"p34ch"}, match: false},
|
||||
})
|
||||
benchCase("/api/v1/:param<int;bool((>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
benchCase("/api/v1/:param<int;max(3000)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/8728382", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/123", params: []string{"123"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
benchCase("/api/v1/:param<int;maxLen(10)>", []testparams{
|
||||
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
|
||||
{url: "/api/v1/87283827683", params: []string{"8728382"}, match: false},
|
||||
{url: "/api/v1/123", params: []string{"123"}, match: true},
|
||||
{url: "/api/v1/true", params: []string{"true"}, match: false},
|
||||
})
|
||||
benchCase("/api/v1/:param<int;range(10,30)>", []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/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},
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue