roda 3.109.0 → 3.110.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/lib/roda/cache.rb +0 -2
  3. data/lib/roda/opts.rb +1 -1
  4. data/lib/roda/plugins/_symbol_class_matchers.rb +2 -4
  5. data/lib/roda/plugins/additional_view_directories.rb +2 -2
  6. data/lib/roda/plugins/assets.rb +173 -142
  7. data/lib/roda/plugins/assets_preloading.rb +26 -10
  8. data/lib/roda/plugins/backtracking_array.rb +2 -2
  9. data/lib/roda/plugins/branch_locals.rb +4 -4
  10. data/lib/roda/plugins/break.rb +2 -2
  11. data/lib/roda/plugins/caching.rb +27 -17
  12. data/lib/roda/plugins/capture_erb.rb +15 -12
  13. data/lib/roda/plugins/chunked.rb +32 -30
  14. data/lib/roda/plugins/class_level_routing.rb +5 -2
  15. data/lib/roda/plugins/class_matchers.rb +11 -6
  16. data/lib/roda/plugins/common_logger.rb +21 -3
  17. data/lib/roda/plugins/conditional_sessions.rb +3 -3
  18. data/lib/roda/plugins/content_for.rb +7 -6
  19. data/lib/roda/plugins/content_security_policy.rb +11 -12
  20. data/lib/roda/plugins/cookie_flags.rb +12 -6
  21. data/lib/roda/plugins/cookies.rb +7 -5
  22. data/lib/roda/plugins/csrf.rb +2 -1
  23. data/lib/roda/plugins/custom_block_results.rb +6 -7
  24. data/lib/roda/plugins/default_headers.rb +2 -2
  25. data/lib/roda/plugins/delay_build.rb +1 -1
  26. data/lib/roda/plugins/delegate.rb +7 -6
  27. data/lib/roda/plugins/direct_call.rb +5 -1
  28. data/lib/roda/plugins/disallow_file_uploads.rb +2 -3
  29. data/lib/roda/plugins/drop_body.rb +3 -6
  30. data/lib/roda/plugins/each_part.rb +4 -6
  31. data/lib/roda/plugins/early_hints.rb +4 -4
  32. data/lib/roda/plugins/empty_root.rb +5 -5
  33. data/lib/roda/plugins/environments.rb +8 -8
  34. data/lib/roda/plugins/erb_h.rb +4 -4
  35. data/lib/roda/plugins/error_email.rb +16 -17
  36. data/lib/roda/plugins/error_handler.rb +24 -32
  37. data/lib/roda/plugins/error_mail.rb +26 -15
  38. data/lib/roda/plugins/exception_page.rb +41 -35
  39. data/lib/roda/plugins/filter_common_logger.rb +6 -4
  40. data/lib/roda/plugins/flash.rb +16 -13
  41. data/lib/roda/plugins/freeze_request_response.rb +24 -0
  42. data/lib/roda/plugins/hash_routes.rb +1 -1
  43. data/lib/roda/plugins/permissions_policy.rb +14 -16
  44. data/lib/roda/plugins/public.rb +11 -0
  45. data/lib/roda/plugins/recheck_precompiled_assets.rb +30 -8
  46. data/lib/roda/plugins/render.rb +3 -0
  47. data/lib/roda/plugins/render_locals.rb +1 -1
  48. data/lib/roda/plugins/sessions.rb +158 -137
  49. data/lib/roda/plugins/symbol_matchers.rb +8 -5
  50. data/lib/roda/plugins/unescape_path.rb +22 -4
  51. data/lib/roda/plugins.rb +24 -0
  52. data/lib/roda/request.rb +8 -7
  53. data/lib/roda/response.rb +9 -7
  54. data/lib/roda/version.rb +1 -1
  55. data/lib/roda.rb +12 -9
  56. metadata +2 -1
@@ -41,7 +41,7 @@ class Roda
41
41
  # end
42
42
  #
43
43
  # The following methods are available for configuring the content security policy,
44
- # which specify the setting (substituting _ with -):
44
+ # which specify the setting (substituting +_+ with +-+):
45
45
  #
46
46
  # * base_uri
47
47
  # * child_src
@@ -92,7 +92,7 @@ class Roda
92
92
  # content_security_policy.get_script_src
93
93
  # # => [:self, :unsafe_eval, 'example.com', [:nonce, 'foobarbaz']]
94
94
  #
95
- # The clear method can be used to remove all settings from the policy. Empty policies
95
+ # The +clear+ method can be used to remove all settings from the policy. Empty policies
96
96
  # do not set any headers. You can use +response.skip_content_security_policy!+ to skip
97
97
  # setting a policy. This is faster than calling +content_security_policy.clear+, since
98
98
  # it does not duplicate the default policy.
@@ -115,7 +115,7 @@ class Roda
115
115
 
116
116
  # Represents a content security policy.
117
117
  class Policy
118
- '
118
+ %w'
119
119
  base-uri
120
120
  child-src
121
121
  connect-src
@@ -136,7 +136,7 @@ class Roda
136
136
  script-src
137
137
  style-src
138
138
  worker-src
139
- '.split.each(&:freeze).each do |setting|
139
+ '.each(&:freeze).each do |setting|
140
140
  meth = setting.tr('-', '_').freeze
141
141
 
142
142
  # Setting method name sets the setting value, or removes it if no args are given.
@@ -153,9 +153,12 @@ class Roda
153
153
  # are given.
154
154
  define_method("add_#{meth}") do |*args|
155
155
  unless args.empty?
156
- @opts[setting] ||= EMPTY_ARRAY
157
- @opts[setting] += args
158
- @opts[setting].freeze
156
+ if @opts[setting]
157
+ @opts[setting] += args
158
+ @opts[setting].freeze
159
+ else
160
+ @opts[setting] = args.freeze
161
+ end
159
162
  end
160
163
  nil
161
164
  end
@@ -187,16 +190,13 @@ class Roda
187
190
  end
188
191
  end
189
192
 
190
- def initialize
191
- clear
192
- end
193
-
194
193
  # Clear all settings, useful to remove any inherited settings.
195
194
  def clear
196
195
  @opts = {}
197
196
  @report_only = nil
198
197
  @header_value = nil
199
198
  end
199
+ alias initialize clear
200
200
 
201
201
  # Do not allow future modifications to any settings.
202
202
  def freeze
@@ -276,7 +276,6 @@ class Roda
276
276
  end
277
277
  end
278
278
 
279
-
280
279
  # Yield the current Content Security Policy to the block.
281
280
  def self.configure(app)
282
281
  policy = app.opts[:content_security_policy] = if policy = app.opts[:content_security_policy]
@@ -60,7 +60,11 @@ class Roda
60
60
  class Error < RodaError
61
61
  end
62
62
 
63
+ Config = Struct.new(*DEFAULTS.keys, :same_site_string)
64
+ private_constant :Config
65
+
63
66
  def self.configure(app, opts=OPTS)
67
+ RodaPlugins.check_supported_opts("the cookie_flags plugin", opts, DEFAULTS.keys)
64
68
  previous = app.cookie_flags || DEFAULTS
65
69
  opts = app.opts[:cookie_flags] = previous.merge(opts)
66
70
 
@@ -72,24 +76,26 @@ class Roda
72
76
  end
73
77
 
74
78
  opts.freeze
79
+ app.opts[:cookie_flags_config] = Config.new(*opts.values_at(*Config.members)).freeze
75
80
  end
76
81
 
77
82
  module ClassMethods
78
83
  RodaPlugins.opt_attr_reader(self, :cookie_flags)
84
+ RodaPlugins.opt_attr_reader(self, :cookie_flags_config)
79
85
  end
80
86
 
81
87
  module InstanceMethods
82
88
  private
83
89
 
84
90
  def _handle_cookie_flags_array(cookies)
85
- opts = self.class.cookie_flags
86
- needs_secure = opts[:secure]
87
- needs_httponly = opts[:httponly]
88
- if needs_same_site = opts[:same_site]
89
- same_site_string = opts[:same_site_string]
91
+ opts = self.class.cookie_flags_config
92
+ needs_secure = opts.secure
93
+ needs_httponly = opts.httponly
94
+ if needs_same_site = opts.same_site
95
+ same_site_string = opts.same_site_string
90
96
  same_site_regexp = /;\s*samesite\s*=\s*(\S+)\s*(?:\z|;)/i
