grape 1.8.0 → 3.3.5

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 (312) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +391 -1
  3. data/CONTRIBUTING.md +3 -11
  4. data/README.md +648 -552
  5. data/UPGRADING.md +803 -7
  6. data/grape.gemspec +11 -14
  7. data/lib/grape/api/instance.rb +68 -158
  8. data/lib/grape/api.rb +71 -107
  9. data/lib/grape/content_types.rb +23 -11
  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 +14 -58
  14. data/lib/grape/dsl/declared.rb +35 -0
  15. data/lib/grape/dsl/desc.rb +15 -63
  16. data/lib/grape/dsl/entity.rb +88 -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 +56 -295
  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 +50 -82
  23. data/lib/grape/dsl/request_response.rb +144 -146
  24. data/lib/grape/dsl/rescue_options.rb +24 -0
  25. data/lib/grape/dsl/routing.rb +261 -186
  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 +19 -0
  30. data/lib/grape/endpoint.rb +277 -250
  31. data/lib/grape/{util/env.rb → env.rb} +1 -8
  32. data/lib/grape/error_formatter/base.rb +56 -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 +5 -25
  38. data/lib/grape/exceptions/base.rb +22 -58
  39. data/lib/grape/exceptions/error_response.rb +45 -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_parameter.rb +1 -1
  53. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  54. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  55. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  56. data/lib/grape/exceptions/validation.rb +25 -9
  57. data/lib/grape/exceptions/validation_array_errors.rb +5 -0
  58. data/lib/grape/exceptions/validation_errors.rb +22 -26
  59. data/lib/grape/formatter/base.rb +16 -0
  60. data/lib/grape/formatter/json.rb +4 -6
  61. data/lib/grape/formatter/serializable_hash.rb +6 -10
  62. data/lib/grape/formatter/txt.rb +3 -5
  63. data/lib/grape/formatter/xml.rb +4 -6
  64. data/lib/grape/formatter.rb +7 -25
  65. data/lib/grape/json.rb +46 -0
  66. data/lib/grape/locale/en.yml +43 -42
  67. data/lib/grape/middleware/auth/base.rb +11 -33
  68. data/lib/grape/middleware/auth/dsl.rb +37 -31
  69. data/lib/grape/middleware/auth/strategies.rb +1 -2
  70. data/lib/grape/middleware/base.rb +56 -33
  71. data/lib/grape/middleware/deprecated_options_hash_access.rb +19 -0
  72. data/lib/grape/middleware/error.rb +183 -91
  73. data/lib/grape/middleware/formatter.rb +94 -105
  74. data/lib/grape/middleware/precomputed_content_types.rb +46 -0
  75. data/lib/grape/middleware/stack.rb +27 -38
  76. data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
  77. data/lib/grape/middleware/versioner/base.rb +72 -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 +13 -32
  81. data/lib/grape/middleware/versioner.rb +7 -14
  82. data/lib/grape/mountable.rb +22 -0
  83. data/lib/grape/namespace.rb +18 -12
  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 +38 -63
  94. data/lib/grape/railtie.rb +9 -0
  95. data/lib/grape/request.rb +168 -26
  96. data/lib/grape/router/base_route.rb +48 -0
  97. data/lib/grape/router/greedy_route.rb +26 -0
  98. data/lib/grape/router/mustermann_pattern.rb +44 -0
  99. data/lib/grape/router/pattern.rb +30 -34
  100. data/lib/grape/router/route.rb +28 -61
  101. data/lib/grape/router.rb +71 -86
  102. data/lib/grape/serve_stream/file_body.rb +1 -0
  103. data/lib/grape/serve_stream/sendfile_response.rb +3 -5
  104. data/lib/grape/serve_stream/stream_response.rb +1 -0
  105. data/lib/grape/testing.rb +33 -0
  106. data/lib/grape/util/api_description.rb +58 -0
  107. data/lib/grape/util/base_inheritable.rb +14 -14
  108. data/lib/grape/util/cache.rb +1 -3
  109. data/lib/grape/util/deep_freeze.rb +35 -0
  110. data/lib/grape/util/endpoint_configuration.rb +1 -1
  111. data/lib/grape/util/header.rb +13 -0
  112. data/lib/grape/util/inheritable_setting.rb +51 -27
  113. data/lib/grape/util/inheritable_values.rb +7 -5
  114. data/lib/grape/util/lazy/base.rb +16 -0
  115. data/lib/grape/util/lazy/block.rb +22 -0
  116. data/lib/grape/util/lazy/value.rb +31 -0
  117. data/lib/grape/util/lazy/value_array.rb +21 -0
  118. data/lib/grape/util/lazy/value_enumerable.rb +31 -0
  119. data/lib/grape/util/lazy/value_hash.rb +21 -0
  120. data/lib/grape/util/media_type.rb +67 -0
  121. data/lib/grape/util/path_normalizer.rb +34 -0
  122. data/lib/grape/util/registry.rb +27 -0
  123. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  124. data/lib/grape/util/stackable_values.rb +12 -14
  125. data/lib/grape/util/translation.rb +42 -0
  126. data/lib/grape/validations/attributes_iterator.rb +43 -27
  127. data/lib/grape/validations/coerce_options.rb +21 -0
  128. data/lib/grape/validations/contract_scope.rb +28 -0
  129. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  130. data/lib/grape/validations/oneof_collector.rb +39 -0
  131. data/lib/grape/validations/param_scope_tracker.rb +62 -0
  132. data/lib/grape/validations/params_documentation.rb +52 -0
  133. data/lib/grape/validations/params_scope.rb +180 -303
  134. data/lib/grape/validations/shared_options.rb +19 -0
  135. data/lib/grape/validations/single_attribute_iterator.rb +2 -2
  136. data/lib/grape/validations/types/array_coercer.rb +4 -7
  137. data/lib/grape/validations/types/custom_type_coercer.rb +41 -85
  138. data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
  139. data/lib/grape/validations/types/dry_type_coercer.rb +15 -28
  140. data/lib/grape/validations/types/json.rb +0 -2
  141. data/lib/grape/validations/types/primitive_coercer.rb +11 -35
  142. data/lib/grape/validations/types/set_coercer.rb +1 -4
  143. data/lib/grape/validations/types/variant_collection_coercer.rb +11 -0
  144. data/lib/grape/validations/types.rb +29 -54
  145. data/lib/grape/validations/validations_spec.rb +164 -0
  146. data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
  147. data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
  148. data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
  149. data/lib/grape/validations/validators/base.rb +120 -36
  150. data/lib/grape/validations/validators/coerce_validator.rb +18 -36
  151. data/lib/grape/validations/validators/contract_scope_validator.rb +45 -0
  152. data/lib/grape/validations/validators/default_validator.rb +13 -16
  153. data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
  154. data/lib/grape/validations/validators/except_values_validator.rb +15 -5
  155. data/lib/grape/validations/validators/length_validator.rb +48 -0
  156. data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
  157. data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
  158. data/lib/grape/validations/validators/oneof_validator.rb +49 -0
  159. data/lib/grape/validations/validators/presence_validator.rb +4 -2
  160. data/lib/grape/validations/validators/regexp_validator.rb +11 -3
  161. data/lib/grape/validations/validators/same_as_validator.rb +6 -15
  162. data/lib/grape/validations/validators/values_validator.rb +36 -65
  163. data/lib/grape/validations.rb +8 -21
  164. data/lib/grape/version.rb +1 -2
  165. data/lib/grape/xml.rb +17 -0
  166. data/lib/grape.rb +75 -288
  167. metadata +79 -291
  168. data/lib/grape/api/helpers.rb +0 -9
  169. data/lib/grape/dsl/api.rb +0 -19
  170. data/lib/grape/dsl/configuration.rb +0 -15
  171. data/lib/grape/eager_load.rb +0 -20
  172. data/lib/grape/exceptions/empty_message_body.rb +0 -11
  173. data/lib/grape/exceptions/missing_option.rb +0 -11
  174. data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
  175. data/lib/grape/exceptions/unknown_options.rb +0 -11
  176. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -27
  177. data/lib/grape/extensions/hash.rb +0 -22
  178. data/lib/grape/extensions/hashie/mash.rb +0 -26
  179. data/lib/grape/http/headers.rb +0 -61
  180. data/lib/grape/middleware/globals.rb +0 -16
  181. data/lib/grape/middleware/helpers.rb +0 -12
  182. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  183. data/lib/grape/router/attribute_translator.rb +0 -63
  184. data/lib/grape/types/invalid_value.rb +0 -8
  185. data/lib/grape/util/json.rb +0 -12
  186. data/lib/grape/util/lazy_block.rb +0 -27
  187. data/lib/grape/util/lazy_object.rb +0 -43
  188. data/lib/grape/util/lazy_value.rb +0 -91
  189. data/lib/grape/util/registrable.rb +0 -15
  190. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  191. data/lib/grape/util/xml.rb +0 -10
  192. data/lib/grape/validations/attributes_doc.rb +0 -58
  193. data/lib/grape/validations/types/build_coercer.rb +0 -94
  194. data/lib/grape/validations/validator_factory.rb +0 -15
  195. data/spec/grape/api/custom_validations_spec.rb +0 -213
  196. data/spec/grape/api/deeply_included_options_spec.rb +0 -56
  197. data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
  198. data/spec/grape/api/documentation_spec.rb +0 -59
  199. data/spec/grape/api/inherited_helpers_spec.rb +0 -114
  200. data/spec/grape/api/instance_spec.rb +0 -103
  201. data/spec/grape/api/invalid_format_spec.rb +0 -45
  202. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
  203. data/spec/grape/api/nested_helpers_spec.rb +0 -50
  204. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
  205. data/spec/grape/api/parameters_modification_spec.rb +0 -41
  206. data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
  207. data/spec/grape/api/recognize_path_spec.rb +0 -21
  208. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
  209. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
  210. data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
  211. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
  212. data/spec/grape/api/shared_helpers_spec.rb +0 -36
  213. data/spec/grape/api_remount_spec.rb +0 -509
  214. data/spec/grape/api_spec.rb +0 -4356
  215. data/spec/grape/dsl/callbacks_spec.rb +0 -45
  216. data/spec/grape/dsl/desc_spec.rb +0 -98
  217. data/spec/grape/dsl/headers_spec.rb +0 -62
  218. data/spec/grape/dsl/helpers_spec.rb +0 -100
  219. data/spec/grape/dsl/inside_route_spec.rb +0 -531
  220. data/spec/grape/dsl/logger_spec.rb +0 -24
  221. data/spec/grape/dsl/middleware_spec.rb +0 -60
  222. data/spec/grape/dsl/parameters_spec.rb +0 -180
  223. data/spec/grape/dsl/request_response_spec.rb +0 -225
  224. data/spec/grape/dsl/routing_spec.rb +0 -275
  225. data/spec/grape/dsl/settings_spec.rb +0 -261
  226. data/spec/grape/dsl/validations_spec.rb +0 -55
  227. data/spec/grape/endpoint/declared_spec.rb +0 -846
  228. data/spec/grape/endpoint_spec.rb +0 -1085
  229. data/spec/grape/entity_spec.rb +0 -336
  230. data/spec/grape/exceptions/base_spec.rb +0 -81
  231. data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
  232. data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
  233. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
  234. data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
  235. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
  236. data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
  237. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
  238. data/spec/grape/exceptions/missing_option_spec.rb +0 -15
  239. data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
  240. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
  241. data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
  242. data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
  243. data/spec/grape/exceptions/validation_spec.rb +0 -19
  244. data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
  245. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
  246. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
  247. data/spec/grape/grape_spec.rb +0 -9
  248. data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
  249. data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
  250. data/spec/grape/integration/rack_spec.rb +0 -51
  251. data/spec/grape/loading_spec.rb +0 -44
  252. data/spec/grape/middleware/auth/base_spec.rb +0 -31
  253. data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
  254. data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
  255. data/spec/grape/middleware/base_spec.rb +0 -221
  256. data/spec/grape/middleware/error_spec.rb +0 -85
  257. data/spec/grape/middleware/exception_spec.rb +0 -294
  258. data/spec/grape/middleware/formatter_spec.rb +0 -461
  259. data/spec/grape/middleware/globals_spec.rb +0 -30
  260. data/spec/grape/middleware/stack_spec.rb +0 -155
  261. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
  262. data/spec/grape/middleware/versioner/header_spec.rb +0 -345
  263. data/spec/grape/middleware/versioner/param_spec.rb +0 -171
  264. data/spec/grape/middleware/versioner/path_spec.rb +0 -62
  265. data/spec/grape/middleware/versioner_spec.rb +0 -21
  266. data/spec/grape/named_api_spec.rb +0 -19
  267. data/spec/grape/parser_spec.rb +0 -86
  268. data/spec/grape/path_spec.rb +0 -252
  269. data/spec/grape/presenters/presenter_spec.rb +0 -71
  270. data/spec/grape/request_spec.rb +0 -126
  271. data/spec/grape/util/inheritable_setting_spec.rb +0 -242
  272. data/spec/grape/util/inheritable_values_spec.rb +0 -79
  273. data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
  274. data/spec/grape/util/stackable_values_spec.rb +0 -128
  275. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
  276. data/spec/grape/validations/attributes_doc_spec.rb +0 -153
  277. data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
  278. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
  279. data/spec/grape/validations/params_scope_spec.rb +0 -1420
  280. data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
  281. data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
  282. data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
  283. data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
  284. data/spec/grape/validations/types_spec.rb +0 -111
  285. data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
  286. data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
  287. data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
  288. data/spec/grape/validations/validators/base_spec.rb +0 -38
  289. data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
  290. data/spec/grape/validations/validators/default_spec.rb +0 -463
  291. data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
  292. data/spec/grape/validations/validators/except_values_spec.rb +0 -192
  293. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
  294. data/spec/grape/validations/validators/presence_spec.rb +0 -315
  295. data/spec/grape/validations/validators/regexp_spec.rb +0 -161
  296. data/spec/grape/validations/validators/same_as_spec.rb +0 -57
  297. data/spec/grape/validations/validators/values_spec.rb +0 -733
  298. data/spec/grape/validations/validators/zh-CN.yml +0 -10
  299. data/spec/grape/validations_spec.rb +0 -2030
  300. data/spec/integration/eager_load/eager_load_spec.rb +0 -15
  301. data/spec/integration/multi_json/json_spec.rb +0 -7
  302. data/spec/integration/multi_xml/xml_spec.rb +0 -7
  303. data/spec/shared/deprecated_class_examples.rb +0 -16
  304. data/spec/shared/versioning_examples.rb +0 -215
  305. data/spec/spec_helper.rb +0 -52
  306. data/spec/support/basic_auth_encode_helpers.rb +0 -11
  307. data/spec/support/chunks.rb +0 -14
  308. data/spec/support/content_type_helpers.rb +0 -15
  309. data/spec/support/endpoint_faker.rb +0 -25
  310. data/spec/support/file_streamer.rb +0 -13
  311. data/spec/support/integer_helpers.rb +0 -13
  312. data/spec/support/versioned_helpers.rb +0 -55
