rack-jet_router 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c763fbc224d9c9c21718bf685203b35694c20f3b
4
- data.tar.gz: 56bc71a1d83180f2970f1d95985fc8b2fa9b91bc
3
+ metadata.gz: dbed04ca4adfdce5a3e82234dd6182e18519dc62
4
+ data.tar.gz: bdec9db77792ffba5bca3e05ae4bf62f35cbf555
5
5
  SHA512:
6
- metadata.gz: b72f539eb7750a754daea1189aa0035e8010a93d7debf74ef7f6a4b927223daf19f4bfbf0165192aab08c0e395289fabc423ee4690e6ab0ff0417603185334f1
7
- data.tar.gz: 565dcf52b9c34e81803474de3c212f2b2b92d543a85057f3ab7959764f724b1ea2a5dc07c11caa4aca06a79aa318410000dabb98001ef4e0cbad4aaaa711fe11
6
+ metadata.gz: 0d90ffbb230351b7f0054d7d382b377dca41832cbfe2a47d6793c0ac88dbc075104535bf4529a6e1c50251365faadefb8d1fa02342d7104420cd8eb6316ea495
7
+ data.tar.gz: af141052683402fd8dedc4538b548bab59c816714e423236fe83cd70d2865f77337f5c6b9e0d93380e49fe4547995e36f256943be6426a20b1d04a82cfe69697
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Rack::JetRouter
2
2
 
3
- ($Release: 1.1.1 $)
3
+ ($Release: 1.2.0 $)
4
4
 
5
5
  Rack::JetRouter is crazy-fast router library for Rack application,
