path.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  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/google/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 an 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 recognizing
  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. // RoutePatternMatch checks if a given path matches a Fiber route pattern.
  106. func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
  107. // See logic in (*Route).match and (*App).register
  108. var ctxParams [maxParams]string
  109. config := Config{}
  110. if len(cfg) > 0 {
  111. config = cfg[0]
  112. }
  113. if path == "" {
  114. path = "/"
  115. }
  116. // Cannot have an empty pattern
  117. if pattern == "" {
  118. pattern = "/"
  119. }
  120. // Pattern always start with a '/'
  121. if pattern[0] != '/' {
  122. pattern = "/" + pattern
  123. }
  124. patternPretty := pattern
  125. // Case-sensitive routing, all to lowercase
  126. if !config.CaseSensitive {
  127. patternPretty = utils.ToLower(patternPretty)
  128. path = utils.ToLower(path)
  129. }
  130. // Strict routing, remove trailing slashes
  131. if !config.StrictRouting && len(patternPretty) > 1 {
  132. patternPretty = utils.TrimRight(patternPretty, '/')
  133. }
  134. parser := parseRoute(patternPretty)
  135. if patternPretty == "/" && path == "/" {
  136. return true
  137. // '*' wildcard matches any path
  138. } else if patternPretty == "/*" {
  139. return true
  140. }
  141. // Does this route have parameters
  142. if len(parser.params) > 0 {
  143. if match := parser.getMatch(path, path, &ctxParams, false); match {
  144. return true
  145. }
  146. }
  147. // Check for a simple match
  148. patternPretty = RemoveEscapeChar(patternPretty)
  149. if len(patternPretty) == len(path) && patternPretty == path {
  150. return true
  151. }
  152. // No match
  153. return false
  154. }
  155. // parseRoute analyzes the route and divides it into segments for constant areas and parameters,
  156. // this information is needed later when assigning the requests to the declared routes
  157. func parseRoute(pattern string) routeParser {
  158. parser := routeParser{}
  159. part := ""
  160. for len(pattern) > 0 {
  161. nextParamPosition := findNextParamPosition(pattern)
  162. // handle the parameter part
  163. if nextParamPosition == 0 {
  164. processedPart, seg := parser.analyseParameterPart(pattern)
  165. parser.params, parser.segs, part = append(parser.params, seg.ParamName), append(parser.segs, seg), processedPart
  166. } else {
  167. processedPart, seg := parser.analyseConstantPart(pattern, nextParamPosition)
  168. parser.segs, part = append(parser.segs, seg), processedPart
  169. }
  170. // reduce the pattern by the processed parts
  171. if len(part) == len(pattern) {
  172. break
  173. }
  174. pattern = pattern[len(part):]
  175. }
  176. // mark last segment
  177. if len(parser.segs) > 0 {
  178. parser.segs[len(parser.segs)-1].IsLast = true
  179. }
  180. parser.segs = addParameterMetaInfo(parser.segs)
  181. return parser
  182. }
  183. // addParameterMetaInfo add important meta information to the parameter segments
  184. // to simplify the search for the end of the parameter
  185. func addParameterMetaInfo(segs []*routeSegment) []*routeSegment {
  186. var comparePart string
  187. segLen := len(segs)
  188. // loop from end to begin
  189. for i := segLen - 1; i >= 0; i-- {
  190. // set the compare part for the parameter
  191. if segs[i].IsParam {
  192. // important for finding the end of the parameter
  193. segs[i].ComparePart = RemoveEscapeChar(comparePart)
  194. } else {
  195. comparePart = segs[i].Const
  196. if len(comparePart) > 1 {
  197. comparePart = utils.TrimRight(comparePart, slashDelimiter)
  198. }
  199. }
  200. }
  201. // loop from begin to end
  202. for i := 0; i < segLen; i++ {
  203. // check how often the compare part is in the following const parts
  204. if segs[i].IsParam {
  205. // check if parameter segments are directly after each other and if one of them is greedy
  206. // in case the next parameter or the current parameter is not a wildcard it's not greedy, we only want one character
  207. if segLen > i+1 && !segs[i].IsGreedy && segs[i+1].IsParam && !segs[i+1].IsGreedy {
  208. segs[i].Length = 1
  209. }
  210. if segs[i].ComparePart == "" {
  211. continue
  212. }
  213. for j := i + 1; j <= len(segs)-1; j++ {
  214. if !segs[j].IsParam {
  215. // count is important for the greedy match
  216. segs[i].PartCount += strings.Count(segs[j].Const, segs[i].ComparePart)
  217. }
  218. }
  219. // check if the end of the segment is a optional slash and then if the segement is optional or the last one
  220. } else if segs[i].Const[len(segs[i].Const)-1] == slashDelimiter && (segs[i].IsLast || (segLen > i+1 && segs[i+1].IsOptional)) {
  221. segs[i].HasOptionalSlash = true
  222. }
  223. }
  224. return segs
  225. }
  226. // findNextParamPosition search for the next possible parameter start position
  227. func findNextParamPosition(pattern string) int {
  228. nextParamPosition := findNextNonEscapedCharsetPosition(pattern, parameterStartChars)
  229. if nextParamPosition != -1 && len(pattern) > nextParamPosition && pattern[nextParamPosition] != wildcardParam {
  230. // search for parameter characters for the found parameter start,
  231. // if there are more, move the parameter start to the last parameter char
  232. for found := findNextNonEscapedCharsetPosition(pattern[nextParamPosition+1:], parameterStartChars); found == 0; {
  233. nextParamPosition++
  234. if len(pattern) > nextParamPosition {
  235. break
  236. }
  237. }
  238. }
  239. return nextParamPosition
  240. }
  241. // analyseConstantPart find the end of the constant part and create the route segment
  242. func (*routeParser) analyseConstantPart(pattern string, nextParamPosition int) (string, *routeSegment) {
  243. // handle the constant part
  244. processedPart := pattern
  245. if nextParamPosition != -1 {
  246. // remove the constant part until the parameter
  247. processedPart = pattern[:nextParamPosition]
  248. }
  249. constPart := RemoveEscapeChar(processedPart)
  250. return processedPart, &routeSegment{
  251. Const: constPart,
  252. Length: len(constPart),
  253. }
  254. }
  255. // analyseParameterPart find the parameter end and create the route segment
  256. func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *routeSegment) {
  257. isWildCard := pattern[0] == wildcardParam
  258. isPlusParam := pattern[0] == plusParam
  259. var parameterEndPosition int
  260. if strings.ContainsRune(pattern, rune(paramConstraintStart)) && strings.ContainsRune(pattern, rune(paramConstraintEnd)) {
  261. parameterEndPosition = findNextCharsetPositionConstraint(pattern[1:], parameterEndChars)
  262. } else {
  263. parameterEndPosition = findNextNonEscapedCharsetPosition(pattern[1:], parameterEndChars)
  264. }
  265. parameterConstraintStart := -1
  266. parameterConstraintEnd := -1
  267. // handle wildcard end
  268. switch {
  269. case isWildCard, isPlusParam:
  270. parameterEndPosition = 0
  271. case parameterEndPosition == -1:
  272. parameterEndPosition = len(pattern) - 1
  273. case !isInCharset(pattern[parameterEndPosition+1], parameterDelimiterChars):
  274. parameterEndPosition++
  275. }
  276. // find constraint part if exists in the parameter part and remove it
  277. if parameterEndPosition > 0 {
  278. parameterConstraintStart = findNextNonEscapedCharsetPosition(pattern[0:parameterEndPosition], parameterConstraintStartChars)
  279. parameterConstraintEnd = findLastCharsetPosition(pattern[0:parameterEndPosition+1], parameterConstraintEndChars)
  280. }
  281. // cut params part
  282. processedPart := pattern[0 : parameterEndPosition+1]
  283. paramName := RemoveEscapeChar(GetTrimmedParam(processedPart))
  284. // Check has constraint
  285. var constraints []*Constraint
  286. if hasConstraint := parameterConstraintStart != -1 && parameterConstraintEnd != -1; hasConstraint {
  287. constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd]
  288. userConstraints := splitNonEscaped(constraintString, string(parameterConstraintSeparatorChars))
  289. constraints = make([]*Constraint, 0, len(userConstraints))
  290. for _, c := range userConstraints {
  291. start := findNextNonEscapedCharsetPosition(c, parameterConstraintDataStartChars)
  292. end := findLastCharsetPosition(c, parameterConstraintDataEndChars)
  293. // Assign constraint
  294. if start != -1 && end != -1 {
  295. constraint := &Constraint{
  296. ID: getParamConstraintType(c[:start]),
  297. }
  298. // remove escapes from data
  299. if constraint.ID != regexConstraint {
  300. constraint.Data = splitNonEscaped(c[start+1:end], string(parameterConstraintDataSeparatorChars))
  301. if len(constraint.Data) == 1 {
  302. constraint.Data[0] = RemoveEscapeChar(constraint.Data[0])
  303. } else if len(constraint.Data) == 2 { // This is fine, we simply expect two parts
  304. constraint.Data[0] = RemoveEscapeChar(constraint.Data[0])
  305. constraint.Data[1] = RemoveEscapeChar(constraint.Data[1])
  306. }
  307. }
  308. // Precompile regex if has regex constraint
  309. if constraint.ID == regexConstraint {
  310. constraint.Data = []string{c[start+1 : end]}
  311. constraint.RegexCompiler = regexp.MustCompile(constraint.Data[0])
  312. }
  313. constraints = append(constraints, constraint)
  314. } else {
  315. constraints = append(constraints, &Constraint{
  316. ID: getParamConstraintType(c),
  317. Data: []string{},
  318. })
  319. }
  320. }
  321. paramName = RemoveEscapeChar(GetTrimmedParam(pattern[0:parameterConstraintStart]))
  322. }
  323. // add access iterator to wildcard and plus
  324. if isWildCard {
  325. routeParser.wildCardCount++
  326. paramName += strconv.Itoa(routeParser.wildCardCount)
  327. } else if isPlusParam {
  328. routeParser.plusCount++
  329. paramName += strconv.Itoa(routeParser.plusCount)
  330. }
  331. segment := &routeSegment{
  332. ParamName: paramName,
  333. IsParam: true,
  334. IsOptional: isWildCard || pattern[parameterEndPosition] == optionalParam,
  335. IsGreedy: isWildCard || isPlusParam,
  336. }
  337. if len(constraints) > 0 {
  338. segment.Constraints = constraints
  339. }
  340. return processedPart, segment
  341. }
  342. // isInCharset check is the given character in the charset list
  343. func isInCharset(searchChar byte, charset []byte) bool {
  344. for _, char := range charset {
  345. if char == searchChar {
  346. return true
  347. }
  348. }
  349. return false
  350. }
  351. // findNextCharsetPosition search the next char position from the charset
  352. func findNextCharsetPosition(search string, charset []byte) int {
  353. nextPosition := -1
  354. for _, char := range charset {
  355. if pos := strings.IndexByte(search, char); pos != -1 && (pos < nextPosition || nextPosition == -1) {
  356. nextPosition = pos
  357. }
  358. }
  359. return nextPosition
  360. }
  361. // findNextCharsetPosition search the last char position from the charset
  362. func findLastCharsetPosition(search string, charset []byte) int {
  363. lastPosition := -1
  364. for _, char := range charset {
  365. if pos := strings.LastIndexByte(search, char); pos != -1 && (pos < lastPosition || lastPosition == -1) {
  366. lastPosition = pos
  367. }
  368. }
  369. return lastPosition
  370. }
  371. // findNextCharsetPositionConstraint search the next char position from the charset
  372. // unlike findNextCharsetPosition, it takes care of constraint start-end chars to parse route pattern
  373. func findNextCharsetPositionConstraint(search string, charset []byte) int {
  374. constraintStart := findNextNonEscapedCharsetPosition(search, parameterConstraintStartChars)
  375. constraintEnd := findNextNonEscapedCharsetPosition(search, parameterConstraintEndChars)
  376. nextPosition := -1
  377. for _, char := range charset {
  378. pos := strings.IndexByte(search, char)
  379. if pos != -1 && (pos < nextPosition || nextPosition == -1) {
  380. if (pos > constraintStart && pos > constraintEnd) || (pos < constraintStart && pos < constraintEnd) {
  381. nextPosition = pos
  382. }
  383. }
  384. }
  385. return nextPosition
  386. }
  387. // findNextNonEscapedCharsetPosition search the next char position from the charset and skip the escaped characters
  388. func findNextNonEscapedCharsetPosition(search string, charset []byte) int {
  389. pos := findNextCharsetPosition(search, charset)
  390. for pos > 0 && search[pos-1] == escapeChar {
  391. if len(search) == pos+1 {
  392. // escaped character is at the end
  393. return -1
  394. }
  395. nextPossiblePos := findNextCharsetPosition(search[pos+1:], charset)
  396. if nextPossiblePos == -1 {
  397. return -1
  398. }
  399. // the previous character is taken into consideration
  400. pos = nextPossiblePos + pos + 1
  401. }
  402. return pos
  403. }
  404. // splitNonEscaped slices s into all substrings separated by sep and returns a slice of the substrings between those separators
  405. // This function also takes a care of escape char when splitting.
  406. func splitNonEscaped(s, sep string) []string {
  407. var result []string
  408. i := findNextNonEscapedCharsetPosition(s, []byte(sep))
  409. for i > -1 {
  410. result = append(result, s[:i])
  411. s = s[i+len(sep):]
  412. i = findNextNonEscapedCharsetPosition(s, []byte(sep))
  413. }
  414. return append(result, s)
  415. }
  416. // getMatch parses the passed url and tries to match it against the route segments and determine the parameter positions
  417. func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool { //nolint: revive // Accepting a bool param is fine here
  418. var i, paramsIterator, partLen int
  419. for _, segment := range routeParser.segs {
  420. partLen = len(detectionPath)
  421. // check const segment
  422. if !segment.IsParam {
  423. i = segment.Length
  424. // is optional part or the const part must match with the given string
  425. // check if the end of the segment is an optional slash
  426. if segment.HasOptionalSlash && partLen == i-1 && detectionPath == segment.Const[:i-1] {
  427. i--
  428. } else if !(i <= partLen && detectionPath[:i] == segment.Const) {
  429. return false
  430. }
  431. } else {
  432. // determine parameter length
  433. i = findParamLen(detectionPath, segment)
  434. if !segment.IsOptional && i == 0 {
  435. return false
  436. }
  437. // take over the params positions
  438. params[paramsIterator] = path[:i]
  439. if !(segment.IsOptional && i == 0) {
  440. // check constraint
  441. for _, c := range segment.Constraints {
  442. if matched := c.CheckConstraint(params[paramsIterator]); !matched {
  443. return false
  444. }
  445. }
  446. }
  447. paramsIterator++
  448. }
  449. // reduce founded part from the string
  450. if partLen > 0 {
  451. detectionPath, path = detectionPath[i:], path[i:]
  452. }
  453. }
  454. if detectionPath != "" && !partialCheck {
  455. return false
  456. }
  457. return true
  458. }
  459. // findParamLen for the expressjs wildcard behavior (right to left greedy)
  460. // look at the other segments and take what is left for the wildcard from right to left
  461. func findParamLen(s string, segment *routeSegment) int {
  462. if segment.IsLast {
  463. return findParamLenForLastSegment(s, segment)
  464. }
  465. if segment.Length != 0 && len(s) >= segment.Length {
  466. return segment.Length
  467. } else if segment.IsGreedy {
  468. // Search the parameters until the next constant part
  469. // special logic for greedy params
  470. searchCount := strings.Count(s, segment.ComparePart)
  471. if searchCount > 1 {
  472. return findGreedyParamLen(s, searchCount, segment)
  473. }
  474. }
  475. if len(segment.ComparePart) == 1 {
  476. if constPosition := strings.IndexByte(s, segment.ComparePart[0]); constPosition != -1 {
  477. return constPosition
  478. }
  479. } else if constPosition := strings.Index(s, segment.ComparePart); constPosition != -1 {
  480. // if the compare part was found, but contains a slash although this part is not greedy, then it must not match
  481. // example: /api/:param/fixedEnd -> path: /api/123/456/fixedEnd = no match , /api/123/fixedEnd = match
  482. if !segment.IsGreedy && strings.IndexByte(s[:constPosition], slashDelimiter) != -1 {
  483. return 0
  484. }
  485. return constPosition
  486. }
  487. return len(s)
  488. }
  489. // findParamLenForLastSegment get the length of the parameter if it is the last segment
  490. func findParamLenForLastSegment(s string, seg *routeSegment) int {
  491. if !seg.IsGreedy {
  492. if i := strings.IndexByte(s, slashDelimiter); i != -1 {
  493. return i
  494. }
  495. }
  496. return len(s)
  497. }
  498. // findGreedyParamLen get the length of the parameter for greedy segments from right to left
  499. func findGreedyParamLen(s string, searchCount int, segment *routeSegment) int {
  500. // check all from right to left segments
  501. for i := segment.PartCount; i > 0 && searchCount > 0; i-- {
  502. searchCount--
  503. if constPosition := strings.LastIndex(s, segment.ComparePart); constPosition != -1 {
  504. s = s[:constPosition]
  505. } else {
  506. break
  507. }
  508. }
  509. return len(s)
  510. }
  511. // GetTrimmedParam trims the ':' & '?' from a string
  512. func GetTrimmedParam(param string) string {
  513. start := 0
  514. end := len(param)
  515. if end == 0 || param[start] != paramStarterChar { // is not a param
  516. return param
  517. }
  518. start++
  519. if param[end-1] == optionalParam { // is ?
  520. end--
  521. }
  522. return param[start:end]
  523. }
  524. // RemoveEscapeChar remove escape characters
  525. func RemoveEscapeChar(word string) string {
  526. if strings.IndexByte(word, escapeChar) != -1 {
  527. return strings.ReplaceAll(word, string(escapeChar), "")
  528. }
  529. return word
  530. }
  531. func getParamConstraintType(constraintPart string) TypeConstraint {
  532. switch constraintPart {
  533. case ConstraintInt:
  534. return intConstraint
  535. case ConstraintBool:
  536. return boolConstraint
  537. case ConstraintFloat:
  538. return floatConstraint
  539. case ConstraintAlpha:
  540. return alphaConstraint
  541. case ConstraintGuid:
  542. return guidConstraint
  543. case ConstraintMinLen, ConstraintMinLenLower:
  544. return minLenConstraint
  545. case ConstraintMaxLen, ConstraintMaxLenLower:
  546. return maxLenConstraint
  547. case ConstraintLen:
  548. return lenConstraint
  549. case ConstraintBetweenLen, ConstraintBetweenLenLower:
  550. return betweenLenConstraint
  551. case ConstraintMin:
  552. return minConstraint
  553. case ConstraintMax:
  554. return maxConstraint
  555. case ConstraintRange:
  556. return rangeConstraint
  557. case ConstraintDatetime:
  558. return datetimeConstraint
  559. case ConstraintRegex:
  560. return regexConstraint
  561. default:
  562. return noConstraint
  563. }
  564. }
  565. //nolint:errcheck // TODO: Properly check _all_ errors in here, log them & immediately return
  566. func (c *Constraint) CheckConstraint(param string) bool {
  567. var err error
  568. var num int
  569. // check data exists
  570. needOneData := []TypeConstraint{minLenConstraint, maxLenConstraint, lenConstraint, minConstraint, maxConstraint, datetimeConstraint, regexConstraint}
  571. needTwoData := []TypeConstraint{betweenLenConstraint, rangeConstraint}
  572. for _, data := range needOneData {
  573. if c.ID == data && len(c.Data) == 0 {
  574. return false
  575. }
  576. }
  577. for _, data := range needTwoData {
  578. if c.ID == data && len(c.Data) < 2 {
  579. return false
  580. }
  581. }
  582. // check constraints
  583. switch c.ID {
  584. case noConstraint:
  585. // Nothing to check
  586. case intConstraint:
  587. _, err = strconv.Atoi(param)
  588. case boolConstraint:
  589. _, err = strconv.ParseBool(param)
  590. case floatConstraint:
  591. _, err = strconv.ParseFloat(param, 32)
  592. case alphaConstraint:
  593. for _, r := range param {
  594. if !unicode.IsLetter(r) {
  595. return false
  596. }
  597. }
  598. case guidConstraint:
  599. _, err = uuid.Parse(param)
  600. case minLenConstraint:
  601. data, _ := strconv.Atoi(c.Data[0])
  602. if len(param) < data {
  603. return false
  604. }
  605. case maxLenConstraint:
  606. data, _ := strconv.Atoi(c.Data[0])
  607. if len(param) > data {
  608. return false
  609. }
  610. case lenConstraint:
  611. data, _ := strconv.Atoi(c.Data[0])
  612. if len(param) != data {
  613. return false
  614. }
  615. case betweenLenConstraint:
  616. data, _ := strconv.Atoi(c.Data[0])
  617. data2, _ := strconv.Atoi(c.Data[1])
  618. length := len(param)
  619. if length < data || length > data2 {
  620. return false
  621. }
  622. case minConstraint:
  623. data, _ := strconv.Atoi(c.Data[0])
  624. num, err = strconv.Atoi(param)
  625. if num < data {
  626. return false
  627. }
  628. case maxConstraint:
  629. data, _ := strconv.Atoi(c.Data[0])
  630. num, err = strconv.Atoi(param)
  631. if num > data {
  632. return false
  633. }
  634. case rangeConstraint:
  635. data, _ := strconv.Atoi(c.Data[0])
  636. data2, _ := strconv.Atoi(c.Data[1])
  637. num, err = strconv.Atoi(param)
  638. if num < data || num > data2 {
  639. return false
  640. }
  641. case datetimeConstraint:
  642. _, err = time.Parse(c.Data[0], param)
  643. case regexConstraint:
  644. if match := c.RegexCompiler.MatchString(param); !match {
  645. return false
  646. }
  647. }
  648. return err == nil
  649. }