@@ -11,15 +11,8 @@ module Grape
11
11
  API_VENDOR = 'api.vendor'
12
12
  API_FORMAT = 'api.format'
13
13
 
14
- RACK_INPUT = 'rack.input'
15
- RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash'
16
- RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'
17
- RACK_REQUEST_FORM_INPUT = 'rack.request.form_input'
18
-
19
- GRAPE_REQUEST = 'grape.request'
20
- GRAPE_REQUEST_HEADERS = 'grape.request.headers'
21
- GRAPE_REQUEST_PARAMS = 'grape.request.params'
22
14
  GRAPE_ROUTING_ARGS = 'grape.routing_args'
23
15
  GRAPE_ALLOWED_METHODS = 'grape.allowed_methods'
16
+ GRAPE_EXCEPTION = 'grape.exception'
24
17
  end
25
18
  end
@@ -2,36 +2,71 @@
2
2
 
3
3
  module Grape
4
4
  module ErrorFormatter
5
- module Base
6
- def present(message, env)
7
- present_options = {}
8
- presented_message = message
9
- if presented_message.is_a?(Hash)
10
- presented_message = presented_message.dup
11
- present_options[:with] = presented_message.delete(:with)
5
+ class Base
6
+ class << self
7
+ # Custom error formatters override +call+. The +error+ is a frozen
8
+ # {Grape::Exceptions::ErrorResponse} carrying +status+/+message+/
9
+ # +headers+/+backtrace+/+original_exception+. +env+ is the Rack env
10
+ # (needed by entity-presenter resolution). +include_backtrace+ and
11
+ # +include_original_exception+ are the request-time toggles set by
12
+ # +rescue_from+; the base implementation embeds the corresponding
13
+ # fields in the response body when they are true.
14
+ def call(error:, env: nil, include_backtrace: false, include_original_exception: false)
15
+ wrapped_message = wrap_message(present(error.message, env))
16
+ if wrapped_message.is_a?(Hash)
17
+ wrapped_message[:backtrace] = error.backtrace if include_backtrace && error.backtrace.present?
18
+ wrapped_message[:original_exception] = error.original_exception.inspect if include_original_exception && error.original_exception
19
+ end
20
+
21
+ format_structured_message(wrapped_message)
12
22
  end