6
6
  derived from [Keight.rb](https://github.com/kwatch/keight/tree/ruby).
@@ -10,22 +10,40 @@ Rack::JetRouter requires Ruby >= 2.0.
10
10
 
11
11
  ## Benchmark
12
12
 
13
- Benchmark script is [here](https://github.com/kwatch/rack-jet_router/blob/dev/bench/bench.rb).
13
+ Benchmark script is [here](https://github.com/kwatch/rack-jet_router/blob/release/bench/bench.rb).
14
14
 
15
- ### JetRouter vs. Rack vs. Sinatra vs. Keight.rb:
15
+ | Name | Version |
16
+ | ------------------ | ------- |
17
+ | Ruby | 2.3.1 |
18
+ | Rack | 1.6.4 |
19
+ | Rack::JetRouter | 1.2.0 |
20
+ | Rack::Multiplexer | 0.0.8 |
21
+ | Sinatra | 1.4.6 |
22
+ | Keight.rb | 0.3.0 |
23
+ | Hanami | 0.8.0 |
24
+
25
+ (Macbook Air, Intel Core i7 1.7GHz, OS X EL Capitan)
26
+
27
+
28
+ ### JetRouter vs. Rack vs. Sinatra vs. Keight.rb vs. Hanami:
16
29
 
17
30
  ```
18
- ## Ranking usec/req Graph (longer=faster)
19
- (Rack plain) /api/aaa01 1.0619 ***************
20
- (Rack plain) /api/aaa01/123 0.8729 ******************
21
- (R::Req+Res) /api/aaa01 9.5361 **
22
- (R::Req+Res) /api/aaa01/123 9.5321 **
23
- (JetRouter) /api/aaa01 1.3231 ************
24
- (JetRouter) /api/aaa01/123 5.9796 ***
25
- (Keight.rb) /api/aaa01 6.4314 **
26
- (Keight.rb) /api/aaa01/123 10.2339 **
27
- (Sinatra) /api/aaa01 104.7575
28
- (Sinatra) /api/aaa01/123 115.8220
31
+ ## Ranking real
32
+ (Rack plain) /api/aaa01 0.9316 (100.0%) ********************
33
+ (Rack plain) /api/aaa01/123 1.0222 ( 91.1%) ******************
34
+ (JetRouter) /api/aaa01 1.4191 ( 65.6%) *************
35
+ (JetRouter) /api/aaa01/123 6.0146 ( 15.5%) ***
36
+ (Multiplexer) /api/aaa01 6.1026 ( 15.3%) ***
37
+ (Keight.rb) /api/aaa01 7.2330 ( 12.9%) ***
38
+ (R::Req+Res) /api/aaa01 10.7835 ( 8.6%) **
39
+ (R::Req+Res) /api/aaa01/123 10.8412 ( 8.6%) **
40
+ (Keight.rb) /api/aaa01/123 10.8708 ( 8.6%) **
41
+ (Hanami::Router) /api/zzz26 11.5185 ( 8.1%) **
42
+ (Hanami::Router) /api/aaa01 11.7033 ( 8.0%) **
43
+ (Hanami::Router) /api/aaa01/123 17.9229 ( 5.2%) *
44
+ (Multiplexer) /api/aaa01/123 18.6987 ( 5.0%) *
45
+ (Sinatra) /api/aaa01 109.7597 ( 0.8%)
46
+ (Sinatra) /api/aaa01/123 121.3258 ( 0.8%)
29
47
  ```
30
48
 
31
49
  * If URL path has no path parameter (such as `/api/hello`),
@@ -34,6 +52,7 @@ Benchmark script is [here](https://github.com/kwatch/rack-jet_router/blob/dev/be
34
52
  Rack::JetRouter becomes slower, but it is enough small (about 6usec/req).
35
53
  * Overhead of Rack::JetRouter is smaller than that of Rack::Reqeuast +
36
54
  Rack::Response.
55
+ * Hanami is a litte slow.
37
56
  * Sinatra is too slow.
38
57
 
39
58
 
@@ -41,17 +60,18 @@ Benchmark script is [here](https://github.com/kwatch/rack-jet_router/blob/dev/be
41
60
 
42
61
  ```
43
62
  ## Ranking usec/req Graph (longer=faster)
44
- (JetRouter) /api/aaa01 1.3231 ************
45
- (JetRouter) /api/aaa01/123 5.9796 ***
46
- (JetRouter) /api/zzz26 1.4089 ***********
47
- (JetRouter) /api/zzz26/789 6.5142 **
48
- (Multiplexer) /api/aaa01 5.9073 ***
49
- (Multiplexer) /api/aaa01/123 18.2102 *
50
- (Multiplexer) /api/zzz26 24.4013 *
51
- (Multiplexer) /api/zzz26/789 36.1558
63
+ (JetRouter) /api/aaa01 1.4191 ( 65.6%) *************
64
+ (JetRouter) /api/zzz26 1.4300 ( 65.1%) *************
65
+ (JetRouter) /api/aaa01/123 6.0146 ( 15.5%) ***
66
+ (Multiplexer) /api/aaa01 6.1026 ( 15.3%) ***
67
+ (JetRouter) /api/zzz26/789 6.9102 ( 13.5%) ***
68
+ (Multiplexer) /api/aaa01/123 18.6987 ( 5.0%) *
69
+ (Multiplexer) /api/zzz26 30.7618 ( 3.0%) *
70
+ (Multiplexer) /api/zzz26/789 42.6660 ( 2.2%)
52
71
  ```
53
72
 
54
- * JetRouter is about 3~5 times faster than Rack::Multiplexer.
73
+ * JetRouter is about 4~6 times faster than Rack::Multiplexer.
74
+ * Rack::Multiplexer is getting worse in promotion to the number of URL paths.
55
75
 
56
76
 
57
77
  ## Examples
@@ -199,6 +219,18 @@ end
199
219
  ```
200
220
 
201
221
 
222
+ ### Auto-redirection.
223
+
224
+ Rack::JetRouter implements auto-redirection.
225
+
226
+ * When `/foo` is provided and `/foo/` is requested, then Rack::JetRouter redirects to `/foo` automatically.
227
+ * When `/foo/` is provided and `/foo` is requested, then Rack::JetRouter redirects to `/foo/` automatically.
228
+
229
+ Notice that auto-redirection is occurred only on `GET` or `HEAD` methods, because
230
+ browser cannot handle redirection on `POST`, `PUT`, and `DELETE` methods correctly.
231
+ Don't depend on auto-redirection feature so much.
232
+
233
+
202
234
  ### Variable URL Path Cache
203
235
 
204
236
  It is useful to classify URL path patterns into two types: fixed and variable.
@@ -249,7 +281,7 @@ Above methods are invoked from `Rack::JetRouter#call()`.
249
281
 
250
282
  ## Copyright and License
251
283
 
252
- $Copyright: copyright(c) 2015 kuwata-lab.com all rights reserved $
284
+ $Copyright: copyright(c) 2015-2016 kuwata-lab.com all rights reserved $
253
285
 
254
286
  $License: MIT License $
255
287
 
@@ -257,6 +289,19 @@ $License: MIT License $
257
289
  ## History
258
290
 
259
291
 
292
+ ### 2016-10-16: Release 1.2.0
293
+
294
+ * Change auto-redirection to be occurred only on GET or HEAD methods.
295
+ * Code is rewrited, especially around `Rack::JetRouter#compile_mapping()`.
296
+ * Update benchmark script to support `Hanabi::Router`.
297
+
298
+
299
+ ### 2015-12-29: Release 1.1.1
300
+
301
+ * Fix benchmark script.
302
+ * Fix document.
303
+
304
+
260
305
  ### 2015-12-28: Release 1.1.0
261
306
 
262
307
  * **NOTICE** `Rack::JetRouter#find()` is renamed to `#lookup()`.<br>
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  ###
4
- ### $Release: 1.1.1 $
4
+ ### $Release: 1.2.0 $
5
5
  ### $Copyright: copyright(c) 2015 kuwata-lab.com all rights reserved $
6
6
  ### $License: MIT License $
7
7
  ###
@@ -55,7 +55,7 @@ module Rack
55
55
  ##
56
56
  class JetRouter
57
57
 
58
- RELEASE = '$Release: 1.1.1 $'.split()[1]
58
+ RELEASE = '$Release: 1.2.0 $'.split()[1]
59
59
 
60
60
  def initialize(mapping, urlpath_cache_size: 0,
61
61
  enable_urlpath_param_range: true)
@@ -64,12 +64,15 @@ module Rack
64
64
  (@urlpath_rexp, # ex: {'/api/books'=>BooksApp}
65
65
  @fixed_urlpath_dict, # ex: [[%r'\A/api/books/([^./]+)\z', ['id'], BookApp]]
66
66
  @variable_urlpath_list, # ex: %r'\A(?:/api(?:/books(?:/[^./]+(\z))))\z'
67
+ @all_entrypoints, # ex: [['/api/books', BooksAPI'], ['/api/orders', OrdersAPI]]
67
68
  ) = compile_mapping(mapping)
68
69
  ## cache for variable urlpath (= containg urlpath parameters)
69
70
  @urlpath_cache_size = urlpath_cache_size
70
71
  @variable_urlpath_cache = urlpath_cache_size > 0 ? {} : nil
71
72
  end
72
73
 
74
+ attr_reader :urlpath_rexp
75
+
73
76
  ## Finds rack app according to PATH_INFO and REQUEST_METHOD and invokes it.
74
77
  def call(env)
75
78
  #; [!fpw8x] finds mapped app according to env['PATH_INFO'].
@@ -77,10 +80,15 @@ module Rack
77
80
  app, urlpath_params = lookup(req_path)
78
81
  #; [!wxt2g] guesses correct urlpath and redirects to it automaticaly when request path not found.
79
82
  #; [!3vsua] doesn't redict automatically when request path is '/'.
80
- unless app || req_path == '/'
83
+ if ! app && should_redirect?(env)
81
84
  location = req_path =~ /\/\z/ ? req_path[0..-2] : req_path + '/'
82
85
  app, urlpath_params = lookup(location)
83
- return redirect_to(location) if app
86
+ if app
87
+ #; [!hyk62] adds QUERY_STRING to redirect location.
88
+ qs = env['QUERY_STRING']
89
+ location = "#{location}?#{qs}" if qs && ! qs.empty?
90
+ return redirect_to(location)
91
+ end
84
92
  end
85
93
  #; [!30x0k] returns 404 when request urlpath not found.
86
94
  return error_not_found(env) unless app
@@ -143,6 +151,12 @@ module Rack
143
151
 
144
152
  alias find lookup # :nodoc: # for backward compatilibity
145
153
 
154
+ ## Yields pair of urlpath pattern and app.
155
+ def each(&block)
156
+ #; [!ep0pw] yields pair of urlpath pattern and app.
157
+ @all_entrypoints.each(&block)
158
+ end
159
+
146
160
  protected
147
161
 
148
162
  ## Returns [404, {...}, [...]]. Override in subclass if necessary.
@@ -157,6 +171,18 @@ module Rack
157
171
  return [405, {"Content-Type"=>"text/plain"}, ["405 Method Not Allowed"]]
158
172
  end
159
173
 
174
+ ## Returns false when request path is '/' or request method is not GET nor HEAD.
175
+ ## (It is not recommended to redirect when request method is POST, PUT or DELETE,
176
+ ## because browser doesn't handle redirect correctly on those methods.)
177
+ def should_redirect?(env)
178
+ #; [!dsu34] returns false when request path is '/'.
179
+ #; [!ycpqj] returns true when request method is GET or HEAD.
180
+ #; [!7q8xu] returns false when request method is POST, PUT or DELETE.
181
+ return false if env['PATH_INFO'] == '/'
182
+ req_method = env['REQUEST_METHOD']
183
+ return req_method == 'GET' || req_method == 'HEAD'
184
+ end
185
+
160
186
  ## Returns [301, {"Location"=>location, ...}, [...]]. Override in subclass if necessary.
161
187
  def redirect_to(location)
162
188
  content = "Redirect to #{location}"
@@ -188,73 +214,73 @@ module Rack
188
214
 
189
215
  ## Compiles urlpath mapping. Called from '#initialize()'.
190
216
  def compile_mapping(mapping)
191
- rexp_buf = ['\A']
192
- fixed_urlpaths = {} # ex: {'/api/books'=>BooksApp}
193
- variable_urlpaths = [] # ex: [[%r'\A/api/books/([^./]+)\z', ['id'], BookApp]]
194
- _compile_array(mapping, rexp_buf, '', '',
195
- fixed_urlpaths, variable_urlpaths)
196
- ## ex: %r'\A(?:/api(?:/books(?:/[^./]+(\z)|/[^./]+/edit(\z))))\z'
197
- rexp_buf << '\z'
198
- urlpath_rexp = Regexp.new(rexp_buf.join())
217
+ ## entry points which has no urlpath parameters
218
+ ## ex:
219
+ ## { '/' => HomeApp,
220
+ ## '/api/books' => BooksApp,
221
+ ## '/api/authors => AuthorsApp,
222
+ ## }
223
+ dict = {}
224
+ ## entry points which has one or more urlpath parameters
225
+ ## ex:
226
+ ## [
227
+ ## [%r!\A/api/books/([^./]+)\z!, ["id"], BookApp, (11..-1)],
228
+ ## [%r!\A/api/authors/([^./]+)\z!, ["id"], AuthorApp, (12..-1)],
229
+ ## ]
230
+ list = []
231
+ #
232
+ all = []
233
+ rexp_str = _compile_mapping(mapping, "", "") do |entry_point|
234
+ obj, urlpath_pat, urlpath_rexp, param_names = entry_point
235
+ all << [urlpath_pat, obj]
236
+ if urlpath_rexp
237
+ range = @enable_urlpath_param_range ? range_of_urlpath_param(urlpath_pat) : nil
238
+ list << [urlpath_rexp, param_names, obj, range]
239
+ else
240
+ dict[urlpath_pat] = obj
241
+ end
242
+ end
243
+ ## ex: %r!^A(?:api(?:/books/[^./]+(\z)|/authors/[^./]+(\z)))\z!
244
+ urlpath_rexp = Regexp.new("\\A#{rexp_str}\\z")
199
245
  #; [!xzo7k] returns regexp, hash, and array.
200
- return urlpath_rexp, fixed_urlpaths, variable_urlpaths
246
+ return urlpath_rexp, dict, list, all
201
247
  end
202
248
 
203
- def _compile_array(mapping, rexp_buf, base_urlpath_pat, urlpath_pat,
204
- fixed_dict, variable_list)
205
- rexp_str, _ = compile_urlpath_pattern(urlpath_pat, false)
206
- rexp_buf << rexp_str
207
- rexp_buf << '(?:'
208
- len = rexp_buf.length
209
- mapping.each do |child_urlpath_pat, obj|
210
- rexp_buf << '|' if rexp_buf.length != len
211
- curr_urlpath_pat = "#{base_urlpath_pat}#{urlpath_pat}"
249
+ def _compile_mapping(mapping, base_urlpath, parent_urlpath, &block)
250
+ arr = []
251
+ mapping.each do |urlpath, obj|
252
+ full_urlpath = "#{base_urlpath}#{urlpath}"
212
253
  #; [!ospaf] accepts nested mapping.
213
254
  if obj.is_a?(Array)
214
- _compile_array(obj, rexp_buf, curr_urlpath_pat, child_urlpath_pat,
215
- fixed_dict, variable_list)
255
+ rexp_str = _compile_mapping(obj, full_urlpath, urlpath, &block)
216
256
  #; [!2ktpf] handles end-point.
217
257
  else
218
- _compile_object(obj, rexp_buf, curr_urlpath_pat, child_urlpath_pat,
219
- fixed_dict, variable_list)
258
+ #; [!guhdc] if mapping dict is specified...
259
+ if obj.is_a?(Hash)
260
+ obj = normalize_mapping_keys(obj)
261
+ end
262
+ #; [!vfytw] handles urlpath pattern as variable when urlpath param exists.
263
+ full_urlpath_rexp_str, param_names = compile_urlpath_pattern(full_urlpath, true)
264
+ if param_names # has urlpath params
265
+ full_urlpath_rexp = Regexp.new("\\A#{full_urlpath_rexp_str}\\z")
266
+ rexp_str, _ = compile_urlpath_pattern(urlpath, false)
267
+ rexp_str << '(\z)'
268
+ entry_point = [obj, full_urlpath, full_urlpath_rexp, param_names]
269
+ #; [!l63vu] handles urlpath pattern as fixed when no urlpath params.
270
+ else # has no urlpath params
271
+ entry_point = [obj, full_urlpath, nil, nil]
272
+ end
273
+ yield entry_point
220
274
  end
275
+ arr << rexp_str if rexp_str
221
276
  end
222
- #; [!gfxgr] deletes unnecessary grouping.
223
- if rexp_buf.length == len
224
- x = rexp_buf.pop() # delete '(?:'
225
- x == '(?:' or raise "assertion failed"
226
- #; [!pv2au] deletes unnecessary urlpath regexp.
227
- x = rexp_buf.pop() # delete rexp_str
228
- x == rexp_str or raise "assertion failed"
229
- #; [!bh9lo] deletes unnecessary grouping which contains only an element.
230
- elsif rexp_buf.length == len + 1
231
- rexp_buf[-2] == '(?:' or raise "assertion failed: rexp_buf[-2]=#{rexp_buf[-2].inspect}"
232
- rexp_buf[-2] = ''
233
- else
234
- rexp_buf << ')'
235
- end
236
- end
237
-
238
- def _compile_object(obj, rexp_buf, base_urlpath_pat, urlpath_pat,
239
- fixed_dict, variable_list)
240
- #; [!guhdc] if mapping dict is specified...
241
- if obj.is_a?(Hash)
242
- obj = normalize_mapping_keys(obj)
243
- end
244
- #; [!l63vu] handles urlpath pattern as fixed when no urlpath params.
245
- full_urlpath_pat = "#{base_urlpath_pat}#{urlpath_pat}"
246
- full_urlpath_rexp_str, param_names = compile_urlpath_pattern(full_urlpath_pat, true)
247
- fixed_pattern = param_names.nil?
248
- if fixed_pattern
249
- fixed_dict[full_urlpath_pat] = obj
250
- #; [!vfytw] handles urlpath pattern as variable when urlpath param exists.
251
- else
252
- rexp_str, _ = compile_urlpath_pattern(urlpath_pat, false)
253
- rexp_buf << (rexp_str << '(\z)')
254
- full_urlpath_rexp = Regexp.new("\\A#{full_urlpath_rexp_str}\\z")
255
- range = @enable_urlpath_param_range ? range_of_urlpath_param(full_urlpath_pat) : nil
256
- variable_list << [full_urlpath_rexp, param_names, obj, range]
257
- end
277
+ #; [!pv2au] deletes unnecessary urlpath regexp.
278
+ return nil if arr.empty?
279
+ #; [!bh9lo] deletes unnecessary grouping.
280
+ parent_urlpath_rexp_str, _ = compile_urlpath_pattern(parent_urlpath, false)
281
+ return "#{parent_urlpath_rexp_str}#{arr[0]}" if arr.length == 1
282
+ #; [!iza1g] adds grouping if necessary.
283
+ return "#{parent_urlpath_rexp_str}(?:#{arr.join('|')})"
258
284
  end
259
285
 
260
286
  ## Compiles '/books/:id' into ['/books/([^./]+)', ["id"]].
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "rack-jet_router"
5
- spec.version = '$Release: 1.1.1 $'.split()[1]
5
+ spec.version = '$Release: 1.2.0 $'.split()[1]
6
6
  spec.authors = ["makoto kuwata"]
7
7
  spec.email = ["kwa(at)kuwata-lab.com"]
8
8
 
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  ###
4
- ### $Release: 1.1.1 $
4
+ ### $Release: 1.2.0 $
5
5
  ### $Copyright: copyright(c) 2015 kuwata-lab.com all rights reserved $
6
6
  ### $License: MIT License $
7
7
  ###
@@ -154,23 +154,39 @@ describe Rack::JetRouter do
154
154
  end
155
155
  end
156
156
 
157
- it "[!gfxgr] deletes unnecessary grouping." do
157
+ it "[!iza1g] adds grouping if necessary." do
158
158
  mapping = [
159
- ['/' , welcome_app],
160
- ['/api/books' , book_list_api],
159
+ ['/api', [
160
+ ['/books', [
161
+ ['/:id' , book_show_api],
162
+ ]],
163
+ ]],
164
+ ['/admin', [
165
+ ['/books', [
166
+ ['/:id' , admin_book_show_app],
167
+ ]],
168
+ ]],
161
169
  ]
162
170
  expected = '
163
171
  \A
172
+ (?:
173
+ /api
174
+ /books
175
+ /[^./]+(\z)
176
+ |
177
+ /admin
178
+ /books
179
+ /[^./]+(\z)
180
+ )
164
181
  \z
165
182
  '.gsub(/\s+/, '')
166
183
  jet_router.instance_exec(self) do |_|
167
184
  rexp, dict, list = compile_mapping(mapping)
168
185
  _.ok {rexp} == Regexp.new(expected)
169
- _.ok {dict} == {
170
- '/' => welcome_app,
171
- '/api/books' => book_list_api,
172
- }
186
+ _.ok {dict} == {}
173
187
  _.ok {list} == [
188
+ [%r'\A/api/books/([^./]+)\z', ['id'], book_show_api, (11..-1)],
189
+ [%r'\A/admin/books/([^./]+)\z', ['id'], admin_book_show_app, (13..-1)],
174
190
  ]
175
191
  end
176
192
  end
@@ -188,17 +204,7 @@ describe Rack::JetRouter do
188
204
  ]],
189
205
  ]],
190
206
  ]
