roda 3.17.0 → 3.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +48 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +22 -4
- data/doc/release_notes/3.18.0.txt +170 -0
- data/lib/roda.rb +249 -26
- data/lib/roda/plugins/_after_hook.rb +4 -26
- data/lib/roda/plugins/_before_hook.rb +30 -2
- data/lib/roda/plugins/branch_locals.rb +2 -2
- data/lib/roda/plugins/class_level_routing.rb +9 -7
- data/lib/roda/plugins/default_headers.rb +15 -1
- data/lib/roda/plugins/default_status.rb +9 -10
- data/lib/roda/plugins/direct_call.rb +38 -0
- data/lib/roda/plugins/error_email.rb +1 -1
- data/lib/roda/plugins/error_handler.rb +37 -11
- data/lib/roda/plugins/hooks.rb +28 -30
- data/lib/roda/plugins/mail_processor.rb +16 -11
- data/lib/roda/plugins/mailer.rb +1 -1
- data/lib/roda/plugins/middleware.rb +13 -3
- data/lib/roda/plugins/multi_route.rb +3 -3
- data/lib/roda/plugins/named_templates.rb +4 -4
- data/lib/roda/plugins/path.rb +13 -8
- data/lib/roda/plugins/render.rb +2 -2
- data/lib/roda/plugins/route_block_args.rb +4 -3
- data/lib/roda/plugins/route_csrf.rb +9 -4
- data/lib/roda/plugins/sessions.rb +2 -1
- data/lib/roda/plugins/shared_vars.rb +1 -1
- data/lib/roda/plugins/static_routing.rb +7 -17
- data/lib/roda/plugins/status_handler.rb +5 -3
- data/lib/roda/plugins/view_options.rb +2 -2
- data/lib/roda/version.rb +1 -1
- data/spec/define_roda_method_spec.rb +257 -0
- data/spec/plugin/class_level_routing_spec.rb +0 -27
- data/spec/plugin/default_headers_spec.rb +7 -0
- data/spec/plugin/default_status_spec.rb +31 -1
- data/spec/plugin/direct_call_spec.rb +28 -0
- data/spec/plugin/error_handler_spec.rb +27 -0
- data/spec/plugin/hooks_spec.rb +21 -0
- data/spec/plugin/middleware_spec.rb +108 -36
- data/spec/plugin/multi_route_spec.rb +12 -0
- data/spec/plugin/route_csrf_spec.rb +27 -0
- data/spec/plugin/sessions_spec.rb +26 -1
- data/spec/plugin/static_routing_spec.rb +25 -3
- data/spec/plugin/status_handler_spec.rb +17 -0
- data/spec/route_spec.rb +39 -0
- data/spec/spec_helper.rb +2 -2
- metadata +9 -3
@@ -1,33 +1,11 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
+
require_relative 'error_handler'
|
4
|
+
|
3
5
|
#
|
4
6
|
class Roda
|
5
7
|
module RodaPlugins
|
6
|
-
#
|
7
|
-
|
8
|
-
# after processing is done by using _roda_after_*
|
9
|
-
# private instance methods that are called in sorted order.
|
10
|
-
# Loaded automatically by the base library if any _roda_after_*
|
11
|
-
# methods are defined.
|
12
|
-
module AfterHook # :nodoc:
|
13
|
-
# Module for internal after hook support.
|
14
|
-
module InstanceMethods
|
15
|
-
# Run internal after hooks with the response
|
16
|
-
def call
|
17
|
-
res = super
|
18
|
-
ensure
|
19
|
-
_roda_after(res)
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
# Empty roda_after method, so nothing breaks if the module is included.
|
25
|
-
# This method will be overridden in most classes using this module.
|
26
|
-
def _roda_after(res)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
register_plugin(:_after_hook, AfterHook)
|
8
|
+
# RODA4: Remove
|
9
|
+
register_plugin(:_after_hook, ErrorHandler)
|
32
10
|
end
|
33
11
|
end
|
@@ -3,9 +3,37 @@
|
|
3
3
|
#
|
4
4
|
class Roda
|
5
5
|
module RodaPlugins
|
6
|
-
#
|
7
|
-
#
|
6
|
+
# Internal before hook module, not for external use.
|
7
|
+
# Allows for plugins to configure the order in which
|
8
|
+
# before processing is done by using _roda_before_*
|
9
|
+
# private instance methods that are called in sorted order.
|
10
|
+
# Loaded automatically by the base library if any _roda_before_*
|
11
|
+
# methods are defined.
|
8
12
|
module BeforeHook # :nodoc:
|
13
|
+
module InstanceMethods
|
14
|
+
# Run internal before hooks - Old Dispatch API.
|
15
|
+
def call(&block)
|
16
|
+
# RODA4: Remove
|
17
|
+
super do
|
18
|
+
_roda_before
|
19
|
+
instance_exec(@_request, &block) # call Fallback
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Run internal before hooks before running the main
|
24
|
+
# roda route.
|
25
|
+
def _roda_run_main_route(r)
|
26
|
+
_roda_before
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# Default empty implementation of _roda_before, usually
|
33
|
+
# overridden by Roda.def_roda_before.
|
34
|
+
def _roda_before
|
35
|
+
end
|
36
|
+
end
|
9
37
|
end
|
10
38
|
|
11
39
|
register_plugin(:_before_hook, BeforeHook)
|
@@ -31,7 +31,7 @@ class Roda
|
|
31
31
|
# Update the default layout locals to use in this branch.
|
32
32
|
def set_layout_locals(opts)
|
33
33
|
if locals = @_layout_locals
|
34
|
-
@_layout_locals =
|
34
|
+
@_layout_locals = locals.merge(opts)
|
35
35
|
else
|
36
36
|
@_layout_locals = opts
|
37
37
|
end
|
@@ -40,7 +40,7 @@ class Roda
|
|
40
40
|
# Update the default view locals to use in this branch.
|
41
41
|
def set_view_locals(opts)
|
42
42
|
if locals = @_view_locals
|
43
|
-
@_view_locals =
|
43
|
+
@_view_locals = locals.merge(opts)
|
44
44
|
else
|
45
45
|
@_view_locals = opts
|
46
46
|
end
|
@@ -62,9 +62,10 @@ class Roda
|
|
62
62
|
|
63
63
|
module ClassMethods
|
64
64
|
# Define routing methods that will store class level routes.
|
65
|
-
[:root, :on, :is, :get, :post, :delete, :head, :options, :link, :patch, :put, :trace, :unlink].each do |
|
66
|
-
define_method(
|
67
|
-
|
65
|
+
[:root, :on, :is, :get, :post, :delete, :head, :options, :link, :patch, :put, :trace, :unlink].each do |request_meth|
|
66
|
+
define_method(request_meth) do |*args, &block|
|
67
|
+
meth = define_roda_method("class_level_routing_#{request_meth}", :any, &block)
|
68
|
+
opts[:class_level_routes] << [request_meth, args, meth].freeze
|
68
69
|
end
|
69
70
|
end
|
70
71
|
|
@@ -91,11 +92,12 @@ class Roda
|
|
91
92
|
# the original response.
|
92
93
|
@_response.send(:initialize)
|
93
94
|
@_response.status = nil
|
94
|
-
result.replace(
|
95
|
-
|
95
|
+
result.replace(_roda_handle_route do
|
96
|
+
r = @_request
|
97
|
+
opts[:class_level_routes].each do |request_meth, args, meth|
|
96
98
|
r.instance_variable_set(:@remaining_path, @_original_remaining_path)
|
97
|
-
r.public_send(
|
98
|
-
|
99
|
+
r.public_send(request_meth, *args) do |*a|
|
100
|
+
send(meth, *a)
|
99
101
|
end
|
100
102
|
end
|
101
103
|
nil
|
@@ -23,7 +23,21 @@ class Roda
|
|
23
23
|
module DefaultHeaders
|
24
24
|
# Merge the given headers into the existing default headers, if any.
|
25
25
|
def self.configure(app, headers={})
|
26
|
-
app.opts[:default_headers] = (app.default_headers || app::RodaResponse::DEFAULT_HEADERS).merge(headers).freeze
|
26
|
+
headers = app.opts[:default_headers] = (app.default_headers || app::RodaResponse::DEFAULT_HEADERS).merge(headers).freeze
|
27
|
+
|
28
|
+
if headers.all?{|k, v| k.is_a?(String) && v.is_a?(String)}
|
29
|
+
response_class = app::RodaResponse
|
30
|
+
owner = response_class.instance_method(:set_default_headers).owner
|
31
|
+
if owner == Base::ResponseMethods || (owner == response_class && app.opts[:set_default_headers_overridder] == response_class)
|
32
|
+
app.opts[:set_default_headers_overridder] = response_class
|
33
|
+
response_class.class_eval(<<-END, __FILE__, __LINE__+1)
|
34
|
+
def set_default_headers
|
35
|
+
h = @headers
|
36
|
+
#{headers.map{|k,v| "h[#{k.inspect}] ||= #{v.inspect}"}.join('; ')}
|
37
|
+
end
|
38
|
+
END
|
39
|
+
end
|
40
|
+
end
|
27
41
|
end
|
28
42
|
|
29
43
|
module ClassMethods
|
@@ -7,8 +7,6 @@ class Roda
|
|
7
7
|
# return a response status integer. This integer will be used as
|
8
8
|
# the default response status (usually 200) if the body has been
|
9
9
|
# written to, and you have not explicitly set a response status.
|
10
|
-
# The block given to the block is instance_execed in the context
|
11
|
-
# of the response.
|
12
10
|
#
|
13
11
|
# Example:
|
14
12
|
#
|
@@ -19,15 +17,16 @@ class Roda
|
|
19
17
|
module DefaultStatus
|
20
18
|
def self.configure(app, &block)
|
21
19
|
raise RodaError, "default_status plugin requires a block" unless block
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
20
|
+
if check_arity = app.opts.fetch(:check_arity, true)
|
21
|
+
unless block.arity == 0
|
22
|
+
if check_arity == :warn
|
23
|
+
RodaPlugins.warn "Arity mismatch in block passed to plugin :default_status. Expected Arity 0, but arguments required for #{block.inspect}"
|
24
|
+
end
|
25
|
+
b = block
|
26
|
+
block = lambda{instance_exec(&b)} # Fallback
|
27
|
+
end
|
30
28
|
end
|
29
|
+
app::RodaResponse.send(:define_method, :default_status, &block)
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
# The direct_call plugin makes the call class method skip the middleware stack
|
7
|
+
# (app.call will still call the middleware).
|
8
|
+
# This can be used as an optimization, as the Roda class itself can be used
|
9
|
+
# as the callable, which is faster than using a lambda.
|
10
|
+
module DirectCall
|
11
|
+
def self.configure(app)
|
12
|
+
app.send(:build_rack_app)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
# Call the application without middlware.
|
17
|
+
def call(env)
|
18
|
+
new(env)._roda_handle_main_route
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# If new_api is true, use the receiver as the base rack app for better
|
24
|
+
# performance.
|
25
|
+
def base_rack_app_callable(new_api=true)
|
26
|
+
if new_api
|
27
|
+
self
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
register_plugin(:direct_call, DirectCall)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -123,7 +123,7 @@ END
|
|
123
123
|
def error_email_content(exception)
|
124
124
|
email_opts = self.class.opts[:error_email]
|
125
125
|
headers = email_opts[:default_headers].call(email_opts, exception)
|
126
|
-
headers =
|
126
|
+
headers = headers.merge(email_opts[:headers])
|
127
127
|
headers = headers.map{|k,v| "#{k}: #{v.gsub(/\r?\n/m, "\r\n ")}"}.sort.join("\r\n")
|
128
128
|
body = email_opts[:body].call(self, exception)
|
129
129
|
"#{headers}\r\n\r\n#{body}"
|
@@ -44,10 +44,6 @@ class Roda
|
|
44
44
|
#
|
45
45
|
# plugin :error_handler, classes: [StandardError, ScriptError, NoMemoryError]
|
46
46
|
module ErrorHandler
|
47
|
-
def self.load_dependencies(app, *)
|
48
|
-
app.plugin :_after_hook
|
49
|
-
end
|
50
|
-
|
51
47
|
DEFAULT_ERROR_HANDLER_CLASSES = [StandardError, ScriptError].freeze
|
52
48
|
|
53
49
|
# If a block is given, automatically call the +error+ method on
|
@@ -72,13 +68,45 @@ class Roda
|
|
72
68
|
|
73
69
|
module InstanceMethods
|
74
70
|
# If an error occurs, set the response status to 500 and call
|
75
|
-
# the error handler.
|
71
|
+
# the error handler. Old Dispatch API.
|
76
72
|
def call
|
77
|
-
|
73
|
+
# RODA4: Remove
|
74
|
+
begin
|
75
|
+
res = super
|
76
|
+
ensure
|
77
|
+
_roda_after(res)
|
78
|
+
end
|
79
|
+
rescue *opts[:error_handler_classes] => e
|
80
|
+
_handle_error(e)
|
81
|
+
end
|
82
|
+
|
83
|
+
# If an error occurs, set the response status to 500 and call
|
84
|
+
# the error handler.
|
85
|
+
def _roda_handle_main_route
|
86
|
+
begin
|
87
|
+
res = super
|
88
|
+
ensure
|
89
|
+
_roda_after(res)
|
90
|
+
end
|
78
91
|
rescue *opts[:error_handler_classes] => e
|
79
|
-
|
80
|
-
|
81
|
-
|
92
|
+
_handle_error(e)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# Default empty implementation of _roda_after, usually
|
98
|
+
# overridden by Roda.def_roda_before.
|
99
|
+
def _roda_after(res)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Handle the given exception using handle_error, using a default status
|
103
|
+
# of 500. Run after hooks on the rack response, but if any error occurs
|
104
|
+
# when doing so, log the error using rack.errors and return the response.
|
105
|
+
def _handle_error(e)
|
106
|
+
res = @_response
|
107
|
+
res.send(:initialize)
|
108
|
+
res.status = 500
|
109
|
+
res = _roda_handle_route{handle_error(e)}
|
82
110
|
begin
|
83
111
|
_roda_after(res)
|
84
112
|
rescue => e2
|
@@ -90,8 +118,6 @@ class Roda
|
|
90
118
|
res
|
91
119
|
end
|
92
120
|
|
93
|
-
private
|
94
|
-
|
95
121
|
# By default, have the error handler reraise the error, so using
|
96
122
|
# the plugin without installing an error handler doesn't change
|
97
123
|
# behavior.
|
data/lib/roda/plugins/hooks.rb
CHANGED
@@ -34,57 +34,55 @@ class Roda
|
|
34
34
|
# hooks can be called with nil if an exception is raised during routing.
|
35
35
|
module Hooks
|
36
36
|
def self.configure(app)
|
37
|
-
app.opts[:
|
38
|
-
app.opts[:
|
37
|
+
app.opts[:after_hooks] ||= []
|
38
|
+
app.opts[:before_hooks] ||= []
|
39
39
|
end
|
40
40
|
|
41
41
|
module ClassMethods
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
# Freeze the array of hook methods when freezing the app.
|
43
|
+
def freeze
|
44
|
+
opts[:after_hooks].freeze
|
45
|
+
opts[:before_hooks].freeze
|
46
|
+
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add an after hook.
|
46
51
|
def after(&block)
|
47
|
-
opts[:
|
48
|
-
|
49
|
-
|
50
|
-
instance_exec(res, &block)
|
51
|
-
end
|
52
|
+
opts[:after_hooks] << define_roda_method("after_hook", 1, &block)
|
53
|
+
if opts[:after_hooks].length == 1
|
54
|
+
class_eval("alias _roda_after_80__hooks #{opts[:after_hooks].first}", __FILE__, __LINE__)
|
52
55
|
else
|
53
|
-
|
56
|
+
class_eval("def _roda_after_80__hooks(res) #{opts[:after_hooks].map{|m| "#{m}(res)"}.join(';')} end", __FILE__, __LINE__)
|
54
57
|
end
|
58
|
+
private :_roda_after_80__hooks
|
59
|
+
def_roda_after
|
60
|
+
nil
|
55
61
|
end
|
56
62
|
|
57
|
-
# Add a before hook.
|
58
|
-
# use a proc that instance_execs the give before proc and
|
59
|
-
# then instance_execs the existing before proc, so that the given
|
60
|
-
# before proc always executes before the previous one.
|
63
|
+
# Add a before hook.
|
61
64
|
def before(&block)
|
62
|
-
opts[:before_hook
|
63
|
-
|
64
|
-
|
65
|
-
instance_exec(&b)
|
66
|
-
end
|
65
|
+
opts[:before_hooks].unshift(define_roda_method("before_hook", 0, &block))
|
66
|
+
if opts[:before_hooks].length == 1
|
67
|
+
class_eval("alias _roda_before_10__hooks #{opts[:before_hooks].first}", __FILE__, __LINE__)
|
67
68
|
else
|
68
|
-
|
69
|
+
class_eval("def _roda_before_10__hooks; #{opts[:before_hooks].join(';')} end", __FILE__, __LINE__)
|
69
70
|
end
|
71
|
+
private :_roda_before_10__hooks
|
72
|
+
def_roda_before
|
73
|
+
nil
|
70
74
|
end
|
71
75
|
end
|
72
76
|
|
73
77
|
module InstanceMethods
|
74
78
|
private
|
75
79
|
|
76
|
-
#
|
80
|
+
# Default method if no after hooks are defined.
|
77
81
|
def _roda_after_80__hooks(res)
|
78
|
-
if b = opts[:after_hook]
|
79
|
-
instance_exec(res, &b)
|
80
|
-
end
|
81
82
|
end
|
82
83
|
|
83
|
-
#
|
84
|
+
# Default method if no before hooks are defined.
|
84
85
|
def _roda_before_10__hooks
|
85
|
-
if b = opts[:before_hook]
|
86
|
-
instance_exec(&b)
|
87
|
-
end
|
88
86
|
end
|
89
87
|
end
|
90
88
|
end
|
@@ -301,7 +301,7 @@ class Roda
|
|
301
301
|
|
302
302
|
begin
|
303
303
|
begin
|
304
|
-
scope.process_mail
|
304
|
+
scope.process_mail
|
305
305
|
rescue UnhandledMail
|
306
306
|
scope.unhandled_mail_hook
|
307
307
|
else
|
@@ -329,16 +329,23 @@ class Roda
|
|
329
329
|
def rcpt(*addresses, &block)
|
330
330
|
opts[:mail_processor_string_routes] ||= {}
|
331
331
|
opts[:mail_processor_regexp_routes] ||= {}
|
332
|
+
string_meth = nil
|
333
|
+
regexp_meth = nil
|
332
334
|
addresses.each do |address|
|
333
335
|
key = case address
|
334
336
|
when String
|
335
|
-
|
337
|
+
unless string_meth
|
338
|
+
string_meth = define_roda_method("mail_processor_string_route_#{address}", 1, &convert_route_block(block))
|
339
|
+
end
|
340
|
+
opts[:mail_processor_string_routes][address] = string_meth
|
336
341
|
when Regexp
|
337
|
-
|
342
|
+
unless regexp_meth
|
343
|
+
regexp_meth = define_roda_method("mail_processor_regexp_route_#{address}", :any, &convert_route_block(block))
|
344
|
+
end
|
345
|
+
opts[:mail_processor_regexp_routes][address] = regexp_meth
|
338
346
|
else
|
339
347
|
raise RodaError, "invalid address format passed to rcpt, should be Array or String"
|
340
348
|
end
|
341
|
-
opts[key][address] = block
|
342
349
|
end
|
343
350
|
nil
|
344
351
|
end
|
@@ -379,25 +386,23 @@ class Roda
|
|
379
386
|
addresses = mail_recipients
|
380
387
|
|
381
388
|
addresses.each do |address|
|
382
|
-
if
|
383
|
-
|
389
|
+
if meth = string_routes[address.to_s.downcase]
|
390
|
+
_roda_handle_route{send(meth, @_request)}
|
384
391
|
return
|
385
392
|
end
|
386
393
|
end
|
387
394
|
|
388
|
-
opts[:mail_processor_regexp_routes].each do |regexp,
|
395
|
+
opts[:mail_processor_regexp_routes].each do |regexp, meth|
|
389
396
|
addresses.each do |address|
|
390
397
|
if md = regexp.match(address)
|
391
|
-
|
392
|
-
instance_exec(r, *md.captures, &blk)
|
393
|
-
end
|
398
|
+
_roda_handle_route{send(meth, @_request, *md.captures)}
|
394
399
|
return
|
395
400
|
end
|
396
401
|
end
|
397
402
|
end
|
398
403
|
end
|
399
404
|
|
400
|
-
|
405
|
+
_roda_handle_main_route
|
401
406
|
|
402
407
|
nil
|
403
408
|
end
|