scorched 0.27 → 1.0.0.pre

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: 492839ee866fd2c9d5eaf33de738c1661008ed93
4
- data.tar.gz: 2ce3ad605482f1d5a96973cddb4fa4ab910d4db7
3
+ metadata.gz: 88dac2edee54f46c4bea80fdb1e9d521ae23eb5e
4
+ data.tar.gz: 8aff4a99ef904555fe547b3f305731fa818c3d46
5
5
  SHA512:
6
- metadata.gz: 2cecfe761d1070e78f7b112dd158aed15768dbe3be19e9953c24c578b36b26487f3f4905632a4346326fe9d3eaa0a948dd7e9724c0aec6643b2bd2f80a02861e
7
- data.tar.gz: 9f21239c86254de3d08c1c03672a053d7a6163b4afafe2c6aa662801702dd36a77b5ed4e93934fce5f74c8d7bf212e84f6cf6757767b7636b5f6f0f374eca374
6
+ metadata.gz: aa17c2cb4ad6bd62eaee321941016471273c1a73a3344e459c57ccd65687aba9ff352b3674e193024afbc5b3f6f65da20b69a35a5a7fddb3446179731200253b
7
+ data.tar.gz: d68e85e3ac64e463ac503d2c6621fee4b60798a055f613e4604287faa80e1753fd2cb03ffa2cf7240b9365100d3c76d3666d165147bc8e04bcfa3e5853ba6aaa
data/CHANGES.md CHANGED
@@ -3,6 +3,11 @@ Changelog
3
3
 
4
4
  _Note that Scorched is yet to reach a v1.0 release. This means breaking changes may still be made. If upgrading the version of Scorched for your project, review this changelog carefully._
5
5
 
6
+ ### v1.0.0.pre
7
+ * Refactored `process` method. Now named `respond`, and breaks out to other methods. The logic surrounding filters and halting has also been modified, as is now more intuitive in my opinion.
8
+ * Removed `@_handled` instance variable, and the related `handled` condition.
9
+ * `redirect` method signature has changed.
10
+ * Scorched now depends on Rack 2.0 or above.
6
11
  ### v0.27
7
12
  * Fixed logic surrounding when a requested is considered "handled" (i.e. matched and dispatched) and exceptions that are raised after dispatch. In simpler terms, the `failed_condition` condition now has better logic in the event of an exception.
8
13
  ### v0.26
data/TODO.md CHANGED
@@ -17,4 +17,3 @@ Refactor Considerations
17
17
  If I undergo a significant refactor of Scorched, here's a list of things I'd like to improve:
18
18
 
19
19
  * Create a basic plugin system for optional features which may incur a performance and/or complexity overhead, e.g. Symbol matchers, content for. This will also negate the need for any kind of `contrib` library.
20
- * Make route dispatching more modular, perhaps by simply breaking it out into more individual methods, making it more practical to override default behaviour.
@@ -1,13 +1,18 @@
1
1
  require File.expand_path('../../lib/scorched.rb', __FILE__)
2
2
 
3
3
  class App < Scorched::Controller
4
- get '/:name' do
5
- @message = greeting(captures[:name])
6
- render :hello
4
+ get '/' do
5
+ 'root'
7
6
  end
8
7
 
9
- def greeting(name)
10
- "Howdy #{name}"
8
+ controller '/' do
9
+ get '/login' do
10
+ 'login'
11
+ end
12
+
13
+ get '/logout' do
14
+ 'logout'
15
+ end
11
16
  end
12
17
  end
13
18
 
@@ -55,7 +55,7 @@ module Scorched
55
55
  [*encodings].any? { |encoding| env['rack-accept.request'].encoding? encoding }
56
56
  },
57
57
  failed_condition: proc { |conditions|
58
- if !matches.empty? && matches.any? { |m| m.failed_condition } && !@_handled
58
+ if !matches.empty? && matches.any? { |m| m.failed_condition } && !@_dispatched
59
59
  matches.first.failed_condition && [*conditions].include?(matches.first.failed_condition[0])
60
60
  end
61
61
  },
