roda 3.70.0 → 3.72.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d8ea04ca767f8be110f6ba14d1fd877a389a2e733e2925020f2ac589f6a9571
4
- data.tar.gz: c9a2d26f41724c05ea1de5e8fb0058093e5fad34fb0c6479dd74badcfea67016
3
+ metadata.gz: d5c28e38d35f59176f159798f68d82bbd30df62a5907ace3a3750b1daf7b4fe6
4
+ data.tar.gz: cb5fd8d5a9d665bc820c55ccbf1fd6a068873ae3e265a1f877bbdf82b51c4d6e
5
5
  SHA512:
6
- metadata.gz: 288a793976f389dfa2fe8fd55fb649590748cbe7ebb9da48f351117bb93da33ceee5848077f53c73430fd943b6be5b69a6d189e8b7424fe9ec60892dbaacff56
7
- data.tar.gz: 3cad6d6823a2fbcd5534521b6e5fbcc7b85c04d1a80e9ca758cac6ab9b4b967a5f019b653de7d4de3c1d32069832f362fd2564ec219252b0f2dd8ccf1c0785c1
6
+ metadata.gz: d8710537511b8f332c443fd41bd279c99de53c0bc9618cd92f763ab7d354f70c4496ba9eafa076438087b5ab52f36e69f649aa00d2fb6f07d5315b7d61aef4a1
7
+ data.tar.gz: e0af216b465facc81e81ab8a495d472685c26aa6b2c1d56c0e48631586555f78f4d5922ac5ee92c6118a08b8162ee35866dc5708155bdbed70d5851b2ff8339b
data/CHANGELOG CHANGED
@@ -1,3 +1,15 @@
1
+ = 3.72.0 (2023-09-12)
2
+
3
+ * Add invalid_request_body plugin for custom handling of invalid request bodies (jeremyevans)
4
+
5
+ * Warn when defining method that expects 1 argument when block requires multiple arguments when :check_arity option is set to :warn (jeremyevans)
6
+
7
+ * Implement the match_hooks plugin using the match_hook_args plugin (jeremyevans)
8
+
9
+ = 3.71.0 (2023-08-14)
10
+
11
+ * Add match_hook_args plugin, similar to match_hooks but support matchers and block args as hook arguments (jeremyevans)
12
+
1
13
  = 3.70.0 (2023-07-12)
2
14
 
3
15
  * Add plain_hash_response_headers plugin, using a plain hash for response headers on Rack 3 for much better performance (jeremyevans)
