grape 2.1.0 → 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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/README.md +4 -3
  4. data/UPGRADING.md +8 -0
  5. data/grape.gemspec +2 -1
  6. data/lib/grape/api/instance.rb +1 -1
  7. data/lib/grape/api.rb +2 -2
  8. data/lib/grape/content_types.rb +13 -8
  9. data/lib/grape/dsl/helpers.rb +7 -3
  10. data/lib/grape/dsl/inside_route.rb +14 -3
  11. data/lib/grape/dsl/parameters.rb +1 -1
  12. data/lib/grape/dsl/request_response.rb +14 -18
  13. data/lib/grape/endpoint.rb +34 -23
  14. data/lib/grape/error_formatter/json.rb +13 -4
  15. data/lib/grape/error_formatter.rb +13 -25
  16. data/lib/grape/exceptions/validation_errors.rb +1 -1
  17. data/lib/grape/formatter.rb +15 -25
  18. data/lib/grape/locale/en.yml +1 -0
  19. data/lib/grape/middleware/base.rb +14 -13
  20. data/lib/grape/middleware/error.rb +13 -11
  21. data/lib/grape/middleware/formatter.rb +2 -2
  22. data/lib/grape/middleware/stack.rb +2 -2
  23. data/lib/grape/middleware/versioner/accept_version_header.rb +8 -31
  24. data/lib/grape/middleware/versioner/header.rb +95 -10
  25. data/lib/grape/middleware/versioner/param.rb +5 -21
  26. data/lib/grape/middleware/versioner/path.rb +11 -31
  27. data/lib/grape/middleware/versioner.rb +5 -14
  28. data/lib/grape/middleware/versioner_helpers.rb +75 -0
  29. data/lib/grape/parser.rb +8 -24
  30. data/lib/grape/router.rb +2 -1
  31. data/lib/grape/validations/attributes_iterator.rb +1 -0
  32. data/lib/grape/validations/params_scope.rb +12 -10
  33. data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
  34. data/lib/grape/validations/validators/length_validator.rb +10 -3
  35. data/lib/grape/version.rb +1 -1
  36. metadata +8 -9
  37. data/lib/grape/util/accept/header.rb +0 -19
  38. data/lib/grape/util/accept_header_handler.rb +0 -105
  39. data/lib/grape/util/registrable.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ea97a4725e49023d0d321920359d449d1a97ac1ea796bf8393f680d512799c7
4
- data.tar.gz: 2f36ea240c2f987ec11479522a6a4f3113f3fb296903baa749305ba60e07ad44
3
+ metadata.gz: dc04f424b8181e92cc304d02d2923952f07e2532ef0291233596813726a2cb68
4
+ data.tar.gz: 46729a20982fc16129540a81061bea229fd3ba5dd81426c1830119b52fe6ccd2
5
5
  SHA512:
6
- metadata.gz: 9b09628eb40cf9fe5f08e01b3391d3a7abca14b24c8bdda9fb6b52d326c97c2edc7ceb05ba8a27826d6f6d902867d5cad1e031884a466b59677e893df81b52e1
7
- data.tar.gz: e1c916b0be5480c0f6cff795eefde716f6056a3f6cc28eb97235cb3c79b503e0028b69286447fb47d3850936a411863166bb8f09dc1de4f21a56b48b439b2572
6
+ metadata.gz: 45e47b5059a37bbf75331c41bb1fdda2487610e3713baf5b8181dcbbae1727c47995503f5dc8a12375fd0765908140540100579afe834dfe96dc4c07cd771b2f
7
+ data.tar.gz: 9692f64fc61714c33035ef59c0d68a9c34fb3725a5842e0fe16cbd4d7025265bef50ea5086a3f61d5ed1f0fd8740435619ff616eed55e45fd3cc48e090a02b7d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,49 @@
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
+
28
+ ### 2.1.2 (2024-06-28)
29
+
30
+ #### Fixes
31
+
32
+ * [#2459](https://github.com/ruby-grape/grape/pull/2459): Autocorrect cops - [@ericproulx](https://github.com/ericproulx).
33
+ * [#3458](https://github.com/ruby-grape/grape/pull/2458): Remove unused Grape::Util::Accept::Header - [@ericproulx](https://github.com/ericproulx).
34
+ * [#2463](https://github.com/ruby-grape/grape/pull/2463): Fix error message indices - [@ericproulx](https://github.com/ericproulx).
35
+
36
+ ### 2.1.1 (2024-06-22)
37
+
38
+ #### Features
39
+
40
+ * [#2450](https://github.com/ruby-grape/grape/pull/2450): Update RuboCop to 1.64.1 - [@ericproulx](https://github.com/ericproulx).
41
+
42
+ #### Fixes
43
+
44
+ * [#2453](https://github.com/ruby-grape/grape/pull/2453): Fix context in rescue_from - [@ericproulx](https://github.com/ericproulx).
45
+ * [#2455](https://github.com/ruby-grape/grape/pull/2455): Fix default response headers to work with Rack 3 - [@ericproulx](https://github.com/ericproulx).
46
+
1
47
  ### 2.1.0 (2024/06/15)
2
48
 
3
49
  #### Features
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.0**.
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'
@@ -46,7 +46,7 @@ module Grape
46
46
  # Parses the API's definition and compiles it into an instance of
47
47
  # Grape::API.
48
48
  def compile
49
- @instance ||= new
49
+ @instance ||= new # rubocop:disable Naming/MemoizedInstanceVariableName
50
50
  end
51
51
 
52
52
  # Wipe the compiled API so we can recompile after changes were made.
data/lib/grape/api.rb CHANGED
@@ -32,7 +32,7 @@ module Grape
32
32
  def inherited(api)
33
33
  super
34
34
 
35
- api.initial_setup(Grape::API == self ? Grape::API::Instance : @base_instance)
35
+ api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)
36
36
  api.override_all_methods!
37
37
  end
38
38
 
@@ -108,7 +108,7 @@ module Grape
108
108
  end
109
109
 
110
110
  def respond_to?(method, include_private = false)
111
- super(method, include_private) || base_instance.respond_to?(method, include_private)
111
+ super || base_instance.respond_to?(method, include_private)
112
112
  end
113
113
 
114
114
  def respond_to_missing?(method, include_private = false)
@@ -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.
@@ -461,6 +468,10 @@ module Grape
461
468
  def http_version
462
469
  env['HTTP_VERSION'] || env[Rack::SERVER_PROTOCOL]
463
470
  end
471
+
472
+ def context
473
+ self
474
+ end
464
475
  end
465
476
  end
466
477
  end
@@ -231,7 +231,7 @@ module Grape
231
231
 
232
232
  alias group requires
233
233
 
234
- class EmptyOptionalValue; end
234
+ class EmptyOptionalValue; end # rubocop:disable Lint/EmptyClass
235
235
 
236
236
  def map_params(params, element, is_array = false)
237
237
  if params.is_a?(Array)
@@ -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
 
@@ -231,8 +232,17 @@ module Grape
231
232
  options[:app].endpoints if options[:app].respond_to?(:endpoints)
232
233
  end
233
234
 
234
- def equals?(e)
235
- (options == e.options) && (inheritable_setting.to_hash == e.inheritable_setting.to_hash)
235
+ def equals?(endpoint)
236
+ (options == endpoint.options) && (inheritable_setting.to_hash == endpoint.inheritable_setting.to_hash)
237
+ end
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"
236
246
  end
237
247
 
238
248
  protected
@@ -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
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class ValidationErrors < Grape::Exceptions::Base
6
6
  ERRORS_FORMAT_KEY = 'grape.errors.format'
7
- DEFAULT_ERRORS_FORMAT = '%{attributes} %{message}'
7
+ DEFAULT_ERRORS_FORMAT = '%<attributes>s %<message>s'
8
8
 
9
9
  include Enumerable
10
10
 
@@ -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: