grape 1.5.2 → 1.7.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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -0
  3. data/CONTRIBUTING.md +2 -1
  4. data/README.md +152 -21
  5. data/UPGRADING.md +86 -2
  6. data/grape.gemspec +5 -5
  7. data/lib/grape/api/instance.rb +14 -18
  8. data/lib/grape/api.rb +18 -13
  9. data/lib/grape/cookies.rb +2 -0
  10. data/lib/grape/dry_types.rb +12 -0
  11. data/lib/grape/dsl/api.rb +0 -2
  12. data/lib/grape/dsl/callbacks.rb +0 -2
  13. data/lib/grape/dsl/configuration.rb +0 -2
  14. data/lib/grape/dsl/desc.rb +2 -19
  15. data/lib/grape/dsl/headers.rb +5 -2
  16. data/lib/grape/dsl/helpers.rb +7 -7
  17. data/lib/grape/dsl/inside_route.rb +43 -30
  18. data/lib/grape/dsl/middleware.rb +4 -6
  19. data/lib/grape/dsl/parameters.rb +8 -10
  20. data/lib/grape/dsl/request_response.rb +9 -8
  21. data/lib/grape/dsl/routing.rb +6 -4
  22. data/lib/grape/dsl/settings.rb +5 -7
  23. data/lib/grape/dsl/validations.rb +0 -15
  24. data/lib/grape/endpoint.rb +21 -36
  25. data/lib/grape/error_formatter/json.rb +9 -7
  26. data/lib/grape/error_formatter/xml.rb +2 -6
  27. data/lib/grape/exceptions/base.rb +2 -2
  28. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  29. data/lib/grape/exceptions/missing_group_type.rb +8 -1
  30. data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
  31. data/lib/grape/exceptions/unsupported_group_type.rb +8 -1
  32. data/lib/grape/exceptions/validation.rb +1 -6
  33. data/lib/grape/formatter/json.rb +1 -0
  34. data/lib/grape/formatter/serializable_hash.rb +2 -1
  35. data/lib/grape/formatter/xml.rb +1 -0
  36. data/lib/grape/locale/en.yml +9 -8
  37. data/lib/grape/middleware/auth/dsl.rb +7 -2
  38. data/lib/grape/middleware/base.rb +3 -1
  39. data/lib/grape/middleware/error.rb +2 -2
  40. data/lib/grape/middleware/formatter.rb +4 -4
  41. data/lib/grape/middleware/stack.rb +2 -2
  42. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
  43. data/lib/grape/middleware/versioner/header.rb +6 -4
  44. data/lib/grape/middleware/versioner/param.rb +1 -0
  45. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  46. data/lib/grape/middleware/versioner/path.rb +2 -0
  47. data/lib/grape/parser/json.rb +1 -1
  48. data/lib/grape/parser/xml.rb +1 -1
  49. data/lib/grape/path.rb +1 -0
  50. data/lib/grape/request.rb +5 -0
  51. data/lib/grape/router/pattern.rb +1 -1
  52. data/lib/grape/router/route.rb +2 -2
  53. data/lib/grape/router.rb +6 -0
  54. data/lib/grape/util/inheritable_setting.rb +1 -3
  55. data/lib/grape/util/json.rb +2 -0
  56. data/lib/grape/util/lazy_value.rb +3 -2
  57. data/lib/grape/util/strict_hash_configuration.rb +1 -1
  58. data/lib/grape/validations/attributes_doc.rb +58 -0
  59. data/lib/grape/validations/params_scope.rb +137 -78
  60. data/lib/grape/validations/types/array_coercer.rb +0 -2
  61. data/lib/grape/validations/types/custom_type_coercer.rb +1 -2
  62. data/lib/grape/validations/types/dry_type_coercer.rb +4 -8
  63. data/lib/grape/validations/types/json.rb +2 -1
  64. data/lib/grape/validations/types/primitive_coercer.rb +16 -8
  65. data/lib/grape/validations/types/set_coercer.rb +0 -2
  66. data/lib/grape/validations/types.rb +98 -30
  67. data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
  68. data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
  69. data/lib/grape/validations/validators/as_validator.rb +14 -0
  70. data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
  71. data/lib/grape/validations/validators/base.rb +82 -70
  72. data/lib/grape/validations/validators/coerce_validator.rb +75 -0
  73. data/lib/grape/validations/validators/default_validator.rb +51 -0
  74. data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
  75. data/lib/grape/validations/validators/except_values_validator.rb +24 -0
  76. data/lib/grape/validations/validators/multiple_params_base.rb +24 -20
  77. data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
  78. data/lib/grape/validations/validators/presence_validator.rb +15 -0
  79. data/lib/grape/validations/validators/regexp_validator.rb +16 -0
  80. data/lib/grape/validations/validators/same_as_validator.rb +29 -0
  81. data/lib/grape/validations/validators/values_validator.rb +88 -0
  82. data/lib/grape/validations.rb +16 -6
  83. data/lib/grape/version.rb +1 -1
  84. data/lib/grape.rb +70 -29
  85. data/spec/grape/api/custom_validations_spec.rb +116 -45
  86. data/spec/grape/api/deeply_included_options_spec.rb +3 -5
  87. data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -3
  88. data/spec/grape/api/documentation_spec.rb +59 -0
  89. data/spec/grape/api/inherited_helpers_spec.rb +0 -2
  90. data/spec/grape/api/instance_spec.rb +0 -1
  91. data/spec/grape/api/invalid_format_spec.rb +2 -2
  92. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -2
  93. data/spec/grape/api/nested_helpers_spec.rb +0 -2
  94. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -2
  95. data/spec/grape/api/parameters_modification_spec.rb +0 -2
  96. data/spec/grape/api/patch_method_helpers_spec.rb +0 -2
  97. data/spec/grape/api/recognize_path_spec.rb +1 -3
  98. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -2
  99. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -2
  100. data/spec/grape/api/routes_with_requirements_spec.rb +8 -10
  101. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -17
  102. data/spec/grape/api/shared_helpers_spec.rb +0 -2
  103. data/spec/grape/api_remount_spec.rb +16 -16
  104. data/spec/grape/api_spec.rb +527 -224
  105. data/spec/grape/config_spec.rb +0 -2
  106. data/spec/grape/dsl/callbacks_spec.rb +2 -3
  107. data/spec/grape/dsl/configuration_spec.rb +0 -2
  108. data/spec/grape/dsl/desc_spec.rb +0 -2
  109. data/spec/grape/dsl/headers_spec.rb +39 -11
  110. data/spec/grape/dsl/helpers_spec.rb +3 -4
  111. data/spec/grape/dsl/inside_route_spec.rb +16 -16
  112. data/spec/grape/dsl/logger_spec.rb +15 -19
  113. data/spec/grape/dsl/middleware_spec.rb +2 -3
  114. data/spec/grape/dsl/parameters_spec.rb +2 -2
  115. data/spec/grape/dsl/request_response_spec.rb +7 -8
  116. data/spec/grape/dsl/routing_spec.rb +11 -10
  117. data/spec/grape/dsl/settings_spec.rb +0 -2
  118. data/spec/grape/dsl/validations_spec.rb +0 -17
  119. data/spec/grape/endpoint/declared_spec.rb +261 -16
  120. data/spec/grape/endpoint_spec.rb +98 -57
  121. data/spec/grape/entity_spec.rb +22 -23
  122. data/spec/grape/exceptions/base_spec.rb +16 -2
  123. data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -2
  124. data/spec/grape/exceptions/invalid_accept_header_spec.rb +61 -24
  125. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -2
  126. data/spec/grape/exceptions/invalid_response_spec.rb +0 -2
  127. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +1 -3
  128. data/spec/grape/exceptions/missing_group_type_spec.rb +21 -0
  129. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -2
  130. data/spec/grape/exceptions/missing_option_spec.rb +1 -3
  131. data/spec/grape/exceptions/unknown_options_spec.rb +0 -2
  132. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -2
  133. data/spec/grape/exceptions/unsupported_group_type_spec.rb +23 -0
  134. data/spec/grape/exceptions/validation_errors_spec.rb +13 -11
  135. data/spec/grape/exceptions/validation_spec.rb +5 -5
  136. data/spec/grape/extensions/param_builders/hash_spec.rb +7 -9
  137. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -10
  138. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -10
  139. data/spec/grape/integration/global_namespace_function_spec.rb +0 -2
  140. data/spec/grape/integration/rack_sendfile_spec.rb +1 -3
  141. data/spec/grape/integration/rack_spec.rb +0 -2
  142. data/spec/grape/loading_spec.rb +8 -10
  143. data/spec/grape/middleware/auth/base_spec.rb +0 -1
  144. data/spec/grape/middleware/auth/dsl_spec.rb +15 -8
  145. data/spec/grape/middleware/auth/strategies_spec.rb +60 -22
  146. data/spec/grape/middleware/base_spec.rb +24 -17
  147. data/spec/grape/middleware/error_spec.rb +8 -3
  148. data/spec/grape/middleware/exception_spec.rb +111 -163
  149. data/spec/grape/middleware/formatter_spec.rb +27 -8
  150. data/spec/grape/middleware/globals_spec.rb +7 -6
  151. data/spec/grape/middleware/stack_spec.rb +14 -14
  152. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -3
  153. data/spec/grape/middleware/versioner/header_spec.rb +30 -15
  154. data/spec/grape/middleware/versioner/param_spec.rb +7 -3
  155. data/spec/grape/middleware/versioner/path_spec.rb +5 -3
  156. data/spec/grape/middleware/versioner_spec.rb +1 -3
  157. data/spec/grape/named_api_spec.rb +0 -2
  158. data/spec/grape/parser_spec.rb +4 -2
  159. data/spec/grape/path_spec.rb +52 -54
  160. data/spec/grape/presenters/presenter_spec.rb +7 -8
  161. data/spec/grape/request_spec.rb +6 -6
  162. data/spec/grape/util/inheritable_setting_spec.rb +7 -8
  163. data/spec/grape/util/inheritable_values_spec.rb +3 -3
  164. data/spec/grape/util/reverse_stackable_values_spec.rb +3 -2
  165. data/spec/grape/util/stackable_values_spec.rb +7 -6
  166. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -1
  167. data/spec/grape/validations/attributes_doc_spec.rb +153 -0
  168. data/spec/grape/validations/attributes_iterator_spec.rb +0 -2
  169. data/spec/grape/validations/instance_behaivour_spec.rb +9 -12
  170. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +1 -2
  171. data/spec/grape/validations/params_scope_spec.rb +361 -96
  172. data/spec/grape/validations/single_attribute_iterator_spec.rb +2 -3
  173. data/spec/grape/validations/types/array_coercer_spec.rb +0 -2
  174. data/spec/grape/validations/types/primitive_coercer_spec.rb +24 -9
  175. data/spec/grape/validations/types/set_coercer_spec.rb +0 -2
  176. data/spec/grape/validations/types_spec.rb +36 -10
  177. data/spec/grape/validations/validators/all_or_none_spec.rb +50 -58
  178. data/spec/grape/validations/validators/allow_blank_spec.rb +135 -141
  179. data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -58
  180. data/spec/grape/validations/validators/coerce_spec.rb +99 -24
  181. data/spec/grape/validations/validators/default_spec.rb +72 -80
  182. data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -79
  183. data/spec/grape/validations/validators/except_values_spec.rb +3 -5
  184. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -79
  185. data/spec/grape/validations/validators/presence_spec.rb +16 -3
  186. data/spec/grape/validations/validators/regexp_spec.rb +25 -33
  187. data/spec/grape/validations/validators/same_as_spec.rb +14 -22
  188. data/spec/grape/validations/validators/values_spec.rb +182 -179
  189. data/spec/grape/validations_spec.rb +149 -80
  190. data/spec/integration/eager_load/eager_load_spec.rb +2 -2
  191. data/spec/integration/multi_json/json_spec.rb +1 -3
  192. data/spec/integration/multi_xml/xml_spec.rb +1 -3
  193. data/spec/shared/versioning_examples.rb +12 -9
  194. data/spec/spec_helper.rb +21 -6
  195. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  196. metadata +125 -115
  197. data/lib/grape/validations/validators/all_or_none.rb +0 -15
  198. data/lib/grape/validations/validators/allow_blank.rb +0 -18
  199. data/lib/grape/validations/validators/as.rb +0 -16
  200. data/lib/grape/validations/validators/at_least_one_of.rb +0 -14
  201. data/lib/grape/validations/validators/coerce.rb +0 -91
  202. data/lib/grape/validations/validators/default.rb +0 -48
  203. data/lib/grape/validations/validators/exactly_one_of.rb +0 -16
  204. data/lib/grape/validations/validators/except_values.rb +0 -22
  205. data/lib/grape/validations/validators/mutual_exclusion.rb +0 -15
  206. data/lib/grape/validations/validators/presence.rb +0 -12
  207. data/lib/grape/validations/validators/regexp.rb +0 -13
  208. data/lib/grape/validations/validators/same_as.rb +0 -26
  209. data/lib/grape/validations/validators/values.rb +0 -83
  210. data/spec/support/eager_load.rb +0 -19