91
97
  end
92
- action = opts[:action]
98
+ action = opts.action
93
99
 
94
100
  cookies.map do |cookie|
95
101
  if needs_secure
@@ -19,8 +19,8 @@ class Roda
19
19
  # plugin :cookies, domain: 'example.com', path: '/api'
20
20
  module Cookies
21
21
  # Allow setting default cookie options when loading the cookies plugin.
22
- def self.configure(app, opts={})
23
- app.opts[:cookies_opts] = (app.cookies_opts||{}).merge(opts).freeze
22
+ def self.configure(app, opts=OPTS)
23
+ app.opts[:cookies_opts] = (app.cookies_opts||OPTS).merge(opts).freeze
24
24
  end
25
25
 
26
26
  module ClassMethods
@@ -28,15 +28,17 @@ class Roda
28
28
  end
29
29
 
30
30
  module ResponseMethods
31
- # Modify the headers to include a Set-Cookie value that
31
+ # Modify the headers to include a <tt>Set-Cookie</tt> value that
32
32
  # deletes the cookie. A value hash can be provided to
33
33
  # override the default one used to delete the cookie.
34
34
  # Example:
35
35
  #
36
36
  # response.delete_cookie('foo')
37
37
  # response.delete_cookie('foo', domain: 'example.org')
38
- def delete_cookie(key, value = {})
39
- ::Rack::Utils.delete_cookie_header!(@headers, key, roda_class.cookies_opts.merge(value))
38
+ def delete_cookie(key, value = OPTS)
39
+ opts = roda_class.cookies_opts
40
+ value = value.empty? ? opts : opts.merge(value)
41
+ ::Rack::Utils.delete_cookie_header!(@headers, key, value)
40
42
  end
41
43
 
42
44
  # Set the cookie with the given key in the headers.
@@ -1,11 +1,12 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- # RODA4: Remove plugin
4
3
 
5
4
  require 'rack/csrf'
6
5
 
7
6
  class Roda
8
7
  module RodaPlugins
8
+ warn("The csrf plugin is deprecated and will be removed in Roda 4. Switch to the route_csrf or sec_fetch_site_csrf plugin, or use the Rack::Csrf middleware directly.", uplevel: 1)
9
+
9
10
  # This plugin is no longer recommended for use, it exists only for
10
11
  # backwards compatibility. Consider using the route_csrf plugin
11
12
  # instead, as that provides stronger CSRF protection.
@@ -5,12 +5,11 @@ class Roda
5
5
  module RodaPlugins
6
6
  # The custom_block_results plugin allows you to specify handling
7
7
  # for different block results. By default, Roda only supports
8
- # nil, false, and string block results, but using this plugin,
8
+ # +nil+, +false+, and string block results, but using this plugin,
9
9
  # you can support other block results.
10
10
  #
11
- # For example, if you wanted to support returning Integer
12
- # block results, and have them set the response status code,
13
- # you could do:
11
+ # For example, if you wanted to support Integer block results, and
12
+ # have the integer set the response status code, you could do:
14
13
  #
15
14
  # plugin :custom_block_results
16
15
  #
@@ -23,12 +22,12 @@ class Roda
23
22
  # end
24
23
  #
25
24
  # The expected use case for this is to customize behavior by
26
- # class, but matching uses ===, so it is possible to use non-class
27
- # objects that respond to === appropriately.
25
+ # class, but matching uses <tt>===</tt>, so it is possible to use non-class
26
+ # objects that respond to <tt>===</tt> appropriately.
28
27
  #
29
28
  # Note that custom block result handling only occurs if the types
30
29
  # are not handled by Roda itself. You cannot use this to modify
31
- # the handling of nil, false, or string results. Additionally,
30
+ # the handling of +nil+, +false+, or string results. Additionally,
32
31
  # if the response body has already been written to before the the
33
32
  # route block exits, then the result of the block is ignored,
34
33
  # and the related +handle_block_result+ block will not be called
@@ -4,7 +4,7 @@
4
4
  class Roda
5
5
  module RodaPlugins
