grape 1.8.0 → 4.0.1

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 (317) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +517 -1
  3. data/CONTRIBUTING.md +19 -11
  4. data/README.md +841 -545
  5. data/UPGRADING.md +1553 -7
  6. data/grape.gemspec +11 -14
  7. data/lib/grape/api/instance.rb +77 -160
  8. data/lib/grape/api.rb +75 -107
  9. data/lib/grape/content_types.rb +67 -9
  10. data/lib/grape/cookies.rb +28 -25
  11. data/lib/grape/declared_params_handler.rb +116 -0
  12. data/lib/grape/dry_types.rb +48 -6
  13. data/lib/grape/dsl/callbacks.rb +8 -58
  14. data/lib/grape/dsl/declared.rb +35 -0
  15. data/lib/grape/dsl/desc.rb +25 -63
  16. data/lib/grape/dsl/entity.rb +106 -0
  17. data/lib/grape/dsl/headers.rb +2 -2
  18. data/lib/grape/dsl/helpers.rb +83 -64
  19. data/lib/grape/dsl/inside_route.rb +69 -296
  20. data/lib/grape/dsl/logger.rb +4 -9
  21. data/lib/grape/dsl/middleware.rb +22 -40
  22. data/lib/grape/dsl/parameters.rb +88 -94
  23. data/lib/grape/dsl/request_response.rb +143 -147
  24. data/lib/grape/dsl/rescue_options.rb +25 -0
  25. data/lib/grape/dsl/routing.rb +333 -191
  26. data/lib/grape/dsl/settings.rb +29 -135
  27. data/lib/grape/dsl/validations.rb +39 -32
  28. data/lib/grape/dsl/version_options.rb +23 -0
  29. data/lib/grape/endpoint/options.rb +25 -0
  30. data/lib/grape/endpoint.rb +319 -269
  31. data/lib/grape/{util/env.rb → env.rb} +8 -8
  32. data/lib/grape/error_formatter/base.rb +57 -21
  33. data/lib/grape/error_formatter/json.rb +13 -15
  34. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  35. data/lib/grape/error_formatter/txt.rb +12 -18
  36. data/lib/grape/error_formatter/xml.rb +3 -13
  37. data/lib/grape/error_formatter.rb +9 -25
  38. data/lib/grape/exceptions/base.rb +22 -58
  39. data/lib/grape/exceptions/error_response.rb +48 -0
  40. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  41. data/lib/grape/exceptions/internal_server_error.rb +16 -0
  42. data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
  43. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  44. data/lib/grape/exceptions/invalid_message_body.rb +1 -1
  45. data/lib/grape/exceptions/invalid_version_header.rb +1 -1
  46. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  47. data/lib/grape/exceptions/method_not_allowed.rb +1 -1
  48. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  49. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  50. data/lib/grape/exceptions/request_error.rb +11 -0
  51. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  52. data/lib/grape/exceptions/unknown_error_formatter.rb +11 -0
  53. data/lib/grape/exceptions/unknown_parameter.rb +1 -1
  54. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  55. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  56. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  57. data/lib/grape/exceptions/validation.rb +28 -11
  58. data/lib/grape/exceptions/validation_array_errors.rb +5 -0
  59. data/lib/grape/exceptions/validation_errors.rb +22 -26
  60. data/lib/grape/formatter/base.rb +16 -0
  61. data/lib/grape/formatter/json.rb +5 -6
  62. data/lib/grape/formatter/serializable_hash.rb +7 -10
  63. data/lib/grape/formatter/txt.rb +3 -5
  64. data/lib/grape/formatter/xml.rb +4 -6
  65. data/lib/grape/formatter.rb +7 -25
  66. data/lib/grape/json.rb +46 -0
  67. data/lib/grape/locale/en.yml +44 -42
  68. data/lib/grape/middleware/auth/base.rb +11 -33
  69. data/lib/grape/middleware/auth/dsl.rb +12 -34
  70. data/lib/grape/middleware/auth/strategies.rb +1 -2
  71. data/lib/grape/middleware/base.rb +56 -33
  72. data/lib/grape/middleware/error.rb +290 -91
  73. data/lib/grape/middleware/formatter.rb +164 -106
  74. data/lib/grape/middleware/precomputed_content_types.rb +51 -0
  75. data/lib/grape/middleware/stack.rb +38 -40
  76. data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
  77. data/lib/grape/middleware/versioner/base.rb +66 -0
  78. data/lib/grape/middleware/versioner/header.rb +44 -129
  79. data/lib/grape/middleware/versioner/param.rb +4 -25
  80. data/lib/grape/middleware/versioner/path.rb +48 -25
  81. data/lib/grape/middleware/versioner.rb +7 -14
  82. data/lib/grape/mountable.rb +22 -0
  83. data/lib/grape/namespace.rb +21 -14
  84. data/lib/grape/params_builder/base.rb +20 -0
  85. data/lib/grape/params_builder/hash.rb +11 -0
  86. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  87. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  88. data/lib/grape/params_builder.rb +15 -0
  89. data/lib/grape/parser/base.rb +16 -0
  90. data/lib/grape/parser/json.rb +6 -8
  91. data/lib/grape/parser/xml.rb +6 -8
  92. data/lib/grape/parser.rb +5 -23
  93. data/lib/grape/path.rb +8 -94
  94. data/lib/grape/precompiled_json.rb +50 -0
  95. data/lib/grape/railtie.rb +9 -0
  96. data/lib/grape/request.rb +200 -26
  97. data/lib/grape/router/base_route.rb +85 -0
  98. data/lib/grape/router/greedy_route.rb +30 -0
  99. data/lib/grape/router/mustermann_pattern.rb +44 -0
  100. data/lib/grape/router/pattern/path.rb +78 -0
  101. data/lib/grape/router/pattern.rb +77 -35
  102. data/lib/grape/router/route.rb +79 -60
  103. data/lib/grape/router.rb +128 -104
  104. data/lib/grape/serve_stream/file_body.rb +7 -0
  105. data/lib/grape/serve_stream/sendfile_response.rb +3 -5
  106. data/lib/grape/serve_stream/stream_response.rb +7 -0
  107. data/lib/grape/testing.rb +33 -0
  108. data/lib/grape/util/api_description.rb +67 -0
  109. data/lib/grape/util/cache.rb +22 -5
  110. data/lib/grape/util/deep_freeze.rb +34 -0
  111. data/lib/grape/util/endpoint_configuration.rb +1 -1
  112. data/lib/grape/util/freeze_on_new.rb +20 -0
  113. data/lib/grape/util/header.rb +13 -0
  114. data/lib/grape/util/inheritable_setting.rb +777 -39
  115. data/lib/grape/util/lazy/base.rb +16 -0
  116. data/lib/grape/util/lazy/block.rb +22 -0
  117. data/lib/grape/util/lazy/value.rb +31 -0
  118. data/lib/grape/util/lazy/value_array.rb +21 -0
  119. data/lib/grape/util/lazy/value_enumerable.rb +31 -0
  120. data/lib/grape/util/lazy/value_hash.rb +21 -0
  121. data/lib/grape/util/media_type.rb +74 -0
  122. data/lib/grape/util/path_normalizer.rb +37 -0
  123. data/lib/grape/util/registry.rb +37 -0
  124. data/lib/grape/util/shadowed_rescue_handlers.rb +49 -0
  125. data/lib/grape/util/stackable_values.rb +40 -16
  126. data/lib/grape/util/translation.rb +42 -0
  127. data/lib/grape/validations/attributes_iterator.rb +61 -28
  128. data/lib/grape/validations/coerce_options.rb +21 -0
  129. data/lib/grape/validations/contract_scope.rb +29 -0
  130. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  131. data/lib/grape/validations/oneof_collector.rb +35 -0
  132. data/lib/grape/validations/param_scope_tracker.rb +62 -0
  133. data/lib/grape/validations/params_documentation.rb +52 -0
  134. data/lib/grape/validations/params_scope.rb +223 -306
  135. data/lib/grape/validations/shared_options.rb +19 -0
  136. data/lib/grape/validations/single_attribute_iterator.rb +6 -4
  137. data/lib/grape/validations/types/array_coercer.rb +7 -12
  138. data/lib/grape/validations/types/custom_type_coercer.rb +47 -85
  139. data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
  140. data/lib/grape/validations/types/dry_type_coercer.rb +17 -28
  141. data/lib/grape/validations/types/json.rb +1 -5
  142. data/lib/grape/validations/types/multiple_type_coercer.rb +5 -3
  143. data/lib/grape/validations/types/primitive_coercer.rb +14 -35
  144. data/lib/grape/validations/types/set_coercer.rb +1 -4
  145. data/lib/grape/validations/types/variant_collection_coercer.rb +16 -3
  146. data/lib/grape/validations/types.rb +29 -54
  147. data/lib/grape/validations/validations_spec.rb +164 -0
  148. data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
  149. data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
  150. data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
  151. data/lib/grape/validations/validators/base.rb +118 -37
  152. data/lib/grape/validations/validators/coerce_validator.rb +26 -38
  153. data/lib/grape/validations/validators/contract_scope_validator.rb +46 -0
  154. data/lib/grape/validations/validators/default_validator.rb +13 -16
  155. data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
  156. data/lib/grape/validations/validators/except_values_validator.rb +15 -5
  157. data/lib/grape/validations/validators/length_validator.rb +50 -0
  158. data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
  159. data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
  160. data/lib/grape/validations/validators/oneof_validator.rb +51 -0
  161. data/lib/grape/validations/validators/presence_validator.rb +4 -2
  162. data/lib/grape/validations/validators/regexp_validator.rb +11 -3
  163. data/lib/grape/validations/validators/same_as_validator.rb +7 -15
  164. data/lib/grape/validations/validators/values_validator.rb +36 -65
  165. data/lib/grape/validations.rb +8 -21
  166. data/lib/grape/version.rb +1 -2
  167. data/lib/grape/xml.rb +17 -0
  168. data/lib/grape.rb +96 -288
  169. metadata +83 -294
  170. data/lib/grape/api/helpers.rb +0 -9
  171. data/lib/grape/dsl/api.rb +0 -19
  172. data/lib/grape/dsl/configuration.rb +0 -15
  173. data/lib/grape/eager_load.rb +0 -20
  174. data/lib/grape/exceptions/empty_message_body.rb +0 -11
  175. data/lib/grape/exceptions/missing_option.rb +0 -11
  176. data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
  177. data/lib/grape/exceptions/unknown_options.rb +0 -11
  178. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -27
  179. data/lib/grape/extensions/hash.rb +0 -22
  180. data/lib/grape/extensions/hashie/mash.rb +0 -26
  181. data/lib/grape/http/headers.rb +0 -61
  182. data/lib/grape/middleware/globals.rb +0 -16
  183. data/lib/grape/middleware/helpers.rb +0 -12
  184. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  185. data/lib/grape/router/attribute_translator.rb +0 -63
  186. data/lib/grape/types/invalid_value.rb +0 -8
  187. data/lib/grape/util/base_inheritable.rb +0 -43
  188. data/lib/grape/util/inheritable_values.rb +0 -31
  189. data/lib/grape/util/json.rb +0 -12
  190. data/lib/grape/util/lazy_block.rb +0 -27
  191. data/lib/grape/util/lazy_object.rb +0 -43
  192. data/lib/grape/util/lazy_value.rb +0 -91
  193. data/lib/grape/util/registrable.rb +0 -15
  194. data/lib/grape/util/reverse_stackable_values.rb +0 -20
  195. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  196. data/lib/grape/util/xml.rb +0 -10
  197. data/lib/grape/validations/attributes_doc.rb +0 -58
  198. data/lib/grape/validations/types/build_coercer.rb +0 -94
  199. data/lib/grape/validations/validator_factory.rb +0 -15
  200. data/spec/grape/api/custom_validations_spec.rb +0 -213
  201. data/spec/grape/api/deeply_included_options_spec.rb +0 -56
  202. data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
  203. data/spec/grape/api/documentation_spec.rb +0 -59
  204. data/spec/grape/api/inherited_helpers_spec.rb +0 -114
  205. data/spec/grape/api/instance_spec.rb +0 -103
  206. data/spec/grape/api/invalid_format_spec.rb +0 -45
  207. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
  208. data/spec/grape/api/nested_helpers_spec.rb +0 -50
  209. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
  210. data/spec/grape/api/parameters_modification_spec.rb +0 -41
  211. data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
  212. data/spec/grape/api/recognize_path_spec.rb +0 -21
  213. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
  214. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
  215. data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
  216. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
  217. data/spec/grape/api/shared_helpers_spec.rb +0 -36
  218. data/spec/grape/api_remount_spec.rb +0 -509
  219. data/spec/grape/api_spec.rb +0 -4356
  220. data/spec/grape/dsl/callbacks_spec.rb +0 -45
  221. data/spec/grape/dsl/desc_spec.rb +0 -98
  222. data/spec/grape/dsl/headers_spec.rb +0 -62
  223. data/spec/grape/dsl/helpers_spec.rb +0 -100
  224. data/spec/grape/dsl/inside_route_spec.rb +0 -531
  225. data/spec/grape/dsl/logger_spec.rb +0 -24
  226. data/spec/grape/dsl/middleware_spec.rb +0 -60
  227. data/spec/grape/dsl/parameters_spec.rb +0 -180
  228. data/spec/grape/dsl/request_response_spec.rb +0 -225
  229. data/spec/grape/dsl/routing_spec.rb +0 -275
  230. data/spec/grape/dsl/settings_spec.rb +0 -261
  231. data/spec/grape/dsl/validations_spec.rb +0 -55
  232. data/spec/grape/endpoint/declared_spec.rb +0 -846
  233. data/spec/grape/endpoint_spec.rb +0 -1085
  234. data/spec/grape/entity_spec.rb +0 -336
  235. data/spec/grape/exceptions/base_spec.rb +0 -81
  236. data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
  237. data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
  238. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
  239. data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
  240. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
  241. data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
  242. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
  243. data/spec/grape/exceptions/missing_option_spec.rb +0 -15
  244. data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
  245. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
  246. data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
  247. data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
  248. data/spec/grape/exceptions/validation_spec.rb +0 -19
  249. data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
  250. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
  251. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
  252. data/spec/grape/grape_spec.rb +0 -9
  253. data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
  254. data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
  255. data/spec/grape/integration/rack_spec.rb +0 -51
  256. data/spec/grape/loading_spec.rb +0 -44
  257. data/spec/grape/middleware/auth/base_spec.rb +0 -31
  258. data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
  259. data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
  260. data/spec/grape/middleware/base_spec.rb +0 -221
  261. data/spec/grape/middleware/error_spec.rb +0 -85
  262. data/spec/grape/middleware/exception_spec.rb +0 -294
  263. data/spec/grape/middleware/formatter_spec.rb +0 -461
  264. data/spec/grape/middleware/globals_spec.rb +0 -30
  265. data/spec/grape/middleware/stack_spec.rb +0 -155
  266. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
  267. data/spec/grape/middleware/versioner/header_spec.rb +0 -345
  268. data/spec/grape/middleware/versioner/param_spec.rb +0 -171
  269. data/spec/grape/middleware/versioner/path_spec.rb +0 -62
  270. data/spec/grape/middleware/versioner_spec.rb +0 -21
  271. data/spec/grape/named_api_spec.rb +0 -19
  272. data/spec/grape/parser_spec.rb +0 -86
  273. data/spec/grape/path_spec.rb +0 -252
  274. data/spec/grape/presenters/presenter_spec.rb +0 -71
  275. data/spec/grape/request_spec.rb +0 -126
  276. data/spec/grape/util/inheritable_setting_spec.rb +0 -242
  277. data/spec/grape/util/inheritable_values_spec.rb +0 -79
  278. data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
  279. data/spec/grape/util/stackable_values_spec.rb +0 -128
  280. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
  281. data/spec/grape/validations/attributes_doc_spec.rb +0 -153
  282. data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
  283. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
  284. data/spec/grape/validations/params_scope_spec.rb +0 -1420
  285. data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
  286. data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
  287. data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
  288. data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
  289. data/spec/grape/validations/types_spec.rb +0 -111
  290. data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
  291. data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
  292. data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
  293. data/spec/grape/validations/validators/base_spec.rb +0 -38
  294. data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
  295. data/spec/grape/validations/validators/default_spec.rb +0 -463
  296. data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
  297. data/spec/grape/validations/validators/except_values_spec.rb +0 -192
  298. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
  299. data/spec/grape/validations/validators/presence_spec.rb +0 -315
  300. data/spec/grape/validations/validators/regexp_spec.rb +0 -161
  301. data/spec/grape/validations/validators/same_as_spec.rb +0 -57
  302. data/spec/grape/validations/validators/values_spec.rb +0 -733
  303. data/spec/grape/validations/validators/zh-CN.yml +0 -10
  304. data/spec/grape/validations_spec.rb +0 -2030
  305. data/spec/integration/eager_load/eager_load_spec.rb +0 -15
  306. data/spec/integration/multi_json/json_spec.rb +0 -7
  307. data/spec/integration/multi_xml/xml_spec.rb +0 -7
  308. data/spec/shared/deprecated_class_examples.rb +0 -16
  309. data/spec/shared/versioning_examples.rb +0 -215
  310. data/spec/spec_helper.rb +0 -52
  311. data/spec/support/basic_auth_encode_helpers.rb +0 -11
  312. data/spec/support/chunks.rb +0 -14
  313. data/spec/support/content_type_helpers.rb +0 -15
  314. data/spec/support/endpoint_faker.rb +0 -25
  315. data/spec/support/file_streamer.rb +0 -13
  316. data/spec/support/integer_helpers.rb +0 -13
  317. data/spec/support/versioned_helpers.rb +0 -55
@@ -1,19 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/exceptions/base'
4
-
5
3
  module Grape
6
4
  module Exceptions
7
- class Validation < Grape::Exceptions::Base
8
- attr_accessor :params, :message_key
5
+ class Validation < Base
6
+ EMPTY_BACKTRACE = [].freeze
7
+
8
+ attr_reader :params, :message_key
9
+
10
+ def initialize(params:, message: nil, status: nil, headers: nil)
11
+ @params = Array(params)
12
+ translated =
13
+ if message
14
+ @message_key = case message
15
+ when Symbol then message
16
+ when Hash then message[:key]
17
+ end
18
+ translate_message(message)
19
+ end
9
20
 
10
- def initialize(params:, message: nil, **args)
11
- @params = params
12
- if message
13
- @message_key = message if message.is_a?(Symbol)
14
- args[:message] = translate_message(message)
15
- end
16
- super(**args)
21
+ super(status:, message: translated, headers:)
22
+ # Pre-seed the backtrace so Ruby's raise skips capture. Validation errors are
23
+ # a hot path (raised per bad attribute) and end up as 400 Bad Request responses;
24
+ # backtraces here point into Grape internals and have no diagnostic value.
25
+ set_backtrace(EMPTY_BACKTRACE)
17
26
  end
18
27
 
