grape 2.1.2 → 2.2.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: 024e352cc91ca60c4f0750b852ce4fba0dc574b33d647ef10958b4db12fc7090
4
- data.tar.gz: d9fb3be3da671011a1bed81d5f986a1688d411903bcf550c9d6ea83c7dcb6e0f
3
+ metadata.gz: dc04f424b8181e92cc304d02d2923952f07e2532ef0291233596813726a2cb68
4
+ data.tar.gz: 46729a20982fc16129540a81061bea229fd3ba5dd81426c1830119b52fe6ccd2
5
5
  SHA512:
6
- metadata.gz: 6172f2a8d7567edcbebb7289d5e41bed65f66ffa9309dd37b03b57cf199bc84945cdfd241df84e6a92ecf28f9099b18b9fe08516470b38721cb256fce5de8a34
7
- data.tar.gz: d5ce8b9292bc9e596a955ac0c748cb2c278daf68246192556a1ee91e726026bdced1586ea4b73818727ff3fd2b0f32df57f3674d7a5f585703cd91c7630f5b71
6
+ metadata.gz: 45e47b5059a37bbf75331c41bb1fdda2487610e3713baf5b8181dcbbae1727c47995503f5dc8a12375fd0765908140540100579afe834dfe96dc4c07cd771b2f
7
+ data.tar.gz: 9692f64fc61714c33035ef59c0d68a9c34fb3725a5842e0fe16cbd4d7025265bef50ea5086a3f61d5ed1f0fd8740435619ff616eed55e45fd3cc48e090a02b7d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
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
+
20
+ ### 2.1.3 (2024-07-13)
21
+
22
+ #### Fixes
23
+
24
+ * [#2467](https://github.com/ruby-grape/grape/pull/2467): Fix repo coverage - [@ericproulx](https://github.com/ericproulx).
25
+ * [#2468](https://github.com/ruby-grape/grape/pull/2468): Align `error!` method signatures across different places - [@numbata](https://github.com/numbata).
26
+ * [#2469](https://github.com/ruby-grape/grape/pull/2469): Fix full path building for lateral scopes - [@numbata](https://github.com/numbata).
27
+
1
28
  ### 2.1.2 (2024-06-28)
2
29
 
3
30
  #### 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.2**.
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
@@ -163,12 +163,19 @@ module Grape
163
163
  # end user with the specified message.
164
164
  #
165
165
  # @param message [String] The message to display.
166
- # @param status [Integer] the HTTP Status Code. Defaults to default_error_status, 500 if not set.
166
+ # @param status [Integer] The HTTP Status Code. Defaults to default_error_status, 500 if not set.
167
167
  # @param additional_headers [Hash] Addtional headers for the response.
168
- def error!(message, status = nil, additional_headers = nil)
168
+ # @param backtrace [Array<String>] The backtrace of the exception that caused the error.
169
+ # @param original_exception [Exception] The original exception that caused the error.
170
+ def error!(message, status = nil, additional_headers = nil, backtrace = nil, original_exception = nil)
169
171
  status = self.status(status || namespace_inheritable(:default_error_status))
170
172
  headers = additional_headers.present? ? header.merge(additional_headers) : header
171
- throw :error, message: message, status: status, headers: headers
173
+ throw :error,
174
+ message: message,
175
+ status: status,
176
+ headers: headers,
177
+ backtrace: backtrace,
178
+ original_exception: original_exception
172
179
  end
173
180
 
174
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