roda 3.34.0 → 3.39.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ea0324d5667f9dcde48ff8fd1025b15e0e06861eea776019c0be9be8c46b097
4
- data.tar.gz: 1712241233e21e7863101192227797976a79f6a3f0895da907a9e2b19242e1ed
3
+ metadata.gz: 4f01105e24f4eae80a69f5734f93d3dfe54b5f6be2489521f1d886053c569102
4
+ data.tar.gz: 2aebf30460c7d5352ee15baf3f884e59dac972e0aadd63aa7ab81445a8a29f65
5
5
  SHA512:
6
- metadata.gz: 6b8d1b37ed94ee40c0b1f12cc20ab9f734b276805044b059c9ea10375cc64d7d2cc8b0b36049bd21e29d3e45eeefcc8aeaffe0b4bbaf2d36043c16838270e2eb
7
- data.tar.gz: 1670e3cde892c8cd42e2856d4e938e6da359d0818ee09f7c28e8ec5f4bc4bd627f3aaa8c48aeac8f6d03ab80994078a5cbce69d99595214f288eb9ab1f860c23
6
+ metadata.gz: d3fd374b3d700106ca814fff8f2dd0c66a3295955f27a453ef5d9fb2ba7fdea583d76ce82f91efb8e2ebcbb97a07e664f3f008689d51eb67ae39aebdd86a5d0e
7
+ data.tar.gz: 59c34c88ab1e5a5bc5017d46707a530c7cd002f4262c30751ba6429983b32477b813d83ada45a19873be711d1d2bd0abd95fe2a5c44c01038ad4a32dbb8f276d
data/CHANGELOG CHANGED
@@ -1,3 +1,33 @@
1
+ = 3.39.0 (2020-12-15)
2
+
3
+ * Speed up relative_path plugin if relative_path or relative_prefix is called more than once (jeremyevans)
4
+
5
+ * Avoid method redefinition warnings in verbose warning mode (jeremyevans)
6
+
7
+ * Make typecast_params.convert! handle explicit nil values the same as missing values (jeremyevans)
8
+
9
+ = 3.38.0 (2020-11-16)
10
+
11
+ * Make error_email and error_mail plugins rescue invalid parameter errors when preparing the email body (jeremyevans)
12
+
13
+ = 3.37.0 (2020-10-16)
14
+
15
+ * Add custom_matchers plugin, for supporting arbitrary objects as matchers (jeremyevans)
16
+
17
+ = 3.36.0 (2020-09-14)
18
+
19
+ * Add multi_public plugin, for serving files from multiple public directories (jeremyevans)
20
+
21
+ * Support report-to directive in the content_security_policy plugin (jeremyevans)
22
+
23
+ * Add Vary response header when using type_routing plugin with Accept request header to prevent caching issues (jeremyevans)
24
+
25
+ = 3.35.0 (2020-08-14)
26
+
27
+ * Add r plugin for r method for accessing request, useful when r local variable is not in scope (jeremyevans)
28
+
29
+ * Warn when loading a plugin with arguments or a block if the plugin does not accept arguments or block (jeremyevans)
30
+
1
31
  = 3.34.0 (2020-07-14)
2
32
 
3
33
  * Remove unnecessary conditionals (jeremyevans)
@@ -248,7 +248,7 @@
248
248
  Note that if there are multiple conversion errors raised inside a
249
249
  convert! or convert_each! block, they are recorded and a single
250
250
  Roda::RodaPlugins::TypecastParams::Error instance is raised after
251
- processing the block. TypecastParams::Error#params_names can be
251
+ processing the block. TypecastParams::Error#param_names can be
252
252
  called on the exception to get an array of all parameter names
253
253
  with conversion issues, and TypecastParams::Error#all_errors
254
254
  can be used to get an array of all Error instances.