191
- expected = '
192
- \A
193
- (?:
194
- /api
195
- (?:
196
- /books2
197
- /[^./]+(\z)
198
- )
199
- )
200
- \z
201
- '.gsub(/\s+/, '')
207
+ expected = '\A/api/books2/[^./]+(\z)\z'
202
208
  jet_router.instance_exec(self) do |_|
203
209
  rexp, dict, list = compile_mapping(mapping)
204
210
  _.ok {rexp} == Regexp.new(expected)
@@ -213,7 +219,7 @@ describe Rack::JetRouter do
213
219
  end
214
220
  end
215
221
 
216
- it "[!bh9lo] deletes unnecessary grouping which contains only an element." do
222
+ it "[!bh9lo] deletes unnecessary grouping." do
217
223
  mapping = [
218
224
  ['/api', [
219
225
  ['/books', [
@@ -223,13 +229,9 @@ describe Rack::JetRouter do
223
229
  ]
224
230
  expected = '
225
231
  \A
226
- (?:
227
- /api
228
- (?:
229
- /books
230
- /[^./]+(\z)
231
- )
232
- )
232
+ /api
233
+ /books
234
+ /[^./]+(\z)
233
235
  \z
234
236
  '.gsub(/\s+/, '')
235
237
  jet_router.instance_exec(self) do |_|
@@ -318,16 +320,10 @@ describe Rack::JetRouter do
318
320
  ]
319
321
  expected = '
320
322
  \A
321
- (?:
322
323
  /admin
323
- (?:
324
- /api
325
- (?:
326
- /books
327
- /[^./]+(\z)
328
- )
329
- )
330
- )
324
+ /api
325
+ /books
326
+ /[^./]+(\z)
331
327
  \z
332
328
  '.gsub(/\s+/, '')
333
329
  jet_router.instance_exec(self) do |_|
@@ -379,6 +375,38 @@ describe Rack::JetRouter do
379
375
  end
380
376
 
381
377
 
378
+ describe '#should_redirect?' do
379
+
380
+ it "[!dsu34] returns false when request path is '/'." do
381
+ jet_router.instance_exec(self) do |_|
382
+ _.ok {should_redirect?(_.new_env('GET' , '/'))} == false
383
+ _.ok {should_redirect?(_.new_env('POST' , '/'))} == false
384
+ _.ok {should_redirect?(_.new_env('PUT' , '/'))} == false
385
+ _.ok {should_redirect?(_.new_env('DELETE', '/'))} == false
386
+ _.ok {should_redirect?(_.new_env('HEAD' , '/'))} == false
387
+ _.ok {should_redirect?(_.new_env('PATCH' , '/'))} == false
388
+ end
389
+ end
390
+
391
+ it "[!ycpqj] returns true when request method is GET or HEAD." do
392
+ jet_router.instance_exec(self) do |_|
393
+ _.ok {should_redirect?(_.new_env('GET' , '/index'))} == true
394
+ _.ok {should_redirect?(_.new_env('HEAD' , '/index'))} == true
395
+ end
396
+ end
397
+
398
+ it "[!7q8xu] returns false when request method is POST, PUT or DELETE." do
399
+ jet_router.instance_exec(self) do |_|
400
+ _.ok {should_redirect?(_.new_env('POST' , '/index'))} == false
401
+ _.ok {should_redirect?(_.new_env('PUT' , '/index'))} == false
402
+ _.ok {should_redirect?(_.new_env('DELETE', '/index'))} == false
403
+ _.ok {should_redirect?(_.new_env('PATCH' , '/index'))} == false
404
+ end
405
+ end
406
+
407
+ end
408
+
409
+
382
410
  describe '#error_not_found()' do
383
411
 
384
412
  it "[!mlruv] returns 404 response." do
@@ -422,9 +450,8 @@ describe Rack::JetRouter do
422
450
  )
423
451
  |
424
452
  /admin
425
- (?:/books
453
+ /books
426
454
  /[^./]+(\z)
427
- )
428
455
  )
429
456
  \z
430
457
  '.gsub(/\s+/, '')
@@ -591,6 +618,13 @@ describe Rack::JetRouter do
591
618
  ok {r.call(new_env(:GET, '/'))} == [404, {"Content-Type"=>"text/plain"}, ["404 Not Found"]]
592
619
  end
593
620
 
621
+ it "[!hyk62] adds QUERY_STRING to redirect location." do
622
+ headers = {"Content-Type"=>"text/plain", "Location"=>"/api/books?x=1&y=2"}
623
+ content = "Redirect to /api/books?x=1&y=2"
624
+ env = new_env(:GET, '/api/books/', {"QUERY_STRING"=>"x=1&y=2"})
625
+ ok {jet_router.call(env)} == [301, headers, [content]]
626
+ end
627
+
594
628
  it "[!30x0k] returns 404 when request urlpath not found." do
595
629
  expected = [404, {"Content-Type"=>"text/plain"}, ["404 Not Found"]]
596
630
  ok {jet_router.call(new_env(:GET, '/xxx'))} == expected
@@ -654,6 +688,28 @@ describe Rack::JetRouter do
654
688
  end
655
689
 
656
690
 
691
+ describe '#each()' do
692
+
693
+ it "[!ep0pw] yields pair of urlpath pattern and app." do
694
+ arr = []
695
+ jet_router.each do |upath, app|
696
+ arr << [upath, app]
697
+ end
698
+ ok {arr[0]} == ["/", welcome_app]
699
+ ok {arr[1]} == ["/index.html", welcome_app]
700
+ ok {arr[2]} == ["/api/books", book_list_api]
701
+ ok {arr[3]} == ["/api/books/new", book_new_api]
702
+ ok {arr[4]} == ["/api/books/:id", book_show_api]
703
+ ok {arr[5]} == ["/api/books/:id/edit", book_edit_api]
704
+ ok {arr[6]} == ["/api/books/:book_id/comments", comment_create_api]
705
+ ok {arr[7]} == ["/api/books/:book_id/comments/:comment_id", comment_update_api]
706
+ ok {arr[8]} == ["/admin/books", {"GET"=>admin_book_list_app, "POST"=>admin_book_create_app}]
707
+ ok {arr[9]} == ["/admin/books/:id", {"GET"=>admin_book_show_app, "PUT"=>admin_book_update_app, "DELETE"=>admin_book_delete_app}]
708
+ end
709
+
710
+ end
711
+
712
+
657
713
  describe 'REQUEST_METHODS' do
658
714
 
659
715
  it "[!haggu] contains available request methods." do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-jet_router
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - makoto kuwata
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-29 00:00:00.000000000 Z
11
+ date: 2016-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler