misc/vim: inform the user when no results are found
[nit.git] / lib / nitcorn / vararg_routes.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014 Alexandre Terrasa <alexandre@moz-code.org>
4 #
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
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
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.
16
17 # Routes with uri parameters.
18 #
19 # Using `vararg_routes`, a `Route` can contain variable parts
20 # that will be matched against a `HttpRequest` path.
21 #
22 # Variable parts of path can be specified using the `:` prefix.
23 #
24 # ## Route matching
25 #
26 # Route can match variables expression.
27 #
28 # ~~~
29 # # We need an Action to try routes.
30 # class DummyAction super Action end
31 # var action = new DummyAction
32 #
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
37 # ~~~
38 #
39 # Route without uri parameters still behave like before.
40 #
41 # ~~~
42 # route = new Route("/users", action)
43 # assert route.match("/users")
44 # assert route.match("/users/1234")
45 # assert not route.match("/issues/1234")
46 # ~~~
47 #
48 # ## Route priority
49 #
50 # Priority depends on the order the routes were added to the `Routes` dispatcher.
51 #
52 # ~~~
53 # var host = new VirtualHost("")
54 # var routes = new Routes(host)
55 #
56 # routes.add new Route("/:a/:b/:c", action)
57 # routes.add new Route("/users/:id", action)
58 # routes.add new Route("/:foo", action)
59 #
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"
63 #
64 # assert routes["/users/"].path == "/users/:id"
65 # assert routes["users/"].path == "/users/:id"
66 # assert routes["/users/1234"].path == "/users/:id"
67 #
68 # assert routes["/users"].path == "/:foo"
69 # assert routes["/"].path == "/:foo"
70 # assert routes[""].path == "/:foo"
71 # ~~~
72 #
73 # ## Accessing uri parameter and values
74 #
75 # Parameters can be accessed by parsing the uri.
76 #
77 # ~~~
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"
83 # ~~~
84 #
85 # Or from the `HttpRequest`.
86 #
87 # ~~~
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
94 # ~~~
95 #
96 # Note that normally, all this work is done by nitcorn.
97 # Params can then be accessed in the `HttpRequest` given to `Action::answer`.
98 module vararg_routes
99
100 import server_config
101 import http_request
102
103 # A route to an `Action` according to a `path`
104 redef class Route
105
106 redef init do
107 super
108 parse_pattern(path)
109 end
110
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("/")
116 for part in parts do
117 if not part.is_empty and part.first == ':' then
118 # is an uri param
119 var name = part.substring(1, part.length)
120 var param = new UriParam(name)
121 pattern_parts.add param
122 else
123 # is a standard string
124 pattern_parts.add new UriString(part)
125 end
126 end
127 end
128
129 # `UriPart` forming `self` pattern.
130 private var pattern_parts = new Array[UriPart]
131
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]
141 var part = parts[i]
142 if not ppart.match(part) then return false
143 end
144 return true
145 end
146
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]
157 var part = parts[i]
158 if not ppart.match(part) then return res
159 if ppart isa UriParam then
160 res[ppart.name] = part
161 end
162 end
163 return res
164 end
165
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)
170 end
171 return path
172 end
173 end
174
175 # A String that compose an URI.
176 #
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
181 end
182
183 # An uri parameter string like `:id`.
184 private class UriParam
185 super UriPart
186
187 # Param `name` in the route uri.
188 var name: String
189
190 # Parameters match everything.
191 redef fun match(part) do return true
192 end
193
194 # A static uri string like `users`.
195 private class UriString
196 super UriPart
197
198 # Uri part string.
199 var string: String
200
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
203 end
204
205 redef class Routes
206 # Use `Route::match` instead of `==`.
207 redef fun [](key) do
208 for route in routes do
209 if route.match(key) then return route
210 end
211 return null
212 end
213 end
214
215 redef class HttpRequest
216
217 # Parameters found in uri associated to their values.
218 var uri_params: Map[String, String] = new HashMap[String, String] is public writable
219
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]
224 end
225
226 # List all uri parameters matched by this request.
227 fun params: Array[String] do return uri_params.keys.to_a
228 end