request_params.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. package context
  2. import (
  3. "fmt"
  4. "reflect"
  5. "strconv"
  6. "strings"
  7. "time"
  8. "github.com/kataras/iris/v12/core/memstore"
  9. )
  10. // RequestParams is a key string - value string storage which
  11. // context's request dynamic path params are being kept.
  12. // Empty if the route is static.
  13. type RequestParams struct {
  14. memstore.Store
  15. }
  16. // Set inserts a parameter value.
  17. // See `Get` too.
  18. func (r *RequestParams) Set(key, value string) {
  19. if ln := len(r.Store); cap(r.Store) > ln {
  20. r.Store = r.Store[:ln+1]
  21. p := &r.Store[ln]
  22. p.Key = key
  23. p.ValueRaw = value
  24. return
  25. }
  26. r.Store = append(r.Store, memstore.Entry{
  27. Key: key,
  28. ValueRaw: value,
  29. })
  30. }
  31. // Get returns a path parameter's value based on its route's dynamic path key.
  32. func (r *RequestParams) Get(key string) string {
  33. for i := range r.Store {
  34. if kv := r.Store[i]; kv.Key == key {
  35. if v, ok := kv.ValueRaw.(string); ok {
  36. return v // it should always be string here on :string parameter.
  37. }
  38. if v, ok := kv.ValueRaw.(fmt.Stringer); ok {
  39. return v.String()
  40. }
  41. return fmt.Sprintf("%v", kv.ValueRaw)
  42. }
  43. }
  44. return ""
  45. }
  46. // GetEntryAt will return the parameter's internal store's `Entry` based on the index.
  47. // If not found it will return an emptry `Entry`.
  48. func (r *RequestParams) GetEntryAt(index int) memstore.Entry {
  49. entry, _ := r.Store.GetEntryAt(index)
  50. return entry
  51. }
  52. // GetEntry will return the parameter's internal store's `Entry` based on its name/key.
  53. // If not found it will return an emptry `Entry`.
  54. func (r *RequestParams) GetEntry(key string) memstore.Entry {
  55. entry, _ := r.Store.GetEntry(key)
  56. return entry
  57. }
  58. // Visit accepts a visitor which will be filled
  59. // by the key-value params.
  60. func (r *RequestParams) Visit(visitor func(key string, value string)) {
  61. r.Store.Visit(func(k string, v interface{}) {
  62. visitor(k, fmt.Sprintf("%v", v)) // always string here.
  63. })
  64. }
  65. // GetTrim returns a path parameter's value without trailing spaces based on its route's dynamic path key.
  66. func (r *RequestParams) GetTrim(key string) string {
  67. return strings.TrimSpace(r.Get(key))
  68. }
  69. // GetEscape returns a path parameter's double-url-query-escaped value based on its route's dynamic path key.
  70. func (r *RequestParams) GetEscape(key string) string {
  71. return DecodeQuery(DecodeQuery(r.Get(key)))
  72. }
  73. // GetDecoded returns a path parameter's double-url-query-escaped value based on its route's dynamic path key.
  74. // same as `GetEscape`.
  75. func (r *RequestParams) GetDecoded(key string) string {
  76. return r.GetEscape(key)
  77. }
  78. // TrimParamFilePart is a middleware which replaces all route dynamic path parameters
  79. // with values that do not contain any part after the last dot (.) character.
  80. //
  81. // Example Code:
  82. //
  83. // package main
  84. //
  85. // import (
  86. // "github.com/kataras/iris/v12"
  87. // )
  88. //
  89. // func main() {
  90. // app := iris.New()
  91. // app.Get("/{uid:string regexp(^[0-9]{1,20}.html$)}", iris.TrimParamFilePart, handler)
  92. // // TrimParamFilePart can be registered as a middleware to a Party (group of routes) as well.
  93. // app.Listen(":8080")
  94. // }
  95. //
  96. // func handler(ctx iris.Context) {
  97. // //
  98. // // The above line is useless now that we've registered the TrimParamFilePart middleware:
  99. // // uid := ctx.Params().GetTrimFileUint64("uid")
  100. // //
  101. //
  102. // uid := ctx.Params().GetUint64Default("uid", 0)
  103. // ctx.Writef("Param value: %d\n", uid)
  104. // }
  105. func TrimParamFilePart(ctx *Context) { // See #2024.
  106. params := ctx.Params()
  107. for i, param := range params.Store {
  108. if value, ok := param.ValueRaw.(string); ok {
  109. if idx := strings.LastIndexByte(value, '.'); idx > 1 /* at least .h */ {
  110. value = value[0:idx]
  111. param.ValueRaw = value
  112. }
  113. }
  114. params.Store[i] = param
  115. }
  116. ctx.Next()
  117. }
  118. // GetTrimFile returns a parameter value but without the last ".ANYTHING_HERE" part.
  119. func (r *RequestParams) GetTrimFile(key string) string {
  120. value := r.Get(key)
  121. if idx := strings.LastIndexByte(value, '.'); idx > 1 /* at least .h */ {
  122. return value[0:idx]
  123. }
  124. return value
  125. }
  126. // GetTrimFileInt same as GetTrimFile but it returns the value as int.
  127. func (r *RequestParams) GetTrimFileInt(key string) int {
  128. value := r.Get(key)
  129. if idx := strings.LastIndexByte(value, '.'); idx > 1 /* at least .h */ {
  130. value = value[0:idx]
  131. }
  132. v, _ := strconv.Atoi(value)
  133. return v
  134. }
  135. // GetTrimFileUint64 same as GetTrimFile but it returns the value as uint64.
  136. func (r *RequestParams) GetTrimFileUint64(key string) uint64 {
  137. value := r.Get(key)
  138. if idx := strings.LastIndexByte(value, '.'); idx > 1 /* at least .h */ {
  139. value = value[0:idx]
  140. }
  141. v, err := strconv.ParseUint(value, 10, strconv.IntSize)
  142. if err != nil {
  143. return 0
  144. }
  145. return v
  146. }
  147. // GetTrimFileUint64 same as GetTrimFile but it returns the value as uint.
  148. func (r *RequestParams) GetTrimFileUint(key string) uint {
  149. return uint(r.GetTrimFileUint64(key))
  150. }
  151. func (r *RequestParams) getRightTrimmed(key string, cutset string) string {
  152. return strings.TrimRight(strings.ToLower(r.Get(key)), cutset)
  153. }
  154. // GetTrimHTML returns a parameter value but without the last ".html" part.
  155. func (r *RequestParams) GetTrimHTML(key string) string {
  156. return r.getRightTrimmed(key, ".html")
  157. }
  158. // GetTrimJSON returns a parameter value but without the last ".json" part.
  159. func (r *RequestParams) GetTrimJSON(key string) string {
  160. return r.getRightTrimmed(key, ".json")
  161. }
  162. // GetTrimXML returns a parameter value but without the last ".xml" part.
  163. func (r *RequestParams) GetTrimXML(key string) string {
  164. return r.getRightTrimmed(key, ".xml")
  165. }
  166. // GetIntUnslashed same as Get but it removes the first slash if found.
  167. // Usage: Get an id from a wildcard path.
  168. //
  169. // Returns -1 and false if not path parameter with that "key" found.
  170. func (r *RequestParams) GetIntUnslashed(key string) (int, bool) {
  171. v := r.Get(key)
  172. if v != "" {
  173. if len(v) > 1 {
  174. if v[0] == '/' {
  175. v = v[1:]
  176. }
  177. }
  178. vInt, err := strconv.Atoi(v)
  179. if err != nil {
  180. return -1, false
  181. }
  182. return vInt, true
  183. }
  184. return -1, false
  185. }
  186. // ParamResolvers is the global param resolution for a parameter type for a specific go std or custom type.
  187. //
  188. // Key is the specific type, which should be unique.
  189. // The value is a function which accepts the parameter index
  190. // and it should return the value as the parameter type evaluator expects it.
  191. //
  192. // i.e [reflect.TypeOf("string")] = func(paramIndex int) interface{} {
  193. // return func(ctx *Context) <T> {
  194. // return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(<T>)
  195. // }
  196. // }
  197. //
  198. // Read https://github.com/kataras/iris/tree/main/_examples/routing/macros for more details.
  199. // Checks for total available request parameters length
  200. // and parameter index based on the hero/mvc function added
  201. // in order to support the MVC.HandleMany("GET", "/path/{ps}/{pssecond} /path/{ps}")
  202. // when on the second requested path, the 'pssecond' should be empty.
  203. var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{
  204. reflect.TypeOf(""): func(paramIndex int) interface{} {
  205. return func(ctx *Context) string {
  206. if ctx.Params().Len() <= paramIndex {
  207. return ""
  208. }
  209. return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(string)
  210. }
  211. },
  212. reflect.TypeOf(int(1)): func(paramIndex int) interface{} {
  213. return func(ctx *Context) int {
  214. if ctx.Params().Len() <= paramIndex {
  215. return 0
  216. }
  217. // v, _ := ctx.Params().GetEntryAt(paramIndex).IntDefault(0)
  218. // return v
  219. return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int)
  220. }
  221. },
  222. reflect.TypeOf(int8(1)): func(paramIndex int) interface{} {
  223. return func(ctx *Context) int8 {
  224. if ctx.Params().Len() <= paramIndex {
  225. return 0
  226. }
  227. return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int8)
  228. }
  229. },
  230. reflect.TypeOf(int16(1)): func(paramIndex int) interface{} {
  231. return func(ctx *Context) int16 {
  232. if ctx.Params().Len() <= paramIndex {
  233. return 0
  234. }
  235. return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int16)
  236. }
  237. },
  238. reflect.TypeOf(int32(1)): func(paramIndex int) interface{} {
  239. return func(ctx *Context) int32 {
  240. if ctx.Params().Len() <= paramIndex {
  241. return 0
  242. }
  243. return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int32)
  244. }
  245. },
  246. reflect.TypeOf(int64(1)): func(paramIndex int) interface{} {
  247. return func(ctx *Context) int64 {
  248. if ctx.Params().Len() <= paramIndex {
  249. return 0
  250. }
  251. return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int64)
  252. }
  253. },
  254. reflect.TypeOf(uint(1)): func(paramIndex int) interface{} {
  255. return func(ctx *Context) uint {
  256. if ctx.Params().Len() <= paramIndex {
  257. return 0
  258. }
  259. return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint)
  260. }
  261. },
  262. reflect.TypeOf(uint8(1)): func(paramIndex int) interface{} {
  263. return func(ctx *Context) uint8 {
  264. if ctx.Params().Len() <= paramIndex {
  265. return 0
  266. }
  267. return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint8)
  268. }
  269. },
  270. reflect.TypeOf(uint16(1)): func(paramIndex int) interface{} {
  271. return func(ctx *Context) uint16 {
  272. if ctx.Params().Len() <= paramIndex {
  273. return 0
  274. }
  275. return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint16)
  276. }
  277. },
  278. reflect.TypeOf(uint32(1)): func(paramIndex int) interface{} {
  279. return func(ctx *Context) uint32 {
  280. if ctx.Params().Len() <= paramIndex {
  281. return 0
  282. }
  283. return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32)
  284. }
  285. },
  286. reflect.TypeOf(uint64(1)): func(paramIndex int) interface{} {
  287. return func(ctx *Context) uint64 {
  288. if ctx.Params().Len() <= paramIndex {
  289. return 0
  290. }
  291. return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint64)
  292. }
  293. },
  294. reflect.TypeOf(true): func(paramIndex int) interface{} {
  295. return func(ctx *Context) bool {
  296. if ctx.Params().Len() <= paramIndex {
  297. return false
  298. }
  299. return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(bool)
  300. }
  301. },
  302. reflect.TypeOf(time.Time{}): func(paramIndex int) interface{} {
  303. return func(ctx *Context) time.Time {
  304. if ctx.Params().Len() <= paramIndex {
  305. return unixEpochTime
  306. }
  307. v, ok := ctx.Params().GetEntryAt(paramIndex).ValueRaw.(time.Time)
  308. if !ok {
  309. return unixEpochTime
  310. }
  311. return v
  312. }
  313. },
  314. reflect.TypeOf(time.Weekday(0)): func(paramIndex int) interface{} {
  315. return func(ctx *Context) time.Weekday {
  316. if ctx.Params().Len() <= paramIndex {
  317. return time.Sunday
  318. }
  319. v, ok := ctx.Params().GetEntryAt(paramIndex).ValueRaw.(time.Weekday)
  320. if !ok {
  321. return time.Sunday
  322. }
  323. return v
  324. }
  325. },
  326. }
  327. // ParamResolverByTypeAndIndex will return a function that can be used to bind path parameter's exact value by its Go std type
  328. // and the parameter's index based on the registered path.
  329. // Usage: nameResolver := ParamResolverByKindAndKey(reflect.TypeOf(""), 0)
  330. // Inside a Handler: nameResolver.Call(ctx)[0]
  331. //
  332. // it will return the reflect.Value Of the exact type of the parameter(based on the path parameters and macros).
  333. //
  334. // It is only useful for dynamic binding of the parameter, it is used on "hero" package and it should be modified
  335. // only when Macros are modified in such way that the default selections for the available go std types are not enough.
  336. //
  337. // Returns empty value and false if "k" does not match any valid parameter resolver.
  338. func ParamResolverByTypeAndIndex(typ reflect.Type, paramIndex int) (reflect.Value, bool) {
  339. /* NO:
  340. // This could work but its result is not exact type, so direct binding is not possible.
  341. resolver := m.ParamResolver
  342. fn := func(ctx *context.Context) interface{} {
  343. entry, _ := ctx.Params().GetEntry(paramName)
  344. return resolver(entry)
  345. }
  346. //
  347. // This works but it is slower on serve-time.
  348. paramNameValue := []reflect.Value{reflect.ValueOf(paramName)}
  349. var fnSignature func(*context.Context) string
  350. return reflect.MakeFunc(reflect.ValueOf(&fnSignature).Elem().Type(), func(in []reflect.Value) []reflect.Value {
  351. return in[0].MethodByName("Params").Call(emptyIn)[0].MethodByName("Get").Call(paramNameValue)
  352. // return []reflect.Value{reflect.ValueOf(in[0].Interface().(*context.Context).Params().Get(paramName))}
  353. })
  354. //
  355. */
  356. r, ok := ParamResolvers[typ]
  357. if !ok || r == nil {
  358. return reflect.Value{}, false
  359. }
  360. return reflect.ValueOf(r(paramIndex)), true
  361. }