19
28
  # Remove all the unnecessary stuff from Grape::Exceptions::Base like status
@@ -21,6 +30,14 @@ module Grape
21
30
  def as_json(*_args)
22
31
  to_s
23
32
  end
33
+
34
+ # Returns +self+ so callers (e.g. +ValidationErrors#initialize+) can treat
35
+ # a single +Validation+ and a +ValidationArrayErrors+ wrapper uniformly via
36
+ # +flat_map(&:errors)+ — Array returns flatten in, non-Array returns
37
+ # (i.e. this +self+) append as one element.
38
+ def errors
39
+ self
40
+ end
24
41
  end
25
42
  end
26
43
  end
@@ -3,10 +3,15 @@
3
3
  module Grape
4
4
  module Exceptions
5
5
  class ValidationArrayErrors < Base
6
+ EMPTY_BACKTRACE = [].freeze
7
+
6
8
  attr_reader :errors
7
9
 
8
10
  def initialize(errors)
11
+ super()
9
12
  @errors = errors
13
+ # Skip backtrace capture — see Grape::Exceptions::Validation for rationale.
14
+ set_backtrace(EMPTY_BACKTRACE)
10
15
  end
11
16
  end
12
17
  end
@@ -1,28 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/exceptions/base'
4
-
5
3
  module Grape