@@ -20,7 +20,8 @@ module Grape
20
20
  def before_each(new_setup = false, &block)
21
21
  @before_each ||= []
22
22
  if new_setup == false
23
- return @before_each unless block_given?
23
+ return @before_each unless block
24
+
24
25
  @before_each << block
25
26
  else
26
27
  @before_each = [new_setup]
@@ -46,9 +47,7 @@ module Grape
46
47
  # @return [Proc]
47
48
  # @raise [NameError] an instance method with the same name already exists
48
49
  def generate_api_method(method_name, &block)
49
- if method_defined?(method_name)
50
- raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name")
51
- end
50
+ raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name") if method_defined?(method_name)
52
51
 
53
52
  define_method(method_name, &block)
54
53
  method = instance_method(method_name)
@@ -106,7 +105,7 @@ module Grape
106
105
  @body = nil
107
106
  @proc = nil
108
107
 
109
- return unless block_given?
108
+ return unless block
110
109
 
111
110
  @source = block
112
111
  @block = self.class.generate_api_method(method_name, &block)
@@ -118,11 +117,9 @@ module Grape
118
117
  inheritable_setting.route[:saved_validations] += namespace_stackable[:validations]
119
118
  parent_declared_params = namespace_stackable[:declared_params]
120
119
 