6
6
  # The default_headers plugin accepts a hash of headers,
7
- # and overrides the default_headers method in the
7
+ # and overrides the +default_headers+ method in the
8
8
  # response class to be a copy of the headers.
9
9
  #
10
10
  # Note that when using this module, you should not
@@ -22,7 +22,7 @@ class Roda
22
22
  # plugin :default_headers, 'Bar'=>'baz'
23
23
  module DefaultHeaders
24
24
  # Merge the given headers into the existing default headers, if any.
25
- def self.configure(app, headers={})
25
+ def self.configure(app, headers=OPTS)
26
26
  app.opts[:default_headers] = (app.default_headers || app::RodaResponse::DEFAULT_HEADERS).merge(headers).freeze
27
27
  end
28
28
 
@@ -3,7 +3,7 @@
3
3
  #
4
4
  class Roda
5
5
  module RodaPlugins
6
- warn("The delay_build plugin no longer has an effect. It will be removed in Roda 4.")
6
+ warn("The delay_build plugin no longer has an effect. It will be removed in Roda 4.", uplevel: 1)
7
7
 
8
8
  module DelayBuild
9
9
  module ClassMethods
@@ -5,10 +5,10 @@ class Roda
5
5
  module RodaPlugins
6
6
  # The delegate plugin allows you to easily setup instance methods in
7
7
  # the scope of the route block that call methods on the related
8
- # request, response, or class which may offer a simpler API in some cases.
8
+ # request, response, or class, which may offer a simpler API in some cases.
9
9
  # Roda doesn't automatically setup such delegate methods because
10
- # it pollutes the application's method namespace, but this plugin
11
- # allows the user to do so.
10
+ # it pollutes the application's method namespace, and doing so should be
11
+ # the user's choice.
12
12
  #
13
13
  # Here's an example based on the README's initial example, using the
14
14
  # request_delegate method to simplify the DSL:
@@ -49,26 +49,27 @@ class Roda
49
49
  # end
50
50
  module Delegate
51
51
  module ClassMethods
52
- # Delegate the given methods to the class
52
+ # Delegate the given instance methods to the class
53
53
  def class_delegate(*meths)
54
54
  meths.each do |meth|
55
55
  define_method(meth){|*a, &block| self.class.public_send(meth, *a, &block)}
56
56
  end
57
57
  end
58
58
 
59
- # Delegate the given methods to the request
59
+ # Delegate the given instance methods to the request
60
60
  def request_delegate(*meths)
61
61
  meths.each do |meth|
62
62
  define_method(meth){|*a, &block| @_request.public_send(meth, *a, &block)}
63
63
  end
64
64
  end
65
65
 
66
- # Delegate the given methods to the response
66
+ # Delegate the given instance methods to the response
67
67
  def response_delegate(*meths)
68
68
  meths.each do |meth|
69
69
  define_method(meth){|*a, &block| @_response.public_send(meth, *a, &block)}
70
70
  end
71
71
  end
72
+ # RODA4: Switch the above calls to ...
72
73
  end
73
74
  end
74
75
 
@@ -7,6 +7,11 @@ class Roda
7
7
  # (app.call will still call the middleware).
8
8
  # This can be used as an optimization, as the Roda class itself can be used
9
9
  # as the callable, which is faster than using a lambda.
10
+ #
11
+ # This plugin is used automatically by Roda when freezing an app, if Roda
12
+ # determines that doing so is safe (where there is no middleware). However,
13
+ # it can be used manually for unfrozen apps, or in cases where middleware
14
+ # is used.
10
15
  module DirectCall
11
16
  def self.configure(app)
12
17
  app.send(:build_rack_app)
@@ -35,4 +40,3 @@ class Roda
35
40
  register_plugin(:direct_call, DirectCall)
36
41
  end
37
42
  end
38
-
@@ -12,7 +12,7 @@ class Roda
12
12
  #
13
13
  # plugin :disallow_file_uploads
14
14
  #
15
- # This plugin is only supported on Rack 1.6+. This plugin does not technically
15
+ # This plugin is supported on Rack 1.6+. This plugin does not technically
16
16
  # block users from uploading files, it only blocks the parsing of request bodies containing