@@ -71,9 +71,6 @@ module Scorched
71
71
  method: proc { |methods|
72
72
  [*methods].include?(request.request_method)
73
73
  },
74
- handled: proc { |bool|
75
- @_handled == bool
76
- },
77
74
  proc: proc { |blocks|
78
75
  [*blocks].all? { |b| instance_exec(&b) }
79
76
  },
@@ -118,7 +115,7 @@ module Scorched
118
115
  unless @instance_cache[key]
119
116
  builder = Rack::Builder.new
120
117
  to_load.each { |proc| builder.instance_exec(self, &proc) }
121
- builder.run(lambda { |env| self.new(env).process })
118
+ builder.run(lambda { |env| self.new(env).respond })
122
119
  @instance_cache[key] = builder.to_app
123
120
  end
124
121
  loaded.merge(to_load)
@@ -274,7 +271,8 @@ module Scorched
274
271
 
275
272
  # This is where the magic happens. Applies filters, matches mappings, applies error handlers, catches :halt and
276
273
  # :pass, etc.
277
- def process
274
+ # Returns a rack-compatible tuple
275
+ def respond
278
276
  inner_error = nil
279
277
  rescue_block = proc do |e|
280
278
  (env['rack.exception'] = e && raise) unless filters[:error].any? do |f|
@@ -285,61 +283,47 @@ module Scorched
285
283
  end
286
284
 
287
285
  begin
288
- catch(:halt) do
289
- if config[:strip_trailing_slash] == :redirect && request.path =~ %r{[^/]/+$}
290
- query_string = request.query_string.empty? ? '' : '?' << request.query_string
291
- redirect(request.path.chomp('/') + query_string, 307)
292
- end
293
- eligable_matches = matches.reject { |m| m.failed_condition }
294
- pass if config[:auto_pass] && eligable_matches.empty?
295
- run_filters(:before)
296
- begin
297
- # Re-order matches based on media_type, ensuring priority and definition order are respected appropriately.
298
- eligable_matches.each_with_index.sort_by { |m,idx|
299
- [
300
- m.mapping[:priority] || 0,
301
- [*m.mapping[:conditions][:media_type]].map { |type|
302
- env['scorched.accept'][:accept].rank(type, true)
303
- }.max || 0,
304
- -idx
305
- ]
306
- }.reverse.each { |match,idx|
307
- request.breadcrumb << match
308
- catch(:pass) {
309
- begin
310
- catch(:halt) do
311
- dispatch(match)
312
- end
313
- rescue
314
- @_handled = true
315
- raise
316
- end
317
- @_handled = true
318
- }
319
- break if @_handled
320
- request.breadcrumb.pop
321
- }
322
- unless @_handled
323
- response.status = (!matches.empty? && eligable_matches.empty?) ? 403 : 404
286
+ if config[:strip_trailing_slash] == :redirect && request.path =~ %r{[^/]/+$}
287
+ query_string = request.query_string.empty? ? '' : '?' << request.query_string
288
+ redirect(request.path.chomp('/') + query_string, status: 307, halt: false)
289
+ return response.finish
290
+ end
291
+ pass if config[:auto_pass] && eligable_matches.empty?
292
+
293
+ if run_filters(:before)
294
+ catch(:halt) {
295
+ begin
296
+ try_matches
297
+ rescue => inner_error
298
+ rescue_block.call(inner_error)
324
299
  end
325
- rescue => inner_error
326
- rescue_block.call(inner_error)
327
- end
328
- run_filters(:after)
329
- true
330
- end || begin
331
- run_filters(:before, true)
332
- run_filters(:after, true)
300
+ }
333
301
  end
302
+ run_filters(:after)
334
303
  rescue => outer_error
335
304
  outer_error == inner_error ? raise : catch(:halt) { rescue_block.call(outer_error) }
336
305
  end
337
306
  response.finish
338
307
  end
339
308
 
