grape 2.1.3 → 2.2.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: 172d51f08cb655e030afc83ecc06cb13ad5c72c9ad1d6f63d6f87d8b45a52772
4
- data.tar.gz: 2d946a6526a7cc848e441fac2d4e0169a0762025d34445307f3945abeeef2b72
3
+ metadata.gz: dc04f424b8181e92cc304d02d2923952f07e2532ef0291233596813726a2cb68
4
+ data.tar.gz: 46729a20982fc16129540a81061bea229fd3ba5dd81426c1830119b52fe6ccd2
5
5
  SHA512:
6
- metadata.gz: eb1ec3cd146f8e4a3f12314159e68177b92548fb7ea3eeac47a425addc3fa04a38c61c566ed3a0655d325bd5d24044b020449aa5702f997d746eef2c5d493928
7
- data.tar.gz: 8c44c721a9e0acbb00997ac40ea4b491c4a3c0315e6f4cc1432a2ea5cff102887e028e10df883eba0b50645219918a3fc5a3a66f36068a9d6abc81b29b75c61f
6
+ metadata.gz: 45e47b5059a37bbf75331c41bb1fdda2487610e3713baf5b8181dcbbae1727c47995503f5dc8a12375fd0765908140540100579afe834dfe96dc4c07cd771b2f
7
+ data.tar.gz: 9692f64fc61714c33035ef59c0d68a9c34fb3725a5842e0fe16cbd4d7025265bef50ea5086a3f61d5ed1f0fd8740435619ff616eed55e45fd3cc48e090a02b7d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ### 2.2.0 (2024-09-14)
2
+
3
+ #### Features
4
+
5
+ * [#2475](https://github.com/ruby-grape/grape/pull/2475): Remove Grape::Util::Registrable - [@ericproulx](https://github.com/ericproulx).
6
+ * [#2484](https://github.com/ruby-grape/grape/pull/2484): Refactor versioner middlewares - [@ericproulx](https://github.com/ericproulx).
7
+ * [#2489](https://github.com/ruby-grape/grape/pull/2489): Add Rails 7.2 in CI workflow - [@ericproulx](https://github.com/ericproulx).
8
+ * [#2493](https://github.com/ruby-grape/grape/pull/2493): MFA required when releasing - [@ericproulx](https://github.com/ericproulx).
9
+
10
+ #### Fixes
11
+
12
+ * [#2471](https://github.com/ruby-grape/grape/pull/2471): Fix absence of original_exception and/or backtrace even if passed in error! - [@numbata](https://github.com/numbata).
13
+ * [#2478](https://github.com/ruby-grape/grape/pull/2478): Fix rescue_from with invalid response - [@ericproulx](https://github.com/ericproulx).
14
+ * [#2480](https://github.com/ruby-grape/grape/pull/2480): Fix rescue_from ValidationErrors exception - [@numbata](https://github.com/numbata).
15
+ * [#2464](https://github.com/ruby-grape/grape/pull/2464): The `length` validator only takes effect for parameters with types that support `#length` method - [@OuYangJinTing](https://github.com/OuYangJinTing).
16
+ * [#2485](https://github.com/ruby-grape/grape/pull/2485): Add `is:` param to length validator - [@dakad](https://github.com/dakad).
17
+ * [#2492](https://github.com/ruby-grape/grape/pull/2492): Fix `Grape::Endpoint#inspect` method - [@ericproulx](https://github.com/ericproulx).
18
+ * [#2496](https://github.com/ruby-grape/grape/pull/2496): Reduce object allocation when compiling - [@ericproulx](https://github.com/ericproulx).
19
+
1
20
  ### 2.1.3 (2024-07-13)
2
21
 
3
22
  #### Fixes
data/README.md CHANGED
@@ -157,8 +157,7 @@ Grape is a REST-like API framework for Ruby. It's designed to run on Rack or com
157
157
 
158
158
  ## Stable Release
159
159
 
160
- You're reading the documentation for the stable release of Grape, **2.1.3**.
161
- Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
160
+ You're reading the documentation for the stable release of Grape, 2.2.0. Please read UPGRADING when upgrading from a previous version.
162
161
 
163
162
  ## Project Resources
164
163
 
@@ -1713,10 +1712,11 @@ end
1713
1712
 
1714
1713
  Parameters with types that support `#length` method can be restricted to have a specific length with the `:length` option.
1715
1714
 
1716
- The validator accepts `:min` or `:max` or both options to validate that the value of the parameter is within the given limits.
1715
+ The validator accepts `:min` or `:max` or both options or only `:is` to validate that the value of the parameter is within the given limits.
1717
1716
 
1718
1717
  ```ruby
1719
1718
  params do
1719
+ requires :code, type: String, length: { is: 2 }
1720
1720
  requires :str, type: String, length: { min: 3 }
1721
1721
  requires :list, type: [Integer], length: { min: 3, max: 5 }
1722
1722
  requires :hash, type: Hash, length: { max: 5 }
@@ -2044,6 +2044,7 @@ end
2044
2044
 
2045
2045
  ```ruby
2046
2046
  params do
2047
+ requires :code, type: String, length: { is: 2, message: 'code is expected to be exactly 2 characters long' }
2047
2048
  requires :str, type: String, length: { min: 5, message: 'str is expected to be atleast 5 characters long' }
2048
2049
  requires :list, type: [Integer], length: { min: 2, max: 3, message: 'list is expected to have between 2 and 3 elements' }
2049
2050
  end
data/UPGRADING.md CHANGED
@@ -1,6 +1,14 @@
1
1
  Upgrading Grape
2
2
  ===============
3
3
 
4
+ ### Upgrading to >= 2.2.0
5
+
6
+ ### `Length` validator
7
+
8
+ After Grape 2.2.0, `length` validator will only take effect for parameters with types that support `#length` method, will not throw `ArgumentError` exception.
9
+
10
+ See [#2464](https://github.com/ruby-grape/grape/pull/2464) for more information.
11
+
4
12
  ### Upgrading to >= 2.1.0
5
13
 
6
14
  #### Optional Builder
data/grape.gemspec CHANGED
@@ -17,7 +17,8 @@ Gem::Specification.new do |s|
17
17
  'bug_tracker_uri' => 'https://github.com/ruby-grape/grape/issues',
18
18
  'changelog_uri' => "https://github.com/ruby-grape/grape/blob/v#{s.version}/CHANGELOG.md",
19
19
  'documentation_uri' => "https://www.rubydoc.info/gems/grape/#{s.version}",
20
- 'source_code_uri' => "https://github.com/ruby-grape/grape/tree/v#{s.version}"
20
+ 'source_code_uri' => "https://github.com/ruby-grape/grape/tree/v#{s.version}",
21
+ 'rubygems_mfa_required' => 'true'
21
22
  }
22
23
 
23
24
  s.add_runtime_dependency 'activesupport', '>= 6'
@@ -2,10 +2,10 @@
2
2
 
3
3
  module Grape
4
4
  module ContentTypes
5
- extend Util::Registrable
5
+ module_function
6
6
 
7
7
  # Content types are listed in order of preference.
8
- CONTENT_TYPES = {
8
+ DEFAULTS = {
9
9
  xml: 'application/xml',
10
10
  serializable_hash: 'application/json',
11
11
  json: 'application/json',
@@ -13,13 +13,18 @@ module Grape
13
13
  txt: 'text/plain'
14
14
  }.freeze
15
15
 
16
- class << self
17
- def content_types_for_settings(settings)
18
- settings&.inject(:merge!)
19
- end
16
+ MIME_TYPES = Grape::ContentTypes::DEFAULTS.except(:serializable_hash).invert.freeze
17
+
18
+ def content_types_for(from_settings)
19
+ from_settings.presence || DEFAULTS
20
+ end
21
+
22
+ def mime_types_for(from_settings)
23
+ return MIME_TYPES if from_settings == Grape::ContentTypes::DEFAULTS
20
24
 
21
- def content_types_for(from_settings)
22
- from_settings.presence || Grape::ContentTypes::CONTENT_TYPES.merge(default_elements)
25
+ from_settings.each_with_object({}) do |(k, v), types_without_params|
26
+ # remove optional parameter
27
+ types_without_params[v.split(';', 2).first] = k
23
28
  end
24
29
  end
25
30
  end
@@ -33,18 +33,22 @@ module Grape
33
33
  # end
34
34
  #
35
35
  def helpers(*new_modules, &block)
36
- include_new_modules(new_modules) if new_modules.any?
37
- include_block(block) if block
36
+ include_new_modules(new_modules)
37
+ include_block(block)
38
38
  include_all_in_scope if !block && new_modules.empty?
39
39
  end
40
40
 
41
41
  protected
42
42
 
43
43
  def include_new_modules(modules)
44
+ return if modules.empty?
45
+
44
46
  modules.each { |mod| make_inclusion(mod) }
45
47
  end
46
48
 
47
49
  def include_block(block)
50
+ return unless block
51
+
48
52
  Module.new.tap do |mod|
49
53
  make_inclusion(mod) { mod.class_eval(&block) }
50
54
  end
@@ -58,7 +62,7 @@ module Grape
58
62
 
59
63
  def include_all_in_scope
60
64
  Module.new.tap do |mod|
61
- namespace_stackable(:helpers).each { |mod_to_include| mod.send :include, mod_to_include }
65
+ namespace_stackable(:helpers).each { |mod_to_include| mod.include mod_to_include }
62
66
  change!
63
67
  end
64
68
  end
@@ -170,7 +170,12 @@ module Grape
170
170
  def error!(message, status = nil, additional_headers = nil, backtrace = nil, original_exception = nil)
171
171
  status = self.status(status || namespace_inheritable(:default_error_status))
172
172
  headers = additional_headers.present? ? header.merge(additional_headers) : header
173
- throw :error, message: message, status: status, headers: headers, backtrace: backtrace, original_exception: original_exception
173
+ throw :error,
174
+ message: message,
175
+ status: status,
176
+ headers: headers,
177
+ backtrace: backtrace,
178
+ original_exception: original_exception
174
179
  end
175
180
 
176
181
  # Creates a Rack response based on the provided message, status, and headers.
@@ -17,18 +17,16 @@ module Grape
17
17
  # Specify the format for the API's serializers.
18
18
  # May be `:json`, `:xml`, `:txt`, etc.
19
19
  def format(new_format = nil)
20
- if new_format
21
- namespace_inheritable(:format, new_format.to_sym)
22
- # define the default error formatters
23
- namespace_inheritable(:default_error_formatter, Grape::ErrorFormatter.formatter_for(new_format, **{}))
24
- # define a single mime type
25
- mime_type = content_types[new_format.to_sym]
26
- raise Grape::Exceptions::MissingMimeType.new(new_format) unless mime_type
27
-
28
- namespace_stackable(:content_types, new_format.to_sym => mime_type)
29
- else
30
- namespace_inheritable(:format)
31
- end
20
+ return namespace_inheritable(:format) unless new_format
21
+
22
+ symbolic_new_format = new_format.to_sym
23
+ namespace_inheritable(:format, symbolic_new_format)
24
+ namespace_inheritable(:default_error_formatter, Grape::ErrorFormatter.formatter_for(symbolic_new_format))
25
+
26
+ content_type = content_types[symbolic_new_format]
27
+ raise Grape::Exceptions::MissingMimeType.new(new_format) unless content_type
28
+
29
+ namespace_stackable(:content_types, symbolic_new_format => content_type)
32
30
  end
33
31
 
34
32
  # Specify a custom formatter for a content-type.
@@ -43,12 +41,10 @@ module Grape
43
41
 
44
42
  # Specify a default error formatter.
45
43
  def default_error_formatter(new_formatter_name = nil)
46
- if new_formatter_name
47
- new_formatter = Grape::ErrorFormatter.formatter_for(new_formatter_name, **{})
48
- namespace_inheritable(:default_error_formatter, new_formatter)
49
- else
50
- namespace_inheritable(:default_error_formatter)
51
- end
44
+ return namespace_inheritable(:default_error_formatter) unless new_formatter_name
45
+
46
+ new_formatter = Grape::ErrorFormatter.formatter_for(new_formatter_name)
47
+ namespace_inheritable(:default_error_formatter, new_formatter)
52
48
  end
53
49
 
54
50
  def error_formatter(format, options)
@@ -114,10 +114,10 @@ module Grape
114
114
  # Update our settings from a given set of stackable parameters. Used when
115
115
  # the endpoint's API is mounted under another one.
116
116
  def inherit_settings(namespace_stackable)
117
- inheritable_setting.route[:saved_validations] += namespace_stackable[:validations]
117
+ parent_validations = namespace_stackable[:validations]
118
+ inheritable_setting.route[:saved_validations].concat(parent_validations) if parent_validations.any?
118
119
  parent_declared_params = namespace_stackable[:declared_params]
119
-
120
- inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params
120
+ inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params.any?
121
121
 
122
122
  endpoints&.each { |e| e.inherit_settings(namespace_stackable) }
123
123
  end
@@ -191,8 +191,7 @@ module Grape
191
191
 
192
192
  def prepare_version
193
193
  version = namespace_inheritable(:version)
194
- return unless version
195
- return if version.empty?
194
+ return if version.blank?
196
195
 
197
196
  version.length == 1 ? version.first : version
198
197
  end
@@ -206,7 +205,9 @@ module Grape
206
205
  end
207
206
 
208
207
  def prepare_path(path)
209
- path_settings = inheritable_setting.to_hash[:namespace_stackable].merge(inheritable_setting.to_hash[:namespace_inheritable])
208
+ namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
209
+ namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash
210
+ path_settings = namespace_stackable_hash.merge!(namespace_inheritable_hash)
210
211
  Path.new(path, namespace, path_settings)
211
212
  end
212
213
 
@@ -235,6 +236,15 @@ module Grape
235
236
  (options == endpoint.options) && (inheritable_setting.to_hash == endpoint.inheritable_setting.to_hash)
236
237
  end
237
238
 
239
+ # The purpose of this override is solely for stripping internals when an error occurs while calling
240
+ # an endpoint through an api. See https://github.com/ruby-grape/grape/issues/2398
241
+ # Otherwise, it calls super.
242
+ def inspect
243
+ return super unless env
244
+
245
+ "#{self.class} in '#{route.origin}' endpoint"
246
+ end
247
+
238
248
  protected
239
249
 
240
250
  def run
@@ -280,36 +290,39 @@ module Grape
280
290
  def build_stack(helpers)
281
291
  stack = Grape::Middleware::Stack.new
282
292
 
293
+ content_types = namespace_stackable_with_hash(:content_types)
294
+ format = namespace_inheritable(:format)
295
+
283
296
  stack.use Rack::Head
284
297
  stack.use Class.new(Grape::Middleware::Error),
285
298
  helpers: helpers,
286
- format: namespace_inheritable(:format),
287
- content_types: namespace_stackable_with_hash(:content_types),
299
+ format: format,
300
+ content_types: content_types,
288
301
  default_status: namespace_inheritable(:default_error_status),
289
302
  rescue_all: namespace_inheritable(:rescue_all),
290
303
  rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
291
304
  default_error_formatter: namespace_inheritable(:default_error_formatter),
292
305
  error_formatters: namespace_stackable_with_hash(:error_formatters),
293
- rescue_options: namespace_stackable_with_hash(:rescue_options) || {},
294
- rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers) || {},
295
- base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers) || {},
306
+ rescue_options: namespace_stackable_with_hash(:rescue_options),
307
+ rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers),
308
+ base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers),
296
309
  all_rescue_handler: namespace_inheritable(:all_rescue_handler),
297
310
  grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler)
298
311
 
299
312
  stack.concat namespace_stackable(:middleware)
300
313
 
301
- if namespace_inheritable(:version)
314
+ if namespace_inheritable(:version).present?
302
315
  stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
303
- versions: namespace_inheritable(:version)&.flatten,
316
+ versions: namespace_inheritable(:version).flatten,
304
317
  version_options: namespace_inheritable(:version_options),
305
318
  prefix: namespace_inheritable(:root_prefix),
306
319
  mount_path: namespace_stackable(:mount_path).first
307
320
  end
308
321
 
309
322
  stack.use Grape::Middleware::Formatter,
310
- format: namespace_inheritable(:format),
323
+ format: format,
311
324
  default_format: namespace_inheritable(:default_format) || :txt,
312
- content_types: namespace_stackable_with_hash(:content_types),
325
+ content_types: content_types,
313
326
  formatters: namespace_stackable_with_hash(:formatters),
314
327
  parsers: namespace_stackable_with_hash(:parsers)
315
328
 
@@ -320,7 +333,9 @@ module Grape
320
333
 
321
334
  def build_helpers
322
335
  helpers = namespace_stackable(:helpers)
323
- Module.new { helpers&.each { |mod_to_include| include mod_to_include } }
336
+ return if helpers.empty?
337
+
338
+ Module.new { helpers.each { |mod_to_include| include mod_to_include } }
324
339
  end
325
340
 
326
341
  private :build_stack, :build_helpers
@@ -339,7 +354,7 @@ module Grape
339
354
  @lazy_initialize_lock.synchronize do
340
355
  return true if @lazy_initialized
341
356
 
342
- @helpers = build_helpers.tap { |mod| self.class.send(:include, mod) }
357
+ @helpers = build_helpers&.tap { |mod| self.class.include mod }
343
358
  @app = options[:app] || build_stack(@helpers)
344
359
 
345
360
  @lazy_initialized = true
@@ -404,9 +419,5 @@ module Grape
404
419
  options[:options_route_enabled] &&
405
420
  env[Rack::REQUEST_METHOD] == Rack::OPTIONS
406
421
  end
407
-
408
- def inspect
409
- "#{self.class} in `#{route.origin}' endpoint"
410
- end
411
422
  end
412
423
  end
@@ -9,17 +9,18 @@ module Grape
9
9
  def call(message, backtrace, options = {}, env = nil, original_exception = nil)
10
10
  result = wrap_message(present(message, env))
11
11
 
12
- rescue_options = options[:rescue_options] || {}
13
- result = result.merge(backtrace: backtrace) if rescue_options[:backtrace] && backtrace && !backtrace.empty?
14
- result = result.merge(original_exception: original_exception.inspect) if rescue_options[:original_exception] && original_exception
12
+ result = merge_rescue_options(result, backtrace, options, original_exception) if result.is_a?(Hash)
13
+
15
14
  ::Grape::Json.dump(result)
16
15
  end
17
16
 
18
17
  private
19
18
 
20
19
  def wrap_message(message)
21
- if message.is_a?(Exceptions::ValidationErrors) || message.is_a?(Hash)
20
+ if message.is_a?(Hash)
22
21
  message
22
+ elsif message.is_a?(Exceptions::ValidationErrors)
23
+ message.as_json
23
24
  else
24
25
  { error: ensure_utf8(message) }
25
26
  end
@@ -30,6 +31,14 @@ module Grape
30
31
 
31
32
  message.encode('UTF-8', invalid: :replace, undef: :replace)
32
33
  end
34
+
35
+ def merge_rescue_options(result, backtrace, options, original_exception)
36
+ rescue_options = options[:rescue_options] || {}
37
+ result = result.merge(backtrace: backtrace) if rescue_options[:backtrace] && backtrace && !backtrace.empty?
38
+ result = result.merge(original_exception: original_exception.inspect) if rescue_options[:original_exception] && original_exception
39
+
40
+ result
41
+ end
33
42
  end
34
43
  end
35
44
  end
@@ -2,34 +2,22 @@
2
2
 
3
3
  module Grape
4
4
  module ErrorFormatter
5
- extend Util::Registrable
5
+ module_function
6
6
 
7
- class << self
8
- def builtin_formatters
9
- @builtin_formatters ||= {
10
- serializable_hash: Grape::ErrorFormatter::Json,
11
- json: Grape::ErrorFormatter::Json,
12
- jsonapi: Grape::ErrorFormatter::Json,
13
- txt: Grape::ErrorFormatter::Txt,
14
- xml: Grape::ErrorFormatter::Xml
15
- }
16
- end
7
+ DEFAULTS = {
8
+ serializable_hash: Grape::ErrorFormatter::Json,
9
+ json: Grape::ErrorFormatter::Json,
10
+ jsonapi: Grape::ErrorFormatter::Json,
11
+ txt: Grape::ErrorFormatter::Txt,
12
+ xml: Grape::ErrorFormatter::Xml
13
+ }.freeze
17
14
 
18
- def formatters(**options)
19
- builtin_formatters.merge(default_elements).merge!(options[:error_formatters] || {})
20
- end
15
+ def formatter_for(format, error_formatters = nil, default_error_formatter = nil)
16
+ select_formatter(error_formatters, format) || default_error_formatter || DEFAULTS[:txt]
17
+ end
21
18
 
22
- def formatter_for(api_format, **options)
23
- spec = formatters(**options)[api_format]
24
- case spec
25
- when nil
26
- options[:default_error_formatter] || Grape::ErrorFormatter::Txt
27
- when Symbol
28
- method(spec)
29
- else
30
- spec
31
- end
32
- end
19
+ def select_formatter(error_formatters, format)
20
+ error_formatters&.key?(format) ? error_formatters[format] : DEFAULTS[format]
33
21
  end
34
22
  end
35
23
  end
@@ -2,34 +2,24 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- extend Util::Registrable
5
+ module_function
6
6
 
7
- class << self
8
- def builtin_formatters
9
- @builtin_formatters ||= {
10
- json: Grape::Formatter::Json,
11
- jsonapi: Grape::Formatter::Json,
12
- serializable_hash: Grape::Formatter::SerializableHash,
13
- txt: Grape::Formatter::Txt,
14
- xml: Grape::Formatter::Xml
15
- }
16
- end
7
+ DEFAULTS = {
8
+ json: Grape::Formatter::Json,
9
+ jsonapi: Grape::Formatter::Json,
10
+ serializable_hash: Grape::Formatter::SerializableHash,
11
+ txt: Grape::Formatter::Txt,
12
+ xml: Grape::Formatter::Xml
13
+ }.freeze
17
14
 
18
- def formatters(**options)
19
- builtin_formatters.merge(default_elements).merge!(options[:formatters] || {})
20
- end
15
+ DEFAULT_LAMBDA_FORMATTER = ->(obj, _env) { obj }
21
16
 
22
- def formatter_for(api_format, **options)
23
- spec = formatters(**options)[api_format]
24
- case spec
25
- when nil
26
- ->(obj, _env) { obj }
27
- when Symbol
28
- method(spec)
29
- else
30
- spec
31
- end
32
- end
17
+ def formatter_for(api_format, formatters)
18
+ select_formatter(formatters, api_format) || DEFAULT_LAMBDA_FORMATTER
19
+ end
20
+
21
+ def select_formatter(formatters, api_format)
22
+ formatters&.key?(api_format) ? formatters[api_format] : DEFAULTS[api_format]
33
23
  end
34
24
  end
35
25
  end
@@ -11,6 +11,7 @@ en:
11
11
  except_values: 'has a value not allowed'
12
12
  same_as: 'is not the same as %{parameter}'
13
13
  length: 'is expected to have length within %{min} and %{max}'
14
+ length_is: 'is expected to have length exactly equal to %{is}'
14
15
  length_min: 'is expected to have length greater than or equal to %{min}'
15
16
  length_max: 'is expected to have length less than or equal to %{max}'
16
17
  missing_vendor_option:
@@ -4,18 +4,17 @@ module Grape
4
4
  module Middleware
5
5
  class Base
6
6
  include Helpers
7
+ include Grape::DSL::Headers
7
8
 
8
9
  attr_reader :app, :env, :options
9
10
 
10
11
  TEXT_HTML = 'text/html'
11
12
 
12
- include Grape::DSL::Headers
13
-
14
13
  # @param [Rack Application] app The standard argument for a Rack middleware.
15
14
  # @param [Hash] options A hash of options, simply stored for use by subclasses.
16
15
  def initialize(app, *options)
17
16
  @app = app
18
- @options = options.any? ? default_options.merge(options.shift) : default_options
17
+ @options = options.any? ? default_options.deep_merge(options.shift) : default_options
19
18
  @app_response = nil
20
19
  end
21
20
 
@@ -61,22 +60,20 @@ module Grape
61
60
  @app_response = Rack::Response.new(@app_response[2], @app_response[0], @app_response[1])
62
61
  end
63
62
 
64
- def content_type_for(format)
65
- HashWithIndifferentAccess.new(content_types)[format]
63
+ def content_types
64
+ @content_types ||= Grape::ContentTypes.content_types_for(options[:content_types])
66
65
  end
67
66
 
68
- def content_types
69
- ContentTypes.content_types_for(options[:content_types])
67
+ def mime_types
68
+ @mime_types ||= Grape::ContentTypes.mime_types_for(content_types)
70
69
  end
71
70
 
72
- def content_type
73
- content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || TEXT_HTML
71
+ def content_type_for(format)
72
+ content_types_indifferent_access[format]
74
73
  end
75
74
 
76
- def mime_types
77
- @mime_types ||= content_types.each_pair.with_object({}) do |(k, v), types_without_params|
78
- types_without_params[v.split(';').first] = k
79
- end
75
+ def content_type
76
+ content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || TEXT_HTML
80
77
  end
81
78
 
82
79
  private
@@ -89,6 +86,10 @@ module Grape
89
86
  when Array then response[1].merge!(headers)
90
87
  end
91
88
  end
89
+
90
+ def content_types_indifferent_access
91
+ @content_types_indifferent_access ||= content_types.with_indifferent_access
92
+ end
92
93
  end
93
94
  end
94
95
  end
@@ -26,7 +26,7 @@ module Grape
26
26
 
27
27
  def initialize(app, *options)
28
28
  super
29
- self.class.send(:include, @options[:helpers]) if @options[:helpers]
29
+ self.class.include(@options[:helpers]) if @options[:helpers]
30
30
  end
31
31
 
32
32
  def call!(env)
@@ -45,7 +45,7 @@ module Grape
45
45
 
46
46
  def format_message(message, backtrace, original_exception = nil)
47
47
  format = env[Grape::Env::API_FORMAT] || options[:format]
48
- formatter = Grape::ErrorFormatter.formatter_for(format, **options)
48
+ formatter = Grape::ErrorFormatter.formatter_for(format, options[:error_formatters], options[:default_error_formatter])
49
49
  return formatter.call(message, backtrace, options, env, original_exception) if formatter
50
50
 
51
51
  throw :error,
@@ -79,7 +79,7 @@ module Grape
79
79
  end
80
80
 
81
81
  def rescue_handler_for_base_only_class(klass)
82
- error, handler = options[:base_only_rescue_handlers].find { |err, _handler| klass == err }
82
+ error, handler = options[:base_only_rescue_handlers]&.find { |err, _handler| klass == err }
83
83
 
84
84
  return unless error
85
85
 
@@ -87,7 +87,7 @@ module Grape
87
87
  end
88
88
 
89
89
  def rescue_handler_for_class_or_its_ancestor(klass)
90
- error, handler = options[:rescue_handlers].find { |err, _handler| klass <= err }
90
+ error, handler = options[:rescue_handlers]&.find { |err, _handler| klass <= err }
91
91
 
92
92
  return unless error
93
93
 
@@ -120,12 +120,12 @@ module Grape
120
120
  handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
121
121
  end
122
122
 
123
- response = error!(response[:message], response[:status], response[:headers]) if error?(response)
124
-
125
- if response.is_a?(Rack::Response)
123
+ if error?(response)
124
+ error_response(response)
125
+ elsif response.is_a?(Rack::Response)
126
126
  response
127
127
  else
128
- run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new, endpoint)
128
+ run_rescue_handler(method(:default_rescue_handler), Grape::Exceptions::InvalidResponse.new, endpoint)
129
129
  end
130
130
  end
131
131
 
@@ -137,7 +137,9 @@ module Grape
137
137
  end
138
138
 
139
139
  def error?(response)
140
- response.is_a?(Hash) && response[:message] && response[:status] && response[:headers]
140
+ return false unless response.is_a?(Hash)
141
+
142
+ response.key?(:message) && response.key?(:status) && response.key?(:headers)
141
143
  end
142
144
  end
143
145
  end
@@ -54,7 +54,7 @@ module Grape
54
54
 
55
55
  def fetch_formatter(headers, options)
56
56
  api_format = mime_types[headers[Rack::CONTENT_TYPE]] || env[Grape::Env::API_FORMAT]
57
- Grape::Formatter.formatter_for(api_format, **options)
57
+ Grape::Formatter.formatter_for(api_format, options[:formatters])
58
58
  end
59
59
 
60
60
  # Set the content type header for the API format if it is not already present.
@@ -97,7 +97,7 @@ module Grape
97
97
  fmt = request.media_type ? mime_types[request.media_type] : options[:default_format]
98
98
 
99
99
  throw :error, status: 415, message: "The provided content-type '#{request.media_type}' is not supported." unless content_type_for(fmt)
100
- parser = Grape::Parser.parser_for fmt, **options
100
+ parser = Grape::Parser.parser_for fmt, options[:parsers]
101
101
  if parser
102
102
  begin
103
103
  body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))