13
23
 
14
- presenter = env[Grape::Env::API_ENDPOINT].entity_class_for_obj(presented_message, present_options)
24
+ def present(message, env)
25
+ # error! accepts a message hash with an optional :with key specifying the entity presenter.
26
+ # Extract it here so the presenter can be resolved and the key is not serialized in the response.
27
+ # See spec/integration/grape_entity/entity_spec.rb for examples.
28
+ with = nil
29
+ if message.is_a?(Hash) && message.key?(:with)
30
+ message = message.dup
31
+ with = message.delete(:with)
32
+ end
15
33
 
16
- unless presenter || env[Grape::Env::GRAPE_ROUTING_ARGS].nil?
17
- # env['api.endpoint'].route does not work when the error occurs within a middleware
18
- # the Endpoint does not have a valid env at this moment
19
- http_codes = env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info].http_codes || []
34
+ presenter = with || env[Grape::Env::API_ENDPOINT].entity_class_for_obj(message)
20
35
 
21
- found_code = http_codes.find do |http_code|
22
- (http_code[0].to_i == env[Grape::Env::API_ENDPOINT].status) && http_code[2].respond_to?(:represent)
23
- end if env[Grape::Env::API_ENDPOINT].request
36
+ unless presenter || env[Grape::Env::GRAPE_ROUTING_ARGS].nil?
37
+ # env['api.endpoint'].route does not work when the error occurs within a middleware
38
+ # the Endpoint does not have a valid env at this moment
39
+ http_codes = env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info].http_codes || []
40
+ found_code = http_codes.find do |http_code|
41
+ (http_code[0].to_i == env[Grape::Env::API_ENDPOINT].status) && http_code[2].respond_to?(:represent)
42
+ end if env[Grape::Env::API_ENDPOINT].request
24
43
 