6
4
  module Exceptions
7
- class ValidationErrors < Grape::Exceptions::Base
8
- ERRORS_FORMAT_KEY = 'grape.errors.format'
9
- DEFAULT_ERRORS_FORMAT = '%{attributes} %{message}'
10
-
11
- include Enumerable
12
-
5
+ class ValidationErrors < Base
13
6
  attr_reader :errors
14
7
 
15
- def initialize(errors: [], headers: {}, **_options)
16
- @errors = errors.group_by(&:params)
17
- super message: full_messages.join(', '), status: 400, headers: headers
18
- end
19
-
20
- def each
21
- errors.each_pair do |attribute, errors|
22
- errors.each do |error|
23
- yield attribute, error
24
- end
25
- end
8
+ def initialize(exceptions: [], headers: {})
9
+ @errors = exceptions.flat_map(&:errors).group_by(&:params)
10
+ super(message: full_messages.join(', '), status: 400, headers:)
26
11
  end
27
12
 
28
13
  def as_json(**_opts)
@@ -39,17 +24,28 @@ module Grape
39
24
  end
40
25
 
41
26
  def full_messages
42
- messages = map do |attributes, error|
43
- I18n.t(
44
- ERRORS_FORMAT_KEY,
45
- default: DEFAULT_ERRORS_FORMAT,
46
- attributes: translate_attributes(attributes),
47
- message: error.message
48
- )
27
+ messages = errors.flat_map do |attributes, errs|
28
+ errs.map do |error|
29
+ translate(
30
+ :format,
31
+ scope: 'grape.errors',
32
+ default: '%<attributes>s %<message>s',
33
+ attributes: translate_attributes(attributes),
34
+ message: error.message
35
+ )
36
+ end
49
37
  end
