syro 3.0.0 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (7) hide show
  1. checksums.yaml +5 -5
  2. data/.gems +3 -3
  3. data/README.md +85 -7
  4. data/lib/syro.rb +79 -11
  5. data/syro.gemspec +2 -2
  6. data/test/all.rb +89 -0
  7. metadata +7 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 26150785e6dd591e63f5e670b6dcc2a27dd01c08
4
- data.tar.gz: ea5d9a93323838ee03e3ca140946cee52a8cf339
2
+ SHA256:
3
+ metadata.gz: 91ee832b6435348393681438b2e8a167c1d5c56a3ebaaf494b4f3840b716adde
4
+ data.tar.gz: 3ba242a11056f3fd32da0b2198b9c10fa35e5c4f4db7217ec044fadf047f01b3
5
5
  SHA512:
6
- metadata.gz: e07343b0890bf4820471ebe54a7947713956e506465b0b5eff37c42fbeb50b0bf0957f57693d34118b0dcbe40ccd669689d11d4f3263330f712bdb2a0eaf2739
7
- data.tar.gz: 50a533a7933292c1b29fbcfd0bd8f8347128e7b6110ba97ce063939086cd86ffd52b2e4023e8800efee0aed43fa8f24a1a713a99317064140abbbdfc231acce6
6
+ metadata.gz: a4b4c73c4b4d731914a119a81b5bfc1154872f47343744532a0334525e0a2f978878015d1df11324376551c6617818193c0b6d462b9fbd54c3cd535b0a5ea78e
7
+ data.tar.gz: '0825e4cd946e7fba9d84907cce3442b53c158baaf2c0678864df1f0648b3834feb1976c1b62facdeb82e6fb172863958b432ba5ab7d067e26ce52bb3133ffea9'
data/.gems CHANGED
@@ -1,4 +1,4 @@
1
1
  cutest -v 1.2.3
2
- rack -v 2.0.1
3
- seg -v 1.1.0
4
- rack-test -v 0.6.3
2
+ seg -v 1.2.0
3
+ rack -v 2.2.3
4
+ rack-test -v 1.1.0
data/README.md CHANGED
@@ -48,10 +48,10 @@ end
48
48
 
49
49
  The block is evaluated in a sandbox where the following methods are
50
50
  available: `env`, `req`, `res`, `path`, `inbox`, `call`, `run`,
51
- `halt`, `consume`, `capture`, `root?` `match`, `default`, `on`,
52
- `root`,`get`, `put`, `head`, `post`, `patch`, `delete` and `options`.
53
- Three other methods are available for customizations: `default_headers`,
54
- `request_class` and `response_class`.
51
+ `halt`, `handle`, `finish!`, `consume`, `capture`, `root?` `match`,
52
+ `default`, `on`, `root`,`get`, `put`, `head`, `post`, `patch`,
53
+ `delete` and `options`. Three other methods are available for
54
+ customizations: `default_headers`, `request_class` and `response_class`.
55
55
 
56
56
  As a recommendation, user created variables should be instance
57
57
  variables. That way they won't mix with the API methods defined in
@@ -83,6 +83,12 @@ argument.
83
83
  `halt`: Terminates the request. It receives an array with the
84
84
  response as per Rack's specification.
85
85
 
86
+ `handle`: Installs a handler for a given status code. It receives
87
+ a status code and a block that will be executed from `finish!`.
88
+
89
+ `finish!`: Terminates the request by executing any installed handlers
90
+ and then halting with the current value of `res.finish`.
91
+
86
92
  `consume`: Match and consume a path segment.
87
93
 
88
94
  `capture`: Match and capture a path segment. The value is stored in
@@ -93,7 +99,7 @@ the inbox.
93
99
  `match`: Receives a String, a Symbol or a boolean, and returns true
94
100
  if it matches the request.
95
101
 
96
- `default`: Receives a block that will be executed inconditionally.
102
+ `default`: Receives a block that will be executed unconditionally.
97
103
 
98
104
  `on`: Receives a value to be matched, and a block that will be
