handler.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. // Package handler is the highest level module of the macro package which makes use the rest of the macro package,
  2. // it is mainly used, internally, by the router package.
  3. package handler
  4. import (
  5. "fmt"
  6. "github.com/kataras/iris/v12/context"
  7. "github.com/kataras/iris/v12/core/memstore"
  8. "github.com/kataras/iris/v12/macro"
  9. )
  10. // ParamErrorHandler is a special type of Iris handler which receives
  11. // any error produced by a path type parameter evaluator and let developers
  12. // customize the output instead of the
  13. // provided error code 404 or anyother status code given on the `else` literal.
  14. //
  15. // Note that the builtin macros return error too, but they're handled
  16. // by the `else` literal (error code). To change this behavior
  17. // and send a custom error response you have to register it:
  18. //
  19. // app.Macros().Get("uuid").HandleError(func(ctx iris.Context, paramIndex int, err error)).
  20. //
  21. // You can also set custom macros by `app.Macros().Register`.
  22. //
  23. // See macro.HandleError to set it.
  24. type ParamErrorHandler = func(*context.Context, int, error) // alias.
  25. // CanMakeHandler reports whether a macro template needs a special macro's evaluator handler to be validated
  26. // before procceed to the next handler(s).
  27. // If the template does not contain any dynamic attributes and a special handler is NOT required
  28. // then it returns false.
  29. func CanMakeHandler(tmpl macro.Template) (needsMacroHandler bool) {
  30. if len(tmpl.Params) == 0 {
  31. return
  32. }
  33. // check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params.
  34. // 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used)
  35. // 2. if we don't have any named params then we don't need a handler too.
  36. for i := range tmpl.Params {
  37. p := tmpl.Params[i]
  38. if p.CanEval() {
  39. // if at least one needs it, then create the handler.
  40. needsMacroHandler = true
  41. if p.HandleError != nil {
  42. // Check for its type.
  43. if _, ok := p.HandleError.(ParamErrorHandler); !ok {
  44. panic(fmt.Sprintf("HandleError input argument must be a type of func(iris.Context, int, error) but got: %T", p.HandleError))
  45. }
  46. }
  47. break
  48. }
  49. }
  50. return
  51. }
  52. // MakeHandler creates and returns a handler from a macro template, the handler evaluates each of the parameters if necessary at all.
  53. // If the template does not contain any dynamic attributes and a special handler is NOT required
  54. // then it returns a nil handler.
  55. func MakeHandler(tmpl macro.Template) context.Handler {
  56. filter := MakeFilter(tmpl)
  57. return func(ctx *context.Context) {
  58. if !filter(ctx) {
  59. if ctx.GetCurrentRoute().StatusErrorCode() == ctx.GetStatusCode() {
  60. ctx.Next()
  61. } else {
  62. ctx.StopExecution()
  63. }
  64. return
  65. }
  66. // if all passed or the next is the registered error handler to handle this status code,
  67. // just continue.
  68. ctx.Next()
  69. }
  70. }
  71. // MakeFilter returns a Filter which reports whether a specific macro template
  72. // and its parameters pass the serve-time validation.
  73. func MakeFilter(tmpl macro.Template) context.Filter {
  74. if !CanMakeHandler(tmpl) {
  75. return nil
  76. }
  77. return func(ctx *context.Context) bool {
  78. for i := range tmpl.Params {
  79. p := tmpl.Params[i]
  80. if !p.CanEval() {
  81. continue // allow.
  82. }
  83. // 07-29-2019
  84. // changed to retrieve by param index in order to support
  85. // different parameter names for routes with
  86. // different param types (and probably different param names i.e {name:string}, {id:uint64})
  87. // in the exact same path pattern.
  88. //
  89. // Same parameter names are not allowed, different param types in the same path
  90. // should have different name e.g. {name} {id:uint64};
  91. // something like {name} and {name:uint64}
  92. // is bad API design and we do NOT allow it by-design.
  93. entry, found := ctx.Params().Store.GetEntryAt(p.Index)
  94. if !found {
  95. // should never happen.
  96. ctx.StatusCode(p.ErrCode) // status code can change from an error handler, set it here.
  97. return false
  98. }
  99. value, passed := p.Eval(entry.String())
  100. if !passed {
  101. ctx.StatusCode(p.ErrCode) // status code can change from an error handler, set it here.
  102. if value != nil && p.HandleError != nil {
  103. // The "value" is an error here, always (see template.Eval).
  104. // This is always a type of ParamErrorHandler at this state (see CanMakeHandler).
  105. p.HandleError.(ParamErrorHandler)(ctx, p.Index, value.(error))
  106. }
  107. return false
  108. }
  109. // Fixes binding different path parameters names,
  110. //
  111. // app.Get("/{fullname:string}", strHandler)
  112. // app.Get("/{id:int}", idHandler)
  113. //
  114. // before that user didn't see anything
  115. // but under the hoods the set-ed value was a type of string instead of type of int,
  116. // because store contained both "fullname" (which set-ed by the router itself on its string representation)
  117. // and "id" by the param evaluator (see core/router/handler.go and bindMultiParamTypesHandler->MakeFilter)
  118. // and the MVC get by index (e.g. 0) therefore
  119. // it got the "fullname" of type string instead of "id" int if /{int} requested.
  120. // which is critical for faster type assertion in the upcoming, new iris dependency injection (20 Feb 2020).
  121. ctx.Params().Store[p.Index] = memstore.Entry{
  122. Key: p.Name,
  123. ValueRaw: value,
  124. }
  125. // for i, v := range ctx.Params().Store {
  126. // fmt.Printf("[%d:%s] macro/handler/handler.go: param passed: %s(%v of type: %T)\n", i, v.Key,
  127. // p.Src, v.ValueRaw, v.ValueRaw)
  128. // }
  129. }
  130. return true
  131. }
  132. }