309
+ # Tries to dispatch to each eligable match. If the first match _passes_, tries the second match and so on.
310
+ # If there are no eligable matches, or all eligable matches pass, an appropriate 4xx response status is set.
311
+ def try_matches
312
+ eligable_matches.each do |match,idx|
313
+ request.breadcrumb << match
314
+ catch(:pass) {
315
+ dispatch(match)
316
+ return true
317
+ }
318
+ request.breadcrumb.pop # Current match passed, so pop the breadcrumb before the next iteration.
319
+ end
320
+ response.status = (!matches.empty? && eligable_matches.empty?) ? 403 : 404
321
+ end
322
+
340
323
  # Dispatches the request to the matched target.
341
- # Overriding this method provides the oppurtunity for one to have more control over how mapping targets are invoked.
324
+ # Overriding this method provides the opportunity for one to have more control over how mapping targets are invoked.
342
325
  def dispatch(match)
326
+ @_dispatched = true
343
327
  target = match.mapping[:target]
344
328
  response.merge! begin
345
329
  if Proc === target
@@ -359,24 +343,41 @@ module Scorched
359
343
  # The `:eligable` attribute of the `Match` object indicates whether the conditions for that mapping passed.
360
344
  # The result is cached for the life time of the controller instance, for the sake of effecient recalling.
361
345
  def matches
362
- return @_matches if @_matches
363
- to_match = request.unmatched_path
364
- to_match = to_match.chomp('/') if config[:strip_trailing_slash] == :ignore && to_match =~ %r{./$}
365
- @_matches = mappings.map { |mapping|
366
- mapping[:pattern].match(to_match) do |match_data|
367
- if match_data.pre_match == ''
368
- if match_data.names.empty?
369
- captures = match_data.captures
370
- else
371
- captures = Hash[match_data.names.map {|v| v.to_sym}.zip(match_data.captures)]
372
- captures.each do |k,v|
373
- captures[k] = symbol_matchers[k][1].call(v) if Array === symbol_matchers[k]
346
+ @_matches ||= begin
347
+ to_match = request.unmatched_path
348
+ to_match = to_match.chomp('/') if config[:strip_trailing_slash] == :ignore && to_match =~ %r{./$}
349
+ mappings.map { |mapping|
350
+ mapping[:pattern].match(to_match) do |match_data|
351
+ if match_data.pre_match == ''
352
+ if match_data.names.empty?
353
+ captures = match_data.captures
354
+ else
355
+ captures = Hash[match_data.names.map {|v| v.to_sym}.zip(match_data.captures)]
356
+ captures.each do |k,v|
357
+ captures[k] = symbol_matchers[k][1].call(v) if Array === symbol_matchers[k]
358
+ end
374
359
  end
360
+ Match.new(mapping, captures, match_data.to_s, check_for_failed_condition(mapping[:conditions]))
375
361
  end
376
- Match.new(mapping, captures, match_data.to_s, check_for_failed_condition(mapping[:conditions]))
377
362
  end
378
- end
379
- }.compact
363
+ }.compact
364
+ end
365
+ end
366
+
367
+ # Returns an ordered list of eligable matches.
368
+ # Orders matches based on media_type, ensuring priority and definition order are respected appropriately.
369
+ # Sorts by mapping priority first, media type appropriateness second, and definition order third.
370
+ def eligable_matches
371
+ @_eligable_matches ||= begin
372
+ matches.select { |m| m.failed_condition.nil? }.each_with_index.sort_by do |m,idx|
373
+ priority = m.mapping[:priority] || 0
374
+ media_type_rank = [*m.mapping[:conditions][:media_type]].map { |type|
375
+ env['scorched.accept'][:accept].rank(type, true)
376
+ }.max || 0
377
+ order = -idx
378
+ [priority, media_type_rank, order]
379
+ end.reverse
380
+ end
380
381
  end
381
382
 
382
383
  # Tests the given conditions, returning the name of the first failed condition, or nil otherwise.
@@ -397,9 +398,10 @@ module Scorched
397
398
  end
398
399
 