99
105
  executed only if the request is matched.
@@ -254,6 +260,78 @@ post do
254
260
  end
255
261
  ```
256
262
 
263
+ Handlers
264
+ --------
265
+
266
+ Status code handlers can be installed with the `handle` command,
267
+ which receives a status code and a block to be executed just before
268
+ finishing the request.
269
+
270
+ By default, if there are no matches in a Syro application the
271
+ response is a `404` with an empty body. If we decide to handle the
272
+ `404` requests and return a string, we can do as follows:
273
+
274
+ ```ruby
275
+ App = Syro.new do
276
+ handle 404 do
277
+ res.text "Not found!"
278
+ end
279
+
280
+ get do
281
+ res.text "Found!
282
+ end
283
+ end
284
+ ```
285
+
286
+ In this example, a `GET` request to `"/"` will return a status `200`
287
+ with the body `"Found!"`. Any other request will return a `404`
288
+ with the body `"Not found!"`.
289
+
290
+ If a new handler is installed for the same status code, the previous
291
+ handler is overwritten. A handler is valid in the current scope and
292
+ in all its nested branches. Blocks that end before the handler is
293
+ installed are not affected.
294
+
295
+ This is a contrived example that shows some edge cases when using handlers:
296
+
297
+ ```ruby
298
+ App = Syro.new do
299
+ on "foo" do
300
+ # 404, empty body
301
+ end
302
+
303
+ handle 404 do
304
+ res.text "Not found!"
305
+ end
306
+
307
+ on "bar" do
308
+ # 404, body is "Not found!"
309
+ end
310
+
311
+ on "baz" do
312
+ # 404, body is "Couldn't find baz"
313
+
314
+ handle 404 do
315
+ res.text "Couldn't find baz"
316
+ end
317
+ end
318
+ end
319
+ ```
320
+
321
+ A request to `"/foo"` will return a `404`, because the request
322
+ method was not matched. But as the `on "foo"` block ends before the
323
+ handler is installed, the result will be a blank screen. On the
324
+ other hand, a request to `"/bar"` will return a `404` with the plain
325
+ text `"Not found!"`.
326
+
327
+ Finally, a request to `"/baz"` will return a `404` with the plain text
328
+ `"Couldn't find baz"`, because by the time the `on "baz"` block ends
329
+ a new handler is installed, and thus the previous one is overwritten.
330
+
331
+ Any status code can be handled this way, even status `200`. In that
332
+ case the handler will behave as a filter to be run after each
333
+ successful request.
334
+
257
335
  Content type
258
336
  ------------
259
337
 
@@ -317,11 +395,11 @@ just use `Rack::Builder`:
317
395
  App = Rack::Builder.new do
318
396
  use Rack::Session::Cookie, secret: "..."
319
397
 
320
- run Syro.new do
398
+ run Syro.new {
321
399
  get do
322
400
  res.write("Hello, world")
323
401
  end
324
- end
402
+ }
325
403
  end
326
404
  ```
327
405
 
data/lib/syro.rb CHANGED
@@ -209,11 +209,23 @@ class Syro
209
209
  end
210
210
 
211
211
  class Deck
212
- module API
213
- def initialize(code)
214
- @syro_code = code
212
+
213
+ # Attaches the supplied block to a subclass of Deck as #dispatch!
214
+ # Returns the subclassed Deck.
215
+ def self.implement(&code)
216
+ Class.new(self) do
217
+ define_method(:dispatch!, code)
218
+ private :dispatch!
219
+
220
+ # Instead of calling inspect on this anonymous class,
221
+ # defer to the superclass which is likely Syro::Deck.
222
+ define_method(:inspect) do
223
+ self.class.superclass.inspect
224
+ end
215
225
  end
226
+ end
216
227
 
228
+ module API
217
229
  def env
218
230
  @syro_env
219
231
  end
@@ -268,9 +280,8 @@ class Syro
268
280
  @syro_inbox = inbox
269
281
 
270
282
  catch(:halt) do
271
- instance_eval(&@syro_code)
272
-
273
- @syro_res.finish
283
+ dispatch!
284
+ finish!
274
285
  end
