github.com/gofiber/fiber/v2@v2.47.0/path.go (about)

     1  // ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
     2  // 📄 Github Repository: https://github.com/gofiber/fiber
     3  // 📌 API Documentation: https://docs.gofiber.io
     4  // ⚠️ This path parser was inspired by ucarion/urlpath (MIT License).
     5  // 💖 Maintained and modified for Fiber by @renewerner87
     6  
     7  package fiber
     8  
     9  import (
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  	"unicode"
    15  
    16  	"github.com/gofiber/fiber/v2/utils"
    17  	"github.com/google/uuid"
    18  )
    19  
    20  // routeParser holds the path segments and param names
    21  type routeParser struct {
    22  	segs          []*routeSegment // the parsed segments of the route
    23  	params        []string        // that parameter names the parsed route
    24  	wildCardCount int             // number of wildcard parameters, used internally to give the wildcard parameter its number
    25  	plusCount     int             // number of plus parameters, used internally to give the plus parameter its number
    26  }
    27  
    28  // paramsSeg holds the segment metadata
    29  type routeSegment struct {
    30  	// const information
    31  	Const string // constant part of the route
    32  	// parameter information
    33  	IsParam     bool   // Truth value that indicates whether it is a parameter or a constant part
    34  	ParamName   string // name of the parameter for access to it, for wildcards and plus parameters access iterators starting with 1 are added
    35  	ComparePart string // search part to find the end of the parameter
    36  	PartCount   int    // how often is the search part contained in the non-param segments? -> necessary for greedy search
    37  	IsGreedy    bool   // indicates whether the parameter is greedy or not, is used with wildcard and plus
    38  	IsOptional  bool   // indicates whether the parameter is optional or not
    39  	// common information
    40  	IsLast           bool          // shows if the segment is the last one for the route
    41  	HasOptionalSlash bool          // segment has the possibility of an optional slash
    42  	Constraints      []*Constraint // Constraint type if segment is a parameter, if not it will be set to noConstraint by default
    43  	Length           int           // length of the parameter for segment, when its 0 then the length is undetermined
    44  	// future TODO: add support for optional groups "/abc(/def)?"
    45  }
    46  
    47  // different special routing signs
    48  const (
    49  	wildcardParam                byte = '*'  // indicates a optional greedy parameter
    50  	plusParam                    byte = '+'  // indicates a required greedy parameter
    51  	optionalParam                byte = '?'  // concludes a parameter by name and makes it optional
    52  	paramStarterChar             byte = ':'  // start character for a parameter with name
    53  	slashDelimiter               byte = '/'  // separator for the route, unlike the other delimiters this character at the end can be optional
    54  	escapeChar                   byte = '\\' // escape character
    55  	paramConstraintStart         byte = '<'  // start of type constraint for a parameter
    56  	paramConstraintEnd           byte = '>'  // end of type constraint for a parameter
    57  	paramConstraintSeparator     byte = ';'  // separator of type constraints for a parameter
    58  	paramConstraintDataStart     byte = '('  // start of data of type constraint for a parameter
    59  	paramConstraintDataEnd       byte = ')'  // end of data of type constraint for a parameter
    60  	paramConstraintDataSeparator byte = ','  // separator of datas of type constraint for a parameter
    61  )
    62  
    63  // TypeConstraint parameter constraint types
    64  type TypeConstraint int16
    65  
    66  type Constraint struct {
    67  	ID            TypeConstraint
    68  	RegexCompiler *regexp.Regexp
    69  	Data          []string
    70  }
    71  
    72  const (
    73  	noConstraint TypeConstraint = iota + 1
    74  	intConstraint
    75  	boolConstraint
    76  	floatConstraint
    77  	alphaConstraint
    78  	datetimeConstraint
    79  	guidConstraint
    80  	minLenConstraint
    81  	maxLenConstraint
    82  	lenConstraint
    83  	betweenLenConstraint
    84  	minConstraint
    85  	maxConstraint
    86  	rangeConstraint
    87  	regexConstraint
    88  )
    89  
    90  // list of possible parameter and segment delimiter
    91  var (
    92  	// slash has a special role, unlike the other parameters it must not be interpreted as a parameter
    93  	routeDelimiter = []byte{slashDelimiter, '-', '.'}
    94  	// list of greedy parameters
    95  	greedyParameters = []byte{wildcardParam, plusParam}
    96  	// list of chars for the parameter recognizing
    97  	parameterStartChars = []byte{wildcardParam, plusParam, paramStarterChar}
    98  	// list of chars of delimiters and the starting parameter name char
    99  	parameterDelimiterChars = append([]byte{paramStarterChar, escapeChar}, routeDelimiter...)
   100  	// list of chars to find the end of a parameter
   101  	parameterEndChars = append([]byte{optionalParam}, parameterDelimiterChars...)
   102  	// list of parameter constraint start
   103  	parameterConstraintStartChars = []byte{paramConstraintStart}
   104  	// list of parameter constraint end
   105  	parameterConstraintEndChars = []byte{paramConstraintEnd}
   106  	// list of parameter separator
   107  	parameterConstraintSeparatorChars = []byte{paramConstraintSeparator}
   108  	// list of parameter constraint data start
   109  	parameterConstraintDataStartChars = []byte{paramConstraintDataStart}
   110  	// list of parameter constraint data end
   111  	parameterConstraintDataEndChars = []byte{paramConstraintDataEnd}
   112  	// list of parameter constraint data separator
   113  	parameterConstraintDataSeparatorChars = []byte{paramConstraintDataSeparator}
   114  )
   115  
   116  // RoutePatternMatch checks if a given path matches a Fiber route pattern.
   117  func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
   118  	// See logic in (*Route).match and (*App).register
   119  	var ctxParams [maxParams]string
   120  
   121  	config := Config{}
   122  	if len(cfg) > 0 {
   123  		config = cfg[0]
   124  	}
   125  
   126  	if path == "" {
   127  		path = "/"
   128  	}
   129  
   130  	// Cannot have an empty pattern
   131  	if pattern == "" {
   132  		pattern = "/"
   133  	}
   134  	// Pattern always start with a '/'
   135  	if pattern[0] != '/' {
   136  		pattern = "/" + pattern
   137  	}
   138  
   139  	patternPretty := pattern
   140  
   141  	// Case sensitive routing, all to lowercase
   142  	if !config.CaseSensitive {
   143  		patternPretty = utils.ToLower(patternPretty)
   144  		path = utils.ToLower(path)
   145  	}
   146  	// Strict routing, remove trailing slashes
   147  	if !config.StrictRouting && len(patternPretty) > 1 {
   148  		patternPretty = utils.TrimRight(patternPretty, '/')
   149  	}
   150  
   151  	parser := parseRoute(patternPretty)
   152  
   153  	if patternPretty == "/" && path == "/" {
   154  		return true
   155  		// '*' wildcard matches any path
   156  	} else if patternPretty == "/*" {
   157  		return true
   158  	}
   159  
   160  	// Does this route have parameters
   161  	if len(parser.params) > 0 {
   162  		if match := parser.getMatch(path, path, &ctxParams, false); match {
   163  			return true
   164  		}
   165  	}
   166  	// Check for a simple match
   167  	patternPretty = RemoveEscapeChar(patternPretty)
   168  	if len(patternPretty) == len(path) && patternPretty == path {
   169  		return true
   170  	}
   171  	// No match
   172  	return false
   173  }
   174  
   175  // parseRoute analyzes the route and divides it into segments for constant areas and parameters,
   176  // this information is needed later when assigning the requests to the declared routes
   177  func parseRoute(pattern string) routeParser {
   178  	parser := routeParser{}
   179  
   180  	part := ""
   181  	for len(pattern) > 0 {
   182  		nextParamPosition := findNextParamPosition(pattern)
   183  		// handle the parameter part
   184  		if nextParamPosition == 0 {
   185  			processedPart, seg := parser.analyseParameterPart(pattern)
   186  			parser.params, parser.segs, part = append(parser.params, seg.ParamName), append(parser.segs, seg), processedPart
   187  		} else {
   188  			processedPart, seg := parser.analyseConstantPart(pattern, nextParamPosition)
   189  			parser.segs, part = append(parser.segs, seg), processedPart
   190  		}
   191  
   192  		// reduce the pattern by the processed parts
   193  		if len(part) == len(pattern) {
   194  			break
   195  		}
   196  		pattern = pattern[len(part):]
   197  	}
   198  	// mark last segment
   199  	if len(parser.segs) > 0 {
   200  		parser.segs[len(parser.segs)-1].IsLast = true
   201  	}
   202  	parser.segs = addParameterMetaInfo(parser.segs)
   203  
   204  	return parser
   205  }
   206  
   207  // addParameterMetaInfo add important meta information to the parameter segments
   208  // to simplify the search for the end of the parameter
   209  func addParameterMetaInfo(segs []*routeSegment) []*routeSegment {
   210  	var comparePart string
   211  	segLen := len(segs)
   212  	// loop from end to begin
   213  	for i := segLen - 1; i >= 0; i-- {
   214  		// set the compare part for the parameter
   215  		if segs[i].IsParam {
   216  			// important for finding the end of the parameter
   217  			segs[i].ComparePart = RemoveEscapeChar(comparePart)
   218  		} else {
   219  			comparePart = segs[i].Const
   220  			if len(comparePart) > 1 {
   221  				comparePart = utils.TrimRight(comparePart, slashDelimiter)
   222  			}
   223  		}
   224  	}
   225  
   226  	// loop from begin to end
   227  	for i := 0; i < segLen; i++ {
   228  		// check how often the compare part is in the following const parts
   229  		if segs[i].IsParam {
   230  			// check if parameter segments are directly after each other and if one of them is greedy
   231  			// in case the next parameter or the current parameter is not a wildcard its not greedy, we only want one character
   232  			if segLen > i+1 && !segs[i].IsGreedy && segs[i+1].IsParam && !segs[i+1].IsGreedy {
   233  				segs[i].Length = 1
   234  			}
   235  			if segs[i].ComparePart == "" {
   236  				continue
   237  			}
   238  			for j := i + 1; j <= len(segs)-1; j++ {
   239  				if !segs[j].IsParam {
   240  					// count is important for the greedy match
   241  					segs[i].PartCount += strings.Count(segs[j].Const, segs[i].ComparePart)
   242  				}
   243  			}
   244  			// check if the end of the segment is a optional slash and then if the segement is optional or the last one
   245  		} else if segs[i].Const[len(segs[i].Const)-1] == slashDelimiter && (segs[i].IsLast || (segLen > i+1 && segs[i+1].IsOptional)) {
   246  			segs[i].HasOptionalSlash = true
   247  		}
   248  	}
   249  
   250  	return segs
   251  }
   252  
   253  // findNextParamPosition search for the next possible parameter start position
   254  func findNextParamPosition(pattern string) int {
   255  	nextParamPosition := findNextNonEscapedCharsetPosition(pattern, parameterStartChars)
   256  	if nextParamPosition != -1 && len(pattern) > nextParamPosition && pattern[nextParamPosition] != wildcardParam {
   257  		// search for parameter characters for the found parameter start,
   258  		// if there are more, move the parameter start to the last parameter char
   259  		for found := findNextNonEscapedCharsetPosition(pattern[nextParamPosition+1:], parameterStartChars); found == 0; {
   260  			nextParamPosition++
   261  			if len(pattern) > nextParamPosition {
   262  				break
   263  			}
   264  		}
   265  	}
   266  
   267  	return nextParamPosition
   268  }
   269  
   270  // analyseConstantPart find the end of the constant part and create the route segment
   271  func (*routeParser) analyseConstantPart(pattern string, nextParamPosition int) (string, *routeSegment) {
   272  	// handle the constant part
   273  	processedPart := pattern
   274  	if nextParamPosition != -1 {
   275  		// remove the constant part until the parameter
   276  		processedPart = pattern[:nextParamPosition]
   277  	}
   278  	constPart := RemoveEscapeChar(processedPart)
   279  	return processedPart, &routeSegment{
   280  		Const:  constPart,
   281  		Length: len(constPart),
   282  	}
   283  }
   284  
   285  // analyseParameterPart find the parameter end and create the route segment
   286  func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *routeSegment) {
   287  	isWildCard := pattern[0] == wildcardParam
   288  	isPlusParam := pattern[0] == plusParam
   289  
   290  	var parameterEndPosition int
   291  	if strings.ContainsRune(pattern, rune(paramConstraintStart)) && strings.ContainsRune(pattern, rune(paramConstraintEnd)) {
   292  		parameterEndPosition = findNextCharsetPositionConstraint(pattern[1:], parameterEndChars)
   293  	} else {
   294  		parameterEndPosition = findNextNonEscapedCharsetPosition(pattern[1:], parameterEndChars)
   295  	}
   296  
   297  	parameterConstraintStart := -1
   298  	parameterConstraintEnd := -1
   299  	// handle wildcard end
   300  	switch {
   301  	case isWildCard, isPlusParam:
   302  		parameterEndPosition = 0
   303  	case parameterEndPosition == -1:
   304  		parameterEndPosition = len(pattern) - 1
   305  	case !isInCharset(pattern[parameterEndPosition+1], parameterDelimiterChars):
   306  		parameterEndPosition++
   307  	}
   308  
   309  	// find constraint part if exists in the parameter part and remove it
   310  	if parameterEndPosition > 0 {
   311  		parameterConstraintStart = findNextNonEscapedCharsetPosition(pattern[0:parameterEndPosition], parameterConstraintStartChars)
   312  		parameterConstraintEnd = findLastCharsetPosition(pattern[0:parameterEndPosition+1], parameterConstraintEndChars)
   313  	}
   314  
   315  	// cut params part
   316  	processedPart := pattern[0 : parameterEndPosition+1]
   317  	paramName := RemoveEscapeChar(GetTrimmedParam(processedPart))
   318  
   319  	// Check has constraint
   320  	var constraints []*Constraint
   321  
   322  	if hasConstraint := parameterConstraintStart != -1 && parameterConstraintEnd != -1; hasConstraint {
   323  		constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd]
   324  		userConstraints := splitNonEscaped(constraintString, string(parameterConstraintSeparatorChars))
   325  		constraints = make([]*Constraint, 0, len(userConstraints))
   326  
   327  		for _, c := range userConstraints {
   328  			start := findNextNonEscapedCharsetPosition(c, parameterConstraintDataStartChars)
   329  			end := findLastCharsetPosition(c, parameterConstraintDataEndChars)
   330  
   331  			// Assign constraint
   332  			if start != -1 && end != -1 {
   333  				constraint := &Constraint{
   334  					ID: getParamConstraintType(c[:start]),
   335  				}
   336  
   337  				// remove escapes from data
   338  				if constraint.ID != regexConstraint {
   339  					constraint.Data = splitNonEscaped(c[start+1:end], string(parameterConstraintDataSeparatorChars))
   340  					if len(constraint.Data) == 1 {
   341  						constraint.Data[0] = RemoveEscapeChar(constraint.Data[0])
   342  					} else if len(constraint.Data) == 2 { // This is fine, we simply expect two parts
   343  						constraint.Data[0] = RemoveEscapeChar(constraint.Data[0])
   344  						constraint.Data[1] = RemoveEscapeChar(constraint.Data[1])
   345  					}
   346  				}
   347  
   348  				// Precompile regex if has regex constraint
   349  				if constraint.ID == regexConstraint {
   350  					constraint.Data = []string{c[start+1 : end]}
   351  					constraint.RegexCompiler = regexp.MustCompile(constraint.Data[0])
   352  				}
   353  
   354  				constraints = append(constraints, constraint)
   355  			} else {
   356  				constraints = append(constraints, &Constraint{
   357  					ID:   getParamConstraintType(c),
   358  					Data: []string{},
   359  				})
   360  			}
   361  		}
   362  
   363  		paramName = RemoveEscapeChar(GetTrimmedParam(pattern[0:parameterConstraintStart]))
   364  	}
   365  
   366  	// add access iterator to wildcard and plus
   367  	if isWildCard {
   368  		routeParser.wildCardCount++
   369  		paramName += strconv.Itoa(routeParser.wildCardCount)
   370  	} else if isPlusParam {
   371  		routeParser.plusCount++
   372  		paramName += strconv.Itoa(routeParser.plusCount)
   373  	}
   374  
   375  	segment := &routeSegment{
   376  		ParamName:  paramName,
   377  		IsParam:    true,
   378  		IsOptional: isWildCard || pattern[parameterEndPosition] == optionalParam,
   379  		IsGreedy:   isWildCard || isPlusParam,
   380  	}
   381  
   382  	if len(constraints) > 0 {
   383  		segment.Constraints = constraints
   384  	}
   385  
   386  	return processedPart, segment
   387  }
   388  
   389  // isInCharset check is the given character in the charset list
   390  func isInCharset(searchChar byte, charset []byte) bool {
   391  	for _, char := range charset {
   392  		if char == searchChar {
   393  			return true
   394  		}
   395  	}
   396  	return false
   397  }
   398  
   399  // findNextCharsetPosition search the next char position from the charset
   400  func findNextCharsetPosition(search string, charset []byte) int {
   401  	nextPosition := -1
   402  	for _, char := range charset {
   403  		if pos := strings.IndexByte(search, char); pos != -1 && (pos < nextPosition || nextPosition == -1) {
   404  			nextPosition = pos
   405  		}
   406  	}
   407  
   408  	return nextPosition
   409  }
   410  
   411  // findNextCharsetPosition search the last char position from the charset
   412  func findLastCharsetPosition(search string, charset []byte) int {
   413  	lastPosition := -1
   414  	for _, char := range charset {
   415  		if pos := strings.LastIndexByte(search, char); pos != -1 && (pos < lastPosition || lastPosition == -1) {
   416  			lastPosition = pos
   417  		}
   418  	}
   419  
   420  	return lastPosition
   421  }
   422  
   423  // findNextCharsetPositionConstraint search the next char position from the charset
   424  // unlike findNextCharsetPosition, it takes care of constraint start-end chars to parse route pattern
   425  func findNextCharsetPositionConstraint(search string, charset []byte) int {
   426  	constraintStart := findNextNonEscapedCharsetPosition(search, parameterConstraintStartChars)
   427  	constraintEnd := findNextNonEscapedCharsetPosition(search, parameterConstraintEndChars)
   428  	nextPosition := -1
   429  
   430  	for _, char := range charset {
   431  		pos := strings.IndexByte(search, char)
   432  
   433  		if pos != -1 && (pos < nextPosition || nextPosition == -1) {
   434  			if (pos > constraintStart && pos > constraintEnd) || (pos < constraintStart && pos < constraintEnd) {
   435  				nextPosition = pos
   436  			}
   437  		}
   438  	}
   439  
   440  	return nextPosition
   441  }
   442  
   443  // findNextNonEscapedCharsetPosition search the next char position from the charset and skip the escaped characters
   444  func findNextNonEscapedCharsetPosition(search string, charset []byte) int {
   445  	pos := findNextCharsetPosition(search, charset)
   446  	for pos > 0 && search[pos-1] == escapeChar {
   447  		if len(search) == pos+1 {
   448  			// escaped character is at the end
   449  			return -1
   450  		}
   451  		nextPossiblePos := findNextCharsetPosition(search[pos+1:], charset)
   452  		if nextPossiblePos == -1 {
   453  			return -1
   454  		}
   455  		// the previous character is taken into consideration
   456  		pos = nextPossiblePos + pos + 1
   457  	}
   458  
   459  	return pos
   460  }
   461  
   462  // splitNonEscaped slices s into all substrings separated by sep and returns a slice of the substrings between those separators
   463  // This function also takes a care of escape char when splitting.
   464  func splitNonEscaped(s, sep string) []string {
   465  	var result []string
   466  	i := findNextNonEscapedCharsetPosition(s, []byte(sep))
   467  
   468  	for i > -1 {
   469  		result = append(result, s[:i])
   470  		s = s[i+len(sep):]
   471  		i = findNextNonEscapedCharsetPosition(s, []byte(sep))
   472  	}
   473  
   474  	return append(result, s)
   475  }
   476  
   477  // getMatch parses the passed url and tries to match it against the route segments and determine the parameter positions
   478  func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool { //nolint: revive // Accepting a bool param is fine here
   479  	var i, paramsIterator, partLen int
   480  	for _, segment := range routeParser.segs {
   481  		partLen = len(detectionPath)
   482  		// check const segment
   483  		if !segment.IsParam {
   484  			i = segment.Length
   485  			// is optional part or the const part must match with the given string
   486  			// check if the end of the segment is a optional slash
   487  			if segment.HasOptionalSlash && partLen == i-1 && detectionPath == segment.Const[:i-1] {
   488  				i--
   489  			} else if !(i <= partLen && detectionPath[:i] == segment.Const) {
   490  				return false
   491  			}
   492  		} else {
   493  			// determine parameter length
   494  			i = findParamLen(detectionPath, segment)
   495  			if !segment.IsOptional && i == 0 {
   496  				return false
   497  			}
   498  			// take over the params positions
   499  			params[paramsIterator] = path[:i]
   500  
   501  			if !(segment.IsOptional && i == 0) {
   502  				// check constraint
   503  				for _, c := range segment.Constraints {
   504  					if matched := c.CheckConstraint(params[paramsIterator]); !matched {
   505  						return false
   506  					}
   507  				}
   508  			}
   509  
   510  			paramsIterator++
   511  		}
   512  
   513  		// reduce founded part from the string
   514  		if partLen > 0 {
   515  			detectionPath, path = detectionPath[i:], path[i:]
   516  		}
   517  	}
   518  	if detectionPath != "" && !partialCheck {
   519  		return false
   520  	}
   521  
   522  	return true
   523  }
   524  
   525  // findParamLen for the expressjs wildcard behavior (right to left greedy)
   526  // look at the other segments and take what is left for the wildcard from right to left
   527  func findParamLen(s string, segment *routeSegment) int {
   528  	if segment.IsLast {
   529  		return findParamLenForLastSegment(s, segment)
   530  	}
   531  
   532  	if segment.Length != 0 && len(s) >= segment.Length {
   533  		return segment.Length
   534  	} else if segment.IsGreedy {
   535  		// Search the parameters until the next constant part
   536  		// special logic for greedy params
   537  		searchCount := strings.Count(s, segment.ComparePart)
   538  		if searchCount > 1 {
   539  			return findGreedyParamLen(s, searchCount, segment)
   540  		}
   541  	}
   542  
   543  	if len(segment.ComparePart) == 1 {
   544  		if constPosition := strings.IndexByte(s, segment.ComparePart[0]); constPosition != -1 {
   545  			return constPosition
   546  		}
   547  	} else if constPosition := strings.Index(s, segment.ComparePart); constPosition != -1 {
   548  		// if the compare part was found, but contains a slash although this part is not greedy, then it must not match
   549  		// example: /api/:param/fixedEnd -> path: /api/123/456/fixedEnd = no match , /api/123/fixedEnd = match
   550  		if !segment.IsGreedy && strings.IndexByte(s[:constPosition], slashDelimiter) != -1 {
   551  			return 0
   552  		}
   553  		return constPosition
   554  	}
   555  
   556  	return len(s)
   557  }
   558  
   559  // findParamLenForLastSegment get the length of the parameter if it is the last segment
   560  func findParamLenForLastSegment(s string, seg *routeSegment) int {
   561  	if !seg.IsGreedy {
   562  		if i := strings.IndexByte(s, slashDelimiter); i != -1 {
   563  			return i
   564  		}
   565  	}
   566  
   567  	return len(s)
   568  }
   569  
   570  // findGreedyParamLen get the length of the parameter for greedy segments from right to left
   571  func findGreedyParamLen(s string, searchCount int, segment *routeSegment) int {
   572  	// check all from right to left segments
   573  	for i := segment.PartCount; i > 0 && searchCount > 0; i-- {
   574  		searchCount--
   575  		if constPosition := strings.LastIndex(s, segment.ComparePart); constPosition != -1 {
   576  			s = s[:constPosition]
   577  		} else {
   578  			break
   579  		}
   580  	}
   581  
   582  	return len(s)
   583  }
   584  
   585  // GetTrimmedParam trims the ':' & '?' from a string
   586  func GetTrimmedParam(param string) string {
   587  	start := 0
   588  	end := len(param)
   589  
   590  	if end == 0 || param[start] != paramStarterChar { // is not a param
   591  		return param
   592  	}
   593  	start++
   594  	if param[end-1] == optionalParam { // is ?
   595  		end--
   596  	}
   597  
   598  	return param[start:end]
   599  }
   600  
   601  // RemoveEscapeChar remove escape characters
   602  func RemoveEscapeChar(word string) string {
   603  	if strings.IndexByte(word, escapeChar) != -1 {
   604  		return strings.ReplaceAll(word, string(escapeChar), "")
   605  	}
   606  	return word
   607  }
   608  
   609  func getParamConstraintType(constraintPart string) TypeConstraint {
   610  	switch constraintPart {
   611  	case ConstraintInt:
   612  		return intConstraint
   613  	case ConstraintBool:
   614  		return boolConstraint
   615  	case ConstraintFloat:
   616  		return floatConstraint
   617  	case ConstraintAlpha:
   618  		return alphaConstraint
   619  	case ConstraintGuid:
   620  		return guidConstraint
   621  	case ConstraintMinLen, ConstraintMinLenLower:
   622  		return minLenConstraint
   623  	case ConstraintMaxLen, ConstraintMaxLenLower:
   624  		return maxLenConstraint
   625  	case ConstraintLen:
   626  		return lenConstraint
   627  	case ConstraintBetweenLen, ConstraintBetweenLenLower:
   628  		return betweenLenConstraint
   629  	case ConstraintMin:
   630  		return minConstraint
   631  	case ConstraintMax:
   632  		return maxConstraint
   633  	case ConstraintRange:
   634  		return rangeConstraint
   635  	case ConstraintDatetime:
   636  		return datetimeConstraint
   637  	case ConstraintRegex:
   638  		return regexConstraint
   639  	default:
   640  		return noConstraint
   641  	}
   642  }
   643  
   644  //nolint:errcheck // TODO: Properly check _all_ errors in here, log them & immediately return
   645  func (c *Constraint) CheckConstraint(param string) bool {
   646  	var err error
   647  	var num int
   648  
   649  	// check data exists
   650  	needOneData := []TypeConstraint{minLenConstraint, maxLenConstraint, lenConstraint, minConstraint, maxConstraint, datetimeConstraint, regexConstraint}
   651  	needTwoData := []TypeConstraint{betweenLenConstraint, rangeConstraint}
   652  
   653  	for _, data := range needOneData {
   654  		if c.ID == data && len(c.Data) == 0 {
   655  			return false
   656  		}
   657  	}
   658  
   659  	for _, data := range needTwoData {
   660  		if c.ID == data && len(c.Data) < 2 {
   661  			return false
   662  		}
   663  	}
   664  
   665  	// check constraints
   666  	switch c.ID {
   667  	case noConstraint:
   668  		// Nothing to check
   669  	case intConstraint:
   670  		_, err = strconv.Atoi(param)
   671  	case boolConstraint:
   672  		_, err = strconv.ParseBool(param)
   673  	case floatConstraint:
   674  		_, err = strconv.ParseFloat(param, 32)
   675  	case alphaConstraint:
   676  		for _, r := range param {
   677  			if !unicode.IsLetter(r) {
   678  				return false
   679  			}
   680  		}
   681  	case guidConstraint:
   682  		_, err = uuid.Parse(param)
   683  	case minLenConstraint:
   684  		data, _ := strconv.Atoi(c.Data[0])
   685  
   686  		if len(param) < data {
   687  			return false
   688  		}
   689  	case maxLenConstraint:
   690  		data, _ := strconv.Atoi(c.Data[0])
   691  
   692  		if len(param) > data {
   693  			return false
   694  		}
   695  	case lenConstraint:
   696  		data, _ := strconv.Atoi(c.Data[0])
   697  
   698  		if len(param) != data {
   699  			return false
   700  		}
   701  	case betweenLenConstraint:
   702  		data, _ := strconv.Atoi(c.Data[0])
   703  		data2, _ := strconv.Atoi(c.Data[1])
   704  		length := len(param)
   705  		if length < data || length > data2 {
   706  			return false
   707  		}
   708  	case minConstraint:
   709  		data, _ := strconv.Atoi(c.Data[0])
   710  		num, err = strconv.Atoi(param)
   711  
   712  		if num < data {
   713  			return false
   714  		}
   715  	case maxConstraint:
   716  		data, _ := strconv.Atoi(c.Data[0])
   717  		num, err = strconv.Atoi(param)
   718  
   719  		if num > data {
   720  			return false
   721  		}
   722  	case rangeConstraint:
   723  		data, _ := strconv.Atoi(c.Data[0])
   724  		data2, _ := strconv.Atoi(c.Data[1])
   725  		num, err = strconv.Atoi(param)
   726  
   727  		if num < data || num > data2 {
   728  			return false
   729  		}
   730  	case datetimeConstraint:
   731  		_, err = time.Parse(c.Data[0], param)
   732  	case regexConstraint:
   733  		if match := c.RegexCompiler.MatchString(param); !match {
   734  			return false
   735  		}
   736  	}
   737  
   738  	return err == nil
   739  }