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 }