25
- presenter = found_code[2] if found_code
26
- end
44
+ presenter = found_code[2] if found_code
45
+ end
46
+
47
+ return message unless presenter
27
48
 
28
- if presenter
29
- embeds = { env: env }
49
+ embeds = { env: }
30
50
  embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
31
- presented_message = presenter.represent(presented_message, embeds).serializable_hash
51
+ presenter.represent(message, embeds).serializable_hash
52
+ end
53
+
54
+ def wrap_message(message)
55
+ return message if message.is_a?(Hash)
56
+
57
+ { message: }
58
+ end
59
+
60
+ def format_structured_message(_structured_message)
61
+ raise NotImplementedError
32
62
  end
33
63
 
34
- presented_message
64
+ private
65
+
66
+ def inherited(klass)
67
+ super
68
+ ErrorFormatter.register(klass)
69
+ end
35
70
  end
36
71
  end
37
72
  end
@@ -2,27 +2,25 @@
2
2
 
3
3
  module Grape
4
4
  module ErrorFormatter
5
- module Json
6
- extend Base
7
-
5
+ class Json < Base
8
6
  class << self
9
- def call(message, backtrace, options = {}, env = nil, original_exception = nil)
10
- result = wrap_message(present(message, env))
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
15
- ::Grape::Json.dump(result)
7
+ def format_structured_message(structured_message)
8
+ ::Grape::Json.dump(structured_message)
16
9
  end