@@ -0,0 +1,12 @@
1
+ = New Features
2
+
3
+ * An r plugin has been added. This plugin adds an r method for the
4
+ request, useful for allowing the use of r.halt and r.redirect even
5
+ in methods where the r local variable is not in scope.
6
+
7
+ = Other Improvements
8
+
9
+ * Attempting to load a plugin with an argument or block when the plugin
10
+ does not accept arguments or a block now warns. This is because a
11
+ future update to support a block or an optional argument could break
12
+ the call.
@@ -0,0 +1,17 @@
1
+ = New Features
2
+
3
+ * A multi_public plugin has been added, which allows serving static
4
+ files from multiple separate directories. This is especially
5
+ useful when there are different access control requirements per
6
+ directory.
7
+
8
+ * The content_security_policy now supports a
9
+ content_security_policy.report_to method to set the
10
+ report-to directive.
11
+
12
+ = Other Improvements
13
+
14
+ * When using the type_routing plugin and performing type routing
15
+ using the Accept request header, the Vary response header will be
16
+ added or updated so that http caches do not cache a response for one
17
+ type and serve it for a different type.
@@ -0,0 +1,42 @@
1
+ = New Features
2
+
3
+ * A custom_matchers plugin has been added, which allows using
4
+ arbitrary objects as matchers, as long as the matcher has been
5
+ registered. You can register matchers using the custom_matcher
6
+ class method, which takes the class of the matcher, and a block
7
+ which is yielded the matcher object. The block should return
8
+ nil or false if the matcher doesn't match, and any other value
9
+ if the matcher does match. Example:
10
+
11
+ plugin :custom_matchers
12
+ method_segment = Struct.new(:request_method, :next_segment)
13
+ custom_matcher(method_segment) do |matcher|
14
+ # self is the request instance ("r" yielded in the route block below)
15
+ if matcher.request_method == self.request_method
16
+ match(matcher.next_segment)
17
+ end
18
+ end
19
+
20
+ get_foo = method_segment.new('GET', 'foo')
21
+ post_any = method_segment.new('POST', String)
22
+ route do |r|
23
+ r.on('baz') do
24
+ r.on(get_foo) do
25
+ # GET method, /baz/foo prefix
26
+ end
27
+
28
+ r.is(post_any) do |seg|
29
+ # for POST /baz/bar, seg is "bar"
30
+ end
31
+ end
32
+
33
+ r.on('quux') do
34
+ r.is(get_foo) do
35
+ # GET method, /quux/foo route
36
+ end
37
+
38
+ r.on(post_any) do |seg|
39
+ # for POST /quux/xyz, seg is "xyz"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,5 @@
1
+ = Improvements
2
+
3
+ * The error_email and error_mail plugins now rescue invalid parameter
4
+ errors when preparing the email body, because you generally don't
5
+ want your error handler to raise an exception.
@@ -0,0 +1,16 @@
1
+ = Improvements
2
+
3
+ * The relative_path plugin is now faster if you are calling
4
+ relative_path or relative_prefix more than once when handling a
5
+ request.
6
+
7
+ * The typecast_params.convert! method in the typecast_params plugin
8
+ now handles explicit nil values the same as missing values.
9
+ Explicit nil values do not generally occur in normal Rack parameter
10
+ parsing, but they can occur when using the json_parser plugin to
11
+ parse JSON requests.
12
+
13
+ * Roda now avoids method redefinition warnings in verbose mode by
14
+ using a self alias. As Ruby 3 is dropping uninitialized instance
15
+ variable warnings, Roda will be verbose warning free if you are
16
+ using Ruby 3.
@@ -114,6 +114,7 @@ class Roda
114
114
  alias_method meth, temp_method
115
115
  undef_method temp_method
116
116
  private meth
117
+ alias_method meth, meth
117
118
  meth = :"#{meth}_arity"
118
119
  elsif required_args > 1
119
120
  b = block
@@ -144,6 +145,7 @@ class Roda
144
145
 
145
146
  define_method(meth, &block)
146
147
  private meth
148
+ alias_method meth, meth
147
149
 
148
150
  if arity_meth
149
151
  required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(instance_method(meth))
@@ -167,6 +169,7 @@ class Roda
167
169
  send(meth, *a)
168
170
  end
169
171
  private arity_meth
172
+ alias_method arity_meth, arity_meth
170
173
  end
171
174
 
172
175
  call_meth
@@ -199,6 +202,7 @@ class Roda
199
202
 
200
203
  private
201
204
 
205
+ alias set_default_headers set_default_headers
202
206
  def set_default_headers
203
207
  @headers['Content-Type'] ||= 'text/html'
204
208
  end
@@ -272,6 +276,12 @@ class Roda
272
276
  raise RodaError, "Cannot add a plugin to a frozen Roda class" if frozen?
273
277
  plugin = RodaPlugins.load_plugin(plugin) if plugin.is_a?(Symbol)