121
- if parent_declared_params
122
- inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten)
123
- end
120
+ inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params
124
121
 
125
- endpoints && endpoints.each { |e| e.inherit_settings(namespace_stackable) }
122
+ endpoints&.each { |e| e.inherit_settings(namespace_stackable) }
126
123
  end
127
124
 
128
125
  def require_option(options, key)
@@ -142,7 +139,7 @@ module Grape
142
139
  end
143
140
 
144
141
  def reset_routes!
145
- endpoints.each(&:reset_routes!) if endpoints
142
+ endpoints&.each(&:reset_routes!)
146
143
  @namespace = nil
147
144
  @routes = nil
148
145
  end
@@ -154,13 +151,9 @@ module Grape
154
151
  reset_routes!
155
152
  routes.each do |route|
156
153
  methods = [route.request_method]
157
- if !namespace_inheritable(:do_not_route_head) && route.request_method == Grape::Http::Headers::GET
158
- methods << Grape::Http::Headers::HEAD
159
- end
154
+ methods << Grape::Http::Headers::HEAD if !namespace_inheritable(:do_not_route_head) && route.request_method == Grape::Http::Headers::GET
160
155
  methods.each do |method|
161
- unless route.request_method == method
162
- route = Grape::Router::Route.new(method, route.origin, **route.attributes.to_h)
163
- end
156
+ route = Grape::Router::Route.new(method, route.origin, **route.attributes.to_h) unless route.request_method == method
164
157
  router.append(route.apply(self))