17
10
 
18
11
  private
19
12
 
20
13
  def wrap_message(message)
21
- if message.is_a?(Exceptions::ValidationErrors) || message.is_a?(Hash)
22
- message
23
- else
24
- { error: ensure_utf8(message) }
25
- end
14
+ # Use +is_a?+ rather than +case/when+ here. +case/when Hash+ matches via
15
+ # +Module#===+, a C-level real-class check that ignores delegation, so a
16
+ # +SimpleDelegator+ wrapping a Hash (e.g. the +OutputBuilder+ returned by
17
+ # +Grape::Entity#serializable_hash+ when an error is presented via an entity)
18
+ # would fall through and be wrapped in a spurious +{ error: ... }+ envelope.
19
+ # +is_a?+ is forwarded by the delegator to the wrapped Hash, so it matches.
20
+ return message if message.is_a?(Hash)
21
+ return message.as_json if message.is_a?(Exceptions::ValidationErrors)
22
+
23
+ { error: ensure_utf8(message) }
26
24
  end
27
25
 
28
26
  def ensure_utf8(message)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ErrorFormatter
5
+ class SerializableHash < Json; end
6
+ end
7
+ end
@@ -2,25 +2,19 @@
2
2
 
3
3
  module Grape
4
4
  module ErrorFormatter
