controller_method_parser.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. package mvc
  2. import (
  3. "errors"
  4. "fmt"
  5. "reflect"
  6. "strconv"
  7. "strings"
  8. "unicode"
  9. "github.com/kataras/iris/core/router"
  10. "github.com/kataras/iris/macro"
  11. )
  12. const (
  13. tokenBy = "By"
  14. tokenWildcard = "Wildcard" // "ByWildcard".
  15. )
  16. // word lexer, not characters.
  17. type methodLexer struct {
  18. words []string
  19. cur int
  20. }
  21. func newMethodLexer(s string) *methodLexer {
  22. l := new(methodLexer)
  23. l.reset(s)
  24. return l
  25. }
  26. func (l *methodLexer) reset(s string) {
  27. l.cur = -1
  28. var words []string
  29. if s != "" {
  30. end := len(s)
  31. start := -1
  32. for i, n := 0, end; i < n; i++ {
  33. c := rune(s[i])
  34. if unicode.IsUpper(c) {
  35. // it doesn't count the last uppercase
  36. if start != -1 {
  37. end = i
  38. words = append(words, s[start:end])
  39. }
  40. start = i
  41. continue
  42. }
  43. end = i + 1
  44. }
  45. if end > 0 && len(s) >= end {
  46. words = append(words, s[start:])
  47. }
  48. }
  49. l.words = words
  50. }
  51. func (l *methodLexer) next() (w string) {
  52. cur := l.cur + 1
  53. if w = l.peek(cur); w != "" {
  54. l.cur++
  55. }
  56. return
  57. }
  58. func (l *methodLexer) skip() {
  59. if cur := l.cur + 1; cur < len(l.words) {
  60. l.cur = cur
  61. } else {
  62. l.cur = len(l.words) - 1
  63. }
  64. }
  65. func (l *methodLexer) peek(idx int) string {
  66. if idx < len(l.words) {
  67. return l.words[idx]
  68. }
  69. return ""
  70. }
  71. func (l *methodLexer) peekNext() (w string) {
  72. return l.peek(l.cur + 1)
  73. }
  74. func genParamKey(argIdx int) string {
  75. return "param" + strconv.Itoa(argIdx) // param0, param1, param2...
  76. }
  77. type methodParser struct {
  78. lexer *methodLexer
  79. fn reflect.Method
  80. macros *macro.Macros
  81. }
  82. func parseMethod(macros *macro.Macros, fn reflect.Method, skipper func(string) bool) (method, path string, err error) {
  83. if skipper(fn.Name) {
  84. return "", "", errSkip
  85. }
  86. p := &methodParser{
  87. fn: fn,
  88. lexer: newMethodLexer(fn.Name),
  89. macros: macros,
  90. }
  91. return p.parse()
  92. }
  93. func methodTitle(httpMethod string) string {
  94. httpMethodFuncName := strings.Title(strings.ToLower(httpMethod))
  95. return httpMethodFuncName
  96. }
  97. var errSkip = errors.New("skip")
  98. var allMethods = append(router.AllMethods[0:], []string{"ALL", "ANY"}...)
  99. func addPathWord(path, w string) string {
  100. if path[len(path)-1] != '/' {
  101. path += "/"
  102. }
  103. path += strings.ToLower(w)
  104. return path
  105. }
  106. func (p *methodParser) parse() (method, path string, err error) {
  107. funcArgPos := 0
  108. path = "/"
  109. // take the first word and check for the method.
  110. w := p.lexer.next()
  111. for _, httpMethod := range allMethods {
  112. possibleMethodFuncName := methodTitle(httpMethod)
  113. if strings.Index(w, possibleMethodFuncName) == 0 {
  114. method = httpMethod
  115. break
  116. }
  117. }
  118. if method == "" {
  119. // this is not a valid method to parse, we just skip it,
  120. // it may be used for end-dev's use cases.
  121. return "", "", errSkip
  122. }
  123. for {
  124. w := p.lexer.next()
  125. if w == "" {
  126. break
  127. }
  128. if w == tokenBy {
  129. funcArgPos++ // starting with 1 because in typ.NumIn() the first is the struct receiver.
  130. // No need for these:
  131. // ByBy will act like /{param:type}/{param:type} as users expected
  132. // if func input arguments are there, else act By like normal path /by.
  133. //
  134. // if p.lexer.peekPrev() == tokenBy || typ.NumIn() == 1 { // ByBy, then act this second By like a path
  135. // a.relPath += "/" + strings.ToLower(w)
  136. // continue
  137. // }
  138. if path, funcArgPos, err = p.parsePathParam(path, w, funcArgPos); err != nil {
  139. return "", "", err
  140. }
  141. continue
  142. }
  143. // static path.
  144. path = addPathWord(path, w)
  145. }
  146. return
  147. }
  148. func (p *methodParser) parsePathParam(path string, w string, funcArgPos int) (string, int, error) {
  149. typ := p.fn.Type
  150. if typ.NumIn() <= funcArgPos {
  151. // By found but input arguments are not there, so act like /by path without restricts.
  152. path = addPathWord(path, w)
  153. return path, funcArgPos, nil
  154. }
  155. var (
  156. paramKey = genParamKey(funcArgPos) // argfirst, argsecond...
  157. m = p.macros.GetMaster() // default (String by-default)
  158. trailings = p.macros.GetTrailings()
  159. )
  160. // string, int...
  161. goType := typ.In(funcArgPos).Kind()
  162. nextWord := p.lexer.peekNext()
  163. if nextWord == tokenWildcard {
  164. p.lexer.skip() // skip the Wildcard word.
  165. if len(trailings) == 0 {
  166. return "", 0, errors.New("no trailing path parameter found")
  167. }
  168. m = trailings[0]
  169. } else {
  170. // validMacros := p.macros.LookupForGoType(goType)
  171. // instead of mapping with a reflect.Kind which has its limitation,
  172. // we map the param types with a go type as a string,
  173. // so custom structs such as "user" can be mapped to a macro with indent || alias == "user".
  174. m = p.macros.Get(strings.ToLower(goType.String()))
  175. if m == nil {
  176. if typ.NumIn() > funcArgPos {
  177. // has more input arguments but we are not in the correct
  178. // index now, maybe the first argument was an `iris/context.Context`
  179. // so retry with the "funcArgPos" incremented.
  180. //
  181. // the "funcArgPos" will be updated to the caller as well
  182. // because we return it among the path and the error.
  183. return p.parsePathParam(path, w, funcArgPos+1)
  184. }
  185. return "", 0, fmt.Errorf("invalid syntax: the standard go type: %s found in controller's function: %s at position: %d does not match any valid macro", goType, p.fn.Name, funcArgPos)
  186. }
  187. }
  188. // /{argfirst:path}, /{argfirst:int64}...
  189. if path[len(path)-1] != '/' {
  190. path += "/"
  191. }
  192. path += fmt.Sprintf("{%s:%s}", paramKey, m.Indent())
  193. if nextWord == "" && typ.NumIn() > funcArgPos+1 {
  194. // By is the latest word but func is expected
  195. // more path parameters values, i.e:
  196. // GetBy(name string, age int)
  197. // The caller (parse) doesn't need to know
  198. // about the incremental funcArgPos because
  199. // it will not need it.
  200. return p.parsePathParam(path, nextWord, funcArgPos+1)
  201. }
  202. return path, funcArgPos, nil
  203. }