roda 3.26.0 → 3.27.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,172 @@
1
+ # frozen-string-literal: true
2
+
3
+ class Roda
4
+ # Base class used for Roda responses. The instance methods for this
5
+ # class are added by Roda::RodaPlugins::Base::ResponseMethods, the class
6
+ # methods are added by Roda::RodaPlugins::Base::ResponseClassMethods.
7
+ class RodaResponse
8
+ @roda_class = ::Roda
9
+ end
10
+
11
+ module RodaPlugins
12
+ module Base
13
+ # Class methods for RodaResponse
14
+ module ResponseClassMethods
15
+ # Reference to the Roda class related to this response class.
16
+ attr_accessor :roda_class
17
+
18
+ # Since RodaResponse is anonymously subclassed when Roda is subclassed,
19
+ # and then assigned to a constant of the Roda subclass, make inspect
20
+ # reflect the likely name for the class.
21
+ def inspect
22
+ "#{roda_class.inspect}::RodaResponse"
23
+ end
24
+ end
25
+
26
+ # Instance methods for RodaResponse
27
+ module ResponseMethods
28
+ DEFAULT_HEADERS = {"Content-Type" => "text/html".freeze}.freeze
29
+
30
+ # The body for the current response.
31
+ attr_reader :body
32
+
33
+ # The hash of response headers for the current response.
34
+ attr_reader :headers
35
+
36
+ # The status code to use for the response. If none is given, will use 200
37
+ # code for non-empty responses and a 404 code for empty responses.
38
+ attr_accessor :status
39
+
40
+ # Set the default headers when creating a response.
41
+ def initialize
42
+ @headers = {}
43
+ @body = []
44
+ @length = 0
45
+ end
46
+
47
+ # Return the response header with the given key. Example:
48
+ #
49
+ # response['Content-Type'] # => 'text/html'
50
+ def [](key)
51
+ @headers[key]
52
+ end
53
+
54
+ # Set the response header with the given key to the given value.
55
+ #
56
+ # response['Content-Type'] = 'application/json'
57
+ def []=(key, value)
58
+ @headers[key] = value
59
+ end
60
+
61
+ # The default headers to use for responses.
62
+ def default_headers
63
+ DEFAULT_HEADERS
64
+ end
65
+
66
+ # Whether the response body has been written to yet. Note
67
+ # that writing an empty string to the response body marks
68
+ # the response as not empty. Example:
69
+ #
70
+ # response.empty? # => true
71
+ # response.write('a')
72
+ # response.empty? # => false
73
+ def empty?
74
+ @body.empty?
75
+ end
76
+
77
+ # Return the rack response array of status, headers, and body
78
+ # for the current response. If the status has not been set,
79
+ # uses the return value of default_status if the body has
80
+ # been written to, otherwise uses a 404 status.
81
+ # Adds the Content-Length header to the size of the response body.
82
+ #
83
+ # Example:
84
+ #
85
+ # response.finish
86
+ # # => [200,
87
+ # # {'Content-Type'=>'text/html', 'Content-Length'=>'0'},
88
+ # # []]
89
+ def finish
90
+ b = @body
91
+ set_default_headers
92
+ h = @headers
93
+
94
+ if b.empty?
95
+ s = @status || 404
96
+ if (s == 304 || s == 204 || (s >= 100 && s <= 199))
97
+ h.delete("Content-Type")
98
+ elsif s == 205
99
+ h.delete("Content-Type")
100
+ h["Content-Length"] = '0'
101
+ else
102
+ h["Content-Length"] ||= '0'
103
+ end
104
+ else
105
+ s = @status || default_status
106
+ h["Content-Length"] ||= @length.to_s
107
+ end
108
+
109
+ [s, h, b]
110
+ end
111
+
112
+ # Return the rack response array using a given body. Assumes a
113
+ # 200 response status unless status has been explicitly set,
114
+ # and doesn't add the Content-Length header or use the existing
115
+ # body.
116
+ def finish_with_body(body)
117
+ set_default_headers
118
+ [@status || default_status, @headers, body]
119
+ end
120
+
121
+ # Return the default response status to be used when the body
122
+ # has been written to. This is split out to make overriding
123
+ # easier in plugins.
124
+ def default_status
125
+ 200
126
+ end
127
+
128
+ # Show response class, status code, response headers, and response body
129
+ def inspect
130
+ "#<#{self.class.inspect} #{@status.inspect} #{@headers.inspect} #{@body.inspect}>"
131
+ end
132
+
133
+ # Set the Location header to the given path, and the status
134
+ # to the given status. Example:
135
+ #
136
+ # response.redirect('foo', 301)
137
+ # response.redirect('bar')
138
+ def redirect(path, status = 302)
139
+ @headers["Location"] = path
140
+ @status = status
141
+ nil
142
+ end
143
+
144
+ # Return the Roda class related to this response.
145
+ def roda_class
146
+ self.class.roda_class
147
+ end
148
+
149
+ # Write to the response body. Returns nil.
150
+ #
151
+ # response.write('foo')
152
+ def write(str)
153
+ s = str.to_s
154
+ @length += s.bytesize
155
+ @body << s
156
+ nil
157
+ end
158
+
159
+ private
160
+
161
+ # For each default header, if a header has not already been set for the
162
+ # response, set the header in the response.
163
+ def set_default_headers
164
+ h = @headers
165
+ default_headers.each do |k,v|
166
+ h[k] ||= v
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 26
7
+ RodaMinorVersion = 27
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -2,7 +2,7 @@ require_relative "spec_helper"
2
2
 