50
38
  messages.uniq!
51
39
  messages
52
40
  end
41
+
42
+ private
43
+
44
+ def translate_attributes(keys)
45
+ keys.map do |key|
46
+ translate(key, scope: 'grape.errors.attributes', default: key.to_s)
47
+ end.join(', ')
48
+ end
53
49
  end
54
50
  end
55
51
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Formatter
5
+ class Base
6
+ def self.call(_object, _env)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def self.inherited(klass)
11
+ super
12
+ Formatter.register(klass)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -2,13 +2,12 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module Json
6
- class << self
7
- def call(object, _env)
8
- return object.to_json if object.respond_to?(:to_json)
5
+ class Json < Base
6
+ def self.call(object, _env)
7
+ return object.to_s if object.is_a?(Grape::PrecompiledJson)
8
+ return object.to_json if object.respond_to?(:to_json)
9
9
 
10
- ::Grape::Json.dump(object)
11
- end
10
+ ::Grape::Json.dump(object)
12
11
  end
13
12
  end
14
13
  end
@@ -2,9 +2,10 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module SerializableHash
5
+ class SerializableHash < Base
6
6
  class << self
7
7
  def call(object, _env)
8
+ return object.to_s if object.is_a?(Grape::PrecompiledJson)
8
9
  return object if object.is_a?(String)