165
158
  end
166
159
  end
@@ -200,6 +193,7 @@ module Grape
200
193
  def prepare_version
201
194
  version = namespace_inheritable(:version) || []
202
195
  return if version.empty?
196
+
203
197
  version.length == 1 ? version.first.to_s : version
204
198
  end
205
199
 
@@ -234,7 +228,7 @@ module Grape
234
228
  # Return the collection of endpoints within this endpoint.
235
229
  # This is the case when an Grape::API mounts another Grape::API.
236
230
  def endpoints
237
- options[:app].endpoints if options[:app] && options[:app].respond_to?(:endpoints)
231
+ options[:app].endpoints if options[:app].respond_to?(:endpoints)
238
232
  end
239
233
 
240
234
  def equals?(e)
@@ -255,14 +249,14 @@ module Grape
255
249
  run_filters befores, :before
256
250
 
257
251
  if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
258
- raise Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => allowed_methods) unless options?
252
+ raise Grape::Exceptions::MethodNotAllowed.new(header.merge('Allow' => allowed_methods)) unless options?
253
+
259
254
  header 'Allow', allowed_methods
260
255
  response_object = ''
261
256
  status 204
262
257
  else
263
258
  run_filters before_validations, :before_validation
264
259
  run_validators validations, request
265
- remove_renamed_params
266
260
  run_filters after_validations, :after_validation
267
261
  response_object = execute
268
262
  end
@@ -328,14 +322,7 @@ module Grape
328
322
  Module.new { helpers.each { |mod_to_include| include mod_to_include } }
329
323
  end
330
324
 
331
- def remove_renamed_params
332
- return unless route_setting(:renamed_params)
333
- route_setting(:renamed_params).flat_map(&:keys).each do |renamed_param|
334
- @params.delete(renamed_param)
335
- end
336
- end
337
-
338
- private :build_stack, :build_helpers, :remove_renamed_params
325
+ private :build_stack, :build_helpers
339
326
 
340
327
  def execute
341
328
  @block ? @block.call(self) : nil
@@ -365,15 +352,13 @@ module Grape
365
352
 
366
353
  ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do
367
354
  validators.each do |validator|
368
- begin
369
- validator.validate(request)
370
- rescue Grape::Exceptions::Validation => e
371
- validation_errors << e
372
- break if validator.fail_fast?
373
- rescue Grape::Exceptions::ValidationArrayErrors => e
374
- validation_errors.concat e.errors
375
- break if validator.fail_fast?
376
- end
355
+ validator.validate(request)
356
+ rescue Grape::Exceptions::Validation => e
357
+ validation_errors << e
358
+ break if validator.fail_fast?
359
+ rescue Grape::Exceptions::ValidationArrayErrors => e
360
+ validation_errors.concat e.errors
361
+ break if validator.fail_fast?
377
362
  end
378
363
  end
379
364
 
@@ -10,12 +10,8 @@ module Grape
10
10
  result = wrap_message(present(message, env))
11
11
 
12
12
  rescue_options = options[:rescue_options] || {}
13
- if rescue_options[:backtrace] && backtrace && !backtrace.empty?
14
- result = result.merge(backtrace: backtrace)
15
- end
16
- if rescue_options[:original_exception] && original_exception
17
- result = result.merge(original_exception: original_exception.inspect)
18
- end
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
19
15
  ::Grape::Json.dump(result)
20
16
  end
21
17
 