17
17
  # multipart file uploads. So if you do not call +r.POST+ (or something that calls it such as
18
18
  # +r.params+), then Roda will not attempt to parse the request body, and an exception will not
@@ -24,7 +24,7 @@ class Roda
24
24
  NO_TEMPFILE = lambda{|_,_| raise Error, "Support for uploading files has been disabled"}
25
25
 
26
26
  module RequestMethods
27
- # HTML escape the input and return the escaped version.
27
+ # Raise an error if there is an attempt to use a tempfile.
28
28
  def initialize(_, env)
29
29
  env['rack.multipart.tempfile_factory'] = NO_TEMPFILE
30
30
  super
@@ -35,4 +35,3 @@ class Roda
35
35
  register_plugin(:disallow_file_uploads, DisallowFileUploads)
36
36
  end
37
37
  end
38
-
@@ -4,9 +4,9 @@
4
4
  class Roda
5
5
  module RodaPlugins
6
6
  # The drop_body plugin automatically drops the body and
7
- # Content-Type/Content-Length headers from the response if
7
+ # +Content-Type+/+Content-Length+ headers from the response if
8
8
  # the response status indicates that the response should
9
- # not include a body (response statuses 100, 101, 102, 204,
9
+ # not include a body (response statuses 100-199, 204,
10
10
  # and 304). For response status 205, the body and Content-Type
11
11
  # headers are dropped, but the Content-length header is set to
12
12
  # '0' instead of being dropped.
@@ -15,16 +15,13 @@ class Roda
15
15
  DROP_BODY_STATUSES = [100, 101, 102, 204, 205, 304].freeze
16
16
  RodaPlugins.deprecate_constant(self, :DROP_BODY_STATUSES)
17
17
 
18
- DROP_BODY_RANGE = 100..199
19
- private_constant :DROP_BODY_RANGE
20
-
21
18
  # If the response status indicates a body should not be
22
19
  # returned, use an empty body and remove the Content-Length
23
20
  # and Content-Type headers.
24
21
  def finish
25
22
  r = super
26
23
  case r[0]
27
- when DROP_BODY_RANGE, 204, 304
24
+ when 100..199, 204, 304
28
25
  r[2] = EMPTY_ARRAY
29
26
  h = r[1]
30
27
  h.delete(RodaResponseHeaders::CONTENT_LENGTH)
@@ -3,8 +3,8 @@
3
3
  #
4
4
  class Roda
5
5
  module RodaPlugins
6
- # The each_part plugin adds an each_part method, which is a
7
- # render_each-like method that treats all keywords as locals.
6
+ # The each_part plugin adds an +each_part+ method, which is similar to
7
+ # +render_each+, but treats all keywords as locals.
8
8
  #
9
9
  # # Can replace this:
10
10
  # render_each(enum, :template, locals: {foo: 'bar'})
@@ -15,7 +15,7 @@ class Roda
15
15
  # On Ruby 2.7+, the part method takes a keyword splat, so you
16
16
  # must pass keywords and not a positional hash for the locals.
17
17
  #
18
- # If you are using the :assume_fixed_locals render plugin option,
18
+ # If you are using the +:assume_fixed_locals+ render plugin option,
19
19
  # template caching is enabled, you are using Ruby 3+, and you
20
20
  # are freezing your Roda application, in addition to providing a
21
21
  # simpler API, this also provides a performance improvement.
@@ -51,9 +51,7 @@ class Roda
51
51
  end
52
52
 
53
53
  module AssumeFixedLocalsInstanceMethods
54
- # simplecov:disable
55
- if RUBY_VERSION >= '3.0'
56
- # simplecov:enable
54
+ if RUBY_VERSION >= '3.0' # simplecov:disable
57
55
  def each_part(enum, template, **locals, &block)
58
56
  if optimized_method = _cached_render_each_template_method(template)
59
57
  optimized_method = optimized_method[0]
@@ -3,14 +3,14 @@
3
3
  #
4
4
  class Roda
5
5
  module RodaPlugins