274
278
  raise RodaError, "Invalid plugin type: #{plugin.class.inspect}" unless plugin.is_a?(Module)
279
+
280
+ if !plugin.respond_to?(:load_dependencies) && !plugin.respond_to?(:configure) && (!args.empty? || block)
281
+ # RODA4: switch from warning to error
282
+ RodaPlugins.warn("Plugin #{plugin} does not accept arguments or a block, but arguments or a block was passed when loading this. This will raise an error in Roda 4.")
283
+ end
284
+
275
285
  plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies)
276
286
  include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods)
277
287
  extend(plugin::ClassMethods) if defined?(plugin::ClassMethods)
@@ -397,6 +407,7 @@ class Roda
397
407
  class_eval("def _roda_before; #{meths.join(';')} end", __FILE__, __LINE__)
398
408
  end
399
409
  private :_roda_before
410
+ alias_method :_roda_before, :_roda_before
400
411
  end
401
412
  end
402
413
 
@@ -413,6 +424,7 @@ class Roda
413
424
  class_eval("def _roda_after(res); #{meths.map{|s| "#{s}(res)"}.join(';')} end", __FILE__, __LINE__)
414
425
  end
415
426
  private :_roda_after
427
+ alias_method :_roda_after, :_roda_after
416
428
  end
417
429
  end
418
430
 
@@ -56,6 +56,7 @@ class Roda
56
56
  # * media_src
57
57
  # * object_src
58
58
  # * plugin_types
59
+ # * report_to
59
60
  # * report_uri
60
61
  # * require_sri_for
61
62
  # * sandbox
@@ -123,6 +124,7 @@ class Roda
123
124
  media-src
124
125
  object-src
125
126
  plugin-types
127
+ report-to
126
128
  report-uri
127
129
  require-sri-for
128
130
  sandbox
@@ -145,9 +147,7 @@ class Roda
145
147
  # add_* method name adds to the setting value, or clears setting if no values
146
148
  # are given.
147
149
  define_method("add_#{meth}") do |*args|
148
- if args.empty?
149
- @opts[setting]
150
- else
150
+ unless args.empty?
151
151
  @opts[setting] ||= EMPTY_ARRAY
152
152
  @opts[setting] += args
153
153
  @opts[setting].freeze
@@ -0,0 +1,87 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The custom_matchers plugin supports using arbitrary objects
7
+ # as matchers, as long as the application has been configured
8
+ # to accept such objects.
9
+ #
10
+ # After loading the plugin, support for custom matchers can be
11
+ # configured using the +custom_matcher+ class method. This
12
+ # method is generally passed the class of the object you want
13
+ # to use as a custom matcher, as well as a block. The block
14
+ # will be called in the context of the request instance
15
+ # with the specific matcher used in the match method.
16
+ #
17
+ # Blocks can append to the captures in order to yield the appropriate
18
+ # values to match blocks, or call request methods that append to the
19
+ # captures.
20
+ #
21
+ # Example:
22
+ #
23
+ # plugin :custom_matchers
24
+ # method_segment = Struct.new(:request_method, :next_segment)
25
+ # custom_matcher(method_segment) do |matcher|
26
+ # # self is the request instance ("r" yielded in the route block below)
27
+ # if matcher.request_method == self.request_method
28
+ # match(matcher.next_segment)
29
+ # end
30
+ # end
31
+ #
32
+ # get_foo = method_segment.new('GET', 'foo')
33
+ # post_any = method_segment.new('POST', String)
34
+ # route do |r|
35
+ # r.on('baz') do
36
+ # r.on(get_foo) do
37
+ # # GET method, /baz/foo prefix
38
+ # end
39
+ #
40
+ # r.is(post_any) do |seg|
41
+ # # for POST /baz/bar, seg is "bar"
42
+ # end
43
+ # end
44
+ #
45
+ # r.on('quux') do
46
+ # r.is(get_foo) do
47
+ # # GET method, /quux/foo route
48
+ # end
49
+ #
50
+ # r.on(post_any) do |seg|
51
+ # # for POST /quux/xyz, seg is "xyz"
52
+ # end
53
+ # end
54
+ # end
55
+ module CustomMatchers
56
+ def self.configure(app)
57
+ app.opts[:custom_matchers] ||= OPTS
58
+ end
59
+
60
+ module ClassMethods
61
+ def custom_matcher(match_class, &block)
62
+ custom_matchers = Hash[opts[:custom_matchers]]
63
+ meth = custom_matchers[match_class] = custom_matchers[match_class] || :"_custom_matcher_#{match_class}"
64
+ opts[:custom_matchers] = custom_matchers.freeze
65
+ self::RodaRequest.send(:define_method, meth, &block)
66
+ nil
67
+ end
68
+ end
69
+
70
+ module RequestMethods
71
+ # Try custom matchers before calling super
72
+ def unsupported_matcher(matcher)
73
+ roda_class.opts[:custom_matchers].each do |match_class, meth|
74
+ if match_class === matcher
75
+ return send(meth, matcher)
76
+ end
77
+ end
78
+
79
+ super
80
+ end
81
+ end
82
+ end
83
+
84
+ register_plugin(:custom_matchers, CustomMatchers)
85
+ end
86
+ end
87
+
@@ -33,6 +33,7 @@ class Roda
33
33
  response_class.class_eval(<<-END, __FILE__, __LINE__+1)
34
34
  private
35
35
 
36
+ alias set_default_headers set_default_headers
36
37
  def set_default_headers
37
38
  h = @headers
38
39
  #{headers.map{|k,v| "h[#{k.inspect}] ||= #{v.inspect}"}.join('; ')}
@@ -54,6 +54,13 @@ class Roda
54
54
  :body=>lambda do |s, e|
55
55
  format = lambda{|h| h.map{|k, v| "#{k.inspect} => #{v.inspect}"}.sort.join("\n")}
56
56
 
57
+ begin
58
+ params = s.request.params
59
+ params = (format[params] unless params.empty?)
60
+ rescue
61
+ params = 'Invalid Parameters!'
62
+ end
63
+
57
64
  message = String.new
58
65
  message << <<END
59
66
  Path: #{s.request.path}
@@ -73,12 +80,12 @@ ENV:
73
80
  #{format[s.env]}
74
81
  END
75
82
 
76
- unless s.request.params.empty?
83
+ if params
77
84
  message << <<END
78
85
 
79
86
  Params:
80
87
 
81
- #{format[s.request.params]}
88
+ #{params}
82
89
  END
83
90
  end
84
91
 
@@ -71,6 +71,13 @@ class Roda
71
71
 
72
72
  format = lambda{|h| h.map{|k, v| "#{k.inspect} => #{v.inspect}"}.sort.join("\n")}
73
73
 
74
+ begin
75
+ params = request.params
76
+ params = (format[params] unless params.empty?)
77
+ rescue
78
+ params = 'Invalid Parameters!'
79
+ end
80
+
74
81
  message = String.new
75
82
  message << <<END
76
83
  Path: #{request.path}
@@ -91,12 +98,12 @@ ENV:
91
98
  #{format[env]}
92
99
  END
93
100
 
94
- unless request.params.empty?
101
+ if params
95
102
  message << <<END
96
103
 
97
104
  Params:
98
105
 
99
- #{format[request.params]}
106
+ #{params}
100
107
  END
101
108
  end
102
109
 
@@ -476,6 +476,8 @@ class Roda
476
476
 
477
477
  undef_method :match_rcpt
478
478
  undef_method :match_text
479
+ undef_method :match_body
480
+ undef_method :match_subject
479
481
 
480
482
  # Same as +header+, but also mark the message as being handled.
481
483
  def handle_header(key, value=nil)
@@ -0,0 +1,87 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The multi_public plugin adds an +r.multi_public+ method that accepts an argument specifying
7
+ # a directory from which to serve static files. It is similar to the public plugin, but
8
+ # allows for multiple separate directories.
9
+ #
10
+ # Here's an example of using the multi_public plugin to serve 3 different types of files
11
+ # from 3 different directories:
12
+ #
13
+ # plugin :multi_public,
14
+ # img: 'static/images',
15
+ # font: 'assets/fonts',
16
+ # form: 'static/forms/pdfs'
17
+ #
18
+ # r.route do
19
+ # r.on "images" do
20
+ # r.multi_public(:img)
21
+ # end
22
+ #
23
+ # r.on "fonts" do
24
+ # r.multi_public(:font)
25
+ # end
26
+ #
27
+ # r.on "forms" do
28
+ # r.multi_public(:form)
29
+ # end
30
+ # end
31
+ #
32
+ # It is possible to simplify the routing tree for this using string keys and an array
33
+ # matcher:
34
+ #
35
+ # plugin :multi_public,
36
+ # 'images' => 'static/images',
37
+ # 'fonts' => 'assets/fonts',
38
+ # 'forms' => 'static/forms/pdfs'
39
+ #
40
+ # r.route do
41
+ # r.on %w"images fonts forms" do |dir|
42
+ # r.multi_public(dir)
43
+ # end
44
+ # end
45
+ #
46
+ # You can provide custom headers and default mime type for each directory using an array
47
+ # of three elements as the value, with the first element being the path, the second
48
+ # being the custom headers, and the third being the default mime type:
49
+ #
50
+ # plugin :multi_public,
51
+ # 'images' => ['static/images', {'Cache-Control'=>'max-age=86400'}, nil],
52
+ # 'fonts' => ['assets/fonts', {'Cache-Control'=>'max-age=31536000'}, 'font/ttf'],
53
+ # 'forms' => ['static/forms/pdfs', nil, 'application/pdf']
54
+ #
55
+ # r.route do
56
+ # r.on %w"images fonts forms" do |dir|
57
+ # r.multi_public(dir)
58
+ # end
59
+ # end
60
+ module MultiPublic
61
+ def self.load_dependencies(app, _, opts=OPTS)
62
+ app.plugin(:public, opts)
63
+ end
64
+
65
+ # Use the given directories to setup servers. Any opts are passed to the public plugin.
66
+ def self.configure(app, directories, _=OPTS)
67
+ roots = app.opts[:multi_public_servers] = (app.opts[:multi_public_servers] || {}).dup
68
+ directories.each do |key, path|
69
+ path, headers, mime = path
70
+ roots[key] = ::Rack::File.new(app.expand_path(path), headers||{}, mime||'text/plain')
71
+ end
72
+ roots.freeze
73
+ end
74
+
75
+ module RequestMethods
76
+ # Serve files from the directory corresponding to the given key if the file exists and
77
+ # this is a GET request.
78
+ def multi_public(key)
79
+ public_serve_with(roda_class.opts[:multi_public_servers].fetch(key))
80
+ end
81
+ end
82
+ end
83
+
84
+ register_plugin(:multi_public, MultiPublic)
85
+ end
86
+ end
87
+
@@ -60,21 +60,7 @@ class Roda
60
60
  module RequestMethods
61
61
  # Serve files from the public directory if the file exists and this is a GET request.
62
62
  def public
63
- if is_get?
64
- path = PARSER.unescape(real_remaining_path)
65
- return if path.include?("\0")
66
-
67
- roda_opts = roda_class.opts
68
- server = roda_opts[:public_server]
69
- path = ::File.join(server.root, *public_path_segments(path))
70
-
71
- public_serve_compressed(server, path, '.br', 'br') if roda_opts[:public_brotli]
72
- public_serve_compressed(server, path, '.gz', 'gzip') if roda_opts[:public_gzip]
73
-
74
- if public_file_readable?(path)
75
- halt public_serve(server, path)
76
- end
77
- end
63
+ public_serve_with(roda_class.opts[:public_server])
78
64
  end
79
65
 
80
66
  private
@@ -101,6 +87,22 @@ class Roda
101
87
  # :nocov:
102
88
  end
103
89
 
90
+ def public_serve_with(server)
91
+ return unless is_get?
92
+ path = PARSER.unescape(real_remaining_path)
93
+ return if path.include?("\0")
94
+
95
+ roda_opts = roda_class.opts
96
+ path = ::File.join(server.root, *public_path_segments(path))
97
+
98
+ public_serve_compressed(server, path, '.br', 'br') if roda_opts[:public_brotli]
99
+ public_serve_compressed(server, path, '.gz', 'gzip') if roda_opts[:public_gzip]
100
+
101
+ if public_file_readable?(path)
102
+ halt public_serve(server, path)
103
+ end
104
+ end
105
+
104
106
  def public_serve_compressed(server, path, suffix, encoding)
105
107
  if env['HTTP_ACCEPT_ENCODING'] =~ /\b#{encoding}\b/
106
108
  compressed_path = path + suffix
@@ -0,0 +1,35 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The r plugin adds an +r+ instance method that will return the request.
7
+ # This allows you to use common Roda idioms such as +r.halt+ and
8
+ # +r.redirect+ even when +r+ isn't a local variable in scope. Example:
9
+ #
10
+ # plugin :r
11
+ #
12
+ # def foo
13
+ # r.redirect "/bar"
14
+ # end
15
+ #
16
+ # route do |r|
17
+ # r.get "foo" do
18
+ # foo
19
+ # end
20
+ # r.get "bar" do
21
+ # "bar"
22
+ # end
23
+ # end
24
+ module R
25
+ module InstanceMethods
26
+ # The request object.
27
+ def r
28
+ @_request
29
+ end
30
+ end
31
+ end
32
+
33
+ register_plugin(:r, R)
34
+ end
35
+ end
@@ -41,6 +41,7 @@ class Roda
41
41
  # Return a relative prefix to append to an absolute path to a relative path
42
42
  # based on the current path of the request.
43
43
  def relative_prefix
44
+ return @_relative_prefix if @_relative_prefix
44
45
  env = @_request.env
45
46
  script_name = env["SCRIPT_NAME"]
46
47
  path_info = env["PATH_INFO"]
@@ -50,16 +51,16 @@ class Roda
50
51
  case script_name.getbyte(0)
51
52
  when nil # SCRIPT_NAME empty
52
53
  unless path_info.getbyte(0) == 47 # PATH_INFO starts with /
53
- return ''
54
+ return(@_relative_prefix = '')
54
55
  end
55
56
  when 47 # SCRIPT_NAME starts with /
56
57
  # nothing
57
58
  else
58
- return ''
59
+ return(@_relative_prefix = '')
59
60
  end
60
61
 
61
62
  slash_count = script_name.count('/') + path_info.count('/')
62
- if slash_count > 1
63
+ @_relative_prefix = if slash_count > 1
63
64
  ("../" * (slash_count - 2)) << ".."
64
65
  else
65
66
  "."
@@ -106,7 +106,7 @@ class Roda
106
106
  # :template_block :: Pass this block when creating the underlying template,
107
107
  # ignored when using :inline. Disables caching of the
108
108
  # template by default.
109
- # :template_class :: Provides the template class to use, inside of using
109
+ # :template_class :: Provides the template class to use, instead of using
110
110
  # Tilt or <tt>Tilt[:engine]</tt>.
111
111
  #
112
112
  # Here's an example of using these options:
@@ -4,7 +4,7 @@
4
4
  class Roda
5
5
  module RodaPlugins
6
6
  # This plugin makes it easier to to respond to specific request data types. User agents can request
7
- # specific data types by either supplying an appropriate +Accept+ header
7
+ # specific data types by either supplying an appropriate +Accept+ request header
8
8
  # or by appending it as file extension to the path.
9
9
  #
10
10
  # Example:
@@ -50,6 +50,10 @@ class Roda
50
50
  # Content-Type header will be set to Roda's default (which you can override via
51
51
  # the default_headers plugin).
52
52
  #
53
+ # If the type routing is based on the +Accept+ request header and not the file extension,
54
+ # then an appropriate +Vary+ header will be set or appended to, so that HTTP caches do
55
+ # not serve the same result for requests with different +Accept+ headers.
56
+ #
53
57
  # To match custom extensions, use the :types option:
54
58
  #
55
59
  # plugin :type_routing, types: {
@@ -137,6 +141,7 @@ class Roda
137
141
  app::RodaRequest.send(:define_method, type) do |&block|
138
142
  on_type(type, &block)
139
143
  end
144
+ app::RodaRequest.send(:alias_method, type, type)
140
145
  end
141
146
 
142
147
  app.opts[:type_routing] = config.freeze
@@ -195,6 +200,7 @@ class Roda
195
200
  @env['HTTP_ACCEPT'].to_s.split(/\s*,\s*/).map do |part|
196
201
  mime, _= part.split(/\s*;\s*/, 2)
197
202
  if sym = mimes[mime]
203
+ response['Vary'] = (vary = response['Vary']) ? "#{vary}, Accept" : 'Accept'
198
204
  return sym
199
205
  end
200
206
  end
@@ -229,7 +229,7 @@ class Roda
229
229
  #
230
230
  # Note that if there are multiple conversion Error raised inside a +convert!+ or +convert_each!+
231
231
  # block, they are recorded and a single TypecastParams::Error instance is raised after
232
- # processing the block. TypecastParams::Error#params_names can be called on the exception to
232
+ # processing the block. TypecastParams::Error#param_names can be called on the exception to
233
233
  # get an array of all parameter names with conversion issues, and TypecastParams::Error#all_errors
234
234
  # can be used to get an array of all Error instances.
235
235
  #
@@ -609,10 +609,9 @@ class Roda
609
609
  when nil
610
610
  keys = (0...@obj.length)
611
611
 
612
- valid = case @obj
613
- when Array
612
+ valid = if @obj.is_a?(Array)
614
613
  true
615
- when Hash
614
+ else
616
615
  keys = keys.map(&:to_s)
617
616
  keys.all?{|k| @obj.has_key?(k)}
618
617
  end
@@ -725,7 +724,7 @@ class Roda
725
724
  raise Error, "parameter #{param_name(nil)} is not a hash" if do_raise
726
725
  return
727
726
  end
728
- present = @obj.has_key?(key)
727
+ present = !@obj[key].nil?
729
728
  when Integer
730
729
  unless @obj.is_a?(Array)
731
730
  raise Error, "parameter #{param_name(nil)} is not an array" if do_raise
@@ -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 = 34
7
+ RodaMinorVersion = 39
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
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.34.0
4
+ version: 3.39.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: 2020-07-14 00:00:00.000000000 Z
11
+ date: 2020-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -210,6 +210,11 @@ extra_rdoc_files:
210
210
  - doc/release_notes/3.32.0.txt
211
211
  - doc/release_notes/3.33.0.txt
212
212
  - doc/release_notes/3.34.0.txt
213
+ - doc/release_notes/3.35.0.txt
214
+ - doc/release_notes/3.36.0.txt
215
+ - doc/release_notes/3.37.0.txt
216
+ - doc/release_notes/3.38.0.txt
217
+ - doc/release_notes/3.39.0.txt
213
218
  files:
214
219
  - CHANGELOG
215
220
  - MIT-LICENSE
@@ -245,6 +250,11 @@ files:
245
250
  - doc/release_notes/3.32.0.txt
246
251
  - doc/release_notes/3.33.0.txt
247
252
  - doc/release_notes/3.34.0.txt
253
+ - doc/release_notes/3.35.0.txt
254
+ - doc/release_notes/3.36.0.txt
255
+ - doc/release_notes/3.37.0.txt
256
+ - doc/release_notes/3.38.0.txt
257
+ - doc/release_notes/3.39.0.txt
248
258
  - doc/release_notes/3.4.0.txt
249
259
  - doc/release_notes/3.5.0.txt
250
260
  - doc/release_notes/3.6.0.txt
@@ -271,6 +281,7 @@ files:
271
281
  - lib/roda/plugins/content_security_policy.rb
272
282
  - lib/roda/plugins/cookies.rb
273
283
  - lib/roda/plugins/csrf.rb
284
+ - lib/roda/plugins/custom_matchers.rb
274
285
  - lib/roda/plugins/default_headers.rb
275
286
  - lib/roda/plugins/default_status.rb
276
287
  - lib/roda/plugins/delay_build.rb
@@ -305,6 +316,7 @@ files:
305
316
  - lib/roda/plugins/middleware.rb
306
317
  - lib/roda/plugins/middleware_stack.rb
307
318
  - lib/roda/plugins/module_include.rb
319
+ - lib/roda/plugins/multi_public.rb
308
320
  - lib/roda/plugins/multi_route.rb
309
321
  - lib/roda/plugins/multi_run.rb
310
322
  - lib/roda/plugins/multi_view.rb
@@ -324,6 +336,7 @@ files:
324
336
  - lib/roda/plugins/placeholder_string_matchers.rb
325
337
  - lib/roda/plugins/precompile_templates.rb
326
338
  - lib/roda/plugins/public.rb
339
+ - lib/roda/plugins/r.rb
327
340
  - lib/roda/plugins/relative_path.rb
328
341
  - lib/roda/plugins/render.rb
329
342
  - lib/roda/plugins/render_each.rb
@@ -381,7 +394,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
381
394
  - !ruby/object:Gem::Version
382
395
  version: '0'
383
396
  requirements: []
384
- rubygems_version: 3.1.2
397
+ rubygems_version: 3.1.4
385
398
  signing_key:
386
399
  specification_version: 4
387
400
  summary: Routing tree web toolkit