@@ -25,9 +21,15 @@ module Grape
25
21
  if message.is_a?(Exceptions::ValidationErrors) || message.is_a?(Hash)
26
22
  message
27
23
  else
28
- { error: message }
24
+ { error: ensure_utf8(message) }
29
25
  end
30
26
  end
27
+
28
+ def ensure_utf8(message)
29
+ return message unless message.respond_to? :encode
30
+
31
+ message.encode('UTF-8', invalid: :replace, undef: :replace)
32
+ end
31
33
  end
32
34
  end
33
35
  end
@@ -11,12 +11,8 @@ module Grape
11
11
 
12
12
  result = message.is_a?(Hash) ? message : { message: message }
13
13
  rescue_options = options[:rescue_options] || {}
14
- if rescue_options[:backtrace] && backtrace && !backtrace.empty?
15
- result = result.merge(backtrace: backtrace)
16
- end
17
- if rescue_options[:original_exception] && original_exception
18
- result = result.merge(original_exception: original_exception.inspect)
19
- end
14
+ result = result.merge(backtrace: backtrace) if rescue_options[:backtrace] && backtrace && !backtrace.empty?
15
+ result = result.merge(original_exception: original_exception.inspect) if rescue_options[:original_exception] && original_exception
20
16
  result.respond_to?(:to_xml) ? result.to_xml(root: :error) : result.to_s
21
17
  end
22
18
  end
@@ -7,12 +7,12 @@ module Grape
7
7
  BASE_ATTRIBUTES_KEY = 'grape.errors.attributes'
8
8
  FALLBACK_LOCALE = :en
9
9
 
10
- attr_reader :status, :message, :headers
10
+ attr_reader :status, :headers
11
11
 
12
12
  def initialize(status: nil, message: nil, headers: nil, **_options)
13
13
  @status = status
14
- @message = message
15
14
  @headers = headers
15
+ super(message)
16
16
  end
17
17
 
18
18
  def [](index)
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class EmptyMessageBody < Base
6
+ def initialize(body_format)
7
+ super(message: compose_message(:empty_message_body, body_format: body_format), status: 400)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -2,10 +2,17 @@
2
2
 
3
3
  module Grape
4
4
  module Exceptions
5
- class MissingGroupTypeError < Base
5
+ class MissingGroupType < Base
6
6
  def initialize
7
7
  super(message: compose_message(:missing_group_type))
8
8
  end
9
9
  end
10
10
  end
11
11
  end
12
+
13
+ Grape::Exceptions::MissingGroupTypeError = Class.new(Grape::Exceptions::MissingGroupType) do
14
+ def initialize(*)
15
+ super
16
+ warn '[DEPRECATION] `Grape::Exceptions::MissingGroupTypeError` is deprecated. Use `Grape::Exceptions::MissingGroupType` instead.'
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class TooManyMultipartFiles < Base
6
+ def initialize(limit)
7
+ super(message: compose_message(:too_many_multipart_files, limit: limit), status: 413)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -2,10 +2,17 @@
2
2
 
3
3
  module Grape
4
4
  module Exceptions
5
- class UnsupportedGroupTypeError < Base
5
+ class UnsupportedGroupType < Base
6
6
  def initialize
7
7
  super(message: compose_message(:unsupported_group_type))
8
8
  end
9
9
  end
10
10
  end
11
11
  end
12
+
13
+ Grape::Exceptions::UnsupportedGroupTypeError = Class.new(Grape::Exceptions::UnsupportedGroupType) do
14
+ def initialize(*)
15
+ super
16
+ warn '[DEPRECATION] `Grape::Exceptions::UnsupportedGroupTypeError` is deprecated. Use `Grape::Exceptions::UnsupportedGroupType` instead.'
17
+ end
18
+ end
@@ -5,8 +5,7 @@ require 'grape/exceptions/base'
5
5
  module Grape
6
6
  module Exceptions
7
7
  class Validation < Grape::Exceptions::Base
8
- attr_accessor :params
9
- attr_accessor :message_key
8
+ attr_accessor :params, :message_key
10
9
 
11
10
  def initialize(params:, message: nil, **args)
12
11
  @params = params
@@ -22,10 +21,6 @@ module Grape
22
21
  def as_json(*_args)
23
22
  to_s
24
23
  end
25
-
26
- def to_s
27
- message
28
- end
29
24
  end
30
25
  end
31
26
  end
@@ -6,6 +6,7 @@ module Grape
6
6
  class << self
7
7
  def call(object, _env)
8
8
  return object.to_json if object.respond_to?(:to_json)
9
+
9
10
  ::Grape::Json.dump(object)
10
11
  end
11
12
  end
@@ -8,13 +8,14 @@ module Grape
8
8
  return object if object.is_a?(String)
9
9
  return ::Grape::Json.dump(serialize(object)) if serializable?(object)
10
10
  return object.to_json if object.respond_to?(:to_json)
11
+
11
12
  ::Grape::Json.dump(object)