6
- # The early_hints plugin allows sending 103 Early Hints responses
7
- # using the rack.early_hints environment variable.
6
+ # The early_hints plugin adds the +send_early_hints+ method for sending
7
+ # 103 Early Hints responses using the +rack.early_hints+ environment variable.
8
8
  # Early hints allow clients to preload necessary files before receiving
9
9
  # the response.
10
10
  module EarlyHints
11
11
  module InstanceMethods
12
- # Send given hash of Early Hints using the rack.early_hints environment variable,
13
- # currenly only supported by puma. hash given should generally have the single
12
+ # Send given hash of Early Hints using the +rack.early_hints+ environment variable,
13
+ # currenly supported by puma and falcon. hash given should generally have the single
14
14
  # key 'Link', and a string or array of strings for each of the early hints.
15
15
  def send_early_hints(hash)
16
16
  if eh_proc = env['rack.early_hints']
@@ -3,10 +3,10 @@
3
3
  #
4
4
  class Roda
5
5
  module RodaPlugins
6
- # The empty_root plugin makes +r.root+ match both on +/+ and
7
- # on the empty string. This is mostly useful when using multiple
8
- # rack applications, where the initial PATH_INFO has been moved
9
- # to SCRIPT_NAME. For example, if you have the following
6
+ # The empty_root plugin makes +r.root+ match both +/+ and
7
+ # the empty string. This is mostly useful when using multiple
8
+ # rack applications, where the initial +PATH_INFO+ has been moved
9
+ # to +SCRIPT_NAME+. For example, if you have the following
10
10
  # applications:
11
11
  #
12
12
  # class App1 < Roda
@@ -28,7 +28,7 @@ class Roda
28
28
  # Then requests for both +/albums/+ and +/albums+ will return
29
29
  # "root". Without this plugin loaded into App2, only requests
30
30
  # for +/albums/+ will return "root", since by default, +r.root+
31
- # matches only when the current PATH_INFO is +/+ and not when
31
+ # matches only when the current +PATH_INFO+ is +/+ and not when
32
32
  # it is empty.
33
33
  module EmptyRoot
34
34
  module RequestMethods
@@ -3,10 +3,10 @@
3
3
  #
4
4
  class Roda
5
5
  module RodaPlugins
6
- # The environments plugin adds a environment class accessor to get
7
- # the environment for the application, 3 predicate class methods
8
- # to check for the current environment (development?, test? and
9
- # production?), and a class configure method that takes environment(s)
6
+ # The environments plugin adds a +environment+ class accessor to get
7
+ # the environment for the application, predicate class methods
8
+ # to check for the current environment (+development?+, +test?+ and
9
+ # +production?+), and a class +configure+ method that takes environment(s)
10
10
  # and yields to the block if the given environment(s) match the
11
11
  # current environment.
12
12
  #
@@ -40,8 +40,8 @@ class Roda
40
40
  # end
41
41
  # end
42
42
  module Environments
43
- # Set the environment to use for the app. Default to ENV['RACK_ENV']
44
- # if no environment is given. If ENV['RACK_ENV'] is not set and
43
+ # Set the environment to use for the app. Default to <tt>ENV['RACK_ENV']</tt>
44
+ # if no environment is given. If <tt>ENV['RACK_ENV']</tt> is not set and
45
45
  # no environment is given, assume the development environment.
46
46
  def self.configure(app, env=ENV["RACK_ENV"])
47
47
  app.opts[:environment] = (env || 'development').to_sym
@@ -53,13 +53,13 @@ class Roda
53
53
  # If no environments are given or one of the given environments
54
54
  # matches the current environment, yield the receiver to the block.
55
55
  def configure(*envs)
56
- if envs.empty? || envs.any?{|s| s == environment}
56
+ if envs.empty? || envs.include?(environment)
57
57
  yield self
58
58
  end
59
59
  end
60
60
 
61
61
  # Override the environment for the application, instead of using
62
- # RACK_ENV.
62
+ # <tt>ENV['RACK_ENV']</tt>.
63
63
  def environment=(v)
64
64
  opts[:environment] = v
65
65
  end
@@ -12,9 +12,9 @@ class Roda
12
12
  #
13
13
  # To make sure that this speeds up applications using the h
14
14
  # plugin, this depends on the h plugin, and overrides the
15
- # h method.
15
+ # +h+ method.
16
16
  #
