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.
- checksums.yaml +4 -4
- data/CHANGELOG +8 -0
- data/doc/release_notes/3.27.0.txt +15 -0
- data/lib/roda.rb +4 -857
- data/lib/roda/cache.rb +35 -0
- data/lib/roda/plugins.rb +53 -0
- data/lib/roda/plugins/halt.rb +8 -3
- data/lib/roda/plugins/multibyte_string_matcher.rb +57 -0
- data/lib/roda/plugins/params_capturing.rb +5 -3
- data/lib/roda/request.rb +625 -0
- data/lib/roda/response.rb +172 -0
- data/lib/roda/version.rb +1 -1
- data/spec/define_roda_method_spec.rb +1 -1
- data/spec/plugin/json_parser_spec.rb +5 -0
- data/spec/plugin/multibyte_string_matcher_spec.rb +44 -0
- data/spec/plugin/sinatra_helpers_spec.rb +2 -2
- metadata +10 -2
@@ -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
|
data/lib/roda/version.rb
CHANGED
@@ -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'=>
|
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.
|
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
|
+
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
|