12
13
  end
13
14
 
14
15
  private
15
16
 
16
17
  def serializable?(object)
17
- object.respond_to?(:serializable_hash) || object.is_a?(Array) && object.all? { |o| o.respond_to? :serializable_hash } || object.is_a?(Hash)
18
+ object.respond_to?(:serializable_hash) || (object.is_a?(Array) && object.all? { |o| o.respond_to? :serializable_hash }) || object.is_a?(Hash)
18
19
  end
19
20
 
20
21
  def serialize(object)
@@ -6,6 +6,7 @@ module Grape
6
6
  class << self
7
7
  def call(object, _env)
8
8
  return object.to_xml if object.respond_to?(:to_xml)
9
+
9
10
  raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
10
11
  end
11
12
  end
@@ -11,8 +11,8 @@ en:
11
11
  except_values: 'has a value not allowed'
12
12
  same_as: 'is not the same as %{parameter}'
13
13
  missing_vendor_option:
14
- problem: 'missing :vendor option.'
15
- summary: 'when version using header, you must specify :vendor option. '
14
+ problem: 'missing :vendor option'
15
+ summary: 'when version using header, you must specify :vendor option'
16
16
  resolution: "eg: version 'v1', using: :header, vendor: 'twitter'"
17
17
  missing_mime_type:
18
18
  problem: 'missing mime type for %{new_format}'
@@ -21,12 +21,12 @@ en:
21
21
  or add your own with content_type :%{new_format}, 'application/%{new_format}'
22
22
  "
23
23
  invalid_with_option_for_represent:
24
- problem: 'You must specify an entity class in the :with option.'
24
+ problem: 'you must specify an entity class in the :with option'
25
25
  resolution: 'eg: represent User, :with => Entity::User'
26
- missing_option: 'You must specify :%{option} options.'
26
+ missing_option: 'you must specify :%{option} options'
27
27
  invalid_formatter: 'cannot convert %{klass} to %{to_format}'
28
28
  invalid_versioner_option:
29
- problem: 'Unknown :using for versioner: %{strategy}'
29
+ problem: 'unknown :using for versioner: %{strategy}'
30
30
  resolution: 'available strategy for :using is :path, :header, :accept_version_header, :param'
31
31
  unknown_validator: 'unknown validator: %{validator_type}'
32
32
  unknown_options: 'unknown options: %{options}'
@@ -44,11 +44,12 @@ en:
44
44
  "when specifying %{body_format} as content-type, you must pass valid
45
45
  %{body_format} in the request's 'body'
46
46
  "
47
+ empty_message_body: 'empty message body supplied with %{body_format} content-type'
48
+ too_many_multipart_files: "the number of uploaded files exceeded the system's configured limit (%{limit})"
47
49
  invalid_accept_header:
48
- problem: 'Invalid accept header'
50
+ problem: 'invalid accept header'
49
51
  resolution: '%{message}'
50
52
  invalid_version_header:
51
- problem: 'Invalid version header'
53
+ problem: 'invalid version header'
52
54
  resolution: '%{message}'
53
55
  invalid_response: 'Invalid response'
54
-
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rack/auth/basic'
4
- require 'active_support/concern'
5
4
 
6
5
  module Grape
7
6
  module Middleware
@@ -32,7 +31,13 @@ module Grape
32
31
 
33
32
  def http_digest(options = {}, &block)
34
33
  options[:realm] ||= 'API Authorization'
35
- options[:opaque] ||= 'secret'
34
+
35
+ if options[:realm].respond_to?(:values_at)
36
+ options[:realm][:opaque] ||= 'secret'
37
+ else
38
+ options[:opaque] ||= 'secret'
39
+ end
40
+
36
41
  auth :http_digest, options, &block
37
42
  end
38
43
  end
@@ -59,7 +59,8 @@ module Grape
59
59
 
60
60
  def response
61
61
  return @app_response if @app_response.is_a?(Rack::Response)
62
- Rack::Response.new(@app_response[2], @app_response[0], @app_response[1])
62
+
63
+ @app_response = Rack::Response.new(@app_response[2], @app_response[0], @app_response[1])
63
64
  end
64
65
 
65
66
  def content_type_for(format)
@@ -84,6 +85,7 @@ module Grape
84
85
 
85
86
  def merge_headers(response)
86
87
  return unless headers.is_a?(Hash)
88
+
87
89
  case response
88
90
  when Rack::Response then response.headers.merge!(headers)
89
91
  when Array then response[1].merge!(headers)
@@ -72,7 +72,7 @@ module Grape
72
72
 
73
73
  def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type })
74
74
  message = ERB::Util.html_escape(message) if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
75
- Rack::Response.new([message], status, headers)
75
+ Rack::Response.new([message], Rack::Utils.status_code(status), headers)
76
76
  end
77
77
 
78
78
  def format_message(message, backtrace, original_exception = nil)
@@ -121,7 +121,7 @@ module Grape
121
121
 
