roda 3.26.0 → 3.27.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|