17
- # The following example will return "&lt;foo&gt;" as the body.
17
+ # The following example will return <tt>"&lt;foo&gt;"</tt> as the body.
18
18
  #
19
19
  # plugin :erb_h
20
20
  #
@@ -25,8 +25,8 @@ class Roda
25
25
  # The faster performance offered by the erb_h plugin is due
26
26
  # to erb/escape avoiding allocations if not needed (returning the
27
27
  # input object if no escaping is needed). That behavior change
28
- # can cause problems if you mutate the result of the h method
29
- # (which can mutate the input), or mutate the input of the h
28
+ # can cause problems if you mutate the result of the +h+ method
29
+ # (which can mutate the input), or mutate the input of the +h+
30
30
  # method after calling it (which can mutate the result).
31
31
  module ErbH
32
32
  def self.load_dependencies(app)
@@ -5,8 +5,8 @@ require 'net/smtp'
5
5
  class Roda
6
6
  module RodaPlugins
7
7
  # The error_email plugin adds an +error_email+ instance method that
8
- # send an email related to the exception. This is most useful if you are
9
- # also using the error_handler plugin:
8
+ # sends an email related to the exception. This is designed for use in
9
+ # combination with the error_handler plugin:
10
10
  #
11
11
  # plugin :error_email, to: 'to@example.com', from: 'from@example.com'
12
12
  # plugin :error_handler do |e|
@@ -16,19 +16,19 @@ class Roda
16
16
  #
17
17
  # It is similar to the error_mail plugin, except that it uses net/smtp
18
18
  # directly instead of using the mail library. If you are not already using the
19
- # mail library in your application, it makes sense to use error_email
20
- # instead of error_mail.
19
+ # mail library in your application, using error_email may be preferable over
20
+ # error_mail in terms of limiting the number of dependencies.
21
21
  #
22
22
  # Options:
23
23
  #
24
- # :filter :: Callable called with the key and value for each parameter, environment
25
- # variable, and session value. If it returns true, the value of the
26
- # parameter is filtered in the email.
27
- # :from :: The From address to use in the email (required)
28
- # :headers :: A hash of additional headers to use in the email (default: empty hash)
29
- # :host :: The SMTP server to use to send the email (default: localhost)
30
- # :prefix :: A prefix to use in the email's subject line (default: no prefix)
31
- # :to :: The To address to use in the email (required)
24
+ # +:filter+ :: Callable called with the key and value for each parameter, environment
25
+ # variable, and session value. If it returns true, the value of the
26
+ # parameter is filtered in the email.
27
+ # +:from+ :: The From address to use in the email (required)
28
+ # +:headers+ :: A hash of additional headers to use in the email (default: empty hash)
29
+ # +:host+ :: The SMTP server to use to send the email (default: localhost)
30
+ # +:prefix+ :: A prefix to use in the email's subject line (default: no prefix)
31
+ # +:to+ :: The To address to use in the email (required)
32
32
  #
33
33
  # The subject of the error email shows the exception class and message.
34
34
  # The body of the error email shows the backtrace of the error and the
@@ -111,17 +111,16 @@ END
111
111
  end
112
112
  }.freeze
113
113
 
114
- # Set default opts for plugin. See ErrorEmail module RDoc for options.
115
114
  def self.configure(app, opts=OPTS)
115
+ RodaPlugins.check_supported_opts("the error_email plugin", opts, [*DEFAULTS.keys, :prefix, :to, :from])
116
+
116
117
  email_opts = app.opts[:error_email] ||= DEFAULTS
117
118
  email_opts = email_opts.merge(opts)
118
- email_opts[:headers] = email_opts[:headers].dup
119
+ email_opts[:headers] = email_opts[:headers].dup.freeze
119
120
  unless email_opts[:to] && email_opts[:from]
120
121
  raise RodaError, "must provide :to and :from options to error_email plugin"
121
122
  end
122
- app.opts[:error_email] = email_opts
123
- app.error_email_opts[:headers].freeze
124
- app.error_email_opts.freeze
123
+ app.opts[:error_email] = email_opts.freeze
125
124
  end
126
125
 
127
126
  module ClassMethods