template.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package macro
  2. import (
  3. "reflect"
  4. "github.com/kataras/iris/v12/macro/interpreter/ast"
  5. "github.com/kataras/iris/v12/macro/interpreter/parser"
  6. )
  7. // Template contains a route's path full parsed template.
  8. //
  9. // Fields:
  10. // Src is the raw source of the path, i.e /users/{id:int min(1)}
  11. // Params is the list of the Params that are being used to the
  12. // path, i.e the min as param name and 1 as the param argument.
  13. type Template struct {
  14. // Src is the original template given by the client
  15. Src string `json:"src"`
  16. Params []TemplateParam `json:"params"`
  17. }
  18. // IsTrailing reports whether this Template is a traling one.
  19. func (t *Template) IsTrailing() bool {
  20. return len(t.Params) > 0 && ast.IsTrailing(t.Params[len(t.Params)-1].Type)
  21. }
  22. // TemplateParam is the parsed macro parameter's template
  23. // they are being used to describe the param's syntax result.
  24. type TemplateParam struct {
  25. Src string `json:"src"` // the unparsed param'false source
  26. // Type is not useful anywhere here but maybe
  27. // it's useful on host to decide how to convert the path template to specific router's syntax
  28. Type ast.ParamType `json:"type"`
  29. Name string `json:"name"`
  30. Index int `json:"index"`
  31. ErrCode int `json:"errCode"`
  32. // Note that, the value MUST BE a type of `handler.ParamErrorHandler`.
  33. HandleError interface{} `json:"-"` /* It's not an typed value because of import-cycle,
  34. // neither a special struct required, see `handler.MakeFilter`. */
  35. TypeEvaluator ParamEvaluator `json:"-"`
  36. Funcs []reflect.Value `json:"-"`
  37. stringInFuncs []func(string) bool
  38. canEval bool
  39. }
  40. func (p TemplateParam) preComputed() TemplateParam {
  41. for _, pfn := range p.Funcs {
  42. if fn, ok := pfn.Interface().(func(string) bool); ok {
  43. p.stringInFuncs = append(p.stringInFuncs, fn)
  44. }
  45. }
  46. // if true then it should be execute the type parameter or its functions
  47. // else it can be ignored,
  48. // i.e {myparam} or {myparam:string} or {myparam:path} ->
  49. // their type evaluator is nil because they don't do any checks and they don't change
  50. // the default parameter value's type (string) so no need for any work).
  51. p.canEval = p.TypeEvaluator != nil || len(p.Funcs) > 0 || p.ErrCode != parser.DefaultParamErrorCode || p.HandleError != nil
  52. return p
  53. }
  54. // CanEval returns true if this "p" TemplateParam should be evaluated in serve time.
  55. // It is computed before server ran and it is used to determinate if a route needs to build a macro handler (middleware).
  56. func (p *TemplateParam) CanEval() bool {
  57. return p.canEval
  58. }
  59. type errorInterface interface {
  60. Error() string
  61. }
  62. // Eval is the most critical part of the TemplateParam.
  63. // It is responsible to return the type-based value if passed otherwise nil.
  64. // If the "paramValue" is the correct type of the registered parameter type
  65. // and all functions, if any, are passed.
  66. //
  67. // It is called from the converted macro handler (middleware)
  68. // from the higher-level component of "kataras/iris/macro/handler#MakeHandler".
  69. func (p *TemplateParam) Eval(paramValue string) (interface{}, bool) {
  70. if p.TypeEvaluator == nil {
  71. for _, fn := range p.stringInFuncs {
  72. if !fn(paramValue) {
  73. return nil, false
  74. }
  75. }
  76. return paramValue, true
  77. }
  78. // fmt.Printf("macro/template.go#L88: Eval for param value: %s and p.Src: %s\n", paramValue, p.Src)
  79. newValue, passed := p.TypeEvaluator(paramValue)
  80. if !passed {
  81. if newValue != nil && p.HandleError != nil { // return this error only when a HandleError was registered.
  82. if _, ok := newValue.(errorInterface); ok {
  83. return newValue, false // this is an error, see `HandleError` and `MakeFilter`.
  84. }
  85. }
  86. return nil, false
  87. }
  88. if len(p.Funcs) > 0 {
  89. paramIn := []reflect.Value{reflect.ValueOf(newValue)}
  90. for _, evalFunc := range p.Funcs {
  91. // or make it as func(interface{}) bool and pass directly the "newValue"
  92. // but that would not be as easy for end-developer, so keep that "slower":
  93. if !evalFunc.Call(paramIn)[0].Interface().(bool) { // i.e func(paramValue int) bool
  94. return nil, false
  95. }
  96. }
  97. }
  98. // fmt.Printf("macro/template.go: passed with value: %v and type: %T\n", newValue, newValue)
  99. return newValue, true
  100. }
  101. // Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions)
  102. // and returns a new Template.
  103. // It builds all the parameter functions for that template
  104. // and their evaluators, it's the api call that makes use the interpeter's parser -> lexer.
  105. func Parse(src string, macros Macros) (Template, error) {
  106. types := make([]ast.ParamType, len(macros))
  107. for i, m := range macros {
  108. types[i] = m
  109. }
  110. tmpl := Template{Src: src}
  111. params, err := parser.Parse(src, types)
  112. if err != nil {
  113. return tmpl, err
  114. }
  115. for idx, p := range params {
  116. m := macros.Lookup(p.Type)
  117. typEval := m.Evaluator
  118. tmplParam := TemplateParam{
  119. Src: p.Src,
  120. Type: p.Type,
  121. Name: p.Name,
  122. Index: idx,
  123. ErrCode: p.ErrorCode,
  124. HandleError: m.handleError,
  125. TypeEvaluator: typEval,
  126. }
  127. for _, paramfn := range p.Funcs {
  128. tmplFn := m.getFunc(paramfn.Name)
  129. if tmplFn == nil { // if not find on this type, check for Master's which is for global funcs too.
  130. if m := macros.GetMaster(); m != nil {
  131. tmplFn = m.getFunc(paramfn.Name)
  132. }
  133. if tmplFn == nil { // if not found then just skip this param.
  134. continue
  135. }
  136. }
  137. evalFn := tmplFn(paramfn.Args)
  138. if evalFn.IsNil() || !evalFn.IsValid() || evalFn.Kind() != reflect.Func {
  139. continue
  140. }
  141. tmplParam.Funcs = append(tmplParam.Funcs, evalFn)
  142. }
  143. tmpl.Params = append(tmpl.Params, tmplParam.preComputed())
  144. }
  145. return tmpl, nil
  146. }
  147. // CountParams returns the length of the dynamic path's input parameters.
  148. func CountParams(fullpath string, macros Macros) int {
  149. tmpl, _ := Parse(fullpath, macros)
  150. return len(tmpl.Params)
  151. }