path.go 22 KB

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