275
286
  end
276
287
 
@@ -278,7 +289,7 @@ class Syro
278
289
  path, script = env[Rack::PATH_INFO], env[Rack::SCRIPT_NAME]
279
290
 
280
291
  env[Rack::PATH_INFO] = @syro_path.curr
281
- env[Rack::SCRIPT_NAME] = @syro_path.prev
292
+ env[Rack::SCRIPT_NAME] = script.to_s + @syro_path.prev
282
293
  env[Syro::INBOX] = inbox
283
294
 
284
295
  halt(app.call(env))
@@ -297,6 +308,64 @@ class Syro
297
308
  throw(:halt, response)
298
309
  end
299
310
 
311
+ # Install a handler for a given status code. Once a handler is
312
+ # installed, it will be called by Syro before halting the
313
+ # request.
314
+ #
315
+ # handle 404 do
316
+ # res.text "Not found!"
317
+ # end
318
+ #
319
+ # If a new handler is installed for the same status code, the
320
+ # previous handler is overwritten. A handler is valid in the
321
+ # current scope and in all its nested branches. Blocks that end
322
+ # before the handler is installed are not affected.
323
+ #
324
+ # For example:
325
+ #
326
+ # on "foo" do
327
+ # # Not found
328
+ # end
329
+ #
330
+ # handle 404 do
331
+ # res.text "Not found!"
332
+ # end
333
+ #
334
+ # on "bar" do
335
+ # # Not found
336
+ # end
337
+ #
338
+ # on "baz" do
339
+ # # Not found
340
+ #
341
+ # handle 404 do
342
+ # res.text "Couldn't find baz"
343
+ # end
344
+ # end
345
+ #
346
+ # A request to "/foo" will return a 404, because the request
347
+ # method was not matched. But as the `on "foo"` block ends
348
+ # before the handler is installed, the result will be a blank
349
+ # screen. On the other hand, a request to "/bar" will return a
350
+ # 404 with the plain text "Not found!".
351
+ #
352
+ # Finally, a request to "/baz" will return a 404 with the plain text
353
+ # "Couldn't find baz", because by the time the `on "baz"` block ends
354
+ # a new handler is installed, and thus the previous one is overwritten.
355
+ #
356
+ # Any status code can be handled this way, even status `200`.
357
+ # In that case the handler will behave as a filter to be run
358
+ # after each successful request.
359
+ #
360
+ def handle(status, &block)
361
+ inbox[status] = block
362
+ end
363
+
364
+ def finish!
365
+ inbox[res.status]&.call
366
+ halt(res.finish)
367
+ end
368
+
300
369
  def consume(arg)
301
370
  @syro_path.consume(arg)
302
371
  end
@@ -319,7 +388,7 @@ class Syro
319
388
  end
320
389
 
321
390
  def default
322
- yield; halt(res.finish)
391
+ yield; finish!
323
392
  end
324
393
 
325
394
  def on(arg)
@@ -363,11 +432,10 @@ class Syro
363
432
  end
364
433
 
365
434
  def initialize(deck = Deck, &code)
366
- @deck = deck
367
- @code = code
435
+ @deck = deck.implement(&code)
368
436
  end
369
437
 
370
438
  def call(env, inbox = env.fetch(Syro::INBOX, {}))
371
- @deck.new(@code).call(env, inbox)
439
+ @deck.new.call(env, inbox)
372
440
  end
373
441
  end
data/syro.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "syro"
3
- s.version = "3.0.0"
3
+ s.version = "3.2.1"
4
4
  s.summary = "Simple router"
5
5
  s.description = "Simple router for web applications"
6
6
  s.authors = ["Michel Martens"]
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.files = `git ls-files`.split("\n")
12
12
 
13
13
  s.add_dependency "seg"
14
- s.add_dependency "rack", "~> 2"
14
+ s.add_dependency "rack", ">= 1.6.0"
15
15
  s.add_development_dependency "cutest"
16
16
  s.add_development_dependency "rack-test"
17
17
  end