5
- module Txt
6
- extend Base
7
-
8
- class << self
9
- def call(message, backtrace, options = {}, env = nil, original_exception = nil)
10
- message = present(message, env)
11
-
12
- result = message.is_a?(Hash) ? ::Grape::Json.dump(message) : message
13
- rescue_options = options[:rescue_options] || {}
14
- if rescue_options[:backtrace] && backtrace && !backtrace.empty?
15
- result += "\r\n backtrace:"
16
- result += backtrace.join("\r\n ")
17
- end
18
- if rescue_options[:original_exception] && original_exception
19
- result += "\r\n original exception:"
20
- result += "\r\n #{original_exception.inspect}"
21
- end
22
- result
5
+ class Txt < Base
6
+ def self.format_structured_message(structured_message)
7
+ message = structured_message[:message] || Grape::Json.dump(structured_message)
8
+ final_message = Array.wrap(message)
9
+ if structured_message.key?(:backtrace)
10
+ final_message << 'backtrace:'
11
+ final_message.concat(structured_message[:backtrace])
12
+ end
13
+ if structured_message.key?(:original_exception)
14
+ final_message << 'original exception:'
15
+ final_message << structured_message[:original_exception]
23
16
  end
17
+ final_message.join("\r\n ")
24
18
  end
25
19
  end
26
20
  end
@@ -2,19 +2,9 @@
2
2
 
3
3
  module Grape
4
4
  module ErrorFormatter
5
- module Xml
6
- extend Base
7
-
8
- class << self
9
- def call(message, backtrace, options = {}, env = nil, original_exception = nil)
10
- message = present(message, env)
11
-
12
- result = message.is_a?(Hash) ? message : { message: message }
13
- rescue_options = options[:rescue_options] || {}
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
16
- result.respond_to?(:to_xml) ? result.to_xml(root: :error) : result.to_s
17
- end
5
+ class Xml < Base
6
+ def self.format_structured_message(structured_message)
7
+ structured_message.respond_to?(:to_xml) ? structured_message.to_xml(root: :error) : structured_message.to_s
18
8
  end
19
9
  end
20
10
  end
@@ -2,34 +2,14 @@
2
2
 
3
3
  module Grape
4
4
  module ErrorFormatter
5
- extend Util::Registrable
5
+ extend Grape::Util::Registry
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
+ module_function
17
8
 
18
- def formatters(**options)
19
- builtin_formatters.merge(default_elements).merge!(options[:error_formatters] || {})
20
- end
9
+ def formatter_for(format, error_formatters = nil, default_error_formatter = nil)
10
+ return error_formatters[format] if error_formatters&.key?(format)
21
11
 
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
12
+ registry[format] || default_error_formatter || Grape::ErrorFormatter::Txt
33
13
  end
34
14
  end
35
15
  end
@@ -3,13 +3,13 @@
3
3
  module Grape
4
4
  module Exceptions
5
5
  class Base < StandardError
6
- BASE_MESSAGES_KEY = 'grape.errors.messages'
7
- BASE_ATTRIBUTES_KEY = 'grape.errors.attributes'
8
- FALLBACK_LOCALE = :en
6
+ include Grape::Util::Translation
7
+
8
+ MESSAGE_STEPS = %w[problem summary resolution].to_h { |s| [s.to_sym, s.capitalize] }.freeze
9
9
 
10
10
  attr_reader :status, :headers
11
11
 
12
- def initialize(status: nil, message: nil, headers: nil, **_options)
12
+ def initialize(status: nil, message: nil, headers: nil)
13
13
  super(message)
14
14
 
15
15
  @status = status
@@ -17,70 +17,34 @@ module Grape
17
17
  end
18
18
 
19
19
  def [](index)
20
- send index
20
+ __send__ index
21
21
  end
22
22
 
23
- protected
24
-
25
- # TODO: translate attribute first
26
- # if BASE_ATTRIBUTES_KEY.key respond to a string message, then short_message is returned
27
- # if BASE_ATTRIBUTES_KEY.key respond to a Hash, means it may have problem , summary and resolution
28
- def compose_message(key, **attributes)
29
- short_message = translate_message(key, **attributes)
30
- if short_message.is_a? Hash
31
- @problem = problem(key, **attributes)
32
- @summary = summary(key, **attributes)
33
- @resolution = resolution(key, **attributes)
34
- [['Problem', @problem], ['Summary', @summary], ['Resolution', @resolution]].each_with_object(+'') do |detail_array, message|
35
- message << "\n#{detail_array[0]}:\n #{detail_array[1]}" unless detail_array[1].blank?
36
- message
37
- end
38
- else
39
- short_message
40
- end
41
- end
23
+ private
42
24
 
43
- def problem(key, **attributes)
44
- translate_message("#{key}.problem".to_sym, **attributes)
45
- end
25
+ def compose_message(key, **)
26
+ short_message = translate_message(key, **)
27
+ return short_message unless short_message.is_a?(Hash)
46
28
 