399
400
  # Redirects to the specified path or URL. An optional HTTP status is also accepted.
400
- def redirect(url, status = (env['HTTP_VERSION'] == 'HTTP/1.1') ? 303 : 302)
401
+ def redirect(url, status: (env['HTTP_VERSION'] == 'HTTP/1.1') ? 303 : 302, halt: true)
401
402
  response['Location'] = absolute(url)
402
- halt(status)
403
+ response.status = status
404
+ self.halt if halt
403
405
  end
404
406
 
405
407
  # call-seq:
@@ -523,10 +525,10 @@ module Scorched
523
525
 
524
526
  # Takes an optional URL, relative to the applications root, and returns a fully qualified URL.
525
527
  # Example: url('/example?show=30') #=> https://localhost:9292/myapp/example?show=30
526
- def url(path = nil)
528
+ def url(path = nil, scheme: nil)
527
529
  return path if path && URI.parse(path).scheme
528
530
  uri = URI::Generic.build(
529
- scheme: env['rack.url_scheme'],
531
+ scheme: scheme || env['rack.url_scheme'],
530
532
  host: env['SERVER_NAME'],
531
533
  port: env['SERVER_PORT'].to_i,
532
534
  path: env['scorched.root_path'],
@@ -588,20 +590,26 @@ module Scorched
588
590
 
589
591
  private
590
592
 
591
- def run_filters(type, forced_only = false)
593
+ # Returns false if any of the filters halted the request. True otherwise.
594
+ def run_filters(type)
595
+ halted = false
592
596
  tracker = env['scorched.executed_filters'] ||= {before: Set.new, after: Set.new}
593
- filters[type].reject{ |f| tracker[type].include?(f) || (forced_only && !f[:force]) }.each do |f|
594
- unless check_for_failed_condition(f[:conditions])
597
+ filters[type].reject { |f| tracker[type].include?(f) }.each do |f|
598
+ unless check_for_failed_condition(f[:conditions]) || (halted && !f[:force])
595
599
  tracker[type] << f
596
- if forced_only
597
- catch(:halt) do
598
- instance_exec(&f[:proc]); true
599
- end or log.warn "Ignored halt while running forced filters."
600
- else
601
- instance_exec(&f[:proc])
602
- end
600
+ halted = true unless run_filter(f)
603
601
  end
604
602
  end
603
+ !halted
604
+ end
605
+
606
+ # Returns false if the filter halted. True otherwise.
607
+ def run_filter(f)
608
+ catch(:halt) do
609
+ instance_exec(&f[:proc])
610
+ return true
611
+ end
612
+ return false
605
613
  end
606
614
 
607
615
  def log(type = nil, message = nil)
@@ -1,3 +1,3 @@
1
1
  module Scorched
2
2
  Match = Struct.new(:mapping, :captures, :path, :failed_condition)
3
- end
3
+ end
@@ -33,6 +33,8 @@ module Scorched
33
33
  end
34
34
  end
35
35
 
36
+ attr_accessor :halted
37
+
36
38
  alias :to_a :finish
37
39
  alias :to_ary :finish
38
40
  end
@@ -1,3 +1,3 @@
1
1
  module Scorched
2
- VERSION = '0.27'
2
+ VERSION = '1.0.0.pre'
3
3
  end
@@ -14,7 +14,7 @@ Gem::Specification.new 'scorched', Scorched::VERSION do |s|
14
14
 
15
15
  s.required_ruby_version = '>= 2.0.0'
16
16
 
17
- s.add_dependency 'rack', '~> 1.4'
17
+ s.add_dependency 'rack', '~> 2.0'
18
18
  s.add_dependency 'rack-accept', '~> 0.4' # Used for Accept-Charset, Accept-Encoding and Accept-Language headers.
19
19
  s.add_dependency 'scorched-accept', '~> 0.1' # Used for Accept header.
20
20
  s.add_dependency 'tilt', '~> 1.4'
@@ -189,7 +189,7 @@ module Scorched
189
189
 
190
190
  it "can coerce symbol-matched values" do
191
191
  app << {pattern: '/:numeric', target: proc { |env| [200, {}, [request.captures[:numeric].class.name]] }}
192
- rt.get('/45').body.should == 'Fixnum'
192
+ rt.get('/45').body.should == 'Integer'
193
193
  end
194
194
 
195
195
  it "matches routes based on priority, otherwise giving precedence to those defined first" do
@@ -681,15 +681,25 @@ module Scorched
681
681
  rt.get('/').status.should == 600
682
682
  end
683
683
 
684
+ it "prevents matches from being invoked if a before filter has halted" do
685
+ app.before { halt}
686
+ app.get('/') { "success" }
687
+ rt.get('/').body.should_not == 'success'
688
+ end
689
+
684
690
  describe "within filters" do
685
- it "short circuits filters if halted within filter" do
686
- app.before { halt }
691
+ it "only short circuits other filters of same type if halted within filter" do
687
692
  app.after { response.status = 600 }
688
- rt.get('/').status.should_not == 600
693
+ app.after { halt }
694
+ app.after { response.status = 601 }
695
+ rt.get('/').status.should == 600
696
+
697
+ app.before { halt }
698
+ rt.get('/').status.should == 600
689
699
  end
690
700
 
691
701
  it "forced filters are always run" do
692
- app.before { halt }
702
+ app.after { halt }
693
703
  app.after(force: true) { response.status = 600 }
694
704
  app.after { response.status = 700 } # Shouldn't run because it's not forced
695
705
  app.get('/') { 'hello' }
@@ -720,7 +730,7 @@ module Scorched
720
730
  end
721
731
 
722
732
  it "allows the HTTP status to be overridden" do
723
- app.get('/') { redirect '/somewhere', 308 }
733
+ app.get('/') { redirect '/somewhere', status: 308 }
724
734
  rt.get('/').status.should == 308
725
735
  end
726
736
 
@@ -736,9 +746,11 @@ module Scorched
736
746
 
737
747
  it "works in filters" do
738
748
  app.error { redirect '/somewhere' }
739
- app.get('/') { raise "Some error" }
740
- rt.get('/').location.should == '/somewhere'
749
+ app.get('/error') { raise "Some error" }
750
+ rt.get('/error').location.should == '/somewhere'
751
+
741
752
  app.before { redirect '/somewhere_else' }
753
+ app.get('/') { redirect '/meow' }
742
754
  rt.get('/').location.should == '/somewhere_else'
743
755
  end
744
756
  end
@@ -778,17 +790,6 @@ module Scorched
778
790
  }.to raise_error(ArgumentError)