data/test/all.rb CHANGED
@@ -79,6 +79,48 @@ comments = Syro.new do
79
79
  end
80
80
  end
81
81
 
82
+ handlers = Syro.new do
83
+ on "without_handler" do
84
+ # Not found
85
+ end
86
+
87
+ handle(404) do
88
+ res.text "Not found!"
89
+ end
90
+
91
+ on "with_handler" do
92
+ # Not found
93
+ end
94
+
95
+ on "with_local_handler" do
96
+ handle(404) do
97
+ res.text "Also not found!"
98
+ end
99
+ end
100
+ end
101
+
102
+ path_info = Syro.new do
103
+ on "foo" do
104
+ get do
105
+ res.text req.path
106
+ end
107
+ end
108
+
109
+ get do
110
+ res.text req.path
111
+ end
112
+ end
113
+
114
+ script_name = Syro.new do
115
+ on "path" do
116
+ run(path_info)
117
+ end
118
+ end
119
+
120
+ exception = Syro.new do
121
+ get { res.text(this_method_does_not_exist) }
122
+ end
123
+
82
124
  app = Syro.new do
83
125
  get do
84
126
  res.write "GET /"
@@ -194,6 +236,10 @@ app = Syro.new do
194
236
  run(json)
195
237
  end
196
238
 
239
+ on "handlers" do
240
+ run(handlers)
241
+ end
242
+
197
243
  on "private" do
198
244
  res.status = 401
199
245
  res.write("Unauthorized")
@@ -214,6 +260,14 @@ app = Syro.new do
214
260
  on "json" do
215
261
  res.json "json!"
216
262
  end
263
+
264
+ on "script" do
265
+ run(script_name)
266
+ end
267
+
268
+ on "exception" do
269
+ run(exception)
270
+ end
217
271
  end
218
272
 
219
273
  setup do
@@ -369,3 +423,38 @@ test "content type" do |f|
369
423
  f.get("/json")
370
424
  assert_equal "application/json", f.last_response.headers["Content-Type"]
371
425
  end
426
+
427
+ test "status code handling" do |f|
428
+ f.get("/handlers")
429
+ assert_equal 404, f.last_response.status
430
+ assert_equal "text/plain", f.last_response.headers["Content-Type"]
431
+ assert_equal "Not found!", f.last_response.body
432
+
433
+ f.get("/handlers/without_handler")
434
+ assert_equal 404, f.last_response.status
435
+ assert_equal nil, f.last_response.headers["Content-Type"]
436
+ assert_equal "", f.last_response.body
437
+
438
+ f.get("/handlers/with_handler")
439
+ assert_equal 404, f.last_response.status
440
+ assert_equal "text/plain", f.last_response.headers["Content-Type"]
441
+ assert_equal "Not found!", f.last_response.body
442
+
443
+ f.get("/handlers/with_local_handler")
444
+ assert_equal 404, f.last_response.status
445
+ assert_equal "text/plain", f.last_response.headers["Content-Type"]
446
+ assert_equal "Also not found!", f.last_response.body
447
+ end
448
+
449
+ test "script name and path info" do |f|
450
+ f.get("/script/path")
451
+ assert_equal 200, f.last_response.status
452
+ assert_equal "/script/path", f.last_response.body
453
+ end
454
+
455
+ test "deck exceptions reference a named class" do |f|
456
+ f.get("/exception")
457
+ rescue NameError => exception
458
+ ensure
459
+ assert exception.to_s.include?("Syro::Deck")
460
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: syro
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michel Martens
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-24 00:00:00.000000000 Z
11
+ date: 2021-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: seg
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: rack
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '2'
33
+ version: 1.6.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '2'
40
+ version: 1.6.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: cutest
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -102,8 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
102
  - !ruby/object:Gem::Version
103
103
  version: '0'
104
104
  requirements: []
105
- rubyforge_project:
106
- rubygems_version: 2.4.5.1
105
+ rubygems_version: 3.0.3
107
106
  signing_key:
108
107
  specification_version: 4
109
108
  summary: Simple router