@@ -0,0 +1,33 @@
1
+ = New Feature
2
+
3
+ * A match_hook_args plugin has been added. This is similar to the
4
+ existing match_hook plugin, but passes through the matchers and
5
+ block arguments (values yielded to the match block). Example:
6
+
7
+ plugin :match_hook_args
8
+
9
+ add_match_hook do |matchers, block_args|
10
+ logger.debug("matchers: #{matchers.inspect}. #{block_args.inspect} yielded.")
11
+ end
12
+
13
+ # Term is an implicit matcher used for terminating matches, and
14
+ # will be included in the array of matchers yielded to the match hook
15
+ # if a terminating match is used.
16
+ term = self.class::RodaRequest::TERM
17
+
18
+ route do |r|
19
+ r.root do
20
+ # for a request for /
21
+ # matchers: nil, block_args: nil
22
+ end
23
+
24
+ r.on 'a', ['b', 'c'], Integer do |segment, id|
25
+ # for a request for /a/b/1
26
+ # matchers: ["a", ["b", "c"], Integer], block_args: ["b", 1]
27
+ end
28
+
29
+ r.get 'd' do
30
+ # for a request for /d
31
+ # matchers: ["d", term], block_args: []
32
+ end
33
+ end
@@ -0,0 +1,48 @@
1
+ = New Features
2
+
3
+ * An invalid_request_body plugin has been added for allowing custom
4
+ handling of invalid request bodies. Roda uses Rack's request body
5
+ parsing, and by default invalid request bodies can result in
6
+ different exceptions based on how the body is invalid and which
7
+ version of Rack is in use.
8
+
9
+ If you want to treat an invalid request body as the submission of
10
+ no parameters, you can use the :empty_hash argument when loading
11
+ the plugin:
12
+
13
+ plugin :invalid_request_body, :empty_hash
14
+
15
+ If you want to return a empty 400 (Bad Request) response if an
16
+ invalid request body is submitted, you can use the :empty_400
17
+ argument when loading the plugin:
18
+
19
+ plugin :invalid_request_body, :empty_400
20
+
21
+ If you want to raise a Roda::RodaPlugins::InvalidRequestBody::Error
22
+ exception if an invalid request body is submitted (which makes it
23
+ easier to handle these exceptions when using the error_handler
24
+ plugin), you can use the :raise argument when loading the plugin:
25
+
26
+ plugin :invalid_request_body, :raise
27
+
28
+ For custom behavior, you can pass a block when loading the plugin
29
+ The block is called with the exception Rack raised when parsing the
30
+ body. The block will be used to define a method in the application's
31
+ RodaRequest class. It can either return a hash of parameters, or
32
+ you can raise a different exception, or you can halt processing and
33
+ return a response:
34
+
35
+ plugin :invalid_request_body do |exception|
36
+ # To treat the exception raised as a submitted parameter
37
+ {body_error: exception}
38
+ end
39
+
40
+ = Other Improvements
41
+
42
+ * When using the check_arity: :warn Roda option, Roda now correctly
43
+ warns when defining a method that expects a single argument when
44
+ the provided block requires multiple arguments.
45
+
46
+ * The match_hooks plugin is now implemented using the match_hook_args
47
+ plugin, simplifying the implementation. This change should be
48
+ transparent unless you were reaching into the internals.
@@ -0,0 +1,107 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The invalid_request_body plugin allows for custom handling of invalid request
7
+ # bodies. Roda uses Rack for parsing request bodies, so by default, any
8
+ # invalid request bodies would result in Rack raising an exception, and the
9
+ # exception could change for different reasons the request body is invalid.
10
+ # This plugin overrides RodaRequest#POST (which parses parameters from request
11
+ # bodies), and if parsing raises an exception, it allows for custom behavior.
12
+ #
13
+ # If you want to treat an invalid request body as the submission of no parameters,
14
+ # you can use the :empty_hash argument when loading the plugin:
15
+ #
16
+ # plugin :invalid_request_body, :empty_hash
17
+ #
18
+ # If you want to return a empty 400 (Bad Request) response if an invalid request
19
+ # body is submitted, you can use the :empty_400 argument when loading the plugin:
20
+ #
21
+ # plugin :invalid_request_body, :empty_400
22
+ #
23
+ # If you want to raise a Roda::RodaPlugins::InvalidRequestBody::Error exception
24
+ # if an invalid request body is submitted (which makes it easier to handle these
25
+ # exceptions when using the error_handler plugin), you can use the :raise argument
26
+ # when loading the plugin:
27
+ #
28
+ # plugin :invalid_request_body, :raise
29
+ #
30
+ # For custom behavior, you can pass a block when loading the plugin. The block
31
+ # is called with the exception Rack raised when parsing the body. The block will
32
+ # be used to define a method in the application's RodaRequest class. It can either
33
+ # return a hash of parameters, or you can raise a different exception, or you
34
+ # can halt processing and return a response:
35
+ #
36
+ # plugin :invalid_request_body do |exception|
37
+ # # To treat the exception raised as a submitted parameter
38
+ # {body_error: exception}
39
+ # end
40
+ module InvalidRequestBody
41
+ # Exception class raised for invalid request bodies.
42
+ Error = Class.new(RodaError)
43
+
44
+ # Set the action to use (:empty_400, :empty_hash, :raise) for invalid request bodies,
45
+ # or use a block for custom behavior.
46
+ def self.configure(app, action=nil, &block)
47
+ if action
48
+ if block
49
+ raise RodaError, "cannot provide both block and action when loading invalid_request_body plugin"
50
+ end
51
+
52
+ method = :"handle_invalid_request_body_#{action}"
53
+ unless RequestMethods.private_method_defined?(method)
54
+ raise RodaError, "invalid invalid_request_body action provided: #{action}"
55
+ end
56
+
57
+ app::RodaRequest.send(:alias_method, :handle_invalid_request_body, method)
58
+ elsif block
59
+ app::RodaRequest.class_eval do
60
+ define_method(:handle_invalid_request_body, &block)
61
+ alias handle_invalid_request_body handle_invalid_request_body
62
+ end
63
+ else
64
+ raise RodaError, "must provide block or action when loading invalid_request_body plugin"
65
+ end
66
+
67
+ app::RodaRequest.send(:private, :handle_invalid_request_body)
68
+ end
69
+
70
+ module RequestMethods
71
+ # Handle invalid request bodies as configured if the default behavior
72
+ # raises an exception.
73
+ def POST
74
+ super
75
+ rescue => e
76
+ handle_invalid_request_body(e)
77
+ end
78
+
79
+ private
80
+
81
+ # Return an empty 400 HTTP response for invalid request bodies.
82
+ def handle_invalid_request_body_empty_400(e)
83
+ response.status = 400
84
+ headers = response.headers
85
+ headers.clear
86
+ headers[RodaResponseHeaders::CONTENT_TYPE] = 'text/html'
87
+ headers[RodaResponseHeaders::CONTENT_LENGTH] ='0'
88
+ throw :halt, response.finish_with_body([])
89
+ end
90
+
91
+ # Treat invalid request bodies by using an empty hash as the
92
+ # POST params.
93
+ def handle_invalid_request_body_empty_hash(e)
94
+ {}
95
+ end
96
+
97
+ # Raise a specific error for all invalid request bodies,
98
+ # to allow for easy rescuing using the error_handler plugin.
99
+ def handle_invalid_request_body_raise(e)
100
+ raise Error, e.message
101
+ end
102
+ end
103
+ end
104
+
105
+ register_plugin(:invalid_request_body, InvalidRequestBody)
106
+ end
107
+ end
@@ -52,6 +52,9 @@ class Roda
52
52
  # using the +:content_type+ option:
53
53
  #
54
54
  # plugin :json, content_type: 'application/xml'
55
+ #
56
+ # This plugin depends on the custom_block_results plugin, and therefore does
57
+ # not support treating String, FalseClass, or NilClass values as JSON.
55
58
  module Json
56
59
  # Set the classes to automatically convert to JSON, and the serializer to use.
57
60
  def self.configure(app, opts=OPTS)
@@ -379,7 +379,7 @@ class Roda
379
379
  end
380
380
 
381
381
  # Perform the processing of mail for this request, first considering
382
- # routes defined via the the class-level +rcpt+ method, and then the
382
+ # routes defined via the class-level +rcpt+ method, and then the
383
383
  # normal routing tree passed in as the block.
384
384
  def process_mail(&block)
385
385
  if string_routes = opts[:mail_processor_string_routes]
@@ -4,7 +4,10 @@
4
4
  class Roda
5
5
  module RodaPlugins
6
6
  # The match_hook plugin adds hooks that are called upon a successful match
7
- # by any of the matchers.
7
+ # by any of the matchers. The hooks do not take any arguments. If you would
8
+ # like hooks that pass the arguments/matchers and values yielded to the route block,
9
+ # use the match_hook_args plugin. This uses the match_hook_args plugin internally,
10
+ # but doesn't pass the matchers and values yielded.
8
11
  #
9
12
  # plugin :match_hook
10
13
  #
@@ -12,56 +15,18 @@ class Roda
12
15
  # logger.debug("#{request.matched_path} matched. #{request.remaining_path} remaining.")
13
16
  # end
14
17
  module MatchHook
15
- def self.configure(app)
16
- app.opts[:match_hooks] ||= []
18
+ def self.load_dependencies(app)
19
+ app.plugin :match_hook_args
17
20
  end
18
21
 
19
22
  module ClassMethods