779
791
  end
780
792
 
781
- it "is not considered a match if a mapping passes the request" do
782
- app.get('/*') { pass }
783
- app.get('/nopass') { }
784
- handled = nil
785
- app.after { handled = @_handled }
786
- rt.get('/').status.should == 404 # 404 if matched, but passed
787
- handled.should be_falsey
788
- rt.get('/nopass').status.should == 200
789
- handled.should be_truthy
790
- end
791
-
792
793
  it "is still considered a match if an exception is raised" do
793
794
  app.post('/') { }
794
795
  app.get('/') { raise "Test error"}
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scorched
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.27'
4
+ version: 1.0.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Wardrop
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-12 00:00:00.000000000 Z
11
+ date: 2017-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.4'
19
+ version: '2.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.4'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rack-accept
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -186,12 +186,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
186
186
  version: 2.0.0
187
187
  required_rubygems_version: !ruby/object:Gem::Requirement
188
188
  requirements:
189
- - - ">="
189
+ - - ">"
190
190
  - !ruby/object:Gem::Version
191
- version: '0'
191
+ version: 1.3.1
192
192
  requirements: []
193
193
  rubyforge_project:
194
- rubygems_version: 2.5.1
194
+ rubygems_version: 2.6.12
195
195
  signing_key:
196
196
  specification_version: 4
197
197
  summary: Light-weight, DRY as a desert, web framework for Ruby