47
- def summary(key, **attributes)
48
- translate_message("#{key}.summary".to_sym, **attributes)
49
- end
29
+ MESSAGE_STEPS.filter_map do |step, label|
30
+ next unless short_message.key?(step)
50
31
 
51
- def resolution(key, **attributes)
52
- translate_message("#{key}.resolution".to_sym, **attributes)
32
+ detail = translate_message(:"#{key}.#{step}", **)
33
+ "\n#{label}:\n #{detail}" if detail.present?
34
+ end.join
53
35
  end
54
36
 
55
- def translate_attributes(keys, **options)
56
- keys.map do |key|
57
- translate("#{BASE_ATTRIBUTES_KEY}.#{key}", default: key, **options)
58
- end.join(', ')
59
- end
60
-
61
- def translate_message(key, **options)
62
- case key
37
+ def translate_message(translation_key, **)
38
+ case translation_key
63
39
  when Symbol
64
- translate("#{BASE_MESSAGES_KEY}.#{key}", default: '', **options)
40
+ translate(translation_key, **)
41
+ when Hash
42
+ translation_key => { key:, **opts }
43
+ translate(key, **opts)
65
44
  when Proc
66
- key.call
67
- else
68
- key
69
- end
70
- end
71
-
72
- def translate(key, **options)
73
- options = options.dup
74
- options[:default] &&= options[:default].to_s
75
- message = ::I18n.translate(key, **options)
76
- message.presence || fallback_message(key, **options)
77
- end
78
-
79
- def fallback_message(key, **options)
80
- if ::I18n.enforce_available_locales && ::I18n.available_locales.exclude?(FALLBACK_LOCALE)
81
- key
45
+ translation_key.call
82
46
  else
83
- ::I18n.translate(key, locale: FALLBACK_LOCALE, **options)
47
+ translation_key
84
48
  end
85
49
  end
86
50
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ # Value object representing the payload thrown via `throw :error, ...`
6
+ # and consumed by `Middleware::Error#error_response`. Replaces the
7
+ # implicit-schema Hash that previously circulated between throw sites
8
+ # and the error middleware.
9
+ ErrorResponse = Data.define(:status, :message, :headers, :backtrace, :original_exception) do
10
+ def initialize(status: nil, message: nil, headers: nil, backtrace: nil, original_exception: nil)
11
+ super
12
+ end
13
+
14
+ def to_s
15
+ "#<#{self.class.name} status=#{status.inspect} message=#{message.inspect} headers=#{headers.inspect}>"
16
+ end
17
+
18
+ def self.from_exception(exception)
19
+ new(
20
+ status: exception.status,
21
+ message: exception.message,
22
+ headers: exception.headers,
23
+ backtrace: exception.backtrace,
24
+ original_exception: exception
25
+ )
26
+ end
27
+
28
+ # Normalize heterogeneous inputs into an ErrorResponse. Preserves the
29
+ # public contract that users can still `throw :error, hash` from their
30
+ # own middleware or `rescue_from` handlers.
31
+ def self.coerce(input)
32
+ case input
33
+ when ErrorResponse
34
+ input
35
+ when Grape::Exceptions::Base
36
+ from_exception(input)
37
+ when Hash
38
+ new(**input.slice(:status, :message, :headers, :backtrace, :original_exception))
39
+ else
40
+ new
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class IncompatibleOptionValues < Base
6
6
  def initialize(option1, value1, option2, value2)
7
- super(message: compose_message(:incompatible_option_values, option1: option1, value1: value1, option2: option2, value2: value2))
7
+ super(message: compose_message(:incompatible_option_values, option1:, value1:, option2:, value2:))
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ # Raised internally when a +rescue_from+ handler itself raises an
6
+ # unrecognised exception. The framework substitutes the original
7
+ # exception with this safe stand-in for rendering, while preserving
8
+ # the original on +env[Grape::Env::GRAPE_EXCEPTION]+ for upstream
9
+ # observability (loggers, error trackers, etc.).
10
+ class InternalServerError < Base
11
+ def initialize
12
+ super(status: 500, message: compose_message(:internal_server_error))
13
+ end
14
+ end
15
+ end
16
+ end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class InvalidAcceptHeader < Base
6
6
  def initialize(message, headers)
7
- super(message: compose_message(:invalid_accept_header, message: message), status: 406, headers: headers)
7
+ super(message: compose_message(:invalid_accept_header, message:), status: 406, headers:)
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class InvalidFormatter < Base
6
6
  def initialize(klass, to_format)