122
122
  def run_rescue_handler(handler, error)
123
123
  if handler.instance_of?(Symbol)
124
- raise NoMethodError, "undefined method `#{handler}'" unless respond_to?(handler)
124
+ raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)
125
125
 
126
126
  handler = public_method(handler)
127
127
  end
@@ -22,6 +22,7 @@ module Grape
22
22
 
23
23
  def after
24
24
  return unless @app_response
25
+
25
26
  status, headers, bodies = *@app_response
26
27
 
27
28
  if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
@@ -79,7 +80,7 @@ module Grape
79
80
  (request.post? || request.put? || request.patch? || request.delete?) &&
80
81
  (!request.form_data? || !request.media_type) &&
81
82
  !request.parseable_data? &&
82
- (request.content_length.to_i > 0 || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED)
83
+ (request.content_length.to_i.positive? || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED)
83
84
 
84
85
  return unless (input = env[Grape::Env::RACK_INPUT])
85
86
 
@@ -96,9 +97,7 @@ module Grape
96
97
  def read_rack_input(body)
97
98
  fmt = request.media_type ? mime_types[request.media_type] : options[:default_format]
98
99
 
99
- unless content_type_for(fmt)
100
- throw :error, status: 415, message: "The provided content-type '#{request.media_type}' is not supported."
101
- end
100
+ throw :error, status: 415, message: "The provided content-type '#{request.media_type}' is not supported." unless content_type_for(fmt)
102
101
  parser = Grape::Parser.parser_for fmt, **options
103
102
  if parser
104
103
  begin
@@ -145,6 +144,7 @@ module Grape
145
144
  fmt = Rack::Utils.parse_nested_query(env[Grape::Http::Headers::QUERY_STRING])[Grape::Http::Headers::FORMAT]
146
145
  # avoid symbol memory leak on an unknown format
147
146
  return fmt.to_sym if content_type_for(fmt)
147
+
148
148
  fmt
149
149
  end
150
150
 
@@ -45,8 +45,8 @@ module Grape
45
45
  @others = []
46
46
  end
47
47
 
48
- def each
49
- @middlewares.each { |x| yield x }
48
+ def each(&block)
49
+ @middlewares.each(&block)
50
50
  end
51
51
 
52
52
  def size
@@ -22,11 +22,9 @@ module Grape
22
22
  def before
23
23
  potential_version = (env[Grape::Http::Headers::HTTP_ACCEPT_VERSION] || '').strip
24
24
 
25
- if strict?
25
+ if strict? && potential_version.empty?
26
26
  # If no Accept-Version header:
27
- if potential_version.empty?
28
- throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.'
29
- end
27
+ throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.'
30
28
  end
31
29
 
32
30
  return if potential_version.empty?
@@ -51,7 +49,7 @@ module Grape
51
49
  # of routes (see Grape::Router) for more information). To prevent
52
50
  # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
53
51
  def cascade?
54
- if options[:version_options] && options[:version_options].key?(:cascade)
52
+ if options[:version_options]&.key?(:cascade)
55
53
  options[:version_options][:cascade]
56
54
  else
57
55
  true
@@ -26,10 +26,10 @@ module Grape
26
26
  # route.
27
27
  class Header < Base
28
28
  VENDOR_VERSION_HEADER_REGEX =
29
- /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z/.freeze
29
+ /\Avnd\.([a-z0-9.\-_!#{Regexp.last_match(0)}\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z/.freeze
30
30
 
31
- HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#\$&\^]+/.freeze
32
- HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))+/.freeze
31
+ HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#{Regexp.last_match(0)}\^]+/.freeze
32
+ HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#{Regexp.last_match(0)}\^]+?)(?:-([a-z0-9*.]+))+/.freeze
33
33
 
34
34
  def before
35
35
  strict_header_checks if strict?
@@ -52,12 +52,14 @@ module Grape
52
52
 
53
53
  def strict_accept_header_presence_check
54
54
  return unless header.qvalues.empty?
55
+
55
56
  fail_with_invalid_accept_header!('Accept header must be set.')
56
57
  end
57
58
 
58
59
  def strict_version_vendor_accept_header_presence_check
59
60
  return unless versions.present?
60
61
  return if an_accept_header_with_version_and_vendor_is_present?
62
+
61
63
  fail_with_invalid_accept_header!('API vendor or version not found.')
62
64
  end
63
65
 
@@ -160,7 +162,7 @@ module Grape
160
162
  # information). To prevent # this behavior, and not add the `X-Cascade`
161
163
  # header, one can set the `:cascade` option to `false`.
162
164
  def cascade?
163
- if version_options && version_options.key?(:cascade)
165
+ if version_options&.key?(:cascade)
164
166
  version_options[:cascade]
165
167
  else
166
168
  true
@@ -32,6 +32,7 @@ module Grape
32
32
  def before
