1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014 Alexandre Terrasa <alexandre@moz-code.org>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Routes with uri parameters.
19 # Using `vararg_routes`, a `Route` can contain variable parts
20 # that will be matched against a `HttpRequest` path.
22 # Variable parts of path can be specified using the `:` prefix.
26 # Route can match variables expression.
29 # # We need an Action to try routes.
30 # class DummyAction super Action end
31 # var action = new DummyAction
33 # var route = new Route("/users/:id", action)
34 # assert not route.match("/users")
35 # assert route.match("/users/1234")
36 # assert route.match("/users/") # empty id
39 # Route without uri parameters still behave like before.
42 # route = new Route("/users", action)
43 # assert route.match("/users")
44 # assert route.match("/users/1234")
45 # assert not route.match("/issues/1234")
50 # Priority depends on the order the routes were added to the `Routes` dispatcher.
53 # var host = new VirtualHost("")
54 # var routes = new Routes(host)
56 # routes.add new Route("/:a/:b/:c", action)
57 # routes.add new Route("/users/:id", action)
58 # routes.add new Route("/:foo", action)
60 # assert routes["/a/b/c"].path == "/:a/:b/:c"
61 # assert routes["/a/b/c/d"].path == "/:a/:b/:c"
62 # assert routes["/users/1234/foo"].path == "/:a/:b/:c"
64 # assert routes["/users/"].path == "/users/:id"
65 # assert routes["users/"].path == "/users/:id"
66 # assert routes["/users/1234"].path == "/users/:id"
68 # assert routes["/users"].path == "/:foo"
69 # assert routes["/"].path == "/:foo"
70 # assert routes[""].path == "/:foo"
73 # ## Accessing uri parameter and values
75 # Parameters can be accessed by parsing the uri.
78 # route = new Route("/users/:id", action)
79 # var params = route.parse_params("/users/1234")
80 # assert params.has_key("id")
81 # assert not params.has_key("foo")
82 # assert params["id"] == "1234"
85 # Or from the `HttpRequest`.
88 # route = new Route("/users/:id", action)
89 # var req = new HttpRequest
90 # req.uri_params = route.parse_params("/users/1234")
91 # assert req.params == ["id"]
92 # assert req.param("id") == "1234"
93 # assert req.param("foo") == null
96 # Note that normally, all this work is done by nitcorn.
97 # Params can then be accessed in the `HttpRequest` given to `Action::answer`.
103 # A route to an `Action` according to a `path`
111 # Cut `path` into `UriParts`.
112 private fun parse_pattern
(path
: nullable String) do
113 if path
== null then return
114 path
= standardize_path
(path
)
115 var parts
= path
.split
("/")
117 if not part
.is_empty
and part
.first
== ':' then
119 var name
= part
.substring
(1, part
.length
)
120 var param
= new UriParam(name
)
121 pattern_parts
.add param
123 # is a standard string
124 pattern_parts
.add
new UriString(part
)
129 # `UriPart` forming `self` pattern.
130 private var pattern_parts
= new Array[UriPart]
132 # Does `self` matches `uri`?
133 fun match
(uri
: nullable String): Bool do
134 if pattern_parts
.is_empty
then return true
135 if uri
== null then return false
136 uri
= standardize_path
(uri
)
137 var parts
= uri
.split
("/")
138 for i
in [0 .. pattern_parts
.length
[ do
139 if i
>= parts
.length
then return false
140 var ppart
= pattern_parts
[i
]
142 if not ppart
.match
(part
) then return false
147 # Extract parameter values from `uri`.
148 fun parse_params
(uri
: nullable String): Map[String, String] do
149 var res
= new HashMap[String, String]
150 if pattern_parts
.is_empty
then return res
151 if uri
== null then return res
152 uri
= standardize_path
(uri
)
153 var parts
= uri
.split
("/")
154 for i
in [0 .. pattern_parts
.length
[ do
155 if i
>= parts
.length
then return res
156 var ppart
= pattern_parts
[i
]
158 if not ppart
.match
(part
) then return res
159 if ppart
isa UriParam then
160 res
[ppart
.name
] = part
166 # Remove first occurence of `/`.
167 private fun standardize_path
(path
: String): String do
168 if not path
.is_empty
and path
.first
== '/' then
169 return path
.substring
(1, path
.length
)
175 # A String that compose an URI.
177 # In practice, UriPart can be parameters or static strings.
178 private interface UriPart
179 # Does `self` matches a part of the uri?
180 fun match
(uri_part
: String): Bool is abstract
183 # An uri parameter string like `:id`.
184 private class UriParam
187 # Param `name` in the route uri.
190 # Parameters match everything.
191 redef fun match
(part
) do return true
194 # A static uri string like `users`.
195 private class UriString
201 # Empty strings match everything otherwise matching is based on string equality.
202 redef fun match
(part
) do return string
.is_empty
or string
== part
206 # Use `Route::match` instead of `==`.
208 for route
in routes
do
209 if route
.match
(key
) then return route
215 redef class HttpRequest
217 # Parameters found in uri associated to their values.
218 var uri_params
: Map[String, String] = new HashMap[String, String] is public
writable
220 # Get the value for parameter `name` or `null`.
221 fun param
(name
: String): nullable String do
222 if not uri_params
.has_key
(name
) then return null
223 return uri_params
[name
]
226 # List all uri parameters matched by this request.
227 fun params
: Array[String] do return uri_params
.keys
.to_a