20
- # Freeze the array of hook methods when freezing the app
21
- def freeze
22
- opts[:match_hooks].freeze
23
- super
24
- end
25
-
26
23
  # Add a match hook.
27
24
  def match_hook(&block)
28
- opts[:match_hooks] << define_roda_method("match_hook", 0, &block)
29
-
30
- if opts[:match_hooks].length == 1
31
- class_eval("alias _match_hook #{opts[:match_hooks].first}", __FILE__, __LINE__)
32
- else
33
- class_eval("def _match_hook; #{opts[:match_hooks].join(';')} end", __FILE__, __LINE__)
34
- end
35
-
36
- public :_match_hook
37
-
25
+ meth = define_roda_method("match_hook", 0, &block)
26
+ add_match_hook{|_,_| send(meth)}
38
27
  nil
39
28
  end
40
29
  end
41
-
42
- module InstanceMethods
43
- # Default empty method if no match hooks are defined.
44
- def _match_hook
45
- end
46
- end
47
-
48
- module RequestMethods
49
- private
50
-
51
- # Call the match hook if yielding to the block before yielding to the block.
52
- def if_match(_)
53
- super do |*a|
54
- scope._match_hook
55
- yield(*a)
56
- end
57
- end
58
-
59
- # Call the match hook before yielding to the block
60
- def always
61
- scope._match_hook
62
- super
63
- end
64
- end
65
30
  end
66
31
 
67
32
  register_plugin :match_hook, MatchHook
