roda 3.26.0 → 3.27.0

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.
@@ -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