3
3
  describe "Roda.define_roda_method" do
4
4
  before do
5
- @scope = app.new({})
5
+ @scope = app.new({'PATH_INFO'=>'/'})
6
6
  end
7
7
 
8
8
  it "should define methods using block" do
@@ -19,6 +19,11 @@ describe "json_parser plugin" do
19
19
  req('rack.input'=>StringIO.new('{"a":{"b":1}'), 'CONTENT_TYPE'=>'text/json', 'REQUEST_METHOD'=>'POST').must_equal [400, {}, []]
20
20
  end
21
21
 
22
+ it "returns 400 for invalid json when using params_capturing plugin" do
23
+ @app.plugin :params_capturing
24
+ req('rack.input'=>StringIO.new('{"a":{"b":1}'), 'CONTENT_TYPE'=>'text/json', 'REQUEST_METHOD'=>'POST').must_equal [400, {}, []]
25
+ end
26
+
22
27
  it "raises by default if r.params is called and a non-hash is submitted" do
23
28
  proc do
24
29
  req('rack.input'=>StringIO.new('[1]'), 'CONTENT_TYPE'=>'text/json', 'REQUEST_METHOD'=>'POST')
@@ -0,0 +1,44 @@
1
+ require_relative "../spec_helper"
2
+
3
+ describe "multibyte_string_matcher plugin" do
4
+ it "uses multibyte safe string matching" do
5
+ str = "\xD0\xB8".dup.force_encoding('UTF-8')
6
+ app(:unescape_path) do |r|
7
+ r.is String do |s|
8
+ s
9
+ end
10
+
11
+ r.is(Integer, /(#{str})/u) do |_, a|
12
+ a
13
+ end
14
+
15
+ r.is(Integer, Integer, str) do
16
+ 'm'
17
+ end
18
+
19
+ r.is(Integer, str, Integer) do
20
+ 'n'
21
+ end
22
+ end
23
+
24
+ body('/%D0%B8').must_equal str
25
+ body('/1/%D0%B8').must_equal str
26
+ status('/1/2/%D0%B8').must_equal 404
27
+ status('/1/%D0%B8/2').must_equal 404
28
+
29
+ status('/1/%D0%B9').must_equal 404
30
+ status('/1/2/%D0%B9').must_equal 404
31
+ status('/1/%D0%B9/2').must_equal 404
32
+
33
+ @app.plugin :multibyte_string_matcher
34
+
35
+ body('/%D0%B8').must_equal str
36
+ body('/1/%D0%B8').must_equal str
37
+ body('/1/2/%D0%B8').must_equal 'm'
38
+ body('/1/%D0%B8/2').must_equal 'n'
39
+
40
+ status('/1/%D0%B9').must_equal 404
41
+ status('/1/2/%D0%B9').must_equal 404
42
+ status('/1/%D0%B9/2').must_equal 404
43
+ end
44
+ end
@@ -228,7 +228,7 @@ describe "sinatra_helpers plugin" do
228
228
 
229
229
  describe 'mime_type' do
230
230
  before do
231
- sin_app{|r| mime_type(r.path).to_s}
231
+ sin_app{|r| mime_type((r.path unless r.path.empty?)).to_s}
232
232
  end
233
233
 
234
234
  it "looks up mime types in Rack's MIME registry" do
@@ -238,7 +238,7 @@ describe "sinatra_helpers plugin" do
238
238
  end
239
239
 
240
240
  it 'returns nil when given nil' do
241
- body('PATH_INFO'=>nil).must_equal ''
241
+ body('PATH_INFO'=>'').must_equal ''
242
242
  end
243
243
 
244
244
  it 'returns nil when media type not registered' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.26.0
4
+ version: 3.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-18 00:00:00.000000000 Z
11
+ date: 2019-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -237,6 +237,7 @@ extra_rdoc_files:
237
237
  - doc/release_notes/3.24.0.txt
238
238
  - doc/release_notes/3.25.0.txt
239
239
  - doc/release_notes/3.26.0.txt
240
+ - doc/release_notes/3.27.0.txt
240
241
  files:
241
242
  - CHANGELOG
242
243
  - MIT-LICENSE
@@ -299,6 +300,7 @@ files:
299
300
  - doc/release_notes/3.24.0.txt
300
301
  - doc/release_notes/3.25.0.txt
301
302
  - doc/release_notes/3.26.0.txt
303
+ - doc/release_notes/3.27.0.txt
302
304
  - doc/release_notes/3.3.0.txt
303
305
  - doc/release_notes/3.4.0.txt
304
306
  - doc/release_notes/3.5.0.txt
@@ -307,6 +309,8 @@ files:
307
309
  - doc/release_notes/3.8.0.txt
308
310
  - doc/release_notes/3.9.0.txt
309
311
  - lib/roda.rb
312
+ - lib/roda/cache.rb
313
+ - lib/roda/plugins.rb
310
314
  - lib/roda/plugins/_after_hook.rb
311
315
  - lib/roda/plugins/_before_hook.rb
312
316
  - lib/roda/plugins/_symbol_regexp_matchers.rb
@@ -361,6 +365,7 @@ files:
361
365
  - lib/roda/plugins/multi_route.rb
362
366
  - lib/roda/plugins/multi_run.rb
363
367
  - lib/roda/plugins/multi_view.rb
368
+ - lib/roda/plugins/multibyte_string_matcher.rb
364
369
  - lib/roda/plugins/named_templates.rb
365
370
  - lib/roda/plugins/not_allowed.rb
366
371
  - lib/roda/plugins/not_found.rb
@@ -404,6 +409,8 @@ files:
404
409
  - lib/roda/plugins/typecast_params.rb
405
410
  - lib/roda/plugins/unescape_path.rb
406
411
  - lib/roda/plugins/view_options.rb
412
+ - lib/roda/request.rb
413
+ - lib/roda/response.rb
407
414
  - lib/roda/session_middleware.rb
408
415
  - lib/roda/version.rb
409
416
  - spec/all.rb
@@ -470,6 +477,7 @@ files:
470
477
  - spec/plugin/multi_route_spec.rb
471
478
  - spec/plugin/multi_run_spec.rb
472
479
  - spec/plugin/multi_view_spec.rb
480
+ - spec/plugin/multibyte_string_matcher_spec.rb
473
481
  - spec/plugin/named_templates_spec.rb
474
482
  - spec/plugin/not_allowed_spec.rb
475
483
  - spec/plugin/not_found_spec.rb