@@ -0,0 +1,93 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The match_hook_args plugin adds hooks that are called upon a successful match
7
+ # by any of the matchers. It is similar to the match_hook plugin, but it allows
8
+ # for passing the matchers and block arguments for each match method.
9
+ #
10
+ # plugin :match_hook_args
11
+ #
12
+ # add_match_hook do |matchers, block_args|
13
+ # logger.debug("matchers: #{matchers.inspect}. #{block_args.inspect} yielded.")
14
+ # end
15
+ #
16
+ # # Term is an implicit matcher used for terminating matches, and
17
+ # # will be included in the array of matchers yielded to the match hook
18
+ # # if a terminating match is used.
19
+ # term = self.class::RodaRequest::TERM
20
+ #
21
+ # route do |r|
22
+ # r.root do
23
+ # # for a request for /
24
+ # # matchers: nil, block_args: nil
25
+ # end
26
+ #
27
+ # r.on 'a', ['b', 'c'], Integer do |segment, id|
28
+ # # for a request for /a/b/1
29
+ # # matchers: ["a", ["b", "c"], Integer], block_args: ["b", 1]
30
+ # end
31
+ #
32
+ # r.get 'd' do
33
+ # # for a request for /d
34
+ # # matchers: ["d", term], block_args: []
35
+ # end
36
+ # end
37
+ module MatchHookArgs
38
+ def self.configure(app)
39
+ app.opts[:match_hook_args] ||= []
40
+ end
41
+
42
+ module ClassMethods
43
+ # Freeze the array of hook methods when freezing the app
44
+ def freeze
45
+ opts[:match_hook_args].freeze
46
+ super
47
+ end
48
+
49
+ # Add a match hook that will be called with matchers and block args.
50
+ def add_match_hook(&block)
51
+ opts[:match_hook_args] << define_roda_method("match_hook_args", :any, &block)
52
+
53
+ if opts[:match_hook_args].length == 1
54
+ class_eval("alias _match_hook_args #{opts[:match_hook_args].first}", __FILE__, __LINE__)
55
+ else
56
+ class_eval("def _match_hook_args(v, a); #{opts[:match_hook_args].map{|m| "#{m}(v, a)"}.join(';')} end", __FILE__, __LINE__)
57
+ end
58
+
59
+ public :_match_hook_args
60
+
61
+ nil
62
+ end
63
+ end
64
+
65
+ module InstanceMethods
66
+ # Default empty method if no match hooks are defined.
67
+ def _match_hook_args(matchers, block_args)
68
+ end
69
+ end
70
+
71
+ module RequestMethods
72
+ private
73
+
74
+ # Call the match hook with matchers and block args if yielding to the block before yielding to the block.
75
+ def if_match(v)
76
+ super do |*a|
77
+ scope._match_hook_args(v, a)
78
+ yield(*a)
79
+ end
80
+ end
81
+
82
+ # Call the match hook with nil matchers and blocks before yielding to the block
83
+ def always
84
+ scope._match_hook_args(nil, nil)
85
+ super
86
+ end
87
+ end
88
+ end
89
+
90
+ register_plugin :match_hook_args, MatchHookArgs
91
+ end
92
+ end
93
+
@@ -346,7 +346,7 @@ class Roda
346
346
  end
347
347
 
348
348
  # The reason behind this error. If this error was caused by a conversion method,
349
- # this will be the the conversion method symbol. If this error was caused
349
+ # this will be the conversion method symbol. If this error was caused
350
350
  # because a value was missing, then it will be +:missing+. If this error was
351
351
  # caused because a value was not the correct type, then it will be +:invalid_type+.
352
352
  attr_accessor :reason
data/lib/roda/version.rb CHANGED
@@ -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 = 70
7
+ RodaMinorVersion = 72
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
data/lib/roda.rb CHANGED
@@ -88,6 +88,7 @@ class Roda
88
88
  end
89
89
  call_meth = meth
90
90
 
91
+ # RODA4: Switch to false # :warn in last Roda 3 version
91
92
  if (check_arity = opts.fetch(:check_arity, true)) && !block.lambda?
92
93
  required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(block)
93
94
 
@@ -117,6 +118,9 @@ class Roda
117
118
  alias_method meth, meth
118
119
  meth = :"#{meth}_arity"
119
120
  elsif required_args > 1
121
+ if check_arity == :warn
122
+ RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but multiple arguments required for #{block.inspect}"
123
+ end
120
124
  b = block
121
125
  block = lambda{|r| instance_exec(r, &b)} # Fallback
122
126
  end
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.70.0
4
+ version: 3.72.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: 2023-07-12 00:00:00.000000000 Z
11
+ date: 2023-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -244,6 +244,8 @@ extra_rdoc_files:
244
244
  - doc/release_notes/3.69.0.txt
245
245
  - doc/release_notes/3.7.0.txt
246
246
  - doc/release_notes/3.70.0.txt
247
+ - doc/release_notes/3.71.0.txt
248
+ - doc/release_notes/3.72.0.txt
247
249
  - doc/release_notes/3.8.0.txt
248
250
  - doc/release_notes/3.9.0.txt
249
251
  files:
@@ -321,6 +323,8 @@ files:
321
323
  - doc/release_notes/3.69.0.txt
322
324
  - doc/release_notes/3.7.0.txt
323
325
  - doc/release_notes/3.70.0.txt
326
+ - doc/release_notes/3.71.0.txt
327
+ - doc/release_notes/3.72.0.txt
324
328
  - doc/release_notes/3.8.0.txt
325
329
  - doc/release_notes/3.9.0.txt
326
330
  - lib/roda.rb
@@ -384,6 +388,7 @@ files:
384
388
  - lib/roda/plugins/host_authorization.rb
385
389
  - lib/roda/plugins/indifferent_params.rb
386
390
  - lib/roda/plugins/inject_erb.rb
391
+ - lib/roda/plugins/invalid_request_body.rb
387
392
  - lib/roda/plugins/json.rb
388
393
  - lib/roda/plugins/json_parser.rb
389
394
  - lib/roda/plugins/link_to.rb
@@ -391,6 +396,7 @@ files:
391
396
  - lib/roda/plugins/mailer.rb
392
397
  - lib/roda/plugins/match_affix.rb
393
398
  - lib/roda/plugins/match_hook.rb
399
+ - lib/roda/plugins/match_hook_args.rb
394
400
  - lib/roda/plugins/middleware.rb
395
401
  - lib/roda/plugins/middleware_stack.rb
396
402
  - lib/roda/plugins/module_include.rb