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
data/UPGRADING.md CHANGED
@@ -1,6 +1,1555 @@
1
1
  Upgrading Grape
2
2
  ===============
3
3
 
4
+ ### Upgrading to >= 4.0.0
5
+
6
+ #### A positional options Hash is no longer accepted by `auth`, `http_basic` or `desc`
7
+
8
+ Deprecated in 3.3 ([#2723](https://github.com/ruby-grape/grape/pull/2723), [#2728](https://github.com/ruby-grape/grape/pull/2728)). These take keyword arguments:
9
+
10
+ ```ruby
11
+ # Before
12
+ auth :custom, { realm: 'r', opaque: 'o' }
13
+ http_basic({ realm: 'API' })
14
+ desc 'Get users', { detail: 'Returns every user' }
15
+
16
+ # After
17
+ auth :custom, realm: 'r', opaque: 'o'
18
+ http_basic realm: 'API'
19
+ desc 'Get users', detail: 'Returns every user'
20
+ ```
21
+
22
+ A leftover positional Hash now raises `ArgumentError` rather than warning. A call that already used bare keyword syntax, or a block, is unaffected.
23
+
24
+ #### `http_digest` is removed
25
+
26
+ `Grape::Middleware::Auth::DSL#http_digest` is gone. Calling it now raises `NoMethodError` while the API class is being defined.
27
+
28
+ Nothing it could reach has existed since **2.0.0**, which removed `Rack::Auth::Digest` along with Grape's `:http_digest` strategy ([#2361](https://github.com/ruby-grape/grape/pull/2361)) after Rack 3 dropped digest authentication. The method survived that removal and kept recording its settings happily, so an API declaring `http_digest` still booted — and then raised `Grape::Exceptions::UnknownAuthStrategy` on the *first request*, from inside the middleware build, as an uncaught exception rather than a response. Failing while the class is defined is the point of removing it.
29
+
30
+ **If you registered your own `:http_digest` strategy**, it still works; call `auth` directly:
31
+
32
+ ```ruby
33
+ Grape::Middleware::Auth::Strategies.add(:http_digest, MyDigestStrategy, ->(settings) { [settings[:realm]] })
34
+
35
+ class API < Grape::API
36
+ auth :http_digest, realm: 'API Authorization', opaque: 'secret' do |username|
37
+ # ...
38
+ end
39
+ end
40
+ ```
41
+
42
+ The removed method supplied two defaults that `auth` does not, so pass them explicitly if you were relying on them: `realm` defaulted to `'API Authorization'`, and `opaque` to `'secret'` (nested inside `realm` when `realm` was itself a Hash).
43
+
44
+ #### A `rescue_from` handler can no longer return or throw a Hash
45
+
46
+ Deprecated in 3.3. A handler that returned `{ message:, status:, headers: }` was read as an error response. Say what you mean instead:
47
+
48
+ ```ruby
49
+ # Before
50
+ rescue_from :all do |e|
51
+ { message: e.message, status: 500, headers: {} }
52
+ end
53
+
54
+ # After
55
+ rescue_from :all do |e|
56
+ error!(e.message, 500)
57
+ end
58
+ ```
59
+
60
+ A handler that still returns such a Hash no longer has it interpreted: the response is an invalid one, which Grape answers with its framework default rather than with the status the Hash carried.
61
+
62
+ #### Middleware `Options` no longer answer `[]`, and the `DEFAULT_OPTIONS` constants are gone
63
+
64
+ Deprecated in 3.3, when middleware options moved to per-class `Options` `Data` value objects. `Grape::Middleware::Error::DEFAULT_OPTIONS`, `Grape::Middleware::Formatter::DEFAULT_OPTIONS` and `Grape::Middleware::Versioner::Base::DEFAULT_OPTIONS` are removed, as is Hash-style access on the `Options` objects themselves:
65
+
66
+ ```ruby
67
+ # Before
68
+ middleware.config[:default_format]
69
+ Grape::Middleware::Formatter::DEFAULT_OPTIONS[:default_format]
70
+
71
+ # After
72
+ middleware.config.default_format
73
+ Grape::Middleware::Formatter::Options.new.default_format
74
+ ```
75
+
76
+ `Grape::Middleware::Base` still supports a plain `DEFAULT_OPTIONS` Hash on middleware that declares no `Options` class, so third-party middleware written against that path is unaffected.
77
+
78
+ #### `Grape::Router.normalize_path` is removed
79
+
80
+ Deprecated in 3.3. Use `Grape::Util::PathNormalizer.call`, which is where the implementation has lived since.
81
+
82
+ #### Custom validators read `@options`, not `@option`
83
+
84
+ `Grape::Validations::Validators::Base` kept `@option` as an alias of `@options` from 3.2 with a note to drop it at the next major. A custom validator reading `@option` now sees `nil`:
85
+
86
+ ```ruby
87
+ # Before
88
+ def validate_param!(attr_name, params)
89
+ return if params[attr_name].length <= @option
90
+ # ...
91
+ end
92
+
93
+ # After
94
+ def validate_param!(attr_name, params)
95
+ return if params[attr_name].length <= @options
96
+ # ...
97
+ end
98
+ ```
99
+
100
+ `@options`, `option_value` and the rest of the documented custom-validator surface are unchanged.
101
+ #### `Grape::ErrorFormatter.formatter_for` answers `nil` for an unregistered format
102
+
103
+ It used to take the API's `default_error_formatter` as a third argument and end in `default_error_formatter || Grape::ErrorFormatter::Txt` — a global registry implementing the caller's fallback policy. It is now a lookup, the same shape as `Grape::Parser.parser_for`:
104
+
105
+ ```ruby
106
+ Grape::ErrorFormatter.formatter_for(:json) # => Grape::ErrorFormatter::Json
107
+ Grape::ErrorFormatter.formatter_for(:unknown) # => nil, was Grape::ErrorFormatter::Txt
108
+ ```
109
+
110
+ Passing a third argument now raises `ArgumentError`. The fallback moved to `Grape::Middleware::Error`, which applies the API's `default_error_formatter` and resolves that to `Grape::ErrorFormatter::Txt` when the API set none.
111
+
112
+ Error responses are unchanged, including the precedence: a formatter registered for the requested format still wins over the API's `default_error_formatter`, which applies to formats that have none of their own.
113
+
114
+ Two things follow at definition time, where the lookup's fallback used to hide them.
115
+
116
+ `default_error_formatter` names an error formatter, so a name nothing is registered under is now an error rather than a silent `Txt`:
117
+
118
+ ```ruby
119
+ default_error_formatter :jsonn
120
+ # => Grape::Exceptions::UnknownErrorFormatter: unknown error formatter: jsonn
121
+ ```
122
+
123
+ `format` names a *format*, and a format with no error formatter of its own is ordinary, so it still succeeds — but the stored setting is now `nil` rather than `Grape::ErrorFormatter::Txt`:
124
+
125
+ ```ruby
126
+ content_type :xls, 'application/vnd.ms-excel'
127
+ format :xls
128
+ MyAPI.default_error_formatter # => nil, was Grape::ErrorFormatter::Txt
129
+ ```
130
+
131
+ The middleware supplies `Txt` at render time, so error responses are unchanged; the setting is only visible if you read it back.
132
+
133
+ #### `error_formatter` now requires a formatter
134
+
135
+ `error_formatter` takes the formatter positionally or as `with:`. Called with neither, it used to register `nil` for that format:
136
+
137
+ ```ruby
138
+ error_formatter :json # no formatter given
139
+ ```
140
+
141
+ `Grape::ErrorFormatter.formatter_for` hands a registered value back as-is, so the format resolved to no formatter at all — and every error response in that format then failed to render and came back as the failsafe `500 Internal Server Error` in `text/plain`, whatever status and message the API had asked for.
142
+
143
+ That call now raises `ArgumentError` when the API is defined, rather than on the first error the API tries to render. Pass the formatter:
144
+
145
+ ```ruby
146
+ error_formatter :json, with: MyErrorFormatter
147
+ ```
148
+
149
+ An API that already passes one is unaffected, and a format with no `error_formatter` of its own keeps falling back to `default_error_formatter` and then to `Grape::ErrorFormatter::Txt`.
150
+
151
+ #### `grape/testing` is no longer loaded unless you require it
152
+
153
+ `Grape::Testing` has been documented as opt-in since 3.3 — "intended for test environments only and is not loaded by default" — but it was never excluded from Grape's eager load, so `require 'grape'` installed it anyway. It extends `Grape::Endpoint` with `before_each` / `reset_before_each` and prepends a wrapper around `Grape::Endpoint#run` that every request goes through.
154
+
155
+ It is now excluded, so the documented behaviour is the actual one. If your suite calls `Grape::Endpoint.before_each` without requiring the module first, it will now raise `NoMethodError`. Add the require the 3.3 note already asked for to your test helper:
156
+
157
+ ```ruby
158
+ require 'grape/testing'
159
+ ```
160
+
161
+ Nothing else changes: the module's API is the same, and referencing the `Grape::Testing` constant still autoloads it.
162
+
163
+ #### The versioner's `pattern` option has been removed
164
+
165
+ `Grape::Middleware::Versioner::Base` accepted a `pattern` option, which `Versioner::Path` matched the candidate version segment against before recording it. It dates from the original versioner (2010), where it was the only way to decide whether the first path segment was a version — there was no declared version list yet.
166
+
167
+ `versions:` took that job over: a segment is checked against the versions the API declared, and an unrecognized one answers 404. `pattern` was never connected to the `version` DSL that arrived later — `DSL::Routing#version` does not accept a `pattern:` keyword and `Endpoint#build_stack` never passed one — so it could only be reached by inserting the middleware yourself:
168
+
169
+ ```ruby
170
+ use Grape::Middleware::Versioner::Path, versions: %w[v1], pattern: /v.+/
171
+ ```
172
+
173
+ which also meant forgoing what `version` does for routing. That now raises `ArgumentError: unknown keyword: :pattern`, at the point the middleware is built rather than on a request.
174
+
175
+ `Grape::Middleware::Versioner::Base::DEFAULT_OPTIONS` no longer carries a `:pattern` key, and `Versioner::Base#pattern` is gone. An API that declares its version through `version 'v1', using: :path` is unaffected — it never had a pattern.
176
+
177
+ The one behaviour that disappears with it: a hand-wired versioner could use `pattern` to say "this segment is not a version at all", leaving `api.version` unset and letting the request through, as distinct from "unknown version", which answers 404. Nothing in the `version` DSL exposed that distinction, and `versions:` cannot express it. If you relied on it, match the segment yourself in a small middleware ahead of the versioner rather than through this option.
178
+
179
+ #### An error response carries a backtrace only when one was asked for
180
+
181
+ Every error response used to materialize the raised exception's backtrace, even though the built-in error formatters render it only under `rescue_from ..., backtrace: true`. It is now assembled only when that option is set, since `Exception#backtrace` builds an Array of location Strings on every call and a Grape error is raised deep inside the request stack.
182
+
183
+ `Grape::Exceptions::ErrorResponse#backtrace` is therefore an empty Array on the payload handed to an error formatter unless the API asked for backtraces. The built-in formatters are unaffected — they already guarded on the `include_backtrace:` argument they are passed.
184
+
185
+ A custom error formatter that reads `error.backtrace` while ignoring that argument will now see an empty Array. Read it from the exception instead, which still travels on the payload:
186
+
187
+ ```ruby
188
+ # before
189
+ error.backtrace
190
+
191
+ # after — equivalent regardless of the rescue options
192
+ error.original_exception&.backtrace || []
193
+ ```
194
+
195
+ `Grape::Exceptions::ErrorResponse.from_exception` no longer stores a backtrace either, for the same reason; `original_exception` is set, so nothing is lost.
196
+
197
+ #### An endpoint that declares helpers is served from a subclass of `Grape::Endpoint`
198
+
199
+ Helpers were included into the singleton class of the per-request copy of an endpoint, which meant every request built a fresh singleton class and re-ran `Module#include`. They are now included once, when the API is compiled, into a subclass owned by that endpoint, and each request copies an instance of it.
200
+
201
+ The endpoint exposed as `env['api.endpoint']` (and handed to `before`/`after` filters, `rescue_from` blocks and middleware) is therefore an instance of that subclass rather than of `Grape::Endpoint` itself, for any endpoint whose scope declares `helpers`. Endpoints with no helpers are unaffected.
202
+
203
+ Inheritance-based checks are unchanged:
204
+
205
+ ```ruby
206
+ endpoint.is_a?(Grape::Endpoint) # => true, as before
207
+ endpoint.kind_of?(Grape::Endpoint) # => true, as before
208
+ Grape::Endpoint === endpoint # => true, as before
209
+ ```
210
+
211
+ Exact-class checks are not, and have no equivalent — use `is_a?`:
212
+
213
+ ```ruby
214
+ # before => true, now => false
215
+ endpoint.instance_of?(Grape::Endpoint)
216
+ endpoint.class == Grape::Endpoint
217
+
218
+ # before => "Grape::Endpoint", now => "Grape::Endpoint(helpers)"
219
+ endpoint.class.name
220
+ endpoint.class.to_s
221
+ ```
222
+
223
+ The subclass carries a temporary name (`Module#set_temporary_name`), so it never answers `nil` to `#name` or renders as a bare `#<Class:0x...>` address. Code that logs or groups by `endpoint.class.name` — an error tracker keying on it, say — will see `Grape::Endpoint(helpers)` for these endpoints, and should match on `is_a?` if it needs to treat both kinds alike.
224
+ #### The `desc` `default` key is renamed to `default_response`
225
+
226
+ grape-swagger reads a route's default response as `route.default_response`, a name Grape never defined — the `desc` DSL wrote the key as `default`, and the reader for it resolved to `Hash#default` on the route's options bag, so it answered `nil` for every route. A default response declared the way the README documented it therefore never reached the generated OpenAPI document.
227
+
228
+ The key, the `desc` block method and the route reader are all named `default_response` now. `default` still works everywhere it did, and warns:
229
+
230
+ ```ruby
231
+ # deprecated
232
+ desc 'Delete a widget' do
233
+ default code: 'default', message: 'unexpected error'
234
+ end
235
+ desc 'Delete a widget', default: { code: 'default' }
236
+ route.default
237
+
238
+ # preferred
239
+ desc 'Delete a widget' do
240
+ default_response code: 'default', message: 'unexpected error'
241
+ end
242
+ desc 'Delete a widget', default_response: { code: 'default' }
243
+ route.default_response
244
+ ```
245
+
246
+ A `desc 'x', default: ...` keyword is stored under `:default_response`, so a route carries one key under one name. Code reading `route.options[:default]` directly — rather than through the reader — has to read `route.options[:default_response]` instead.
247
+ #### A group's type check is now the same for `requires` and `optional`
248
+
249
+ The rule that a group declaration needs a type (`Array`, `Hash`, `JSON` or `Array[JSON]`) was implemented twice — once in `ParamsScope#new_scope` for `requires`, once inline in `optional` — and the two copies had drifted. `optional` read the type before merging in the attributes of an enclosing `with`, so a group type supplied by `with` was invisible to it:
250
+
251
+ ```ruby
252
+ params do
253
+ with(type: Hash) do
254
+ requires(:a) { requires :b, type: String } # accepted
255
+ optional(:a) { optional :b, type: String } # raised MissingGroupType
256
+ end
257
+ end
258
+ ```
259
+
260
+ Both are accepted now. A group with no type from either the declaration or an enclosing `with` still raises `Grape::Exceptions::MissingGroupType`, and an unsupported type still raises `Grape::Exceptions::UnsupportedGroupType`.
261
+
262
+ One case stops raising. `optional :a, using: SomeEntity do ... end` — a block *and* `using:` — raised `MissingGroupType` before; it now ignores the block and documents the entity's params, which is what `requires` has always done with that combination. If you have such a declaration, the block was never going to be applied; delete it or drop `using:`.
263
+ #### The `presence` option of `requires` and `optional` is deprecated
264
+
265
+ `presence` is how the params DSL tells the validation pipeline that a parameter is required. It travelled as a key written into the same options Hash that carries an API's own options, so an API could set it — and got opposite results depending on which method it was passed to, because `requires` overwrote it while nothing overwrote it for `optional`:
266
+
267
+ ```ruby
268
+ requires :a, presence: false # ignored: :a is still required
269
+ optional :a, presence: true # honoured: :a is actually required
270
+ ```
271
+
272
+ Both still resolve exactly as they did, and now emit a deprecation warning. Declare the parameter with `requires` to make it required and `optional` to make it optional; the option will be ignored outright in a future release.
273
+
274
+ #### A `with` block's `message` now reaches the presence validator
275
+
276
+ The presence entry for a `requires` was built before the attributes of an enclosing `with` were merged in, so a group-level `message:` never reached it. It does now:
277
+
278
+ ```ruby
279
+ params do
280
+ with(message: 'custom missing') do
281
+ requires :a
282
+ end
283
+ end
284
+
285
+ # before: "a is missing"
286
+ # now: "a custom missing"
287
+ ```
288
+
289
+ An API that sets `message:` on a `with` block and relies on the generic wording for missing parameters will see its own message instead.
290
+
291
+ #### `refresh_already_mounted` is no longer a `mount` option
292
+
293
+ `mount` accepted an undocumented `refresh_already_mounted` option that replaced any endpoint already mounted for the same base API instead of adding a second one. It exists for Grape's own re-mounting machinery: when a class-level method runs after a `mount`, `Grape::API.refresh_mount_step` replays that mount, and replaying it naively would duplicate the endpoint.
294
+
295
+ That decision belongs to the replay, not to the call, so the endpoint replacement moved into the private `refresh_mounted_api` which is the only caller that ever wanted it. Nothing changes for the re-mounting itself.
296
+
297
+ Passing the option to `mount` still works and now warns; it will be ignored in a future release. There is no replacement, because an API has no reason to ask for it — mounting the same app twice on purpose is still two endpoints.
298
+
299
+ #### A `mount` target is recognised as a mapping only when it is a `Hash`
300
+
301
+ `mount` decided between "a Hash of app => path" and "a bare app mounted at /" with `mounts.respond_to?(:each_pair)`. A `Struct` and an `OpenStruct` answer that too, and neither can express an app => path mapping — their keys are member names — so a Rack app that happened to be one was read as a mapping and silently mounted its own field value as a path:
302
+
303
+ ```ruby
304
+ RackStruct = Struct.new(:name) do
305
+ def call(env) = [200, {}, ['ok']]
306
+ end
307
+
308
+ mount RackStruct.new('x')
309
+
310
+ # before: mounted at "/x", because :name => 'x' was read as app => path
311
+ # now: mounted at "/", as the Rack app it is
312
+ ```
313
+
314
+ The test is now `mounts.is_a?(Hash)`. Every Hash-like people pass — `HashWithIndifferentAccess`, `Hashie::Mash`, `ActiveSupport::OrderedOptions` — subclasses `Hash` and is unaffected.
315
+
316
+ #### `use`, `helpers`, `rescue_from` and other registrations no longer reach routes defined above them
317
+
318
+ A route captures the middleware, helpers, callbacks and rescue handlers registered above it. That was already true most of the time, but not always: `Grape::Util::InheritableSetting#point_in_time_copy` copied a scope's stackable store and its rescue-handler maps shallowly, so the nested Arrays and Hashes stayed shared with the scope. A registration added *after* an endpoint was defined therefore still reached that endpoint — but only when the key already held at least one registration when the endpoint was defined, since otherwise the scope allocated a fresh store only for itself.
319
+
320
+ The outcome depended on something the API never expressed:
321
+
322
+ ```ruby
323
+ class A < Grape::API
324
+ use Middleware1
325
+ get('/x') { } # endpoint defined here
326
+ use Middleware2 # applied to GET /x
327
+ end
328
+
329
+ class B < Grape::API
330
+ get('/x') { } # endpoint defined here
331
+ use Middleware2 # NOT applied to GET /x
332
+ end
333
+ ```
334
+
335
+ `A` and `B` state the same thing and behaved differently. Both now behave like `B`. The same held for `rescue_from` declared below a route.
336
+
337
+ **What can break.** An API that declares `use` (or `helpers`, a filter such as `before`, or `rescue_from`) below its routes and relies on it applying to them. That arrangement only ever worked when an earlier registration for the same key happened to seed the stack, so it was never dependable, but code written against it will now see the middleware or helper silently not run.
338
+
339
+ **The fix is to move the registration above the routes it should cover**, which is where Grape's documentation has always placed it:
340
+
341
+ ```ruby
342
+ class A < Grape::API
343
+ use Middleware1
344
+ use Middleware2
345
+ get('/x') { }
346
+ end
347
+ ```
348
+
349
+ Nothing changes for the ordinary arrangement — registrations declared before a route, or inherited from an enclosing namespace or a mounting API, still apply exactly as before, including values an enclosing scope gains after the nested scope was created.
350
+ #### `rescue_from :grape_exceptions` now outranks a catch-all class handler
351
+
352
+ `rescue_from :grape_exceptions` is an opt-in to keep Grape's own errors rendering with their own status — a validation failure answers `400` rather than whatever the application's catch-all returns.
353
+
354
+ It only ever worked against `rescue_from :all`. Written as a class instead, a catch-all is a *registered* handler, which `Grape::Middleware::Error` consults first, and Grape's exceptions are `StandardError`s — so the opt-in was silently inert:
355
+
356
+ ```ruby
357
+ rescue_from StandardError do
358
+ error!('server error', 500)
359
+ end
360
+ rescue_from :grape_exceptions
361
+ ```
362
+
363
+ | request | before | 4.0 |
364
+ | --- | --- | --- |
365
+ | fails parameter validation | **500** `server error` | `400` |
366
+ | raises an application error | `500` `server error` | unchanged |
367
+
368
+ **What can break.** An API that registers a catch-all as a class *and* opts into `:grape_exceptions` will now answer Grape's own status for Grape's own errors, where it previously answered the catch-all's. That is what the opt-in asks for, so the change makes the two spellings agree — but a client or test asserting the catch-all's status for a validation failure will see the new one.
369
+
370
+ Precedence is unchanged in every other case. A handler registered for a specific Grape exception class is more precise than the opt-in and still wins:
371
+
372
+ ```ruby
373
+ rescue_from Grape::Exceptions::ValidationErrors do
374
+ error!('unprocessable', 422) # still runs
375
+ end
376
+ rescue_from StandardError { ... }
377
+ rescue_from :grape_exceptions
378
+ ```
379
+
380
+ Application errors still reach the catch-all, `rescue_from :all` behaves as before, and `Grape::Exceptions::InvalidVersionHeader` is still never rescued, so version cascading keeps working. An API that does not use `rescue_from :grape_exceptions` is unaffected.
381
+
382
+ #### `redirect` renders its default message as plain text
383
+
384
+ The `redirect` API set the content type to `text/plain`, but delegated body rendering to the formatter. With an API defaulting to JSON, a `redirect` would render the body as JSON with a `text/plain` content-type header.
385
+
386
+ ```ruby
387
+ class API < Grape::API
388
+ format :json
389
+ get('/r') { redirect '/there' }
390
+ end
391
+ ```
392
+
393
+ ```
394
+ Content-Type: text/plain
395
+
396
+ "This resource has been moved temporarily to /there."
397
+ ```
398
+
399
+ Grape now renders the message it generates with the txt formatter, so the body is the plain sentence the content type claims:
400
+
401
+ ```
402
+ Content-Type: text/plain
403
+
404
+ This resource has been moved temporarily to /there.
405
+ ```
406
+
407
+ **What can break.** Code that parses a redirect body — `JSON.parse(response.body)` on a redirect succeeded before and now raises — or a test asserting on the encoded form. On a JSON API the `Location` header, the status and the `Content-Type` are unchanged, so a client that follows the redirect is unaffected.
408
+
409
+ On an API whose formatter cannot serialize a String, such as `format :xml`, `redirect` did not work at all: the formatter raised, and the response was a `500` carrying an error document and no `Location` header. Those APIs now get the `302` and the `Location` they always should have.
410
+
411
+ This applies only to the message Grape generates. A body you pass yourself is still rendered by the API's formatter, unchanged:
412
+
413
+ ```ruby
414
+ redirect '/there', body: { message: 'moved' } # still {"message":"moved"} on a JSON API
415
+ ```
416
+
417
+ #### Path params are tagged UTF-8 instead of ASCII-8BIT
418
+
419
+ Params captured from the request path — `route_param`, `:id`-style segments, splats — now come back tagged `UTF-8`. They used to carry the `ASCII-8BIT` encoding of Rack's `PATH_INFO`, because Mustermann decodes the path against that raw string and nothing re-tagged the result. Query and body params were already `UTF-8`, since Rack tags those itself.
420
+
421
+ **Why UTF-8.** Nothing obliges a client to send it — HTTP treats the request target as octets, and Rack's SPEC has CGI keys carry non-ASCII as `ASCII-8BIT`. UTF-8 is a convention rather than a guarantee. But it is the convention Rack itself already applies to everything *except* the path: `Rack::QueryParser#unescape` decodes the query string and form bodies with `URI.decode_www_form_component(string, Encoding::UTF_8)`, which tags the result without validating it.
422
+
423
+ One request carrying the same invalid octets in three places, before this change:
424
+
425
+ | source | encoding | `valid_encoding?` |
426
+ | --- | --- | --- |
427
+ | query — `?q=%C3%28` | `UTF-8` | `false` |
428
+ | form body — `form=%C3%28` | `UTF-8` | `false` |
429
+ | path — `/%C3%28` | **`ASCII-8BIT`** | `true` |
430
+
431
+ The bytes are equally malformed in all three; only the label differed. The path reads `true` merely because `ASCII-8BIT` considers every byte sequence valid. So this change is not Grape adopting an outside convention — it is Grape agreeing with the library handing it the request. The path param was the odd one out only because Mustermann decodes against `PATH_INFO` directly and never had Rack's `unescape` applied to it.
432
+
433
+ Grape does exactly what Rack does: re-tag, do not validate. The bytes are untouched, so octets that are *not* UTF-8 stay detectably invalid rather than being scrubbed into something the client never sent. (Rails takes the same approach for path captures — `ActionDispatch::Journey::Router` force-encodes each one to UTF-8 after unescaping.)
434
+
435
+ **What this fixes.** An API's own declarations used to disagree with themselves depending on where a value arrived from — a binary string never equals the UTF-8 literal it was written as:
436
+
437
+ ```ruby
438
+ params { requires :id, type: String, values: ['café'] }
439
+ ```
440
+
441
+ | request | before | 4.0 |
442
+ | --- | --- | --- |
443
+ | `GET /?id=café` (query) | `200` | unchanged |
444
+ | `GET /café` (path) | `400 "id does not have a valid value"` | `200` |
445
+
446
+ The same held for `same_as`, `except_values`, and any comparison an endpoint made against a non-ASCII literal. Serialization was affected too: a non-ASCII path param rendered into a JSON response emitted an encoding warning from the `json` gem, and is slated to raise there in json 3.0.
447
+
448
+ **What can break.** Only the encoding tag changes; the bytes are untouched, and an invalid byte sequence stays invalid rather than being scrubbed. Comparisons against pure-ASCII strings are unaffected. Code that relied on a path param being binary — concatenating one with genuinely binary data, for instance — can now raise `Encoding::CompatibilityError`, and should call `.b` on the param to opt back into binary:
449
+
450
+ ```ruby
451
+ params[:id].b + binary_blob
452
+ ```
453
+
454
+ An application that worked around the old behavior with its own `force_encoding(Encoding::UTF_8)` needs no change; that call is now a no-op.
455
+
456
+ **Restoring the old behavior wholesale.** If enough code depends on binary path params that patching each site is impractical, a `before` filter at the top of your root API re-tags them all back. `env['grape.routing_args']` holds exactly the params that came from the path, so query and body params are left alone, and `before` runs ahead of validation, so declarations see the binary strings too:
457
+
458
+ ```ruby
459
+ class API < Grape::API
460
+ before do
461
+ env['grape.routing_args']&.each_key do |key|
462
+ next if key == :route_info
463
+
464
+ value = params[key]
465
+ params[key] = value.b if value.is_a?(String)
466
+ params[key] = value.map(&:b) if value.is_a?(Array)
467
+ end
468
+ end
469
+
470
+ # ... mounts and routes
471
+ end
472
+ ```
473
+
474
+ Declared in the root API, this covers mounted APIs too. Treat it as a migration aid rather than a permanent setting: it restores the inconsistency this change fixes, so `values: ['café']` will keep rejecting `GET /café` while accepting `?id=café`.
475
+ #### A failed error rendering answers 500 instead of escaping the middleware stack
476
+
477
+ When Grape could not render an error response — an error formatter handed a payload it cannot serialize, most often — the exception escaped every middleware above Grape and reached the application server. Rendering runs inside `Grape::Middleware::Error#call!`'s own `rescue` clause, so that clause did not cover it.
478
+
479
+ Grape now answers `500` instead: first retrying the API's format with the framework's own `Internal Server Error` message, then falling back to a bare `text/plain` body if even that cannot be rendered.
480
+
481
+ This mirrors what `ActionDispatch::ShowExceptions#render_exception` does in Rails: try the application's own error rendering, and fall back to a bare `500 Internal Server Error` in `text/plain` when that rendering is itself broken.
482
+
483
+ **What can break.** Code that observed these exceptions by letting them propagate — a test asserting `expect { get '/' }.to raise_error`, most directly — no longer sees them raised. The exception is published on the rack env instead, under both Grape's own key and the conventional one that error trackers read:
484
+
485
+ ```ruby
486
+ env[Grape::Env::RACK_EXCEPTION] # 'rack.exception' — what trackers collect
487
+ env[Grape::Env::GRAPE_EXCEPTION] # 'grape.exception' — same object, Grape's key
488
+ ```
489
+
490
+ An error tracker mounted as Rack middleware above Grape therefore keeps reporting these with no change on your side: sentry-ruby, for one, collects `env['rack.exception'] || env['sinatra.error']` for exactly this case — an exception that was handled rather than raised. The failure is also written to `rack.errors`, so it lands in the server log even with no tracker installed.
491
+
492
+ **Opting out.** To keep the pre-4.0 behaviour and have the exception propagate out of the middleware stack:
493
+
494
+ ```ruby
495
+ Grape.configure do |config|
496
+ config.raise_rendering_errors = true
497
+ end
498
+ ```
499
+
500
+ With this on, the failsafe never runs: the exception is re-raised untouched, so neither env key is set and nothing is written to `rack.errors` — whatever caught it before catches it again.
501
+
502
+ Exceptions that no `rescue_from` matches still propagate exactly as before; only rendering failures changed.
503
+
504
+ #### `Array`/`Set` of an unsupported type is rejected when the API is defined
505
+
506
+ Declaring a collection whose element type Grape cannot coerce — `type: Array[Foo]` or `type: Set[Foo]` where `Foo` is neither a primitive, a structure, nor a valid custom type — now raises as soon as the `params` block is evaluated, i.e. while the API class is being loaded:
507
+
508
+ ```
509
+ ArgumentError: type Foo should support coercion via `[]`
510
+ ```
511
+
512
+ This is a **load-time** failure. An application that boots today can fail to boot on 4.0 without any request being made.
513
+
514
+ A valid custom type is one that implements a class-level `parse` taking exactly one argument (`Grape::Validations::Types.custom?`). A class that implements neither that nor `[]` was never coercible, so such a declaration was always a misconfiguration — but it used to surface much later, and much less clearly.
515
+
516
+ **What changed.** Nothing about which types are supported; only *when* the element coercer is built. [#2817](https://github.com/ruby-grape/grape/pull/2817) made `ArrayCoercer` build it eagerly in the constructor rather than memoizing it on first use, because coercers are shared across requests and must not create state at request time. Building it eagerly means the unsupported element type is discovered while the route is being defined.
517
+
518
+ Previously the coercer was built on the first request that actually supplied the parameter. An API that declared the parameter but was never sent one — a documentation-only declaration, for instance — never built it and never raised. When a request did supply it, the same `ArgumentError` was raised inside the coercer and swallowed by the coercion validator into a generic `400`:
519
+
520
+ ```json
521
+ { "error": "foo is invalid" }
522
+ ```
523
+
524
+ so the misconfiguration presented as a puzzling per-request validation failure rather than as a broken declaration.
525
+
526
+ Note that the non-collection form has always raised at definition time:
527
+
528
+ | declaration | before | 4.0 |
529
+ | --- | --- | --- |
530
+ | `type: Foo` | `ArgumentError` when defined | unchanged |
531
+ | `type: Array[Foo]` | accepted; `400 "is invalid"` per request | `ArgumentError` when defined |
532
+
533
+ The collection form's leniency was an accident of the lazy build, not a supported behavior. The two are now consistent.
534
+
535
+ **Fixing a declaration that now raises.** Give the type a one-argument `parse`, which is what makes it a custom type:
536
+
537
+ ```ruby
538
+ class Foo
539
+ def self.parse(value)
540
+ new(value)
541
+ end
542
+ end
543
+
544
+ params { requires :foos, type: Array[Foo] }
545
+ ```
546
+
547
+ Or coerce the collection yourself, in which case Grape does not build an element coercer at all:
548
+
549
+ ```ruby
550
+ params { requires :foos, type: Array[Foo], coerce_with: ->(value) { Array(value).map { |v| Foo.new(v) } } }
551
+ ```
552
+
553
+ Or, if the class is only there to describe the parameter and never to coerce it — the common case behind this break — drop `type:` and document it instead:
554
+
555
+ ```ruby
556
+ params { requires :foos, documentation: { type: Foo } }
557
+ ```
558
+
559
+ Defining `self.[]` on the class silences the load-time error, because that is the escape hatch for `dry-types` objects, but it does **not** make the type coercible: such a parameter still fails with `400 "is invalid"` on every request. Prefer `parse` or `coerce_with`.
560
+
561
+ #### The `cascade` getter returns the configured value
562
+
563
+ Calling `cascade` with no argument used to report whether cascading had been *configured at all* (`cascade false` still read back as `true`, contradicting the actual runtime behavior, which was correctly disabled). It now returns the configured value itself — `true`/`false` as set, or `true` when never set. Code that used the getter to detect "was `cascade` called" rather than "does this API cascade" must track that separately.
564
+ #### A cascading route hands over to every remaining route
565
+
566
+ A route that answers with `X-Cascade: pass` — what a version mismatch does by default — now hands the request to every remaining matching route, in registration order, before the router gives up. Previously it handed over to the *last* route registered for the path and stopped there, so with three or more routes sharing a path (typically three mounted API versions) every route in between was unreachable.
567
+
568
+ Two consequences:
569
+
570
+ * **A middle version is now served.** `mount v1; mount v2; mount v3` alongside a catch-all `route :any, '*path'` answered `406 API version not found` for v2, while v1 and v3 worked; v2 is now served.
571
+ * **An unmatched version reaches a catch-all.** When a catch-all ANY route is present, a request whose version matches nothing now falls through to it instead of surfacing the versioner's `406`, which is what `cascade: true` (the default) asks for. Declare `version ..., cascade: false` to keep the hard 406 — that path is unchanged and never consults the catch-all.
572
+ #### `Grape::Util::InheritableValues` has been removed
573
+
574
+ Inheritable settings no longer layer a scope's values over a store handed down from the enclosing scope. Each `Grape::Util::InheritableSetting` now keeps only what its own scope assigned, and resolves a lookup by walking `#parent` — the same move `Grape::Util::StackableValues` got in [#2823](https://github.com/ruby-grape/grape/pull/2823), and the reason `Grape::Util::InheritableValues` no longer has anything to do.
575
+
576
+ Resolution is unchanged in every observable way: the nearest scope wins, a scope may override an inherited value with an explicit `nil`, and a value an enclosing scope gains later is still visible through scopes already nested inside it. Only the class is gone. Nothing in Grape referenced it outside `InheritableSetting`; code that constructed one directly should keep a plain Hash per scope and resolve against the parent chain.
577
+
578
+ #### `forward_match` is no longer exposed on routes
579
+
580
+ `forward_match` is an internal, construction-time detail that decides how a route matches incoming paths (a prefix match for mounted Rack apps, the compiled pattern otherwise). It is now passed to `Grape::Router::Route` as a keyword argument and derived from the mounted app instead of being carried in the route's options.
581
+
582
+ As a result it is no longer readable through `route.options[:forward_match]` or the `route.forward_match` reader — both previously returned the flag. Nothing in Grape consumed either, so this only affects code that introspected routes directly; there is no replacement, as the value is now purely internal.
583
+
584
+ #### `Grape::Endpoint.new` takes `http_methods:` instead of `method:`
585
+
586
+ `Grape::Endpoint.new` now receives the HTTP verb(s) under the `http_methods:` keyword instead of `method:`, matching the name used everywhere else. If you build endpoints directly (uncommon — this is an internal API normally driven by the routing DSL), rename the keyword:
587
+
588
+ ```ruby
589
+ # before
590
+ Grape::Endpoint.new(settings, method: :get, path: '/foo', for: self)
591
+
592
+ # after
593
+ Grape::Endpoint.new(settings, http_methods: :get, path: '/foo', for: self)
594
+ ```
595
+
596
+ Relatedly, an endpoint's public `options` Hash no longer carries `:method` or `:path`. Nothing in Grape read `:method`, and the only reader of `:path` was an internal test; both values are available from the route instead — `route.request_method` for the verb and `route.path` for the (compiled) path, which is what grape-swagger and other introspection already use. The raw definition-time path array is no longer exposed on the endpoint.
597
+
598
+ #### `Grape::Endpoint.new` takes `api:` instead of `for:`
599
+
600
+ The keyword identifying the API an endpoint belongs to has been renamed from `for:` — a reserved word whose value can't be referenced as a local — to `api:`:
601
+
602
+ ```ruby
603
+ # before
604
+ Grape::Endpoint.new(settings, http_methods: :get, path: '/foo', for: my_api)
605
+
606
+ # after
607
+ Grape::Endpoint.new(settings, http_methods: :get, path: '/foo', api: my_api)
608
+ ```
609
+
610
+ The owning API is no longer carried on the endpoint's public `options` Hash — `endpoint.options[:for]` is gone. Use the new `endpoint.api` reader instead.
611
+
612
+ #### Route metadata is exposed through readers, not the `options` Hash
613
+
614
+ A route's computed metadata — `version`, `namespace`, `prefix`, `requirements`, `anchor` and `settings` — is now exposed through plain readers instead of being merged into the route's `options` Hash. `namespace`, `prefix` and `settings` are passed to `Grape::Router::Route` as explicit keyword arguments; `version`, `anchor` and `requirements` are read from the route's pattern (they shape how it matches).
615
+
616
+ The readers are unchanged — keep using them:
617
+
618
+ ```ruby
619
+ route.version # => 'v1'
620
+ route.namespace # => '/things'
621
+ route.prefix # => 'api'
622
+ route.requirements # => {}
623
+ route.anchor # => true
624
+ route.settings # => { ... }
625
+ ```
626
+
627
+ What changed is the raw bag. `route.options` now holds only what was declared for the route, so it no longer carries the *computed* values for these keys — `route.options[:version]`, `[:namespace]`, `[:prefix]` and `[:settings]` return `nil`. Nothing in Grape or grape-swagger read them that way (grape-swagger uses the `route.prefix` and `route.settings` readers). `requirements` and `anchor` — which can be supplied as route options (e.g. a mount's `anchor: false`) — are covered separately below: they too are now first-class endpoint inputs and likewise no longer appear in `route.options`. The effective value always comes from the reader.
628
+
629
+ #### `Grape::Endpoint` no longer accepts a `format:` keyword
630
+
631
+ The `:format` member was removed from the endpoint's internal `Options`. Nothing ever passed `format:` to `Grape::Endpoint.new` — `config.format` was always `nil` — so this has no runtime effect (the `(.:format)`/`(.json)` route suffix comes from `Grape::Path`, not from this value). But `Grape::Endpoint.new(..., format: …)` now raises `unknown keyword: :format` instead of silently ignoring it.
632
+
633
+ `Grape::Router::Pattern#initialize` no longer accepts `format:` either. It was never given a non-`nil` value — a route's `:format` capture comes from the path suffix built by `Grape::Path`, not from the pattern — so the keyword was dead. `Grape::Router::Pattern.new(..., format: …)` now raises `unknown keyword: :format`.
634
+
635
+ #### `desc` no longer populates `namespace_setting(:description)`
636
+
637
+ `desc` used to store its settings under both the route scope (`route_setting(:description)`) and the namespace scope (`namespace_setting(:description)`). The namespace copy was write-only — the namespace scope isn't wired for inheritance and nothing in Grape or grape-swagger ever read it — so `desc` now writes only the route scope. `namespace_setting(:description)` returns `nil`; read a route's description through `route_setting(:description)` or the `route.description` reader.
638
+ #### `Grape::Router::Route#params` no longer takes an argument
639
+
640
+ `Route#params` used to do two jobs depending on its argument: `route.params(input)` extracted param values from a matched request path, while `route.params` (no argument) returned the route's declared param definitions. These are now separate methods:
641
+
642
+ ```ruby
643
+ route.params # declared param definitions, keyed by name (unchanged)
644
+ route.params_for(input) # values extracted from a matched path (was route.params(input))
645
+ ```
646
+
647
+ The no-argument form is unchanged — grape-swagger and other documentation consumers keep using `route.params`. Only the value-extraction form moved, and it is internal to the router; if you called `route.params(input)` directly, switch to `route.params_for(input)`.
648
+
649
+ #### `params` is a first-class endpoint input, no longer in `route.options`
650
+
651
+ A route's declared params were previously carried inside the `route_options` bag and reachable as `route.options[:params]`. They are now composed into their own endpoint input (`Grape::Endpoint::Options` gains a `:params` member and `Grape::Endpoint.new` a `params:` keyword) and exposed only through `route.params`. `route.options[:params]` now returns `nil`. Nothing in Grape or grape-swagger read it that way — grape-swagger uses the `route.params` method — so this only affects code that reached into the options Hash for params directly.
652
+
653
+ #### `requirements` and `anchor` are first-class endpoint inputs, no longer in `route.options`
654
+
655
+ Like `params` above, a route's `requirements` and `anchor` were previously carried inside the `route_options` bag. They are now composed into their own endpoint inputs (`Grape::Endpoint::Options` gains `:requirements` and `:anchor` members, and `Grape::Endpoint.new` gains `requirements:` and `anchor:` keywords) and exposed only through the `route.requirements` and `route.anchor` readers. `route.options[:requirements]` and `route.options[:anchor]` now return `nil` — including for a mount's `anchor: false`. Nothing in Grape or grape-swagger read them that way, so this only affects code that reached into the options Hash for these keys directly.
656
+
657
+ #### Params state is recorded through `InheritableSetting` accessors
658
+
659
+ The state accumulated by `params` / `contract` blocks — validator instances, declared params, param documentation and reusable named params — is now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting` (`validations` / `add_validation`, `declared_params` / `add_declared_params`, `params_documentation` / `add_params_documentation`, `named_params` / `add_named_params`, `reset_validations!`) instead of raw `namespace_stackable` keys, following the same move made for rescue handlers. The keys' storage is unchanged for now, so `namespace_stackable[:validations]` and friends still return the same values, but they should be considered internal.
660
+
661
+ Related contract changes, none with known external consumers:
662
+
663
+ * `Grape::Endpoint#inherit_settings` now takes the parent `Grape::Util::InheritableSetting` instead of that setting's raw `namespace_stackable` store.
664
+ * The endpoint's request-time validator snapshot moved from `route_setting(:saved_validations)` to `route_setting(:validations)`, symmetric with `route_setting(:declared_params)`. The vestigial `saved_` prefix dated back to the 2014 settings refactor. Neither key appears in `route.settings`, as before.
665
+ * `Grape::Util::InheritableSetting#api_class` (a Hash nothing in Grape ever wrote to) and `#point_in_time_copies` (only ever used internally) are removed.
666
+ #### Callback filters are recorded through `InheritableSetting` accessors
667
+
668
+ The filter blocks registered by `before`, `before_validation`, `after_validation`, `after` and `finally` are now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting` — `add_callback(name, block)` to record, and `callbacks` returning a Hash keyed by the DSL method names (`callbacks[:before]`, `callbacks[:finally]`, …) — instead of raw `namespace_stackable` keys, following the same move made for rescue handlers. The keys' storage is unchanged for now, so `namespace_stackable[:befores]` and friends still return the same values, but the pluralized keys should be considered internal.
669
+ #### Rescue configuration is recorded through `InheritableSetting` accessors
670
+
671
+ The remaining rescue state written by `rescue_from` is now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting`, completing the encapsulation started with `rescue_handlers` / `add_rescue_handlers`:
672
+
673
+ * `rescue_options` / `add_rescue_options(options)` replace `namespace_stackable[:rescue_options]`. The reader absorbs the nearest-scope-wins convention (previously the `&.last` at the read site) and returns a single `Grape::DSL::RescueOptions` or `nil`, not the raw stack.
674
+ * `add_all_rescue_handler(handler)`, `add_grape_exceptions_rescue_handler(handler)` and `add_internal_grape_exceptions_rescue_handler(handler)` replace the direct `namespace_inheritable` writes for the `rescue_from :all` / `:grape_exceptions` / `:internal_grape_exceptions` meta selectors; each records its handler and flips the associated flags.
675
+ * `rescue_all?`, `rescue_grape_exceptions?`, `all_rescue_handler`, `grape_exceptions_rescue_handler` and `internal_grape_exceptions_rescue_handler` replace the corresponding `namespace_inheritable` reads. The two `?` readers return `false` (rather than `nil`) when never set; `Grape::Middleware::Error` only ever used them in boolean context, so behavior is unchanged.
676
+
677
+ The keys' storage is unchanged for now, so `namespace_stackable[:rescue_options]` and the `namespace_inheritable` keys still return the same values, but they should be considered internal.
678
+ #### Content negotiation state is recorded through `InheritableSetting` accessors
679
+
680
+ The state written by the `content_type`, `format`, `formatter`, `parser` and `error_formatter` DSL methods is now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting` (`content_types` / `add_content_type`, `formatters` / `add_formatter`, `parsers` / `add_parser`, `error_formatters` / `add_error_formatter`) instead of raw `namespace_stackable` keys, following the same move made for rescue handlers. Each writer records one single-entry Hash per registration and each reader absorbs the `namespace_stackable_with_hash` deep-merge convention, returning the merged name => value Hash (nearest scope wins) or `nil` when nothing is registered. The keys' storage is unchanged for now, so `namespace_stackable[:content_types]` and friends still return the same values, but they should be considered internal.
681
+ #### `represent` registrations are recorded through `InheritableSetting` accessors
682
+
683
+ The model-class => entity-class registrations written by `represent` are now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting` (`representations` / `add_representation(model_class, entity_class)`) instead of raw `namespace_stackable` keys, following the same move made for rescue handlers. The reader absorbs the `namespace_stackable_with_hash` deep-merge convention, returning the merged Hash (nearest scope wins) or `nil` when nothing is registered. The key's storage is unchanged for now, so `namespace_stackable[:representations]` still returns the same values, but it should be considered internal.
684
+
685
+ Also removed: the `namespace_stackable[:representations] ||= []` line in `Grape::Endpoint#initialize`. It was provably inert — `Grape::Util::StackableValues#[]` always returns an array (a frozen empty one for never-written keys), which is truthy, so the `||=` assignment could never execute. No settings state changes as a result.
686
+ #### Middleware and helper registrations are recorded through `InheritableSetting` accessors
687
+
688
+ The state written by the middleware DSL (`use`, `insert`, `insert_before`, `insert_after`) and by `helpers` is now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting` (`middleware` / `add_middleware`, `helpers` / `add_helper`) instead of raw `namespace_stackable` keys, following the same move made for rescue handlers. The keys' storage is unchanged for now, so `namespace_stackable[:middleware]` and `namespace_stackable[:helpers]` still return the same values, but they should be considered internal.
689
+
690
+ One micro-change: the `middleware` DSL reader dropped its `|| []` fallback — `Grape::Util::StackableValues#[]` never returns `nil`, so the fallback was dead code. When no middleware is registered the reader now returns the shared frozen empty array instead of a fresh mutable one; no caller in Grape or grape-swagger mutates the returned array.
691
+ #### Namespace and mount-path registrations are recorded through `InheritableSetting` accessors
692
+
693
+ The `Grape::Namespace` objects registered by the `namespace` DSL (and its `group` / `resource` / `resources` / `segment` aliases) and the mount path recorded by `mount` are now written and read through dedicated accessors on `Grape::Util::InheritableSetting` instead of raw `namespace_stackable` keys, following the same move made for rescue handlers:
694
+
695
+ * `namespaces` / `add_namespace(namespace)` replace `namespace_stackable[:namespace]` (not to be confused with the pre-existing `namespace` reader, which returns the namespace-settings store).
696
+ * `namespace_path` returns the normalized joined path prefix, absorbing the `Grape::Namespace.joined_space_path(...)` call previously spelled out at the read sites, and `namespace_requirements` returns the requirements declared by registered namespaces, absorbing the `filter_map(&:requirements)`.
697
+ * `mount_path` / `add_mount_path(path)` replace `namespace_stackable[:mount_path]`; the reader absorbs the outermost-wins convention (previously the `.first` at the read site in `Endpoint#build_stack`).
698
+
699
+ The keys' storage is unchanged for now, so `namespace_stackable[:namespace]` and `namespace_stackable[:mount_path]` still return the same values, but they should be considered internal.
700
+ #### Contract key maps are recorded through `InheritableSetting` accessors
701
+
702
+ The Dry::Schema key maps registered by `contract` blocks are now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting` (`contract_key_maps` / `add_contract_key_map(key_map)`) instead of the raw `namespace_stackable[:contract_key_map]` key, following the same move made for rescue handlers. The key's storage is unchanged for now, so `namespace_stackable[:contract_key_map]` still returns the same values, but it should be considered internal.
703
+ #### Format and error-response defaults are recorded through `InheritableSetting` accessors
704
+
705
+ The nearest-wins scalars written by the `format`, `default_format`, `default_error_formatter` and `default_error_status` DSL methods are now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting` (`format` / `format=`, `default_format` / `default_format=`, `default_error_formatter` / `default_error_formatter=`, `default_error_status` / `default_error_status=`) instead of raw `namespace_inheritable` keys — the first batch of the `namespace_inheritable` cleanup, following the pattern used for `namespace_stackable`. Since these are overriding assignments rather than stacking registrations, the writers use plain `=` instead of the `add_*` naming. The keys' storage is unchanged for now, so `namespace_inheritable[:format]` and friends still return the same values, but they should be considered internal.
706
+ #### Versioning state is recorded through `InheritableSetting` accessors
707
+
708
+ The nearest-wins scalars written by the `version`, `prefix` and `cascade` DSL methods are now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting` (`version` / `version=`, `version_options` / `version_options=`, `root_prefix` / `root_prefix=`, `cascade` / `cascade=`) instead of raw `namespace_inheritable` keys. `cascade` is presence-sensitive — an explicit `nil` is distinct from never-set — so the accessor family includes `cascade_defined?`, absorbing the `namespace_inheritable.key?(:cascade)` checks previously spelled out in `DSL::Routing#cascade` and `Grape::API::Instance#cascade?`. The keys' storage is unchanged for now, so `namespace_inheritable[:version]` and friends still return the same values, but they should be considered internal.
709
+ #### Routing scope flags are recorded through `InheritableSetting` accessors
710
+
711
+ The flags flipped by `do_not_route_head!`, `do_not_route_options!`, `do_not_document!` and `lint!` are now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting` (`!` writers, `?` readers) instead of raw `namespace_inheritable` keys. The `?` readers return `false` (rather than `nil`) when never set; every consumer only used them in boolean context, so behavior is unchanged. The keys' storage is unchanged for now, so `namespace_inheritable[:do_not_route_head]` and friends still return the same values, but they should be considered internal.
712
+ #### `build_params_with` and `auth` are recorded through `InheritableSetting` accessors
713
+
714
+ The params-builder strategy written by `build_with` (both the API-level and the params-block DSL) and the authentication configuration written by the `auth` DSL are now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting` (`build_params_with` / `build_params_with=`, `auth` / `auth=`) instead of raw `namespace_inheritable` keys, completing the per-key `namespace_inheritable` cleanup. The keys' storage is unchanged for now, so `namespace_inheritable[:build_params_with]` and `namespace_inheritable[:auth]` still return the same values, but they should be considered internal.
715
+
716
+ #### `InheritableSetting`'s raw stores are internal, except `namespace_stackable`
717
+
718
+ The per-key encapsulation documented in the sections above is now enforced where the ecosystem allows: `Grape::Util::InheritableSetting#namespace_inheritable` is protected and `#namespace_stackable_with_hash` is private — every `namespace_inheritable` key is reachable only through its dedicated accessor.
719
+
720
+ `#namespace_stackable` deliberately **remains public**: grape-swagger reads it directly (`swagger_documentation_adder.rb` reads `[:namespace]` and `[:mount_path]`, `token_owner_resolver.rb` reads `[:helpers]`, and `request_param_parsers/route.rb` walks the `StackableValues#inherited_values` chain to collect per-scope params). Treat it as read-only from outside Grape and prefer the dedicated accessors (`namespaces`, `mount_paths`, `helpers`, …) for everything they cover; the reader may be narrowed in a future major once grape-swagger has migrated. The `route`, `namespace` and `global` scratch stores (backing `route_setting` / `namespace_setting` / `global_setting`) also remain public and unchanged.
721
+
722
+ Two supporting changes:
723
+
724
+ * `Router::Pattern::Path` no longer receives a merged dump of both stores; `Endpoint#to_routes` passes the new `InheritableSetting#path_settings` snapshot — a `Grape::Util::InheritableSetting::PathSettings` value object (`Data`) — which carries exactly the six values `Path` reads (full mount-path stack, root prefix, format, raw content-types stack, version, version options) with always-present keys. `Path`'s `key?`-presence guards became nil-tolerant truthiness checks — equivalent under the snapshot. `Endpoint#prepare_default_path_settings` is removed.
725
+ * `InheritableSetting#mount_paths` is added, exposing the full mount-path stack (one entry per mount level, outermost first), complementing `#mount_path`, which returns only the outermost entry.
726
+
727
+ #### `Grape::Util::StackableValues` is a read-only view, not a store
728
+
729
+ Stackable registrations now live on `Grape::Util::InheritableSetting` itself, as one plain Hash of `key => Array` per scope, and inheritance is resolved by walking `#parent` — the same recursion `#rescue_handlers` already used. Previously the registrations lived in a `StackableValues` instance which maintained its own parallel chain through `#inherited_values`, so every scope carried two links to its parent.
730
+
731
+ `InheritableSetting#namespace_stackable` still returns a `Grape::Util::StackableValues`, still resolves reads across the chain, and still exposes `#new_values` / `#inherited_values` / `#keys` / `#to_hash`, so the grape-swagger call sites listed above keep working unchanged. What changed:
732
+
733
+ * It is now a **view built on demand**, not the backing store. Each call returns a new instance, so the object is no longer identity-stable across calls, and writing to it registers nothing.
734
+ * `StackableValues#[]=` and `#delete` are removed — the class is read-only. Registration happens through the semantic `add_*` accessors on `InheritableSetting`.
735
+ * `StackableValues.new` now takes `(new_values, inherited_values)` instead of `(inherited_values = nil)`.
736
+ * `Grape::Util::BaseInheritable` is removed. It existed only to share plumbing between `StackableValues` and `InheritableValues`; with the former no longer a store, its contents moved into `Grape::Util::InheritableValues`, whose public behavior is unchanged (its now-unused `#keys` is dropped).
737
+
738
+ Settings semantics are otherwise deliberately identical, including two pre-existing quirks worth knowing about, since neither is fixed here:
739
+
740
+ * A stack reader returns the backing Array itself when only the current scope registered anything (a frozen empty Array when nothing is registered anywhere), so callers must treat the result as read-only.
741
+ * `#point_in_time_copy` copies the per-scope Hash shallowly, so the per-key Arrays stay shared with the source. A registration made on a scope *after* an endpoint was defined in it is therefore still visible to that endpoint — but only when the same key already had a registration when the endpoint was defined. This is why `use SomeMiddleware` written below a route can still apply to it.
742
+
743
+ ### Upgrading to >= 3.3
744
+
745
+ #### Minimum required Ruby is now 3.3
746
+
747
+ Grape no longer supports Ruby 3.2; 3.3 is now the minimum (`required_ruby_version = '>= 3.3'`). Upgrade your runtime to Ruby 3.3 or newer before bumping Grape.
748
+
749
+ #### `mustermann-grape` is no longer a dependency
750
+
751
+ Grape's path-pattern grammar (previously the `mustermann-grape` gem) now lives in Grape itself as `Grape::Router::MustermannPattern`, and Grape depends on `mustermann` directly. This is transparent for normal Grape usage.
752
+
753
+ The inlined class is no longer registered as a Mustermann type, so if your app called `Mustermann.new(pattern, type: :grape)` and relied on Grape loading `mustermann-grape` for you, add it to your Gemfile explicitly:
754
+
755
+ ```ruby
756
+ gem 'mustermann-grape'
757
+ ```
758
+
759
+ #### `Grape::Exceptions::ValidationErrors.new` keyword renamed `errors:` → `exceptions:`
760
+
761
+ `Grape::Exceptions::ValidationErrors#initialize` now takes its input array under the `exceptions:` keyword instead of `errors:`. The kwarg accepts a mix of `Grape::Exceptions::Validation` and `Grape::Exceptions::ValidationArrayErrors` instances; `ValidationArrayErrors` wrappers are flattened internally via `flat_map(&:errors)`. The `errors` reader on the constructed instance (the grouped `{params => [Validation, ...]}` Hash) is unchanged.
762
+
763
+ ```ruby
764
+ # before
765
+ Grape::Exceptions::ValidationErrors.new(errors: [validation, validation_array_errors], headers:)
766
+
767
+ # after
768
+ Grape::Exceptions::ValidationErrors.new(exceptions: [validation, validation_array_errors], headers:)
769
+ ```
770
+
771
+ #### `Grape::Exceptions::ValidationErrors` no longer mixes in `Enumerable`
772
+
773
+ `Grape::Exceptions::ValidationErrors` no longer includes `Enumerable` and no longer defines a public `#each`. The Enumerable surface (`#each`, `#map`, `#select`, `#to_a`, etc.) was undocumented and untested; the documented accessors — `#errors`, `#full_messages`, `#message`, `#as_json` — are unchanged.
774
+
775
+ If a `rescue_from` block iterated over the exception instance, switch to `#errors`:
776
+
777
+ ```ruby
778
+ # before
779
+ rescue_from Grape::Exceptions::ValidationErrors do |e|
780
+ e.each { |attribute, error| ... }
781
+ end
782
+
783
+ # after
784
+ rescue_from Grape::Exceptions::ValidationErrors do |e|
785
+ e.errors.each do |attributes, errs|
786
+ errs.each { |error| ... }
787
+ end
788
+ end
789
+ ```
790
+
791
+ #### `rescue_from` rejects meta selectors mixed with exception classes
792
+
793
+ `rescue_from` used to silently drop additional exception classes when its first argument was a meta selector (`:all`, `:grape_exceptions`, `:internal_grape_exceptions`). It now raises `ArgumentError` so the misuse is caught at definition time:
794
+
795
+ ```ruby
796
+ # previously: MyError was silently dropped — only :all took effect
797
+ rescue_from :all, MyError, with: :handler
798
+
799
+ # now: ArgumentError ("rescue_from :all does not accept additional arguments")
800
+ # split into two declarations instead:
801
+ rescue_from :all, with: :handler
802
+ rescue_from MyError, with: :other_handler
803
+ ```
804
+
805
+ Calls that only use one meta selector or only use exception classes (the documented forms) are unaffected.
806
+
807
+ #### `auth` and `http_basic` now take keyword arguments
808
+
809
+ `Grape::Middleware::Auth::DSL#auth` and `#http_basic` now accept their options as keyword arguments instead of a positional `Hash`. Calls using bare keyword syntax or a block are unaffected:
810
+
811
+ ```ruby
812
+ http_basic realm: 'API' do |u, p|
813
+ # ...
814
+ end
815
+
816
+ auth :my_strategy, realm: 'API', &proc
817
+ ```
818
+
819
+ Passing a positional options `Hash` still works but is deprecated and will be removed in a future release:
820
+
821
+ ```ruby
822
+ # deprecated
823
+ http_basic({ realm: 'API' })
824
+ auth :my_strategy, { realm: 'API' }
825
+
826
+ # preferred
827
+ http_basic(realm: 'API')
828
+ auth :my_strategy, realm: 'API'
829
+ ```
830
+
831
+ #### Middleware options now route through per-class `Options` `Data` value objects
832
+
833
+ `Grape::Middleware::Error`, `Grape::Middleware::Formatter`, and `Grape::Middleware::Versioner::Base` each declare an `Options` `Data.define` and route their `**options` kwargs through it on `initialize`. This means **unknown kwargs now raise `ArgumentError`** instead of being silently swallowed:
834
+
835
+ ```ruby
836
+ # previously: silently swallowed (Formatter doesn't actually read :rescue_options)
837
+ Grape::Middleware::Formatter.new(app, rescue_options: { backtrace: true })
838
+
839
+ # now: ArgumentError (unknown keyword: :rescue_options)
840
+ ```
841
+
842
+ Each `Options` class accepts exactly the kwargs the middleware actually reads. The supported sets:
843
+
844
+ - `Middleware::Error::Options`: `all_rescue_handler`, `base_only_rescue_handlers`, `content_types`, `default_error_formatter`, `default_message`, `default_status`, `error_formatters`, `format`, `grape_exceptions_rescue_handler`, `internal_grape_exceptions_rescue_handler`, `rescue_all`, `rescue_grape_exceptions`, `rescue_handlers`, `rescue_options`.
845
+ - `Middleware::Formatter::Options`: `content_types`, `default_format`, `format`, `formatters`, `parsers`.
846
+ - `Middleware::Versioner::Base::Options`: `content_types`, `format`, `mount_path`, `pattern`, `prefix`, `version_options`, `versions`.
847
+
848
+ The `Hash`-based `options` reader on `Grape::Middleware::Base` continues to return a frozen Hash representation of the Data (`config.to_h.freeze`) for back-compat with subclasses that read `options[:key]`. A new `config` reader exposes the typed Data instance — prefer the named accessors going forward:
849
+
850
+ ```ruby
851
+ # back-compat (still works)
852
+ options[:format]
853
+
854
+ # preferred
855
+ config.format
856
+ # or, on converted middlewares, just `format` (provided via def_delegators)
857
+ ```
858
+
859
+ `Options#[]` is defined as a Hash-style shim with a deprecation warning so legacy `data[:key]` callers get a migration nudge:
860
+
861
+ ```ruby
862
+ # emits Grape.deprecator warning
863
+ Grape::Middleware::Error::Options.new[:format]
864
+ ```
865
+
866
+ #### `DEFAULT_OPTIONS` constants on converted middlewares are deprecated
867
+
868
+ `Grape::Middleware::Error::DEFAULT_OPTIONS`, `Grape::Middleware::Formatter::DEFAULT_OPTIONS`, and `Grape::Middleware::Versioner::Base::DEFAULT_OPTIONS` still exist as a frozen `Hash` representation of the `Options` defaults (`Options.new.to_h.freeze`), for back-compat with any code that referenced these constants directly. They will be removed in a future release; introspect the `Options` `Data` class itself instead.
869
+
870
+ #### `Grape::Middleware::Globals` removed
871
+
872
+ `Grape::Middleware::Globals` and the three env constants it set (`Grape::Env::GRAPE_REQUEST`, `Grape::Env::GRAPE_REQUEST_HEADERS`, `Grape::Env::GRAPE_REQUEST_PARAMS`) have been deleted. The middleware was introduced in 2013 (commit `9987090b`) but never mounted by Grape's own stack — the `Grape::Request` it built is now constructed directly inside `Grape::Endpoint`. Nothing in `lib/` read those env keys.
873
+
874
+ If you mounted `Grape::Middleware::Globals` in your own Rack stack to populate `env['grape.request']` for downstream middleware, replicate it locally:
875
+
876
+ ```ruby
877
+ class MyGlobals
878
+ def initialize(app); @app = app; end
879
+
880
+ def call(env)
881
+ request = Grape::Request.new(env)
882
+ env['grape.request'] = request
883
+ env['grape.request.headers'] = request.headers
884
+ env['grape.request.params'] = request.params if env['rack.input']
885
+ @app.call(env)
886
+ end
887
+ end
888
+ ```
889
+
890
+ The original implementation is preserved in git history at [`6b4111b3:lib/grape/middleware/globals.rb`](https://github.com/ruby-grape/grape/blob/6b4111b3/lib/grape/middleware/globals.rb).
891
+
892
+ #### `error_formatter` now receives a `Grape::Exceptions::ErrorResponse` value object
893
+
894
+ Custom error formatters now receive a frozen `Grape::Exceptions::ErrorResponse` as the `error:` keyword argument, alongside three request-time context kwargs. The new signature:
895
+
896
+ ```ruby
897
+ def call(error:, env: nil, include_backtrace: false, include_original_exception: false)
898
+ ```
899
+
900
+ `error` is the same value object the middleware uses internally, with `status` / `message` / `headers` / `backtrace` / `original_exception` accessors. The two `include_*` booleans are forwarded from the matching `rescue_from` options (previously buried inside `options[:rescue_options]`).
901
+
902
+ Existing positional formatters break and need to be updated:
903
+
904
+ ```ruby
905
+ # Before
906
+ error_formatter :txt, ->(message, backtrace, options, env, original_exception) { ... }
907
+
908
+ module CustomFormatter
909
+ def self.call(message, backtrace, options, env, original_exception)
910
+ ...
911
+ end
912
+ end
913
+
914
+ # After — pick fields off `error`
915
+ error_formatter :txt, ->(error:, **) { "[#{error.status}] #{error.message}" }
916
+
917
+ module CustomFormatter
918
+ def self.call(error:, **)
919
+ { status: error.status, message: error.message, backtrace: error.backtrace }
920
+ end
921
+ end
922
+ ```
923
+
924
+ Migration:
925
+
926
+ | Old positional arg | New |
927
+ | --- | --- |
928
+ | `message` | `error.message` |
929
+ | `backtrace` | `error.backtrace` |
930
+ | `original_exception` | `error.original_exception` |
931
+ | `options[:rescue_options][:backtrace]` | `include_backtrace` (kwarg) |
932
+ | `options[:rescue_options][:original_exception]` | `include_original_exception` (kwarg) |
933
+ | `env` | `env` (kwarg, still passed) |
934
+ | HTTP status | `error.status` (newly exposed) |
935
+ | Response headers | `error.headers` (newly exposed) |
936
+
937
+ The remaining middleware-options keys (`default_status`, `format`, `rescue_handlers`, …) were framework-internal and have never been part of the documented contract.
938
+
939
+ The change resolves [#2527](https://github.com/ruby-grape/grape/issues/2527): the HTTP `status` and the response `headers` are now part of the formatter contract, so JSON:API–style error bodies (which embed the status code) and header-aware formatters can be written without reaching into `env[Grape::Env::API_ENDPOINT]`.
940
+
941
+ #### `version` now takes explicit keyword arguments
942
+
943
+ `version` previously accepted `**options` and silently ignored any keys it didn't use. It now declares its options explicitly:
944
+
945
+ ```ruby
946
+ def version(*args, using: :path, cascade: true, parameter: 'apiver', strict: false, vendor: nil, &block)
947
+ ```
948
+
949
+ Passing an unrecognised keyword now raises `ArgumentError` instead of being swallowed. The most common offender is `format:` — it was never a `version` option (response format is set with `format`/`default_format`, and header-versioned requests carry the format in their `Accept` header), but the old splat let `version 'v1', using: :header, vendor: 'x', format: :json` through as a no-op.
950
+
951
+ ```ruby
952
+ # Before — `format:` silently ignored
953
+ version 'v1', using: :header, vendor: 'x', format: :json
954
+
955
+ # After
956
+ version 'v1', using: :header, vendor: 'x' # set responses with `format :json` / `default_format :json`
957
+ ```
958
+
959
+ Recognized keys are `using:`, `cascade:`, `parameter:`, `strict:`, `vendor:`. Calls using only those are unaffected.
960
+
961
+ #### `Grape::Middleware::Base#options` is now frozen
962
+
963
+ `@options` is frozen at the end of `Grape::Middleware::Base#initialize` (after `merge_default_options`). The hash is initialized once and treated as immutable for the lifetime of the middleware. Custom middleware that mutates `options[...]` at runtime will now raise `FrozenError`.
964
+
965
+ If your custom middleware was patching its own options on the fly:
966
+
967
+ ```ruby
968
+ # Before
969
+ class MyMiddleware < Grape::Middleware::Base
970
+ def before
971
+ options[:flag] = compute_flag
972
+ # ...
973
+ end
974
+ end
975
+
976
+ # After — store mutable runtime state on a dedicated ivar
977
+ class MyMiddleware < Grape::Middleware::Base
978
+ def before
979
+ @flag = compute_flag
980
+ # ...
981
+ end
982
+ end
983
+ ```
984
+
985
+ Reading `options[...]` is unchanged.
986
+
987
+ #### Throw `:error` payloads are now `Grape::Exceptions::ErrorResponse`
988
+
989
+ The payload thrown via `throw :error, ...` is now a `Grape::Exceptions::ErrorResponse` value object instead of a `Hash`. If you `catch(:error)` and inspect the payload, switch from `payload[:status]` to `payload.status` (or `payload[:message]` to `payload.message`, etc.). User-defined `throw :error, hash` calls continue to work — `Middleware::Error#error_response` coerces Hashes, exceptions, and `ErrorResponse` instances at the boundary.
990
+
991
+ Returning or throwing a `Hash` with `:message`, `:status`, and `:headers` from a `rescue_from` handler is now deprecated and will be removed in a future release. Use `error!(...)` or return/throw a `Grape::Exceptions::ErrorResponse` instead.
992
+
993
+ #### `Grape::Request#grape_routing_args` has been removed
994
+
995
+ `grape_routing_args` was previously public to support third-party `params_builder` extensions, which have since been removed. With no remaining callers, the method has been removed. If you were calling it externally, read `env[Grape::Env::GRAPE_ROUTING_ARGS]` directly.
996
+
997
+ #### `endpoint_run_filters.grape` notification no longer fired for empty filter lists
998
+
999
+ `ActiveSupport::Notifications` subscribers listening to `endpoint_run_filters.grape` will no longer receive an event when the filter list for a given phase (`:before`, `:before_validation`, `:after_validation`, `:after`, `:finally`) is empty. Previously every phase emitted an event on every request regardless of whether any filters were registered. If you relied on these events to infer per-phase timing, subscribe to `endpoint_run.grape` (which always fires once per request) or register a no-op filter to keep the phase instrumented.
1000
+
1001
+ #### `Grape::Endpoint.before_each` moved to `Grape::Testing`
1002
+
1003
+ `Grape::Endpoint.before_each` and `Grape::Endpoint.reset_before_each` are now only available after requiring `grape/testing`. This module is intended for test environments only and is not loaded by default.
1004
+
1005
+ Add the following to your test helper:
1006
+
1007
+ ```ruby
1008
+ require 'grape/testing'
1009
+ ```
1010
+
1011
+ The `before_each` method now always requires a block — calling it without one raises `ArgumentError`. To clear registered hooks, use the new dedicated `reset_before_each` method:
1012
+
1013
+ ```ruby
1014
+ # Before
1015
+ after { Grape::Endpoint.before_each nil }
1016
+
1017
+ # After
1018
+ after { Grape::Endpoint.reset_before_each }
1019
+ ```
1020
+
1021
+ #### `Grape::Endpoint#logger` now returns the API's configured logger
1022
+
1023
+ Calling `logger` inside a route handler, filter (`before` / `before_validation` / `after_validation` / `after` / `finally`), or `rescue_from` block previously raised `NoMethodError` unless the application defined a helper:
1024
+
1025
+ ```ruby
1026
+ class MyAPI < Grape::API
1027
+ logger Logger.new($stdout)
1028
+
1029
+ helpers do
1030
+ def logger
1031
+ MyAPI.logger
1032
+ end
1033
+ end
1034
+ end
1035
+ ```
1036
+
1037
+ `Grape::Endpoint` now exposes `#logger` directly, so the helper is no longer necessary:
1038
+
1039
+ ```ruby
1040
+ class MyAPI < Grape::API
1041
+ logger Logger.new($stdout)
1042
+ # logger is now reachable inside route handlers, filters, and rescue_from blocks
1043
+ end
1044
+ ```
1045
+
1046
+ **Helper override still wins.** Helpers are mixed into the endpoint's singleton class via `singleton_class.include(@helpers)`, and singleton-class methods take precedence over instance methods on `Grape::Endpoint`. If your application already defines `logger` in a `helpers` block (or a module included via `helpers`), that definition continues to override `Endpoint#logger`. You can safely keep the helper or remove it — both paths produce the same result for the canonical `MyAPI.logger` case above.
1047
+
1048
+ **Behaviour change for code that didn't define a helper.** If your code references `logger` inside an endpoint context *without* a corresponding `helpers` definition, that call previously raised `NoMethodError` and now returns the API's configured logger. This is almost always the intended behaviour, but if you were relying on the `NoMethodError` (for instance to short-circuit logging in test environments via `rescue NoMethodError`), update your code to check `respond_to?(:logger)` or to gate logging on a feature flag.
1049
+
1050
+ #### Exceptions raised inside `rescue_from` blocks are now caught
1051
+
1052
+ Previously, an exception raised inside a `rescue_from` block was uncaught and bubbled up to Rack, producing the Rack default 500 page. The framework now catches and routes it:
1053
+
1054
+ 1. If the re-raised exception's class has a registered `rescue_from` handler, that handler runs (one redispatch only — a second raise stops the chain).
1055
+ 2. If the re-raised exception is a `Grape::Exceptions::Base` subclass, it is rendered via the default Grape error path with its own `status` and `message`.
1056
+ 3. Otherwise, the original exception is exposed on `env['grape.exception']` for upstream Rack middleware to observe, and the response is a generic `Grape::Exceptions::InternalServerError` (`500 Internal Server Error`) — the original exception's message is **not** rendered to the API consumer.
1057
+
1058
+ This means deliberate re-raises in a `rescue_from` block (e.g. translating one exception class into another) now compose with the rest of your `rescue_from` configuration, and accidental crashes (typos, `NoMethodError`, …) no longer leak internal detail to API consumers.
1059
+
1060
+ The framework deliberately does **not** log unhandled internal exceptions itself — formatting and destination are application concerns. To log, forward to an error tracker, or customize the response shape for these errors, register a `rescue_from :internal_grape_exceptions` handler:
1061
+
1062
+ ```ruby
1063
+ rescue_from :internal_grape_exceptions do |e|
1064
+ Sentry.capture_exception(e)
1065
+ error!({ message: 'Something went wrong' }, 500)
1066
+ end
1067
+ ```
1068
+
1069
+ When this handler is registered, the framework hands the original exception to you and you own the response shape entirely.
1070
+
1071
+ If you relied on the old behaviour and want raw exception messages exposed in development, register a catch-all handler:
1072
+
1073
+ ```ruby
1074
+ rescue_from StandardError do |e|
1075
+ error!({ message: e.message, class: e.class.name }, 500)
1076
+ end
1077
+ ```
1078
+
1079
+
1080
+ ### Upgrading to >= 3.2
1081
+
1082
+ #### Rack parameter parsing errors now raise `Grape::Exceptions::RequestError`
1083
+
1084
+ Rack errors raised during parameter parsing (malformed multipart, parameter type conflicts, encoding issues, etc.) are now wrapped in `Grape::Exceptions::RequestError` instead of their previous specific exception classes (`Grape::Exceptions::EmptyMessageBody`, `Grape::Exceptions::TooManyMultipartFiles`, `Grape::Exceptions::TooDeepParameters`, `Grape::Exceptions::ConflictingTypes`, `Grape::Exceptions::InvalidParameters`). Those classes have been removed.
1085
+
1086
+ If you rescue any of these specific exceptions, update your rescue clauses to use `Grape::Exceptions::RequestError`:
1087
+
1088
+ ```ruby
1089
+ # Before
1090
+ rescue Grape::Exceptions::ConflictingTypes, Grape::Exceptions::TooDeepParameters => e
1091
+ # ...
1092
+
1093
+ # After
1094
+ rescue Grape::Exceptions::RequestError => e
1095
+ # ...
1096
+ ```
1097
+
1098
+ The error message is now forwarded directly from Rack rather than translated through Grape's locale system. On Rack 3, all Rack bad-request errors share the `Rack::BadRequest` marker module and are covered by a single rescue.
1099
+
1100
+ #### `endpoint_run_validators.grape` notification no longer fired when there are no validators
1101
+
1102
+ `ActiveSupport::Notifications` subscribers listening to `endpoint_run_validators.grape` will no longer receive an event for endpoints that have no validators. If you rely on this notification to measure every request, subscribe to `endpoint_run.grape` instead, which always fires.
1103
+
1104
+ #### Custom validators: use `default_message_key` and `validation_error!`
1105
+
1106
+ Validators are now instantiated once at definition time and frozen. Any setup should happen in `initialize`, not in `validate_param!`.
1107
+
1108
+ If your custom validator did work in `validate_param!` that only depends on the validator's options (not the param value), move it to `initialize`. A common case is compiling a value derived from options — for example, building a `Regexp`. Previously this may have been cached back into `@options`, which now raises `FrozenError` since `@options` and its nested values are deep-frozen by the base class:
1109
+
1110
+ **Before:**
1111
+ ```ruby
1112
+ class MyValidator < Grape::Validations::Validators::Base
1113
+ def validate_param!(attr_name, params)
1114
+ # raises FrozenError: @options is frozen, cannot store compiled pattern back into it
1115
+ @options[:compiled] ||= Regexp.new(@options[:pattern])
1116
+ validation_error!(attr_name) unless params[attr_name].match?(@options[:compiled])
1117
+ end
1118
+ end
1119
+ ```
1120
+
1121
+ **After:**
1122
+ ```ruby
1123
+ class MyValidator < Grape::Validations::Validators::Base
1124
+ def initialize(attrs, options, required, scope, opts)
1125
+ super
1126
+ @pattern = Regexp.new(@options[:pattern]).freeze
1127
+ end
1128
+
1129
+ def validate_param!(attr_name, params)
1130
+ validation_error!(attr_name) unless params[attr_name].match?(@pattern)
1131
+ end
1132
+ end
1133
+ ```
1134
+
1135
+ Any Array or Hash derived from options and stored in an ivar should be frozen, since the validator instance is shared across requests. `@options` itself (and any nested Hash/Array/String values within it) is deep-frozen by the base class, so mutations like `@options[:values] << 'extra'` will also raise a `FrozenError`.
1136
+
1137
+ #### Custom validators: rename `@option` to `@options`
1138
+
1139
+ The instance variable holding the validator's option value has been renamed from `@option` to `@options`. `@option` remains as an alias for backwards compatibility but will be removed in the next major release. Update any custom validators to use `@options` instead.
1140
+
1141
+ Several new helpers are available — see [Available helpers](README.md#available-helpers) in the README for full documentation and examples.
1142
+
1143
+ #### `with` now uses keyword arguments
1144
+
1145
+ The `with` DSL method now uses `**opts` instead of a positional hash. Calls using bare keyword syntax are unaffected:
1146
+
1147
+ ```ruby
1148
+ # still works
1149
+ with(type: String, documentation: { in: 'body' }) { ... }
1150
+ ```
1151
+
1152
+ However, passing an explicit hash literal will now raise an `ArgumentError`:
1153
+
1154
+ ```ruby
1155
+ # raises ArgumentError
1156
+ with({ type: String }) { ... }
1157
+ ```
1158
+
1159
+ See [#2663](https://github.com/ruby-grape/grape/pull/2663) for more information.
1160
+
1161
+ #### Custom validators: use `translate` instead of `I18n` directly
1162
+
1163
+ `Grape::Util::Translation` is now included in `Grape::Validations::Validators::Base`. Custom validators that previously called `I18n.t` or `I18n.translate` directly should switch to the `translate`, which provides the same `:en` fallback logic used by all built-in validators.
1164
+
1165
+ Key points:
1166
+ - `scope` defaults to `'grape.errors.messages'` — no need to specify it for standard error message keys.
1167
+ - Interpolation variables are passed directly to I18n.
1168
+ - `format` is no longer needed — `translate` returns the fully interpolated string.
1169
+
1170
+ ```ruby
1171
+ # Before
1172
+ raise Grape::Exceptions::Validation.new(
1173
+ params: [@scope.full_name(attr_name)],
1174
+ message: format(I18n.t(:my_key, scope: 'grape.errors.messages'), min: 2, max: 10)
1175
+ )
1176
+
1177
+ # After
1178
+ raise Grape::Exceptions::Validation.new(
1179
+ params: [@scope.full_name(attr_name)],
1180
+ message: translate(:my_key, min: 2, max: 10)
1181
+ )
1182
+ ```
1183
+
1184
+ See [#2662](https://github.com/ruby-grape/grape/pull/2662) for more information.
1185
+
1186
+ ### Upgrading to >= 3.1
1187
+
1188
+ #### Explicit kwargs for `namespace` and `route_param`
1189
+
1190
+ The `API#namespace` and `route_param` methods are now defined with `**options` instead of `options = {}`. In addtion, `requirements` in explicitly defined so it's not in `options` anymore. You can still call `requirements` like before but `options[:requirements]` will be empty. For `route_param`, `type` is also an explicit parameter so it's not in `options` anymore. See [#2647](https://github.com/ruby-grape/grape/pull/2647) for more information.
1191
+
1192
+ #### ParamsBuilder Grape::Extensions
1193
+
1194
+ Deprecated [ParamsBuilder's extensions](https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#params-builder) have been removed.
1195
+
1196
+ #### Enhanced API compile!
1197
+
1198
+ Endpoints are now "compiled" instead of lazy loaded. Historically, when calling `YourAPI.compile!` in `config.ru` (or just receiving the first API call), only routing was compiled see [Grape::Router#compile!](https://github.com/ruby-grape/grape/blob/bf90e95c3b17c415c944363b1c07eb9727089ee7/lib/grape/router.rb#L41-L54) and endpoints were lazy loaded. Now, it's part of the API compilation. See [#2645](https://github.com/ruby-grape/grape/pull/2645) for more information.
1199
+
1200
+ ### Upgrading to >= 3.0.0
1201
+
1202
+ #### Ruby 3+ Argument Delegation Modernization
1203
+
1204
+ Grape has been modernized to use Ruby 3+'s preferred argument delegation patterns. This change replaces `args.extract_options!` with explicit `**kwargs` parameters throughout the codebase.
1205
+
1206
+ - All DSL methods now use explicit keyword arguments (`**kwargs`) instead of extracting options from mixed argument lists
1207
+ - Method signatures are now more explicit and follow Ruby 3+ best practices
1208
+ - The `active_support/core_ext/array/extract_options` dependency has been removed
1209
+
1210
+ Passing the options of `requires`, `optional` and `use` as a trailing positional Hash still works, but is deprecated:
1211
+
1212
+ ```ruby
1213
+ params do
1214
+ requires :id, { type: Integer } # deprecated
1215
+ requires :id, type: Integer # do this instead
1216
+ end
1217
+ ```
1218
+
1219
+ Under `extract_options!` the braces made no difference. They do now: Ruby only turns a trailing Hash into keyword arguments when it is written without braces, so a braced Hash lands in the splat and would otherwise be taken for a parameter name.
1220
+
1221
+ See [#2618](https://github.com/ruby-grape/grape/pull/2618) for more information.
1222
+
1223
+ #### Configuration API Migration from ActiveSupport::Configurable to Dry::Configurable
1224
+
1225
+ Grape has migrated from `ActiveSupport::Configurable` to `Dry::Configurable` for its configuration system since its [deprecated](https://github.com/rails/rails/blob/1cdd190a25e483b65f1f25bbd0f13a25d696b461/activesupport/lib/active_support/configurable.rb#L3-L7).
1226
+
1227
+ See [#2617](https://github.com/ruby-grape/grape/pull/2617) for more information.
1228
+
1229
+ #### Endpoint execution simplified and `return` deprecated
1230
+
1231
+ Executing a endpoint's block has been simplified and calling `return` in it has been deprecated. Use `next` instead.
1232
+
1233
+ See [#2577](https://github.com/ruby-grape/grape/pull/2577) for more information.
1234
+
1235
+ #### Old Deprecations Clean Up
1236
+
1237
+ - `rack_response` has been removed in favor of using `error!`.
1238
+ - `Grape::Exceptions::MissingGroupType` and `Grape::Exceptions::UnsupportedGroupType` aliases `MissingGroupTypeError and `UnsupportedGroupType` have been removed.
1239
+ - `Grape::Validations::Base` has been removed in favor of `Grape::Validations::Validators::Base`.
1240
+
1241
+ See [2573](https://github.com/ruby-grape/grape/pull/2573) for more information.
1242
+
1243
+ ### Upgrading to >= 2.4.0
1244
+
1245
+ #### Grape::Middleware::Auth::Base
1246
+ `type` is now validated at compile time and will raise a `Grape::Exceptions::UnknownAuthStrategy` if unknown.
1247
+
1248
+ #### Grape::Middleware::Base
1249
+
1250
+ - Second argument `options` is now a double splat (**) instead of single splat (*). If you're redefining `initialize` in your middleware and/or calling `super` in it, you might have to adapt the signature and the `super` call. Also, you might have to remove `{}` if you're pass `options` as a literal `Hash` or add `**` if you're using a variable.
1251
+ - `Grape::Middleware::Helpers` has been removed. The equivalent method `context` is now part of `Grape::Middleware::Base`.
1252
+
1253
+ #### Grape::Http::Headers, Grape::Util::Lazy::Object
1254
+
1255
+ Both have been removed. See [2554](https://github.com/ruby-grape/grape/pull/2554).
1256
+ Here are the notable changes:
1257
+
1258
+ - Constants like `HTTP_ACCEPT` have been replaced by their literal value.
1259
+ - `SUPPORTED_METHODS` has been moved to `Grape` module.
1260
+ - `HTTP_HEADERS` has been moved to `Grape::Request` and renamed `KNOWN_HEADERS`. The last has been refreshed with new headers, and it's not lazy anymore.
1261
+ - `SUPPORTED_METHODS_WITHOUT_OPTIONS` and `find_supported_method` have been removed.
1262
+
1263
+ #### Grape::Middleware::Base
1264
+
1265
+ - Constant `TEXT_HTML` has been removed in favor of using literal string 'text/html'.
1266
+ - `rack_request` and `query_params` have been added. Feel free to call these in your middlewares.
1267
+
1268
+ #### Params Builder
1269
+
1270
+ - Passing a class to `build_with` or `Grape.config.param_builder` has been deprecated in favor of a symbolized short_name. See `SHORTNAME_LOOKUP` in [params_builder](lib/grape/params_builder.rb).
1271
+ - Including Grape's extensions like `Grape::Extensions::Hashie::Mash::ParamBuilder` has been deprecated in favor of using `build_with` at the route level.
1272
+
1273
+ #### Accept Header Negotiation Harmonized
1274
+
1275
+ [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept) header is now fully interpreted through `Rack::Utils.best_q_match` which is following [RFC2616 14.1](https://datatracker.ietf.org/doc/html/rfc2616#section-14.1). Since [Grape 2.1.0](https://github.com/ruby-grape/grape/blob/master/CHANGELOG.md#210-20240615), the [header versioning strategy](https://github.com/ruby-grape/grape?tab=readme-ov-file#header) was adhering to it, but `Grape::Middleware::Formatter` never did.
1276
+
1277
+ Your API might act differently since it will strictly follow the [RFC2616 14.1](https://datatracker.ietf.org/doc/html/rfc2616#section-14.1) when interpreting the `Accept` header. Here are the differences:
1278
+
1279
+ ##### Invalid or missing quality ranking
1280
+ The following used to yield `application/xml` and now will yield `application/json` as the preferred media type:
1281
+ - `application/json;q=invalid,application/xml;q=0.5`
1282
+ - `application/json,application/xml;q=1.0`
1283
+
1284
+ For the invalid case, the value `invalid` was automatically `to_f` and `invalid.to_f` equals `0.0`. Now, since it doesn't match [Rack's regex](https://github.com/rack/rack/blob/3-1-stable/lib/rack/utils.rb#L138), its interpreted as non provided and its quality ranking equals 1.0.
1285
+
1286
+ For the non provided case, 1.0 was automatically assigned and in a case of multiple best matches, the first was returned based on Ruby's sort_by `quality`. Now, 1.0 is still assigned and the last is returned in case of multiple best matches. See [Rack's implementation](https://github.com/rack/rack/blob/e8f47608668d507e0f231a932fa37c9ca551c0a5/lib/rack/utils.rb#L167) of the RFC.
1287
+
1288
+ ##### Considering the closest generic when vendor tree
1289
+ Excluding the [header versioning strategy](https://github.com/ruby-grape/grape?tab=readme-ov-file#header), whenever a media type with the [vendor tree](https://datatracker.ietf.org/doc/html/rfc6838#section-3.2) leading facet `vnd.` like `application/vnd.api+json` was provided, Grape would also consider its closest generic when negotiating. In that case, `application/json` was added to the negotiation. Now, it will just consider the provided media types without considering any closest generics, and you'll need to [register](https://github.com/ruby-grape/grape?tab=readme-ov-file#api-formats) it.
1290
+ You can find the official vendor tree registrations on [IANA](https://www.iana.org/assignments/media-types/media-types.xhtml)
1291
+
1292
+ #### Custom Validators
1293
+
1294
+ If you now receive an error of `'Grape::Validations.require_validator': unknown validator: your_custom_validation (Grape::Exceptions::UnknownValidator)` after upgrading to 2.4.0 then you will need to ensure that you require the `your_custom_validation` file before your Grape API code is loaded.
1295
+
1296
+ See [2533](https://github.com/ruby-grape/grape/issues/2533) for more information.
1297
+
1298
+ ### Upgrading to >= 2.3.0
1299
+
1300
+ ### `content_type` vs `api.format` inside API
1301
+
1302
+ Before 2.3.0, `content_type` had priority over `env['api.format']` when set in an API, which was incorrect. The priority has been flipped and `env['api.format']` will be checked first.
1303
+ In addition, the function `api_format` has been added. Instead of setting `env['api.format']` directly, you can call `api_format`.
1304
+ See [#2506](https://github.com/ruby-grape/grape/pull/2506) for more information.
1305
+
1306
+ #### Remove Deprecated Methods and Options
1307
+
1308
+ - Deprecated `file` method has been removed. Use `send_file` or `stream`.
1309
+ See [#2500](https://github.com/ruby-grape/grape/pull/2500) for more information.
1310
+
1311
+ - The `except` and `proc` options have been removed from the `values` validator. Use `except_values` validator or assign `proc` directly to `values`.
1312
+ See [#2501](https://github.com/ruby-grape/grape/pull/2501) for more information.
1313
+
1314
+ - `Passing an options hash and a block to 'desc'` deprecation has been removed. Move all hash options to block instead.
1315
+ See [#2502](https://github.com/ruby-grape/grape/pull/2502) for more information.
1316
+
1317
+ ### Upgrading to >= 2.2.0
1318
+
1319
+ ### `Length` validator
1320
+
1321
+ After Grape 2.2.0, `length` validator will only take effect for parameters with types that support `#length` method, will not throw `ArgumentError` exception.
1322
+
1323
+ See [#2464](https://github.com/ruby-grape/grape/pull/2464) for more information.
1324
+
1325
+ ### Upgrading to >= 2.1.0
1326
+
1327
+ #### Optional Builder
1328
+
1329
+ The `builder` gem dependency has been made optional as it's only used when generating XML. If your code does, add `builder` to your `Gemfile`.
1330
+
1331
+ See [#2445](https://github.com/ruby-grape/grape/pull/2445) for more information.
1332
+
1333
+ #### Deep Merging of Parameter Attributes
1334
+
1335
+ Grape now uses `deep_merge` to combine parameter attributes within the `with` method. Previously, attributes defined at the parameter level would override those defined at the group level.
1336
+ With deep merge, attributes are now combined, allowing for more detailed and nuanced API specifications.
1337
+
1338
+ For example:
1339
+
1340
+ ```ruby
1341
+ with(documentation: { in: 'body' }) do
1342
+ optional :vault, documentation: { default: 33 }
1343
+ end
1344
+ ```
1345
+
1346
+ Before it was equivalent to:
1347
+
1348
+ ```ruby
1349
+ optional :vault, documentation: { default: 33 }
1350
+ ```
1351
+
1352
+ After it is an equivalent of:
1353
+
1354
+ ```ruby
1355
+ optional :vault, documentation: { in: 'body', default: 33 }
1356
+ ```
1357
+
1358
+ See [#2432](https://github.com/ruby-grape/grape/pull/2432) for more information.
1359
+
1360
+ #### Zeitwerk
1361
+
1362
+ Grape's autoloader has been updated and it's now based on [Zeitwerk](https://github.com/fxn/zeitwerk).
1363
+ If you MP (Monkey Patch) some files and you're not following the [file structure](https://github.com/fxn/zeitwerk?tab=readme-ov-file#file-structure), you might end up with a Zeitwerk error.
1364
+
1365
+ See [#2363](https://github.com/ruby-grape/grape/pull/2363) for more information.
1366
+
1367
+ #### Changes in rescue_from
1368
+
1369
+ The `rack_response` method has been deprecated and the `error_response` method has been removed. Use `error!` instead.
1370
+
1371
+ See [#2414](https://github.com/ruby-grape/grape/pull/2414) for more information.
1372
+
1373
+ #### Change in parameters precedence
1374
+
1375
+ When using together with `Grape::Extensions::Hash::ParamBuilder`, `route_param` takes higher precedence over a regular parameter defined with same name, which now matches the default param builder behavior.
1376
+
1377
+ This was a regression introduced by [#2326](https://github.com/ruby-grape/grape/pull/2326) in Grape v1.8.0.
1378
+
1379
+ ```ruby
1380
+ Grape.configure do |config|
1381
+ config.param_builder = Grape::Extensions::Hash::ParamBuilder
1382
+ end
1383
+
1384
+ params do
1385
+ requires :foo, type: String
1386
+ end
1387
+ route_param :foo do
1388
+ get do
1389
+ { value: params[:foo] }
1390
+ end
1391
+ end
1392
+ ```
1393
+
1394
+ Request:
1395
+
1396
+ ```bash
1397
+ curl -X POST -H "Content-Type: application/json" localhost:9292/bar -d '{"foo": "baz"}'
1398
+ ```
1399
+
1400
+ Response prior to v1.8.0:
1401
+
1402
+ ```json
1403
+ {
1404
+ "value": "bar"
1405
+ }
1406
+ ```
1407
+
1408
+ v1.8.0..v2.0.0:
1409
+
1410
+ ```json
1411
+ {
1412
+ "value": "baz"
1413
+ }
1414
+ ```
1415
+
1416
+ v2.1.0+:
1417
+
1418
+ ```json
1419
+ {
1420
+ "value": "bar"
1421
+ }
1422
+ ```
1423
+
1424
+ See [#2378](https://github.com/ruby-grape/grape/pull/2378) for details.
1425
+
1426
+ #### Grape::Router::Route.route_xxx methods have been removed
1427
+
1428
+ - `route_method` is accessible through `request_method`
1429
+ - `route_path` is accessible through `path`
1430
+ - Any other `route_xyz` are accessible through `options[xyz]`
1431
+
1432
+ #### Instance variables scope
1433
+
1434
+ Due to the changes done in [#2377](https://github.com/ruby-grape/grape/pull/2377), the instance variables defined inside each of the endpoints (or inside a `before` validator) are now accessible inside the `rescue_from`. The behavior of the instance variables was undefined until `2.1.0`.
1435
+
1436
+ If you were using the same variable name defined inside an endpoint or `before` validator inside a `rescue_from` handler, you need to take in mind that you can start getting different values or you can be overriding values.
1437
+
1438
+ Before:
1439
+ ```ruby
1440
+ class TwitterAPI < Grape::API
1441
+ before do
1442
+ @var = 1
1443
+ end
1444
+
1445
+ get '/' do
1446
+ puts @var # => 1
1447
+ raise
1448
+ end
1449
+
1450
+ rescue_from :all do
1451
+ puts @var # => nil
1452
+ end
1453
+ end
1454
+ ```
1455
+
1456
+ After:
1457
+ ```ruby
1458
+ class TwitterAPI < Grape::API
1459
+ before do
1460
+ @var = 1
1461
+ end
1462
+
1463
+ get '/' do
1464
+ puts @var # => 1
1465
+ raise
1466
+ end
1467
+
1468
+ rescue_from :all do
1469
+ puts @var # => 1
1470
+ end
1471
+ end
1472
+ ```
1473
+
1474
+ #### Recognizing Path
1475
+
1476
+ Grape now considers the types of the configured `route_params` in order to determine the endpoint that matches with the performed request.
1477
+
1478
+ So taking into account this `Grape::API` class
1479
+
1480
+ ```ruby
1481
+ class Books < Grape::API
1482
+ resource :books do
1483
+ route_param :id, type: Integer do
1484
+ # GET /books/:id
1485
+ get do
1486
+ #...
1487
+ end
1488
+ end
1489
+
1490
+ resource :share do
1491
+ # POST /books/share
1492
+ post do
1493
+ # ....
1494
+ end
1495
+ end
1496
+ end
1497
+ end
1498
+ ```
1499
+
1500
+ Before:
1501
+ ```ruby
1502
+ API.recognize_path '/books/1' # => /books/:id
1503
+ API.recognize_path '/books/share' # => /books/:id
1504
+ API.recognize_path '/books/other' # => /books/:id
1505
+ ```
1506
+
1507
+ After:
1508
+ ```ruby
1509
+ API.recognize_path '/books/1' # => /books/:id
1510
+ API.recognize_path '/books/share' # => /books/share
1511
+ API.recognize_path '/books/other' # => nil
1512
+ ```
1513
+
1514
+ This implies that before this changes, when you performed `/books/other` and it matched with the `/books/:id` endpoint, you get a `400 Bad Request` response because the type of the provided `:id` param was not an `Integer`. However, after upgrading to version `2.1.0` you will get a `404 Not Found` response, because there is not a defined endpoint that matches with `/books/other`.
1515
+
1516
+ See [#2379](https://github.com/ruby-grape/grape/pull/2379) for more information.
1517
+
1518
+ ### Upgrading to >= 2.0.0
1519
+
1520
+ #### Headers
1521
+
1522
+ As per [rack/rack#1592](https://github.com/rack/rack/issues/1592) Rack 3 is following the HTTP/2+ semantics which require header names to be lower case. To avoid compatibility issues, starting with Grape 1.9.0, headers will be cased based on what version of Rack you are using.
1523
+
1524
+ Given this request:
1525
+
1526
+ ```shell
1527
+ curl -H "Content-Type: application/json" -H "Secret-Password: foo" ...
1528
+ ```
1529
+
1530
+ If you are using Rack 3 in your application then the headers will be set to:
1531
+
1532
+ ```ruby
1533
+ { "content-type" => "application/json", "secret-password" => "foo"}
1534
+ ```
1535
+
1536
+ This means if you are checking for header values in your application, you would need to change your code to use downcased keys.
1537
+
1538
+ ```ruby
1539
+ get do
1540
+ # This would use headers['Secret-Password'] in Rack < 3
1541
+ error!('Unauthorized', 401) unless headers['secret-password'] == 'swordfish'
1542
+ end
1543
+ ```
1544
+
1545
+ See [#2355](https://github.com/ruby-grape/grape/pull/2355) for more information.
1546
+
1547
+ #### Digest auth deprecation
1548
+
1549
+ Digest auth has been removed along with the deprecation of `Rack::Auth::Digest` in Rack 3.
1550
+
1551
+ See [#2294](https://github.com/ruby-grape/grape/issues/2294) for more information.
1552
+
4
1553
  ### Upgrading to >= 1.7.0
5
1554
 
6
1555
  #### Exceptions renaming
@@ -230,7 +1779,7 @@ class Api < Grape::API
230
1779
  params[:my_param]
231
1780
  end
232
1781
  get '/example', params: { my_param: nil }
233
- # 1.3.1 = []
1782
+ # 1.3.3 = []
234
1783
  # 1.3.2 = nil
235
1784
  end
236
1785
  ```
@@ -439,8 +1988,7 @@ end
439
1988
 
440
1989
  ##### `name` (and other caveats) of the mounted API
441
1990
 
442
- After the patch, the mounted API is no longer a Named class inheriting from `Grape::API`, it is an anonymous class
443
- which inherit from `Grape::API::Instance`.
1991
+ After the patch, the mounted API is no longer a Named class inheriting from `Grape::API`, it is an anonymous class which inherit from `Grape::API::Instance`.
444
1992
 
445
1993
  What this means in practice, is:
446
1994
 
@@ -820,8 +2368,7 @@ See [#1114](https://github.com/ruby-grape/grape/pull/1114) for more information.
820
2368
 
821
2369
  #### Bypasses formatters when status code indicates no content
822
2370
 
823
- To be consistent with rack and it's handling of standard responses associated with no content, both default and custom formatters will now
824
- be bypassed when processing responses for status codes defined [by rack](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567)
2371
+ To be consistent with rack and it's handling of standard responses associated with no content, both default and custom formatters will now be bypassed when processing responses for status codes defined [by rack](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567)
825
2372
 
826
2373
  See [#1190](https://github.com/ruby-grape/grape/pull/1190) for more information.
827
2374
 
@@ -1262,8 +2809,7 @@ As replacement can be used
1262
2809
  * `Grape::Middleware::Auth::Digest` => [`Rack::Auth::Digest::MD5`](https://github.com/rack/rack/blob/master/lib/rack/auth/digest/md5.rb)
1263
2810
  * `Grape::Middleware::Auth::OAuth2` => [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2](https://github.com/nov/rack-oauth2)
1264
2811
 
1265
- If this is not possible you can extract the middleware files from [grape v0.7.0](https://github.com/ruby-grape/grape/tree/v0.7.0/lib/grape/middleware/auth)
1266
- and host these files within your application
2812
+ If this is not possible you can extract the middleware files from [grape v0.7.0](https://github.com/ruby-grape/grape/tree/v0.7.0/lib/grape/middleware/auth) and host these files within your application
1267
2813
 
1268
2814
  See [#703](https://github.com/ruby-grape/Grape/pull/703) for more information.
1269
2815