7
- super(message: compose_message(:invalid_formatter, klass: klass, to_format: to_format))
7
+ super(message: compose_message(:invalid_formatter, klass:, to_format:))
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class InvalidMessageBody < Base
6
6
  def initialize(body_format)
7
- super(message: compose_message(:invalid_message_body, body_format: body_format), status: 400)
7
+ super(message: compose_message(:invalid_message_body, body_format:), status: 400)
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class InvalidVersionHeader < Base
6
6
  def initialize(message, headers)
7
- super(message: compose_message(:invalid_version_header, message: message), status: 406, headers: headers)
7
+ super(message: compose_message(:invalid_version_header, message:), status: 406, headers:)
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class InvalidVersionerOption < Base
6
6
  def initialize(strategy)
7
- super(message: compose_message(:invalid_versioner_option, strategy: strategy))
7
+ super(message: compose_message(:invalid_versioner_option, strategy:))
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class MethodNotAllowed < Base
6
6
  def initialize(headers)
7
- super(message: '405 Not Allowed', status: 405, headers: headers)
7
+ super(message: '405 Not Allowed', status: 405, headers:)
8
8
  end
9
9
  end
10
10
  end
@@ -9,5 +9,3 @@ module Grape
9
9
  end
10
10
  end
11
11
  end
12
-
13
- Grape::Exceptions::MissingGroupTypeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Grape::Exceptions::MissingGroupTypeError', 'Grape::Exceptions::MissingGroupType')
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class MissingMimeType < Base
6
6
  def initialize(new_format)
7
- super(message: compose_message(:missing_mime_type, new_format: new_format))
7
+ super(message: compose_message(:missing_mime_type, new_format:))
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class RequestError < Base
6
+ def initialize(status: 400)
7
+ super(message: $ERROR_INFO&.message, status:)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class UnknownAuthStrategy < Base
6
+ def initialize(strategy:)
7
+ super(message: compose_message(:unknown_auth_strategy, strategy:))
8
+ end
9
+ end
10
+ end
11
+ end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class UnknownParameter < Base
6
6
  def initialize(param)
7
- super(message: compose_message(:unknown_parameter, param: param))
7
+ super(message: compose_message(:unknown_parameter, param:))
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class UnknownParamsBuilder < Base
6
+ def initialize(params_builder_type)
7
+ super(message: compose_message(:unknown_params_builder, params_builder_type:))
8
+ end
9
+ end
10
+ end
11
+ end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class UnknownValidator < Base
6
6
  def initialize(validator_type)
7
- super(message: compose_message(:unknown_validator, validator_type: validator_type))
7
+ super(message: compose_message(:unknown_validator, validator_type:))
8
8
  end
9
9
  end
10
10
  end
@@ -9,5 +9,3 @@ module Grape
9
9
  end
10
10
  end
11
11
  end
12
-
13
- Grape::Exceptions::UnsupportedGroupTypeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Grape::Exceptions::UnsupportedGroupTypeError', 'Grape::Exceptions::UnsupportedGroupType')
@@ -1,19 +1,27 @@
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
9
 
10
- def initialize(params:, message: nil, **args)
11
- @params = params
10
+ def initialize(params:, message: nil, status: nil, headers: nil)
11
+ @params = Array(params)
12
12
  if message
13
- @message_key = message if message.is_a?(Symbol)
14
- args[:message] = translate_message(message)
13
+ @message_key = case message
14
+ when Symbol then message
15
+ when Hash then message[:key]
16
+ end
17
+ message = translate_message(message)
15
18
  end
16
- super(**args)
19
+
20
+ super(status:, message:, headers:)
21
+ # Pre-seed the backtrace so Ruby's raise skips capture. Validation errors are
22
+ # a hot path (raised per bad attribute) and end up as 400 Bad Request responses;
23
+ # backtraces here point into Grape internals and have no diagnostic value.
24
+ set_backtrace(EMPTY_BACKTRACE)
17
25
  end
18
26
 
19
27
  # Remove all the unnecessary stuff from Grape::Exceptions::Base like status
@@ -21,6 +29,14 @@ module Grape
21
29
  def as_json(*_args)
22
30
  to_s
23
31
  end
32
+
33
+ # Returns +self+ so callers (e.g. +ValidationErrors#initialize+) can treat
34
+ # a single +Validation+ and a +ValidationArrayErrors+ wrapper uniformly via
35
+ # +flat_map(&:errors)+ — Array returns flatten in, non-Array returns
36
+ # (i.e. this +self+) append as one element.
37
+ def errors
38
+ self
39
+ end
24
40
  end
25
41
  end
26
42
  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