33
33
  potential_version = Rack::Utils.parse_nested_query(env[Grape::Http::Headers::QUERY_STRING])[paramkey]
34
34
  return if potential_version.nil?
35
+
35
36
  throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' } if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
36
37
  env[Grape::Env::API_VERSION] = potential_version
37
38
  env[Grape::Env::RACK_REQUEST_QUERY_HASH].delete(paramkey) if env.key? Grape::Env::RACK_REQUEST_QUERY_HASH
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'English'
3
4
  module Rack
4
5
  module Accept
5
6
  module Header
6
- ALLOWED_CHARACTERS = %r{^([a-z*]+)\/([a-z0-9*\&\^\-_#\$!.+]+)(?:;([a-z0-9=;]+))?$}.freeze
7
+ ALLOWED_CHARACTERS = %r{^([a-z*]+)/([a-z0-9*&\^\-_#{$ERROR_INFO}.+]+)(?:;([a-z0-9=;]+))?$}.freeze
7
8
  class << self
8
9
  # Corrected version of https://github.com/mjackson/rack-accept/blob/master/lib/rack/accept/header.rb#L40-L44
9
10
  def parse_media_type(media_type)
@@ -37,6 +37,7 @@ module Grape
37
37
  pieces = path.split('/')
38
38
  potential_version = pieces[1]
39
39
  return unless potential_version&.match?(options[:pattern])
40
+
40
41
  throw :error, status: 404, message: '404 API Version Not Found' if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
41
42
  env[Grape::Env::API_VERSION] = potential_version
42
43
  end
@@ -45,6 +46,7 @@ module Grape
45
46
 
46
47
  def mounted_path?(path)
47
48
  return false unless mount_path && path.start_with?(mount_path)
49
+
48
50
  rest = path.slice(mount_path.length..-1)
49
51
  rest.start_with?('/') || rest.empty?
50
52
  end
@@ -8,7 +8,7 @@ module Grape
8
8
  ::Grape::Json.load(object)
9
9
  rescue ::Grape::Json::ParseError
10
10
  # handle JSON parsing errors via the rescue handlers or provide error message
11
- raise Grape::Exceptions::InvalidMessageBody, 'application/json'
11
+ raise Grape::Exceptions::InvalidMessageBody.new('application/json')
12
12
  end
13
13
  end
14
14
  end
@@ -8,7 +8,7 @@ module Grape
8
8
  ::Grape::Xml.parse(object)
9
9
  rescue ::Grape::Xml::ParseError
10
10
  # handle XML parsing errors via the rescue handlers or provide error message
11
- raise Grape::Exceptions::InvalidMessageBody, 'application/xml'
11
+ raise Grape::Exceptions::InvalidMessageBody.new('application/xml')
12
12
  end
13
13
  end
14
14
  end
data/lib/grape/path.rb CHANGED
@@ -91,6 +91,7 @@ module Grape
91
91
 
92
92
  def split_setting(key)
93
93
  return if settings[key].nil?
94
+
94
95
  settings[key].to_s.split('/')
95
96
  end
96
97
  end
data/lib/grape/request.rb CHANGED
@@ -15,6 +15,10 @@ module Grape
15
15
 
16
16
  def params
17
17
  @params ||= build_params
18
+ rescue EOFError
19
+ raise Grape::Exceptions::EmptyMessageBody.new(content_type)
20
+ rescue Rack::Multipart::MultipartPartLimitError
21
+ raise Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit)
18
22
  end
19
23
 
20
24
  def headers
@@ -35,6 +39,7 @@ module Grape
35
39
  Grape::Util::LazyObject.new do
36
40
  env.each_pair.with_object({}) do |(k, v), headers|
37
41
  next unless k.to_s.start_with? HTTP_PREFIX
42
+
38
43
  transformed_header = Grape::Http::Headers::HTTP_HEADERS[k] || transform_header(k)
39
44
  headers[transformed_header] = v
40
45
  end
@@ -41,7 +41,7 @@ module Grape
41
41
  end
42
42
 
43
43
  pattern = -pattern.split('/').tap do |parts|
44
- parts[parts.length - 1] = '?' + parts.last
44
+ parts[parts.length - 1] = "?#{parts.last}"
45
45
  end.join('/') if pattern.end_with?('*path')
46
46
 
47
47
  PatternCache[[pattern, suffix]]
@@ -84,8 +84,8 @@ module Grape
84
84
  path, line = *location.scan(SOURCE_LOCATION_REGEXP).first
85
85
  path = File.realpath(path) if Pathname.new(path).relative?
86
86
  expected ||= name
87
- warn <<-WARNING
88
- #{path}:#{line}: The route_xxx methods such as route_#{name} have been deprecated, please use #{expected}.
87
+ warn <<~WARNING
88
+ #{path}:#{line}: The route_xxx methods such as route_#{name} have been deprecated, please use #{expected}.
89
89
  WARNING
90
90
  end
91
91
  end