9
10
  return ::Grape::Json.dump(serialize(object)) if serializable?(object)
10
11
  return object.to_json if object.respond_to?(:to_json)
@@ -19,15 +20,11 @@ module Grape
19
20
  end
20
21
 
21
22
  def serialize(object)
22
- if object.respond_to? :serializable_hash
23
- object.serializable_hash
24
- elsif array_serializable?(object)
25
- object.map(&:serializable_hash)
26
- elsif object.is_a?(Hash)
27
- object.transform_values { |v| serialize(v) }
28
- else
29
- object
30
- end
23
+ return object.serializable_hash if object.respond_to?(:serializable_hash)
24
+ return object.map(&:serializable_hash) if array_serializable?(object)
25
+ return object.transform_values { |v| serialize(v) } if object.is_a?(Hash)
26
+
27
+ object
31
28
  end
32
29
 
33
30
  def array_serializable?(object)
@@ -2,11 +2,9 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module Txt
6
- class << self
7
- def call(object, _env)
8
- object.respond_to?(:to_txt) ? object.to_txt : object.to_s
9
- end
5
+ class Txt < Base
6
+ def self.call(object, _env)
7
+ object.respond_to?(:to_txt) ? object.to_txt : object.to_s
10
8
  end
11
9
  end
12
10
  end
@@ -2,13 +2,11 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module Xml
6
- class << self
7
- def call(object, _env)
8
- return object.to_xml if object.respond_to?(:to_xml)
5
+ class Xml < Base
6
+ def self.call(object, _env)
7
+ return object.to_xml if object.respond_to?(:to_xml)
9
8
 
10
- raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
11
- end
9
+ raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
12
10
  end
13
11
  end
14
12
  end
@@ -2,34 +2,16 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- extend Util::Registrable
5
+ extend Grape::Util::Registry
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
+ module_function
17
8
 
18
- def formatters(**options)
19
- builtin_formatters.merge(default_elements).merge!(options[:formatters] || {})
20
- end
9
+ DEFAULT_LAMBDA_FORMATTER = ->(obj, _env) { obj }
21
10
 
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
11
+ def formatter_for(api_format, formatters)
12
+ return formatters[api_format] if formatters&.key?(api_format)
13
+
14
+ registry[api_format] || DEFAULT_LAMBDA_FORMATTER
33
15
  end
34
16
  end
35
17
  end
data/lib/grape/json.rb ADDED
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ if defined?(::MultiJSON)
5
+ # Since multi_json 1.21.0, MultiJSON.dump is deprecated in favor of
6
+ # MultiJSON.generate (removed in 2.0). Keep Grape's dump surface but route
7
+ # it to the non-deprecated name — identical output, no deprecation warning.
8
+ # https://github.com/sferik/multi_json/blob/v1.21.1/CHANGELOG.md#deprecated
9
+ module Json
10
+ ParseError = ::MultiJSON::ParseError
11
+
12
+ class << self
13
+ def dump(object)
14
+ ::MultiJSON.generate(object)
15
+ end
16
+
17
+ # parse is not deprecated; it's re-exposed (not renamed) because this
18
+ # facade is its own module and no longer inherits MultiJSON's methods.
19
+ def parse(source)
20
+ ::MultiJSON.parse(source)
21
+ end
22
+ end
23
+ end
24
+ elsif defined?(::MultiJson)
25
+ # Legacy multi_json (< 1.21) predates generate/parse and only exposes
26
+ # dump/load. Map Grape's surface onto them so the call sites stay
27
+ # engine-agnostic (these names are not deprecated on < 1.21).
28
+ module Json
29
+ # Mutually exclusive with the MultiJSON branch above; only one runs.
30
+ ParseError = ::MultiJson::ParseError # rubocop:disable Lint/ConstantReassignment
31
+
32
+ class << self
33
+ def dump(object)
34
+ ::MultiJson.dump(object)
35
+ end
36
+
37
+ def parse(source)
38
+ ::MultiJson.load(source)
39
+ end
40
+ end
41
+ end
42
+ else
43
+ Json = ::JSON
44
+ Json::ParseError = Json::ParserError
45
+ end
46
+ end
@@ -1,55 +1,57 @@
1
+ ---
1
2
  en:
2
3
  grape:
3
4
  errors:
4
- format: ! '%{attributes} %{message}'
5
+ format: '%{attributes} %{message}'
5
6
  messages:
6
- coerce: 'is invalid'
7
- presence: 'is missing'
8
- regexp: 'is invalid'
7
+ all_or_none: 'provide all or none of parameters'
8
+ at_least_one: 'are missing, at least one parameter must be provided'
9
9
  blank: 'is empty'
10
- values: 'does not have a valid value'
10
+ coerce: 'is invalid'
11
+ exactly_one: 'are missing, exactly one parameter must be provided'
11
12
  except_values: 'has a value not allowed'
12
- same_as: 'is not the same as %{parameter}'
13
- missing_vendor_option:
14
- problem: 'missing :vendor option'
15
- summary: 'when version using header, you must specify :vendor option'
16
- resolution: "eg: version 'v1', using: :header, vendor: 'twitter'"
17
- missing_mime_type:
18
- problem: 'missing mime type for %{new_format}'
19
- resolution:
20
- "you can choose existing mime type from Grape::ContentTypes::CONTENT_TYPES
21
- or add your own with content_type :%{new_format}, 'application/%{new_format}'
22
- "
23
- invalid_with_option_for_represent:
24
- problem: 'you must specify an entity class in the :with option'
25
- resolution: 'eg: represent User, :with => Entity::User'
26
- missing_option: 'you must specify :%{option} options'
27
- invalid_formatter: 'cannot convert %{klass} to %{to_format}'
28
- invalid_versioner_option:
29
- problem: 'unknown :using for versioner: %{strategy}'
30
- resolution: 'available strategy for :using is :path, :header, :accept_version_header, :param'
31
- unknown_validator: 'unknown validator: %{validator_type}'
32
- unknown_options: 'unknown options: %{options}'
33
- unknown_parameter: 'unknown parameter: %{param}'
34
13
  incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
35
- mutual_exclusion: 'are mutually exclusive'
36
- at_least_one: 'are missing, at least one parameter must be provided'
37
- exactly_one: 'are missing, exactly one parameter must be provided'
38
- all_or_none: 'provide all or none of parameters'
39
- missing_group_type: 'group type is required'
40
- unsupported_group_type: 'group type must be Array, Hash, JSON or Array[JSON]'
41
- invalid_message_body:
42
- problem: "message body does not match declared format"
43
- resolution:
44
- "when specifying %{body_format} as content-type, you must pass valid
45
- %{body_format} in the request's 'body'
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})"
14
+ internal_server_error: 'Internal Server Error'
49
15
  invalid_accept_header:
50
16
  problem: 'invalid accept header'
51
17
  resolution: '%{message}'
18
+ invalid_formatter: 'cannot convert %{klass} to %{to_format}'
19
+ invalid_message_body:
20
+ problem: 'message body does not match declared format'
21
+ resolution: 'when specifying %{body_format} as content-type, you must pass valid %{body_format} in the request''s ''body'' '
22
+ invalid_response: 'Invalid response'
52
23
  invalid_version_header:
53
24
  problem: 'invalid version header'
54
25
  resolution: '%{message}'
55
- invalid_response: 'Invalid response'
26
+ invalid_versioner_option:
27
+ problem: 'unknown :using for versioner: %{strategy}'
28
+ resolution: 'available strategy for :using is :path, :header, :accept_version_header, :param'
29
+ invalid_with_option_for_represent:
30
+ problem: 'you must specify an entity class in the :with option'
31
+ resolution: 'eg: represent User, :with => Entity::User'
32
+ length: 'is expected to have length within %{min} and %{max}'
33
+ length_is: 'is expected to have length exactly equal to %{is}'
34
+ length_max: 'is expected to have length less than or equal to %{max}'
35
+ length_min: 'is expected to have length greater than or equal to %{min}'
36
+ missing_group_type: 'group type is required'
37
+ missing_mime_type:
38
+ problem: 'missing mime type for %{new_format}'
39
+ resolution: 'you can choose existing mime type from Grape::ContentTypes::CONTENT_TYPES or add your own with content_type :%{new_format}, ''application/%{new_format}'' '
40
+ missing_option: 'you must specify :%{option} options'
41
+ missing_vendor_option:
42
+ problem: 'missing :vendor option'
43
+ resolution: 'eg: version ''v1'', using: :header, vendor: ''twitter'''
44
+ summary: 'when version using header, you must specify :vendor option'
45
+ mutual_exclusion: 'are mutually exclusive'
46
+ oneof: 'does not match any of the allowed schemas'
47
+ presence: 'is missing'
48
+ regexp: 'is invalid'
49
+ same_as: 'is not the same as %{parameter}'
50
+ unknown_auth_strategy: 'unknown auth strategy: %{strategy}'
51
+ unknown_error_formatter: 'unknown error formatter: %{error_formatter_type}'
52
+ unknown_options: 'unknown options: %{options}'
53
+ unknown_parameter: 'unknown parameter: %{param}'
54
+ unknown_params_builder: 'unknown params_builder: %{params_builder_type}'
55
+ unknown_validator: 'unknown validator: %{validator_type}'
56
+ unsupported_group_type: 'group type must be Array, Hash, JSON or Array[JSON]'
57
+ values: 'does not have a valid value'
@@ -1,44 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/auth/basic'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  module Auth
8
- class Base
9
- include Helpers
10
-
11
- attr_accessor :options, :app, :env
6
+ class Base < Grape::Middleware::Base
7
+ def initialize(app, **options)
8
+ super
9
+ return unless options.key?(:type)
12
10
 
