From 9b905608cae33e2dc010ddba368927ad8293fb77 Mon Sep 17 00:00:00 2001 From: Mohab Abd El-Dayem Date: Mon, 6 Jun 2022 18:03:30 +0200 Subject: [PATCH 01/15] Segment parameters constraints and determining it's type --- path.go | 117 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 103 insertions(+), 14 deletions(-) diff --git a/path.go b/path.go index c847db4414..08c281286e 100644 --- a/path.go +++ b/path.go @@ -15,10 +15,11 @@ import ( // routeParser holds the path segments and param names type routeParser struct { - segs []*routeSegment // the parsed segments of the route - params []string // that parameter names the parsed route - wildCardCount int // number of wildcard parameters, used internally to give the wildcard parameter its number - plusCount int // number of plus parameters, used internally to give the plus parameter its number + segs []*routeSegment // the parsed segments of the route + params []string // that parameter names the parsed route + paramsConstraints []TypeConstraint // the types of each parameter + wildCardCount int // number of wildcard parameters, used internally to give the wildcard parameter its number + plusCount int // number of plus parameters, used internally to give the plus parameter its number } // paramsSeg holds the segment metadata @@ -33,20 +34,46 @@ 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 + Constraint TypeConstraint // 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 + +) + +// parameter constraint types + +type TypeConstraint int16 + +const ( + noConstraint TypeConstraint = iota + intConstraint + boolConstraint + floatConstraint + alphaConstraint + datetimeConstraint + guidConstraint + minLengthConstraint + maxLengthConstraint + exactLengthConstraint + BetweenLengthConstraint + minConstraint + maxConstraint + rangeConstraint + regexConstraint ) // list of possible parameter and segment delimiter @@ -59,6 +86,10 @@ var ( parameterDelimiterChars = append([]byte{paramStarterChar}, 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} ) // parseRoute analyzes the route and divides it into segments for constant areas and parameters, @@ -176,6 +207,8 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r isWildCard := pattern[0] == wildcardParam isPlusParam := pattern[0] == plusParam parameterEndPosition := findNextNonEscapedCharsetPosition(pattern[1:], parameterEndChars) + var parameterConstraintStart int = -1 + var parameterConstraintEnd int = -1 // handle wildcard end if isWildCard || isPlusParam { @@ -185,10 +218,28 @@ 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) + + } + + hasConstraint := (parameterConstraintStart != -1 && parameterConstraintEnd != -1) + + constraintType := noConstraint // cut params part processedPart := pattern[0 : parameterEndPosition+1] + paramName := RemoveEscapeChar(GetTrimmedParam(processedPart)) + + if hasConstraint { + constraintType = getParamConstraintType(pattern[parameterConstraintStart+1 : parameterConstraintEnd]) + paramName = RemoveEscapeChar(GetTrimmedParam(pattern[0:parameterConstraintStart])) + } + // add access iterator to wildcard and plus if isWildCard { routeParser.wildCardCount++ @@ -197,15 +248,53 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r routeParser.plusCount++ paramName += strconv.Itoa(routeParser.plusCount) } - return processedPart, &routeSegment{ ParamName: paramName, IsParam: true, IsOptional: isWildCard || pattern[parameterEndPosition] == optionalParam, IsGreedy: isWildCard || isPlusParam, + Constraint: constraintType, } } +func getParamConstraintType(constraintPart string) TypeConstraint { + switch constraintPart { + + case "int": + return intConstraint + case "bool": + return boolConstraint + case "float": + return floatConstraint + case "datetime": + return datetimeConstraint + case "alpha": + return alphaConstraint + case "guid": + return guidConstraint + case "minlength": + return minLengthConstraint + case "maxlength": + return maxLengthConstraint + case "exactlength": + return exactLengthConstraint + case "betweenlength": + return BetweenLengthConstraint + case "min": + return minConstraint + case "max": + return maxConstraint + case "range": + return rangeConstraint + case "regex": + return regexConstraint + default: + return noConstraint + + } + +} + // isInCharset check is the given character in the charset list func isInCharset(searchChar byte, charset []byte) bool { for _, char := range charset { From a7f75f4ea43f92a0e6a3f2be3e3b12952edfae63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Sat, 6 Aug 2022 12:29:08 +0300 Subject: [PATCH 02/15] add parsing for constraints. --- path.go | 154 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 136 insertions(+), 18 deletions(-) diff --git a/path.go b/path.go index 08c281286e..134ae04e32 100644 --- a/path.go +++ b/path.go @@ -7,19 +7,23 @@ package fiber import ( + "errors" + "regexp" "strconv" "strings" + "time" + "unicode" + "github.com/gofiber/fiber/v2/internal/uuid" "github.com/gofiber/fiber/v2/utils" ) // routeParser holds the path segments and param names type routeParser struct { - segs []*routeSegment // the parsed segments of the route - params []string // that parameter names the parsed route - paramsConstraints []TypeConstraint // the types of each parameter - wildCardCount int // number of wildcard parameters, used internally to give the wildcard parameter its number - plusCount int // number of plus parameters, used internally to give the plus parameter its number + segs []*routeSegment // the parsed segments of the route + params []string // that parameter names the parsed route + wildCardCount int // number of wildcard parameters, used internally to give the wildcard parameter its number + plusCount int // number of plus parameters, used internally to give the plus parameter its number } // paramsSeg holds the segment metadata @@ -34,10 +38,10 @@ 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 - Constraint TypeConstraint // 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 + IsLast bool // shows if the segment is the last one for the route + HasOptionalSlash bool // segment has the possibility of an optional slash + Constraint *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)?" } @@ -55,9 +59,13 @@ const ( ) // parameter constraint types - type TypeConstraint int16 +type Constraint struct { + ID TypeConstraint + Data []string +} + const ( noConstraint TypeConstraint = iota intConstraint @@ -207,8 +215,8 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r isWildCard := pattern[0] == wildcardParam isPlusParam := pattern[0] == plusParam parameterEndPosition := findNextNonEscapedCharsetPosition(pattern[1:], parameterEndChars) - var parameterConstraintStart int = -1 - var parameterConstraintEnd int = -1 + parameterConstraintStart := -1 + parameterConstraintEnd := -1 // handle wildcard end if isWildCard || isPlusParam { @@ -226,17 +234,42 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r } - hasConstraint := (parameterConstraintStart != -1 && parameterConstraintEnd != -1) + // Some vars for constraint feature + constraint := &Constraint{ + ID: noConstraint, + } - constraintType := noConstraint // cut params part processedPart := pattern[0 : parameterEndPosition+1] + paramName := RemoveEscapeChar(GetTrimmedParam(processedPart)) + // Check has constraint + if hasConstraint := (parameterConstraintStart != -1 && parameterConstraintEnd != -1); hasConstraint { + constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd] - paramName := RemoveEscapeChar(GetTrimmedParam(processedPart)) + start := 0 + end := 0 + haveData := false + for k, v := range constraintString { + if v == rune('(') { + start = k + 1 + continue + } + + if v == rune(')') && start != 0 { + end = k + haveData = true + break + } + } + + if haveData { + constraint.ID = getParamConstraintType(constraintString[:start-1]) + constraint.Data = strings.Split(constraintString[start:end], ",") + } else { + constraint.ID = getParamConstraintType(pattern[parameterConstraintStart+1 : parameterConstraintEnd]) + } - if hasConstraint { - constraintType = getParamConstraintType(pattern[parameterConstraintStart+1 : parameterConstraintEnd]) paramName = RemoveEscapeChar(GetTrimmedParam(pattern[0:parameterConstraintStart])) } @@ -253,7 +286,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r IsParam: true, IsOptional: isWildCard || pattern[parameterEndPosition] == optionalParam, IsGreedy: isWildCard || isPlusParam, - Constraint: constraintType, + Constraint: constraint, } } @@ -359,6 +392,12 @@ func (routeParser *routeParser) getMatch(detectionPath, path string, params *[ma } // take over the params positions params[paramsIterator] = path[:i] + + // check constraint + if matched := segment.Constraint.CheckConstraint(params[paramsIterator]); !matched { + return false + } + paramsIterator++ } @@ -457,3 +496,82 @@ func RemoveEscapeChar(word string) string { } return word } + +func (c *Constraint) CheckConstraint(param string) bool { + var err error + var num int + + 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) { + err = errors.New("") + } + } + case guidConstraint: + _, err = uuid.Parse(param) + case minLengthConstraint: + data, _ := strconv.Atoi(c.Data[0]) + + if len(param) < data { + err = errors.New("") + } + case maxLengthConstraint: + data, _ := strconv.Atoi(c.Data[0]) + + if len(param) > data { + err = errors.New("") + } + case exactLengthConstraint: + data, _ := strconv.Atoi(c.Data[0]) + + if len(param) != data { + err = errors.New("") + } + case BetweenLengthConstraint: + data, _ := strconv.Atoi(c.Data[0]) + data2, _ := strconv.Atoi(c.Data[1]) + length := len(param) + + if !(length >= data && length <= data2) { + err = errors.New("") + } + case minConstraint: + data, _ := strconv.Atoi(c.Data[0]) + num, err = strconv.Atoi(param) + + if num < data { + err = errors.New("") + } + case maxConstraint: + data, _ := strconv.Atoi(c.Data[0]) + num, err = strconv.Atoi(param) + + if num > data { + err = errors.New("") + } + case rangeConstraint: + data, _ := strconv.Atoi(c.Data[0]) + data2, _ := strconv.Atoi(c.Data[1]) + num, err = strconv.Atoi(param) + + if !(num >= data && num <= data2) { + err = errors.New("") + } + case datetimeConstraint: + _, err = time.Parse(param, c.Data[0]) + case regexConstraint: + match, _ := regexp.MatchString(c.Data[0], param) + if !match { + err = errors.New("") + } + } + + return err == nil +} From a1204767d8bbe681b06bc708a0fda0435202e1d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Sat, 6 Aug 2022 17:03:45 +0300 Subject: [PATCH 03/15] fix tests --- path.go | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/path.go b/path.go index 134ae04e32..cfb4428359 100644 --- a/path.go +++ b/path.go @@ -234,16 +234,13 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r } - // Some vars for constraint feature - constraint := &Constraint{ - ID: noConstraint, - } - // cut params part processedPart := pattern[0 : parameterEndPosition+1] paramName := RemoveEscapeChar(GetTrimmedParam(processedPart)) // Check has constraint + var constraint *Constraint + if hasConstraint := (parameterConstraintStart != -1 && parameterConstraintEnd != -1); hasConstraint { constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd] @@ -263,11 +260,12 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r } } + constraint = &Constraint{ + ID: getParamConstraintType(constraintString[:start-1]), + } + if haveData { - constraint.ID = getParamConstraintType(constraintString[:start-1]) constraint.Data = strings.Split(constraintString[start:end], ",") - } else { - constraint.ID = getParamConstraintType(pattern[parameterConstraintStart+1 : parameterConstraintEnd]) } paramName = RemoveEscapeChar(GetTrimmedParam(pattern[0:parameterConstraintStart])) @@ -281,13 +279,19 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r routeParser.plusCount++ paramName += strconv.Itoa(routeParser.plusCount) } - return processedPart, &routeSegment{ + + segment := &routeSegment{ ParamName: paramName, IsParam: true, IsOptional: isWildCard || pattern[parameterEndPosition] == optionalParam, IsGreedy: isWildCard || isPlusParam, - Constraint: constraint, } + + if constraint != nil { + segment.Constraint = constraint + } + + return processedPart, segment } func getParamConstraintType(constraintPart string) TypeConstraint { From 48a3da1c6addf63e40706a63407f29b0cf50ba2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Sat, 6 Aug 2022 18:03:33 +0300 Subject: [PATCH 04/15] add tests, benchs & some fixes. --- path.go | 111 +++++++++++++++++------------- path_test.go | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+), 47 deletions(-) diff --git a/path.go b/path.go index cfb4428359..d73947acae 100644 --- a/path.go +++ b/path.go @@ -242,7 +242,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r var constraint *Constraint if hasConstraint := (parameterConstraintStart != -1 && parameterConstraintEnd != -1); hasConstraint { - constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd] + constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd+1] start := 0 end := 0 @@ -253,19 +253,20 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r continue } - if v == rune(')') && start != 0 { + if v == rune(')') && start != 0 && constraintString[k+1] == '>' { end = k haveData = true break } } - constraint = &Constraint{ - ID: getParamConstraintType(constraintString[:start-1]), - } - + // Assign constraint + constraint = &Constraint{} if haveData { + constraint.ID = getParamConstraintType(constraintString[:start-1]) constraint.Data = strings.Split(constraintString[start:end], ",") + } else { + constraint.ID = getParamConstraintType(pattern[parameterConstraintStart+1 : parameterConstraintEnd]) } paramName = RemoveEscapeChar(GetTrimmedParam(pattern[0:parameterConstraintStart])) @@ -294,44 +295,6 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r return processedPart, segment } -func getParamConstraintType(constraintPart string) TypeConstraint { - switch constraintPart { - - case "int": - return intConstraint - case "bool": - return boolConstraint - case "float": - return floatConstraint - case "datetime": - return datetimeConstraint - case "alpha": - return alphaConstraint - case "guid": - return guidConstraint - case "minlength": - return minLengthConstraint - case "maxlength": - return maxLengthConstraint - case "exactlength": - return exactLengthConstraint - case "betweenlength": - return BetweenLengthConstraint - case "min": - return minConstraint - case "max": - return maxConstraint - case "range": - return rangeConstraint - case "regex": - return regexConstraint - default: - return noConstraint - - } - -} - // isInCharset check is the given character in the charset list func isInCharset(searchChar byte, charset []byte) bool { for _, char := range charset { @@ -398,8 +361,10 @@ func (routeParser *routeParser) getMatch(detectionPath, path string, params *[ma params[paramsIterator] = path[:i] // check constraint - if matched := segment.Constraint.CheckConstraint(params[paramsIterator]); !matched { - return false + if segment.Constraint != nil { + if matched := segment.Constraint.CheckConstraint(params[paramsIterator]); !matched { + return false + } } paramsIterator++ @@ -501,10 +466,62 @@ func RemoveEscapeChar(word string) string { return word } +func getParamConstraintType(constraintPart string) TypeConstraint { + switch constraintPart { + case "int": + return intConstraint + case "bool": + return boolConstraint + case "float": + return floatConstraint + case "alpha": + return alphaConstraint + case "guid": + return guidConstraint + case "minLen": + return minLengthConstraint + case "maxLen": + return maxLengthConstraint + case "exactLen": + return exactLengthConstraint + case "betweenLen": + return BetweenLengthConstraint + case "min": + return minConstraint + case "max": + return maxConstraint + case "range": + return rangeConstraint + case "datetime": + return datetimeConstraint + case "regex": + return regexConstraint + default: + return noConstraint + } + +} + func (c *Constraint) CheckConstraint(param string) bool { var err error var num int + // check data exists + needOneData := []TypeConstraint{minLengthConstraint, maxLengthConstraint, exactLengthConstraint, minConstraint, maxConstraint, datetimeConstraint, regexConstraint} + needTwoData := []TypeConstraint{BetweenLengthConstraint, 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 + } + } + switch c.ID { case intConstraint: _, err = strconv.Atoi(param) @@ -569,7 +586,7 @@ func (c *Constraint) CheckConstraint(param string) bool { err = errors.New("") } case datetimeConstraint: - _, err = time.Parse(param, c.Data[0]) + _, err = time.Parse(c.Data[0], param) case regexConstraint: match, _ := regexp.MatchString(c.Data[0], param) if !match { diff --git a/path_test.go b/path_test.go index 246cd58f5d..68804b5bc2 100644 --- a/path_test.go +++ b/path_test.go @@ -142,6 +142,101 @@ func Test_Path_matchParams(t *testing.T) { } } } + testCase("/api/v1/:param", []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", []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", []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", []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", []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", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: false}, + }) + testCase("/api/v1/:param", []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", []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", []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", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: false}, + {url: "/api/v1/ent", params: []string{"ent"}, match: false}, + }) + testCase("/api/v1/:param", []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", []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", []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", []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", []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", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: false}, + {url: "/api/v1/2022-02-01", params: []string{"2022-12-30"}, match: true}, + }) + testCase("/api/v1/:param", []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/*", []testparams{ {url: "/api/v1/entity", params: []string{"entity", ""}, match: true}, {url: "/api/v1/entity/", params: []string{"entity", ""}, match: true}, @@ -504,4 +599,99 @@ 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", []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", []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", []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", []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", []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", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: false}, + }) + benchCase("/api/v1/:param", []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", []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", []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", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: false}, + {url: "/api/v1/ent", params: []string{"ent"}, match: false}, + }) + benchCase("/api/v1/:param", []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", []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", []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", []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", []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", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: false}, + {url: "/api/v1/2022-02-01", params: []string{"2022-12-30"}, match: true}, + }) + benchCase("/api/v1/:param", []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}, + })*/ } From f9a552ce797dcd25332340f69f1914e135aa513d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Sun, 7 Aug 2022 11:38:12 +0300 Subject: [PATCH 05/15] fix regex & datetime tests. --- path.go | 5 +- path_test.go | 198 +++++++++++++++++++++++++-------------------------- 2 files changed, 101 insertions(+), 102 deletions(-) diff --git a/path.go b/path.go index d73947acae..764339b593 100644 --- a/path.go +++ b/path.go @@ -243,12 +243,11 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r if hasConstraint := (parameterConstraintStart != -1 && parameterConstraintEnd != -1); hasConstraint { constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd+1] - start := 0 end := 0 haveData := false for k, v := range constraintString { - if v == rune('(') { + if v == rune('(') && start == 0 { start = k + 1 continue } @@ -264,7 +263,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r constraint = &Constraint{} if haveData { constraint.ID = getParamConstraintType(constraintString[:start-1]) - constraint.Data = strings.Split(constraintString[start:end], ",") + constraint.Data = strings.Split(RemoveEscapeChar(constraintString[start:end]), ",") } else { constraint.ID = getParamConstraintType(pattern[parameterConstraintStart+1 : parameterConstraintEnd]) } diff --git a/path_test.go b/path_test.go index 68804b5bc2..cfa3fbb262 100644 --- a/path_test.go +++ b/path_test.go @@ -142,101 +142,6 @@ func Test_Path_matchParams(t *testing.T) { } } } - testCase("/api/v1/:param", []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", []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", []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", []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", []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", []testparams{ - {url: "/api/v1/entity", params: []string{"entity"}, match: false}, - {url: "/api/v1/8728382", params: []string{"8728382"}, match: false}, - }) - testCase("/api/v1/:param", []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", []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", []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", []testparams{ - {url: "/api/v1/entity", params: []string{"entity"}, match: false}, - {url: "/api/v1/ent", params: []string{"ent"}, match: false}, - }) - testCase("/api/v1/:param", []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", []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", []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", []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", []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", []testparams{ - {url: "/api/v1/entity", params: []string{"entity"}, match: false}, - {url: "/api/v1/8728382", params: []string{"8728382"}, match: false}, - {url: "/api/v1/2022-02-01", params: []string{"2022-12-30"}, match: true}, - }) - testCase("/api/v1/:param", []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/*", []testparams{ {url: "/api/v1/entity", params: []string{"entity", ""}, match: true}, {url: "/api/v1/entity/", params: []string{"entity", ""}, match: true}, @@ -511,6 +416,101 @@ func Test_Path_matchParams(t *testing.T) { {url: "/api", params: nil, match: false}, {url: "/api/:test", params: nil, match: false}, }) + testCase("/api/v1/:param", []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", []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", []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", []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", []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", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: false}, + }) + testCase("/api/v1/:param", []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", []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", []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", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: false}, + {url: "/api/v1/ent", params: []string{"ent"}, match: false}, + }) + testCase("/api/v1/:param", []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", []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", []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", []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", []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", []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", []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}, + }) } func Test_Utils_GetTrimmedParam(t *testing.T) { @@ -683,15 +683,15 @@ func Benchmark_Path_matchParams(t *testing.B) { {url: "/api/v1/5", params: []string{"5"}, match: true}, {url: "/api/v1/15", params: []string{"15"}, match: false}, }) - /*benchCase("/api/v1/:param", []testparams{ + benchCase("/api/v1/:param", []testparams{ {url: "/api/v1/entity", params: []string{"entity"}, match: false}, {url: "/api/v1/8728382", params: []string{"8728382"}, match: false}, - {url: "/api/v1/2022-02-01", params: []string{"2022-12-30"}, match: true}, + {url: "/api/v1/2005-11-01", params: []string{"2005-11-01"}, match: true}, }) - benchCase("/api/v1/:param", []testparams{ + benchCase("/api/v1/:param", []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}, - })*/ + }) } From 10d6ea0deefbf04fd07bda269d8353941c7075c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Sun, 7 Aug 2022 18:13:20 +0300 Subject: [PATCH 06/15] clean up constraint parser, multiple constraint support. --- path.go | 89 ++++++++++++++++++++++++++++------------------------ path_test.go | 27 ++++++++++++++-- 2 files changed, 73 insertions(+), 43 deletions(-) diff --git a/path.go b/path.go index 764339b593..012e4aa999 100644 --- a/path.go +++ b/path.go @@ -38,24 +38,27 @@ 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 - Constraint *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 + 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 - paramConstraintStart byte = '<' // start of type constraint for a parameter - paramConstraintEnd byte = '>' // end of type constraint for a parameter - + 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 @@ -98,6 +101,14 @@ var ( 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, @@ -239,35 +250,31 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r paramName := RemoveEscapeChar(GetTrimmedParam(processedPart)) // Check has constraint - var constraint *Constraint + var constraints []*Constraint if hasConstraint := (parameterConstraintStart != -1 && parameterConstraintEnd != -1); hasConstraint { - constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd+1] - start := 0 - end := 0 - haveData := false - for k, v := range constraintString { - if v == rune('(') && start == 0 { - start = k + 1 - continue - } - - if v == rune(')') && start != 0 && constraintString[k+1] == '>' { - end = k - haveData = true - break + 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 { + constraints = append(constraints, &Constraint{ + ID: getParamConstraintType(c[:start]), + Data: strings.Split(RemoveEscapeChar(c[start+1:end]), string(parameterConstraintDataSeparatorChars)), + }) + } else { + constraints = append(constraints, &Constraint{ + ID: getParamConstraintType(c), + Data: []string{}, + }) } } - // Assign constraint - constraint = &Constraint{} - if haveData { - constraint.ID = getParamConstraintType(constraintString[:start-1]) - constraint.Data = strings.Split(RemoveEscapeChar(constraintString[start:end]), ",") - } else { - constraint.ID = getParamConstraintType(pattern[parameterConstraintStart+1 : parameterConstraintEnd]) - } - paramName = RemoveEscapeChar(GetTrimmedParam(pattern[0:parameterConstraintStart])) } @@ -287,8 +294,8 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r IsGreedy: isWildCard || isPlusParam, } - if constraint != nil { - segment.Constraint = constraint + if len(constraints) > 0 { + segment.Constraints = constraints } return processedPart, segment @@ -360,8 +367,8 @@ func (routeParser *routeParser) getMatch(detectionPath, path string, params *[ma params[paramsIterator] = path[:i] // check constraint - if segment.Constraint != nil { - if matched := segment.Constraint.CheckConstraint(params[paramsIterator]); !matched { + for _, c := range segment.Constraints { + if matched := c.CheckConstraint(params[paramsIterator]); !matched { return false } } diff --git a/path_test.go b/path_test.go index cfa3fbb262..554dfda11d 100644 --- a/path_test.go +++ b/path_test.go @@ -505,12 +505,35 @@ func Test_Path_matchParams(t *testing.T) { {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", []testparams{ + testCase("/api/v1/:param", []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", []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", []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", []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", []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) { @@ -688,7 +711,7 @@ func Benchmark_Path_matchParams(t *testing.B) { {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", []testparams{ + benchCase("/api/v1/:param", []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}, From c0e178837cf67efe6e4ace2e954a2dd1fd085aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Sun, 7 Aug 2022 18:23:52 +0300 Subject: [PATCH 07/15] update --- path.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/path.go b/path.go index 012e4aa999..8d4075a79a 100644 --- a/path.go +++ b/path.go @@ -8,6 +8,7 @@ package fiber import ( "errors" + "fmt" "regexp" "strconv" "strings" @@ -592,6 +593,7 @@ func (c *Constraint) CheckConstraint(param string) bool { err = errors.New("") } case datetimeConstraint: + fmt.Print(time.Parse(c.Data[0], param)) _, err = time.Parse(c.Data[0], param) case regexConstraint: match, _ := regexp.MatchString(c.Data[0], param) From 3ff89d8766a7e3725e472304e2ad51082d386005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Sun, 7 Aug 2022 18:44:23 +0300 Subject: [PATCH 08/15] regex customization. --- app.go | 78 ++++++++++++++++++++++++++++----------------- path.go | 16 ++++++---- router.go | 13 ++++++-- utils/regex.go | 4 +++ utils/regex_test.go | 14 ++++++++ 5 files changed, 86 insertions(+), 39 deletions(-) create mode 100644 utils/regex.go create mode 100644 utils/regex_test.go diff --git a/app.go b/app.go index 85029c670e..a1bf692af7 100644 --- a/app.go +++ b/app.go @@ -20,6 +20,7 @@ import ( "os" "path/filepath" "reflect" + "regexp" "runtime" "sort" "strconv" @@ -72,16 +73,17 @@ type Storage interface { // ErrorHandler defines a function that will process all errors // returned from any handlers in the stack -// cfg := fiber.Config{} -// cfg.ErrorHandler = func(c *Ctx, err error) error { -// code := StatusInternalServerError -// if e, ok := err.(*Error); ok { -// code = e.Code -// } -// c.Set(HeaderContentType, MIMETextPlainCharsetUTF8) -// return c.Status(code).SendString(err.Error()) -// } -// app := fiber.New(cfg) +// +// cfg := fiber.Config{} +// cfg.ErrorHandler = func(c *Ctx, err error) error { +// code := StatusInternalServerError +// if e, ok := err.(*Error); ok { +// code = e.Code +// } +// c.Set(HeaderContentType, MIMETextPlainCharsetUTF8) +// return c.Status(code).SendString(err.Error()) +// } +// app := fiber.New(cfg) type ErrorHandler = func(*Ctx, error) error // Error represents an error that occurred while handling a request. @@ -331,6 +333,13 @@ type Config struct { // Default: json.Unmarshal JSONDecoder utils.JSONUnmarshal `json:"-"` + // When set by an external client of Fiber it will use the provided implementation of a + // RegexMatcher + // + // Allowing for flexibility in using another regex library for matching + // Default: regexp.MatchString + RegexMatcher utils.RegexMatch `json:"-"` + // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only) // WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chose. // @@ -440,12 +449,15 @@ var DefaultErrorHandler = func(c *Ctx, err error) error { } // New creates a new Fiber named instance. -// app := fiber.New() +// +// app := fiber.New() +// // You can pass optional configuration options by passing a Config struct: -// app := fiber.New(fiber.Config{ -// Prefork: true, -// ServerHeader: "Fiber", -// }) +// +// app := fiber.New(fiber.Config{ +// Prefork: true, +// ServerHeader: "Fiber", +// }) func New(config ...Config) *App { // Create a new app app := &App{ @@ -511,6 +523,9 @@ func New(config ...Config) *App { if app.config.JSONDecoder == nil { app.config.JSONDecoder = json.Unmarshal } + if app.config.RegexMatcher == nil { + app.config.RegexMatcher = regexp.MatchString + } if app.config.Network == "" { app.config.Network = NetworkTCP4 } @@ -608,15 +623,15 @@ func (app *App) GetRoute(name string) Route { // Use registers a middleware route that will match requests // with the provided prefix (which is optional and defaults to "/"). // -// 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() -// }) +// 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() +// }) // // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... func (app *App) Use(args ...interface{}) Router { @@ -709,8 +724,9 @@ func (app *App) All(path string, handlers ...Handler) Router { } // 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) +// +// api := app.Group("/api") +// api.Get("/users", handler) func (app *App) Group(prefix string, handlers ...Handler) Router { if len(handlers) > 0 { app.register(methodUse, prefix, handlers...) @@ -778,8 +794,8 @@ func (app *App) Listener(ln net.Listener) error { // Listen serves HTTP requests from the given addr. // -// app.Listen(":8080") -// app.Listen("127.0.0.1:8080") +// app.Listen(":8080") +// app.Listen("127.0.0.1:8080") func (app *App) Listen(addr string) error { // Start prefork if app.config.Prefork { @@ -806,7 +822,8 @@ func (app *App) Listen(addr string) error { // ListenTLS serves HTTPS requests from the given addr. // certFile and keyFile are the paths to TLS certificate and key file: -// app.ListenTLS(":8080", "./cert.pem", "./cert.key") +// +// app.ListenTLS(":8080", "./cert.pem", "./cert.key") func (app *App) ListenTLS(addr, certFile, keyFile string) error { // Check for valid cert/key path if len(certFile) == 0 || len(keyFile) == 0 { @@ -847,7 +864,8 @@ func (app *App) ListenTLS(addr, certFile, keyFile string) error { // ListenMutualTLS serves HTTPS requests from the given addr. // certFile, keyFile and clientCertFile are the paths to TLS certificate and key file: -// app.ListenMutualTLS(":8080", "./cert.pem", "./cert.key", "./client.pem") +// +// app.ListenMutualTLS(":8080", "./cert.pem", "./cert.key", "./client.pem") func (app *App) ListenMutualTLS(addr, certFile, keyFile, clientCertFile string) error { // Check for valid cert/key path if len(certFile) == 0 || len(keyFile) == 0 { diff --git a/path.go b/path.go index 8d4075a79a..b0b510c9bd 100644 --- a/path.go +++ b/path.go @@ -8,7 +8,6 @@ package fiber import ( "errors" - "fmt" "regexp" "strconv" "strings" @@ -344,7 +343,13 @@ func findNextNonEscapedCharsetPosition(search string, charset []byte) int { } // 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 { +func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool, matcher ...utils.RegexMatch) bool { + // Override matcher + var regexMatch = regexp.MatchString + if len(matcher) > 1 { + regexMatch = matcher[0] + } + var i, paramsIterator, partLen int for _, segment := range routeParser.segs { partLen = len(detectionPath) @@ -369,7 +374,7 @@ func (routeParser *routeParser) getMatch(detectionPath, path string, params *[ma // check constraint for _, c := range segment.Constraints { - if matched := c.CheckConstraint(params[paramsIterator]); !matched { + if matched := c.CheckConstraint(params[paramsIterator], regexMatch); !matched { return false } } @@ -509,7 +514,7 @@ func getParamConstraintType(constraintPart string) TypeConstraint { } -func (c *Constraint) CheckConstraint(param string) bool { +func (c *Constraint) CheckConstraint(param string, matcher utils.RegexMatch) bool { var err error var num int @@ -593,10 +598,9 @@ func (c *Constraint) CheckConstraint(param string) bool { err = errors.New("") } case datetimeConstraint: - fmt.Print(time.Parse(c.Data[0], param)) _, err = time.Parse(c.Data[0], param) case regexConstraint: - match, _ := regexp.MatchString(c.Data[0], param) + match, _ := matcher(c.Data[0], param) if !match { err = errors.New("") } diff --git a/router.go b/router.go index 69cc3c00da..4f6cbf8aa9 100644 --- a/router.go +++ b/router.go @@ -6,6 +6,7 @@ package fiber import ( "fmt" + "regexp" "sort" "strconv" "strings" @@ -61,7 +62,13 @@ type Route struct { Handlers []Handler `json:"-"` // Ctx handlers } -func (r *Route) match(detectionPath, path string, params *[maxParams]string) (match bool) { +func (r *Route) match(detectionPath, path string, params *[maxParams]string, matcher ...utils.RegexMatch) (match bool) { + // Override matcher + var regexMatch = regexp.MatchString + if len(matcher) > 1 { + regexMatch = matcher[0] + } + // root detectionPath check if r.root && detectionPath == "/" { return true @@ -77,7 +84,7 @@ func (r *Route) match(detectionPath, path string, params *[maxParams]string) (ma // Does this route have parameters if len(r.Params) > 0 { // Match params - if match := r.routeParser.getMatch(detectionPath, path, params, r.use); match { + if match := r.routeParser.getMatch(detectionPath, path, params, r.use, regexMatch); match { // Get params from the path detectionPath return match } @@ -113,7 +120,7 @@ func (app *App) next(c *Ctx) (match bool, err error) { route := tree[c.indexRoute] // Check if it matches the request path - match = route.match(c.detectionPath, c.path, &c.values) + match = route.match(c.detectionPath, c.path, &c.values, c.app.config.RegexMatcher) // No match, next route if !match { diff --git a/utils/regex.go b/utils/regex.go new file mode 100644 index 0000000000..d8bab92d82 --- /dev/null +++ b/utils/regex.go @@ -0,0 +1,4 @@ +package utils + +// RegexMatch returns the matching s by regex pattern. +type RegexMatch func(pattern string, s string) (matched bool, err error) diff --git a/utils/regex_test.go b/utils/regex_test.go new file mode 100644 index 0000000000..d3786f5d29 --- /dev/null +++ b/utils/regex_test.go @@ -0,0 +1,14 @@ +package utils + +import ( + "regexp" + "testing" +) + +func Test_Golang_Regex_Match(t *testing.T) { + var matcher RegexMatch = regexp.MatchString + + matched, err := matcher(`^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$`, `(555) 555-5555`) + AssertEqual(t, nil, err) + AssertEqual(t, true, matched) +} From d688c5f297fc572866a3c578d61c66c76db2f48a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Tue, 9 Aug 2022 00:13:48 +0300 Subject: [PATCH 09/15] constants, remove variadic methods. --- helpers.go | 21 +++++++++++++++- path.go | 67 ++++++++++++++++++++++---------------------------- path_test.go | 5 ++-- router.go | 11 ++------- router_test.go | 13 +++++----- 5 files changed, 62 insertions(+), 55 deletions(-) diff --git a/helpers.go b/helpers.go index e49fbfd352..511e6dbf4c 100644 --- a/helpers.go +++ b/helpers.go @@ -15,6 +15,7 @@ import ( "os" "path/filepath" "reflect" + "regexp" "strings" "time" "unsafe" @@ -134,7 +135,7 @@ func methodExist(ctx *Ctx) (exist bool) { continue } // Check if it matches the request path - match := route.match(ctx.detectionPath, ctx.path, &ctx.values) + match := route.match(ctx.detectionPath, ctx.path, &ctx.values, regexp.MatchString) // No match, next route if match { // We matched @@ -695,3 +696,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" +) diff --git a/path.go b/path.go index 9e449efbac..7141ddd813 100644 --- a/path.go +++ b/path.go @@ -8,7 +8,6 @@ package fiber import ( "errors" - "regexp" "strconv" "strings" "time" @@ -77,10 +76,10 @@ const ( alphaConstraint datetimeConstraint guidConstraint - minLengthConstraint - maxLengthConstraint - exactLengthConstraint - BetweenLengthConstraint + minLenConstraint + maxLenConstraint + exactLenConstraint + betweenLenConstraint minConstraint maxConstraint rangeConstraint @@ -345,13 +344,7 @@ func findNextNonEscapedCharsetPosition(search string, charset []byte) int { } // 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, matcher ...utils.RegexMatch) bool { - // Override matcher - var regexMatch = regexp.MatchString - if len(matcher) > 1 { - regexMatch = matcher[0] - } - +func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool, matcher utils.RegexMatch) bool { var i, paramsIterator, partLen int for _, segment := range routeParser.segs { partLen = len(detectionPath) @@ -376,7 +369,7 @@ func (routeParser *routeParser) getMatch(detectionPath, path string, params *[ma // check constraint for _, c := range segment.Constraints { - if matched := c.CheckConstraint(params[paramsIterator], regexMatch); !matched { + if matched := c.CheckConstraint(params[paramsIterator], matcher); !matched { return false } } @@ -482,33 +475,33 @@ func RemoveEscapeChar(word string) string { func getParamConstraintType(constraintPart string) TypeConstraint { switch constraintPart { - case "int": + case ConstraintInt: return intConstraint - case "bool": + case ConstraintBool: return boolConstraint - case "float": + case ConstraintFloat: return floatConstraint - case "alpha": + case ConstraintAlpha: return alphaConstraint - case "guid": + case ConstraintGuid: return guidConstraint - case "minLen": - return minLengthConstraint - case "maxLen": - return maxLengthConstraint - case "exactLen": - return exactLengthConstraint - case "betweenLen": - return BetweenLengthConstraint - case "min": + case ConstraintMinLen: + return minLenConstraint + case ConstraintMaxLen: + return maxLenConstraint + case ConstraintExactLen: + return exactLenConstraint + case ConstraintBetweenLen: + return betweenLenConstraint + case ConstraintMin: return minConstraint - case "max": + case ConstraintMax: return maxConstraint - case "range": + case ConstraintRange: return rangeConstraint - case "datetime": + case ConstraintDatetime: return datetimeConstraint - case "regex": + case ConstraintRegex: return regexConstraint default: return noConstraint @@ -521,8 +514,8 @@ func (c *Constraint) CheckConstraint(param string, matcher utils.RegexMatch) boo var num int // check data exists - needOneData := []TypeConstraint{minLengthConstraint, maxLengthConstraint, exactLengthConstraint, minConstraint, maxConstraint, datetimeConstraint, regexConstraint} - needTwoData := []TypeConstraint{BetweenLengthConstraint, rangeConstraint} + 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 { @@ -551,25 +544,25 @@ func (c *Constraint) CheckConstraint(param string, matcher utils.RegexMatch) boo } case guidConstraint: _, err = uuid.Parse(param) - case minLengthConstraint: + case minLenConstraint: data, _ := strconv.Atoi(c.Data[0]) if len(param) < data { err = errors.New("") } - case maxLengthConstraint: + case maxLenConstraint: data, _ := strconv.Atoi(c.Data[0]) if len(param) > data { err = errors.New("") } - case exactLengthConstraint: + case exactLenConstraint: data, _ := strconv.Atoi(c.Data[0]) if len(param) != data { err = errors.New("") } - case BetweenLengthConstraint: + case betweenLenConstraint: data, _ := strconv.Atoi(c.Data[0]) data2, _ := strconv.Atoi(c.Data[1]) length := len(param) diff --git a/path_test.go b/path_test.go index f8c647ca53..addcdf842a 100644 --- a/path_test.go +++ b/path_test.go @@ -6,6 +6,7 @@ package fiber import ( "fmt" + "regexp" "testing" "github.com/gofiber/fiber/v2/utils" @@ -146,7 +147,7 @@ func Test_Path_matchParams(t *testing.T) { testCase := func(r string, cases []testparams) { parser := parseRoute(r) for _, c := range cases { - match := parser.getMatch(c.url, c.url, &ctxParams, c.partialCheck) + match := parser.getMatch(c.url, c.url, &ctxParams, c.partialCheck, regexp.MatchString) 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)) @@ -594,7 +595,7 @@ func Benchmark_Path_matchParams(t *testing.B) { } t.Run(r+" | "+state+" | "+c.url, func(b *testing.B) { for i := 0; i <= b.N; i++ { - if match := parser.getMatch(c.url, c.url, &ctxParams, c.partialCheck); match { + if match := parser.getMatch(c.url, c.url, &ctxParams, c.partialCheck, regexp.MatchString); match { // Get params from the original path matchRes = true } diff --git a/router.go b/router.go index 4f6cbf8aa9..384a2de26d 100644 --- a/router.go +++ b/router.go @@ -6,7 +6,6 @@ package fiber import ( "fmt" - "regexp" "sort" "strconv" "strings" @@ -62,13 +61,7 @@ type Route struct { Handlers []Handler `json:"-"` // Ctx handlers } -func (r *Route) match(detectionPath, path string, params *[maxParams]string, matcher ...utils.RegexMatch) (match bool) { - // Override matcher - var regexMatch = regexp.MatchString - if len(matcher) > 1 { - regexMatch = matcher[0] - } - +func (r *Route) match(detectionPath, path string, params *[maxParams]string, matcher utils.RegexMatch) (match bool) { // root detectionPath check if r.root && detectionPath == "/" { return true @@ -84,7 +77,7 @@ func (r *Route) match(detectionPath, path string, params *[maxParams]string, mat // Does this route have parameters if len(r.Params) > 0 { // Match params - if match := r.routeParser.getMatch(detectionPath, path, params, r.use, regexMatch); match { + if match := r.routeParser.getMatch(detectionPath, path, params, r.use, matcher); match { // Get params from the path detectionPath return match } diff --git a/router_test.go b/router_test.go index d8a8c323f4..4077f305da 100644 --- a/router_test.go +++ b/router_test.go @@ -12,6 +12,7 @@ import ( "fmt" "io/ioutil" "net/http/httptest" + "regexp" "strings" "testing" @@ -87,17 +88,17 @@ func Test_Route_Match_Star(t *testing.T) { routeParser: routeParser{}, } params := [maxParams]string{} - match := route.match("", "", ¶ms) + match := route.match("", "", ¶ms, regexp.MatchString) utils.AssertEqual(t, true, match) utils.AssertEqual(t, [maxParams]string{}, params) // with parameter - match = route.match("/favicon.ico", "/favicon.ico", ¶ms) + match = route.match("/favicon.ico", "/favicon.ico", ¶ms, regexp.MatchString) utils.AssertEqual(t, true, match) utils.AssertEqual(t, [maxParams]string{"favicon.ico"}, params) // without parameter again - match = route.match("", "", ¶ms) + match = route.match("", "", ¶ms, regexp.MatchString) utils.AssertEqual(t, true, match) utils.AssertEqual(t, [maxParams]string{}, params) } @@ -639,7 +640,7 @@ func Benchmark_Route_Match(b *testing.B) { }) b.ResetTimer() for n := 0; n < b.N; n++ { - match = route.match("/user/keys/1337", "/user/keys/1337", ¶ms) + match = route.match("/user/keys/1337", "/user/keys/1337", ¶ms, regexp.MatchString) } utils.AssertEqual(b, true, match) @@ -669,7 +670,7 @@ func Benchmark_Route_Match_Star(b *testing.B) { b.ResetTimer() for n := 0; n < b.N; n++ { - match = route.match("/user/keys/bla", "/user/keys/bla", ¶ms) + match = route.match("/user/keys/bla", "/user/keys/bla", ¶ms, regexp.MatchString) } utils.AssertEqual(b, true, match) @@ -700,7 +701,7 @@ func Benchmark_Route_Match_Root(b *testing.B) { b.ResetTimer() for n := 0; n < b.N; n++ { - match = route.match("/", "/", ¶ms) + match = route.match("/", "/", ¶ms, regexp.MatchString) } utils.AssertEqual(b, true, match) From da88a655573f410793bb8597e347b25040eb3888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Tue, 9 Aug 2022 00:21:37 +0300 Subject: [PATCH 10/15] add some benchs, refactor constraint check funtion. --- path.go | 20 ++++++++++---------- path_test.go | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/path.go b/path.go index 7141ddd813..8a100e4aed 100644 --- a/path.go +++ b/path.go @@ -7,7 +7,6 @@ package fiber import ( - "errors" "strconv" "strings" "time" @@ -529,6 +528,7 @@ func (c *Constraint) CheckConstraint(param string, matcher utils.RegexMatch) boo } } + // check constraints switch c.ID { case intConstraint: _, err = strconv.Atoi(param) @@ -539,7 +539,7 @@ func (c *Constraint) CheckConstraint(param string, matcher utils.RegexMatch) boo case alphaConstraint: for _, r := range param { if !unicode.IsLetter(r) { - err = errors.New("") + return false } } case guidConstraint: @@ -548,19 +548,19 @@ func (c *Constraint) CheckConstraint(param string, matcher utils.RegexMatch) boo data, _ := strconv.Atoi(c.Data[0]) if len(param) < data { - err = errors.New("") + return false } case maxLenConstraint: data, _ := strconv.Atoi(c.Data[0]) if len(param) > data { - err = errors.New("") + return false } case exactLenConstraint: data, _ := strconv.Atoi(c.Data[0]) if len(param) != data { - err = errors.New("") + return false } case betweenLenConstraint: data, _ := strconv.Atoi(c.Data[0]) @@ -568,21 +568,21 @@ func (c *Constraint) CheckConstraint(param string, matcher utils.RegexMatch) boo length := len(param) if !(length >= data && length <= data2) { - err = errors.New("") + return false } case minConstraint: data, _ := strconv.Atoi(c.Data[0]) num, err = strconv.Atoi(param) if num < data { - err = errors.New("") + return false } case maxConstraint: data, _ := strconv.Atoi(c.Data[0]) num, err = strconv.Atoi(param) if num > data { - err = errors.New("") + return false } case rangeConstraint: data, _ := strconv.Atoi(c.Data[0]) @@ -590,14 +590,14 @@ func (c *Constraint) CheckConstraint(param string, matcher utils.RegexMatch) boo num, err = strconv.Atoi(param) if !(num >= data && num <= data2) { - err = errors.New("") + return false } case datetimeConstraint: _, err = time.Parse(c.Data[0], param) case regexConstraint: match, _ := matcher(c.Data[0], param) if !match { - err = errors.New("") + return false } } diff --git a/path_test.go b/path_test.go index addcdf842a..39dc815fcb 100644 --- a/path_test.go +++ b/path_test.go @@ -733,4 +733,27 @@ func Benchmark_Path_matchParams(t *testing.B) { {url: "/api/v1/peach", params: []string{"peach"}, match: true}, {url: "/api/v1/p34ch", params: []string{"p34ch"}, match: false}, }) + benchCase("/api/v1/:param", []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", []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", []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", []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}, + }) } From 491c72b183da593795a5539c0fd11d80f56ded77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Tue, 9 Aug 2022 00:27:05 +0300 Subject: [PATCH 11/15] more readable conditions --- path.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/path.go b/path.go index 8a100e4aed..d69cf33a67 100644 --- a/path.go +++ b/path.go @@ -566,8 +566,7 @@ func (c *Constraint) CheckConstraint(param string, matcher utils.RegexMatch) boo data, _ := strconv.Atoi(c.Data[0]) data2, _ := strconv.Atoi(c.Data[1]) length := len(param) - - if !(length >= data && length <= data2) { + if length < data || length > data2 { return false } case minConstraint: @@ -589,7 +588,7 @@ func (c *Constraint) CheckConstraint(param string, matcher utils.RegexMatch) boo data2, _ := strconv.Atoi(c.Data[1]) num, err = strconv.Atoi(param) - if !(num >= data && num <= data2) { + if num < data || num > data2 { return false } case datetimeConstraint: From a596c66ddc86ad011fc3fed99569ff1278fa29eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Thu, 11 Aug 2022 19:29:54 +0300 Subject: [PATCH 12/15] fix tests --- path.go | 38 +++++++++++++++++++++++++++++++++++--- path_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/path.go b/path.go index d69cf33a67..3c3a028c02 100644 --- a/path.go +++ b/path.go @@ -225,10 +225,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 @@ -242,7 +248,6 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r if parameterEndPosition > 0 { parameterConstraintStart = findNextNonEscapedCharsetPosition(pattern[0:parameterEndPosition], parameterConstraintStartChars) parameterConstraintEnd = findNextNonEscapedCharsetPosition(pattern[0:parameterEndPosition+1], parameterConstraintEndChars) - } // cut params part @@ -323,6 +328,33 @@ func findNextCharsetPosition(search string, charset []byte) int { return nextPosition } +// findNextCharsetPositionConstraint search the next char position from the charset +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) diff --git a/path_test.go b/path_test.go index 39dc815fcb..d14d3c2f9d 100644 --- a/path_test.go +++ b/path_test.go @@ -757,3 +757,27 @@ func Benchmark_Path_matchParams(t *testing.B) { {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, regexp.MatchString) + 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", []testparams{ + {url: "/api/v1/2005-11-01", params: []string{"2005-11-01"}, match: true}, + }) +} From 2d41ff79daa762b389a9b059f46418f3e680adcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Thu, 11 Aug 2022 20:27:13 +0300 Subject: [PATCH 13/15] precompile regex --- app.go | 11 ----------- helpers.go | 3 +-- path.go | 24 +++++++++++++++++------- path_test.go | 7 +++---- router.go | 6 +++--- router_test.go | 13 ++++++------- utils/regex.go | 4 ---- utils/regex_test.go | 14 -------------- 8 files changed, 30 insertions(+), 52 deletions(-) delete mode 100644 utils/regex.go delete mode 100644 utils/regex_test.go diff --git a/app.go b/app.go index 3ce0ed8114..92671efca1 100644 --- a/app.go +++ b/app.go @@ -16,7 +16,6 @@ import ( "net/http" "net/http/httputil" "reflect" - "regexp" "strconv" "strings" "sync" @@ -324,13 +323,6 @@ type Config struct { // Default: json.Unmarshal JSONDecoder utils.JSONUnmarshal `json:"-"` - // When set by an external client of Fiber it will use the provided implementation of a - // RegexMatcher - // - // Allowing for flexibility in using another regex library for matching - // Default: regexp.MatchString - RegexMatcher utils.RegexMatch `json:"-"` - // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only) // WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chose. // @@ -520,9 +512,6 @@ func New(config ...Config) *App { if app.config.JSONDecoder == nil { app.config.JSONDecoder = json.Unmarshal } - if app.config.RegexMatcher == nil { - app.config.RegexMatcher = regexp.MatchString - } if app.config.Network == "" { app.config.Network = NetworkTCP4 } diff --git a/helpers.go b/helpers.go index 511e6dbf4c..f5e1ee55d8 100644 --- a/helpers.go +++ b/helpers.go @@ -15,7 +15,6 @@ import ( "os" "path/filepath" "reflect" - "regexp" "strings" "time" "unsafe" @@ -135,7 +134,7 @@ func methodExist(ctx *Ctx) (exist bool) { continue } // Check if it matches the request path - match := route.match(ctx.detectionPath, ctx.path, &ctx.values, regexp.MatchString) + match := route.match(ctx.detectionPath, ctx.path, &ctx.values) // No match, next route if match { // We matched diff --git a/path.go b/path.go index 3c3a028c02..729c26ed77 100644 --- a/path.go +++ b/path.go @@ -7,6 +7,7 @@ package fiber import ( + "regexp" "strconv" "strings" "time" @@ -63,8 +64,9 @@ const ( type TypeConstraint int16 type Constraint struct { - ID TypeConstraint - Data []string + ID TypeConstraint + RegexCompiler *regexp.Regexp + Data []string } const ( @@ -375,7 +377,7 @@ func findNextNonEscapedCharsetPosition(search string, charset []byte) int { } // 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, matcher utils.RegexMatch) bool { +func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool { var i, paramsIterator, partLen int for _, segment := range routeParser.segs { partLen = len(detectionPath) @@ -400,7 +402,7 @@ func (routeParser *routeParser) getMatch(detectionPath, path string, params *[ma // check constraint for _, c := range segment.Constraints { - if matched := c.CheckConstraint(params[paramsIterator], matcher); !matched { + if matched := c.CheckConstraint(params[paramsIterator]); !matched { return false } } @@ -540,7 +542,7 @@ func getParamConstraintType(constraintPart string) TypeConstraint { } -func (c *Constraint) CheckConstraint(param string, matcher utils.RegexMatch) bool { +func (c *Constraint) CheckConstraint(param string) bool { var err error var num int @@ -626,8 +628,16 @@ func (c *Constraint) CheckConstraint(param string, matcher utils.RegexMatch) boo case datetimeConstraint: _, err = time.Parse(c.Data[0], param) case regexConstraint: - match, _ := matcher(c.Data[0], param) - if !match { + if c.RegexCompiler == nil { + compiler, err := regexp.Compile(c.Data[0]) + if err != nil { + return false + } + + c.RegexCompiler = compiler + } + + if match := c.RegexCompiler.MatchString(param); !match { return false } } diff --git a/path_test.go b/path_test.go index d14d3c2f9d..7ad514098f 100644 --- a/path_test.go +++ b/path_test.go @@ -6,7 +6,6 @@ package fiber import ( "fmt" - "regexp" "testing" "github.com/gofiber/fiber/v2/utils" @@ -147,7 +146,7 @@ func Test_Path_matchParams(t *testing.T) { testCase := func(r string, cases []testparams) { parser := parseRoute(r) for _, c := range cases { - match := parser.getMatch(c.url, c.url, &ctxParams, c.partialCheck, regexp.MatchString) + 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)) @@ -595,7 +594,7 @@ func Benchmark_Path_matchParams(t *testing.B) { } t.Run(r+" | "+state+" | "+c.url, func(b *testing.B) { for i := 0; i <= b.N; i++ { - if match := parser.getMatch(c.url, c.url, &ctxParams, c.partialCheck, regexp.MatchString); match { + if match := parser.getMatch(c.url, c.url, &ctxParams, c.partialCheck); match { // Get params from the original path matchRes = true } @@ -770,7 +769,7 @@ func Test_Path_matchParams0(t *testing.T) { testCase := func(r string, cases []testparams) { parser := parseRoute(r) for _, c := range cases { - match := parser.getMatch(c.url, c.url, &ctxParams, c.partialCheck, regexp.MatchString) + 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)) diff --git a/router.go b/router.go index 384a2de26d..69cc3c00da 100644 --- a/router.go +++ b/router.go @@ -61,7 +61,7 @@ type Route struct { Handlers []Handler `json:"-"` // Ctx handlers } -func (r *Route) match(detectionPath, path string, params *[maxParams]string, matcher utils.RegexMatch) (match bool) { +func (r *Route) match(detectionPath, path string, params *[maxParams]string) (match bool) { // root detectionPath check if r.root && detectionPath == "/" { return true @@ -77,7 +77,7 @@ func (r *Route) match(detectionPath, path string, params *[maxParams]string, mat // Does this route have parameters if len(r.Params) > 0 { // Match params - if match := r.routeParser.getMatch(detectionPath, path, params, r.use, matcher); match { + if match := r.routeParser.getMatch(detectionPath, path, params, r.use); match { // Get params from the path detectionPath return match } @@ -113,7 +113,7 @@ func (app *App) next(c *Ctx) (match bool, err error) { route := tree[c.indexRoute] // Check if it matches the request path - match = route.match(c.detectionPath, c.path, &c.values, c.app.config.RegexMatcher) + match = route.match(c.detectionPath, c.path, &c.values) // No match, next route if !match { diff --git a/router_test.go b/router_test.go index 4077f305da..d8a8c323f4 100644 --- a/router_test.go +++ b/router_test.go @@ -12,7 +12,6 @@ import ( "fmt" "io/ioutil" "net/http/httptest" - "regexp" "strings" "testing" @@ -88,17 +87,17 @@ func Test_Route_Match_Star(t *testing.T) { routeParser: routeParser{}, } params := [maxParams]string{} - match := route.match("", "", ¶ms, regexp.MatchString) + match := route.match("", "", ¶ms) utils.AssertEqual(t, true, match) utils.AssertEqual(t, [maxParams]string{}, params) // with parameter - match = route.match("/favicon.ico", "/favicon.ico", ¶ms, regexp.MatchString) + match = route.match("/favicon.ico", "/favicon.ico", ¶ms) utils.AssertEqual(t, true, match) utils.AssertEqual(t, [maxParams]string{"favicon.ico"}, params) // without parameter again - match = route.match("", "", ¶ms, regexp.MatchString) + match = route.match("", "", ¶ms) utils.AssertEqual(t, true, match) utils.AssertEqual(t, [maxParams]string{}, params) } @@ -640,7 +639,7 @@ func Benchmark_Route_Match(b *testing.B) { }) b.ResetTimer() for n := 0; n < b.N; n++ { - match = route.match("/user/keys/1337", "/user/keys/1337", ¶ms, regexp.MatchString) + match = route.match("/user/keys/1337", "/user/keys/1337", ¶ms) } utils.AssertEqual(b, true, match) @@ -670,7 +669,7 @@ func Benchmark_Route_Match_Star(b *testing.B) { b.ResetTimer() for n := 0; n < b.N; n++ { - match = route.match("/user/keys/bla", "/user/keys/bla", ¶ms, regexp.MatchString) + match = route.match("/user/keys/bla", "/user/keys/bla", ¶ms) } utils.AssertEqual(b, true, match) @@ -701,7 +700,7 @@ func Benchmark_Route_Match_Root(b *testing.B) { b.ResetTimer() for n := 0; n < b.N; n++ { - match = route.match("/", "/", ¶ms, regexp.MatchString) + match = route.match("/", "/", ¶ms) } utils.AssertEqual(b, true, match) diff --git a/utils/regex.go b/utils/regex.go deleted file mode 100644 index d8bab92d82..0000000000 --- a/utils/regex.go +++ /dev/null @@ -1,4 +0,0 @@ -package utils - -// RegexMatch returns the matching s by regex pattern. -type RegexMatch func(pattern string, s string) (matched bool, err error) diff --git a/utils/regex_test.go b/utils/regex_test.go deleted file mode 100644 index d3786f5d29..0000000000 --- a/utils/regex_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package utils - -import ( - "regexp" - "testing" -) - -func Test_Golang_Regex_Match(t *testing.T) { - var matcher RegexMatch = regexp.MatchString - - matched, err := matcher(`^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$`, `(555) 555-5555`) - AssertEqual(t, nil, err) - AssertEqual(t, true, matched) -} From e92eadd8a5a699b2d09505ab37218bc8ee291ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Mon, 15 Aug 2022 17:25:30 +0300 Subject: [PATCH 14/15] precompile regex when parsing the route --- path.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/path.go b/path.go index 729c26ed77..aaabe0852b 100644 --- a/path.go +++ b/path.go @@ -70,7 +70,7 @@ type Constraint struct { } const ( - noConstraint TypeConstraint = iota + noConstraint TypeConstraint = iota + 1 intConstraint boolConstraint floatConstraint @@ -270,10 +270,17 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r // Assign constraint if start != -1 && end != -1 { - constraints = append(constraints, &Constraint{ + 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), @@ -628,15 +635,6 @@ func (c *Constraint) CheckConstraint(param string) bool { case datetimeConstraint: _, err = time.Parse(c.Data[0], param) case regexConstraint: - if c.RegexCompiler == nil { - compiler, err := regexp.Compile(c.Data[0]) - if err != nil { - return false - } - - c.RegexCompiler = compiler - } - if match := c.RegexCompiler.MatchString(param); !match { return false } From 3aacd73be5395663a6357424850d0fb9f7947ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Efe=20=C3=87etin?= Date: Mon, 15 Aug 2022 19:40:36 +0300 Subject: [PATCH 15/15] update comments Co-authored-by: wernerr --- path.go | 1 + 1 file changed, 1 insertion(+) diff --git a/path.go b/path.go index aaabe0852b..16d1b2a37d 100644 --- a/path.go +++ b/path.go @@ -338,6 +338,7 @@ func findNextCharsetPosition(search string, charset []byte) int { } // 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