PageRenderTime 42ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/src/fan/Router.fan

https://bitbucket.org/afrankvt/draft/
Unknown | 240 lines | 200 code | 40 blank | 0 comment | 0 complexity | 0f16b236ed36debbb7c3f06fd79dcabc MD5 | raw file
  1. //
  2. // Copyright (c) 2011, Andy Frank
  3. // Licensed under the MIT License
  4. //
  5. // History:
  6. // 14 May 2011 Andy Frank Creation
  7. //
  8. using web
  9. **************************************************************************
  10. ** Router
  11. **************************************************************************
  12. **
  13. ** Router handles routing URIs to method handlers.
  14. **
  15. const class Router
  16. {
  17. ** Constructor.
  18. new make(|This| f) { f(this) }
  19. ** RouteGroup configuration.
  20. const RouteGroup[] groups := [,]
  21. ** Route configuration.
  22. const Route[] routes := [,]
  23. ** Match a request to Route. If no matches are found, returns
  24. ** 'null'. The first route that matches is chosen. Routes
  25. ** from `groups` are matched before `routes`.
  26. RouteMatch? match(Uri uri, Str method)
  27. {
  28. for (i:=0; i<groups.size; i++)
  29. {
  30. g := groups[i]
  31. m := _match(g.meta, g.routes, uri, method)
  32. if (m != null) return m
  33. }
  34. return _match(null, routes, uri, method)
  35. }
  36. private RouteMatch? _match([Str:Obj]? meta, Route[] list, Uri uri, Str method)
  37. {
  38. for (i:=0; i<list.size; i++)
  39. {
  40. r := list[i]
  41. m := r.match(uri, method)
  42. if (m != null) return RouteMatch(meta, r, m)
  43. }
  44. return null
  45. }
  46. }
  47. **************************************************************************
  48. ** RouteGroup
  49. **************************************************************************
  50. **
  51. ** RouteGroup models a set of Routes with optional meta-data.
  52. ** If any Routes are matched in a RouteGroup, the meta-data
  53. ** will be stored and available in:
  54. **
  55. ** Str:Obj meta := req.stash["draft.route.meta"]
  56. **
  57. const class RouteGroup
  58. {
  59. ** It-block ctor.
  60. new make(|This| f) { f(this) }
  61. ** Meta-data for this group.
  62. const Str:Obj meta := [:]
  63. ** Routes for this group.
  64. const Route[] routes
  65. }
  66. **************************************************************************
  67. ** Route
  68. **************************************************************************
  69. **
  70. ** Route models how a URI pattern gets routed to a method handler.
  71. ** Example patterns:
  72. **
  73. ** Pattern Uri Args
  74. ** -------------- ------------ ----------
  75. ** "/" `/` [:]
  76. ** "/foo/{bar}" `/foo/12` ["bar":"12"]
  77. ** "/foo/*" `/foo/x/y/z` [:]
  78. ** "/foo/{bar}/*" `/foo/x/y/z` ["bar":"x"]
  79. **
  80. const class Route
  81. {
  82. ** Constructor.
  83. new make(Str pattern, Str method, Method handler)
  84. {
  85. this.pattern = pattern
  86. this.method = method
  87. this.handler = handler
  88. try
  89. {
  90. this.tokens = pattern == "/"
  91. ? RouteToken#.emptyList
  92. : pattern[1..-1].split('/').map |v| { RouteToken(v) }
  93. varIndex := tokens.findIndex |t| { t.type == RouteToken.vararg }
  94. if (varIndex != null && varIndex != tokens.size-1) throw Err()
  95. }
  96. catch (Err err) throw ArgErr("Invalid pattern $pattern.toCode", err)
  97. }
  98. ** URI pattern for this route.
  99. const Str pattern
  100. // TODO FIXIT: confusing b/w HTTP method and Method Handler
  101. ** HTTP method used for this route.
  102. const Str method
  103. ** Method handler for this route. If this method is an instance
  104. ** method, a new intance of the parent type is created before
  105. ** invoking the method.
  106. const Method handler
  107. ** Match this route against the request arguments. If route can
  108. ** be be matched, return the pattern arguments, or return 'null'
  109. ** for no match.
  110. [Str:Str]? match(Uri uri, Str method)
  111. {
  112. // if methods not equal, no match
  113. if (method != this.method) return null
  114. // if size unequal, we know there is no match
  115. path := uri.path
  116. if (tokens.last?.type == RouteToken.vararg)
  117. {
  118. if (path.size < tokens.size) return null
  119. }
  120. else if (tokens.size != path.size) return null
  121. // iterate tokens looking for matches
  122. map := Str:Str[:]
  123. for (i:=0; i<path.size; i++)
  124. {
  125. p := path[i]
  126. t := tokens[i]
  127. switch (t.type)
  128. {
  129. case RouteToken.literal: if (t.val != p) return null
  130. case RouteToken.arg: map[t.val] = p
  131. case RouteToken.vararg: break
  132. }
  133. }
  134. return map
  135. }
  136. ** Parsed tokens.
  137. private const RouteToken[] tokens
  138. }
  139. **************************************************************************
  140. ** RouteToken
  141. **************************************************************************
  142. **
  143. ** RouteToken models each path token in a URI pattern.
  144. **
  145. internal const class RouteToken
  146. {
  147. ** Constructor.
  148. new make(Str val)
  149. {
  150. if (val[0] == '*')
  151. {
  152. this.val = val
  153. this.type = vararg
  154. }
  155. else if (val[0] == '{' && val[-1] == '}')
  156. {
  157. this.val = val[1..-2]
  158. this.type = arg
  159. }
  160. else
  161. {
  162. this.val = val
  163. this.type = literal
  164. }
  165. }
  166. ** Token type.
  167. const Int type
  168. ** Token value.
  169. const Str val
  170. ** Str value is "$type:$val".
  171. override Str toStr() { "$type:$val" }
  172. ** Type id for a literal token.
  173. static const Int literal := 0
  174. ** Type id for an argument token.
  175. static const Int arg := 1
  176. ** Type id for vararg token.
  177. static const Int vararg := 2
  178. }
  179. **************************************************************************
  180. ** RouteMatch
  181. **************************************************************************
  182. **
  183. ** RouteMatch models a matched Route instance.
  184. **
  185. const class RouteMatch
  186. {
  187. ** Constructor
  188. new make([Str:Obj]? meta, Route route, Str:Str args)
  189. {
  190. this.meta = meta
  191. this.route = route
  192. this.args = args
  193. }
  194. ** Optional meta-data for match.
  195. const [Str:Obj]? meta
  196. ** Matched route instance.
  197. const Route route
  198. ** Arguments for matched Route.
  199. const Str:Str args
  200. }