13
- def initialize(app, *options)
14
- @app = app
15
- @options = options.shift
11
+ @auth_strategy = Grape::Middleware::Auth::Strategies[options[:type]]
12
+ raise Grape::Exceptions::UnknownAuthStrategy.new(strategy: options[:type]) unless @auth_strategy
16
13
  end
17
14
 
18
- def call(env)
19
- dup._call(env)
20
- end
21
-
22
- def _call(env)
23
- self.env = env
24
-
25
- if options.key?(:type)
26
- auth_proc = options[:proc]
27
- auth_proc_context = context
28
-
29
- strategy_info = Grape::Middleware::Auth::Strategies[options[:type]]
30
-
31
- throw(:error, status: 401, message: 'API Authorization Failed.') if strategy_info.blank?
32
-
33
- strategy = strategy_info.create(@app, options) do |*args|
34
- auth_proc_context.instance_exec(*args, &auth_proc)
35
- end
36
-
37
- strategy.call(env)
38
-
39
- else
40
- app.call(env)
41
- end
15
+ def call!(env)
16
+ @env = env
17
+ @auth_strategy.create(app, options) do |*args|
18
+ context.instance_exec(*args, &options[:proc])
19
+ end.call(env)
42
20
  end
43
21
  end
44
22
  end
@@ -1,45 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/auth/basic'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  module Auth
8
6
  module DSL
9
- extend ActiveSupport::Concern
10
-
11
- module ClassMethods
12
- # Add an authentication type to the API. Currently
13
- # only `:http_basic`, `:http_digest` are supported.
14
- def auth(type = nil, options = {}, &block)
15
- if type
16
- namespace_inheritable(:auth, options.reverse_merge(type: type.to_sym, proc: block))
17
- use Grape::Middleware::Auth::Base, namespace_inheritable(:auth)
18
- else
19
- namespace_inheritable(:auth)
20
- end
21
- end
22
-
23
- # Add HTTP Basic authorization to the API.
24
- #
25
- # @param [Hash] options A hash of options.
26
- # @option options [String] :realm "API Authorization" The HTTP Basic realm.
27
- def http_basic(options = {}, &block)
28
- options[:realm] ||= 'API Authorization'
29
- auth :http_basic, options, &block
30
- end
7
+ def auth(type = nil, **options, &block)
8
+ return inheritable_setting.auth unless type
31
9
 
32
- def http_digest(options = {}, &block)
33
- options[:realm] ||= 'API Authorization'
34
-
35
- if options[:realm].respond_to?(:values_at)
36
- options[:realm][:opaque] ||= 'secret'
37
- else
38
- options[:opaque] ||= 'secret'
39
- end
10
+ inheritable_setting.auth = { type: type.to_sym, proc: block }.merge!(options)
11
+ use Grape::Middleware::Auth::Base, inheritable_setting.auth
12
+ end
40
13
 
41
- auth :http_digest, options, &block
42
- end
14
+ # Add HTTP Basic authorization to the API.
15
+ #
16
+ # @param options [Hash] a hash of options
17
+ # @option options [String] :realm "API Authorization" the HTTP Basic realm
18
+ def http_basic(**options, &)
19
+ options[:realm] ||= 'API Authorization'
20
+ auth(:http_basic, **options, &)
43
21
  end
44
22
  end
45
23
  end
@@ -12,8 +12,7 @@ module Grape
12
12
 
13
13
  def auth_strategies
14
14
  @auth_strategies ||= {
15
- http_basic: StrategyInfo.new(Rack::Auth::Basic, ->(settings) { [settings[:realm]] }),
16
- http_digest: StrategyInfo.new(Rack::Auth::Digest::MD5, ->(settings) { [settings[:realm], settings[:opaque]] })
15
+ http_basic: StrategyInfo.new(Rack::Auth::Basic, ->(settings) { [settings[:realm]] })
17
16
  }
18
17
  end
19
18