grape 2.0.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +151 -1
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +404 -334
  5. data/UPGRADING.md +279 -7
  6. data/grape.gemspec +8 -8
  7. data/lib/grape/api/instance.rb +34 -66
  8. data/lib/grape/api.rb +47 -70
  9. data/lib/grape/content_types.rb +13 -10
  10. data/lib/grape/cookies.rb +31 -24
  11. data/lib/grape/dry_types.rb +0 -2
  12. data/lib/grape/dsl/api.rb +0 -2
  13. data/lib/grape/dsl/desc.rb +49 -44
  14. data/lib/grape/dsl/headers.rb +2 -2
  15. data/lib/grape/dsl/helpers.rb +8 -4
  16. data/lib/grape/dsl/inside_route.rb +67 -54
  17. data/lib/grape/dsl/parameters.rb +10 -9
  18. data/lib/grape/dsl/request_response.rb +14 -18
  19. data/lib/grape/dsl/routing.rb +34 -17
  20. data/lib/grape/dsl/validations.rb +13 -0
  21. data/lib/grape/endpoint.rb +120 -118
  22. data/lib/grape/{util/env.rb → env.rb} +0 -5
  23. data/lib/grape/error_formatter/base.rb +51 -21
  24. data/lib/grape/error_formatter/json.rb +7 -15
  25. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  26. data/lib/grape/error_formatter/txt.rb +11 -17
  27. data/lib/grape/error_formatter/xml.rb +3 -13
  28. data/lib/grape/error_formatter.rb +5 -25
  29. data/lib/grape/exceptions/base.rb +18 -30
  30. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  31. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  32. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  33. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  34. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  35. data/lib/grape/exceptions/validation.rb +5 -6
  36. data/lib/grape/exceptions/validation_array_errors.rb +1 -0
  37. data/lib/grape/exceptions/validation_errors.rb +4 -6
  38. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
  39. data/lib/grape/extensions/hash.rb +7 -2
  40. data/lib/grape/extensions/hashie/mash.rb +3 -5
  41. data/lib/grape/formatter/base.rb +16 -0
  42. data/lib/grape/formatter/json.rb +4 -6
  43. data/lib/grape/formatter/serializable_hash.rb +1 -1
  44. data/lib/grape/formatter/txt.rb +3 -5
  45. data/lib/grape/formatter/xml.rb +4 -6
  46. data/lib/grape/formatter.rb +7 -25
  47. data/lib/grape/{util/json.rb → json.rb} +1 -3
  48. data/lib/grape/locale/en.yml +46 -42
  49. data/lib/grape/middleware/auth/base.rb +11 -34
  50. data/lib/grape/middleware/auth/dsl.rb +23 -31
  51. data/lib/grape/middleware/base.rb +41 -23
  52. data/lib/grape/middleware/error.rb +77 -76
  53. data/lib/grape/middleware/formatter.rb +48 -79
  54. data/lib/grape/middleware/globals.rb +1 -3
  55. data/lib/grape/middleware/stack.rb +26 -37
  56. data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
  57. data/lib/grape/middleware/versioner/base.rb +74 -0
  58. data/lib/grape/middleware/versioner/header.rb +59 -126
  59. data/lib/grape/middleware/versioner/param.rb +4 -25
  60. data/lib/grape/middleware/versioner/path.rb +10 -34
  61. data/lib/grape/middleware/versioner.rb +7 -14
  62. data/lib/grape/namespace.rb +4 -5
  63. data/lib/grape/params_builder/base.rb +18 -0
  64. data/lib/grape/params_builder/hash.rb +11 -0
  65. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  66. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  67. data/lib/grape/params_builder.rb +32 -0
  68. data/lib/grape/parser/base.rb +16 -0
  69. data/lib/grape/parser/json.rb +6 -8
  70. data/lib/grape/parser/xml.rb +6 -8
  71. data/lib/grape/parser.rb +5 -23
  72. data/lib/grape/path.rb +38 -60
  73. data/lib/grape/request.rb +161 -30
  74. data/lib/grape/router/base_route.rb +39 -0
  75. data/lib/grape/router/greedy_route.rb +20 -0
  76. data/lib/grape/router/pattern.rb +45 -31
  77. data/lib/grape/router/route.rb +28 -57
  78. data/lib/grape/router.rb +56 -43
  79. data/lib/grape/util/base_inheritable.rb +4 -4
  80. data/lib/grape/util/cache.rb +0 -3
  81. data/lib/grape/util/endpoint_configuration.rb +1 -1
  82. data/lib/grape/util/header.rb +13 -0
  83. data/lib/grape/util/inheritable_values.rb +0 -2
  84. data/lib/grape/util/lazy/block.rb +29 -0
  85. data/lib/grape/util/lazy/value.rb +38 -0
  86. data/lib/grape/util/lazy/value_array.rb +21 -0
  87. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  88. data/lib/grape/util/lazy/value_hash.rb +21 -0
  89. data/lib/grape/util/media_type.rb +70 -0
  90. data/lib/grape/util/registry.rb +27 -0
  91. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  92. data/lib/grape/util/stackable_values.rb +1 -6
  93. data/lib/grape/util/strict_hash_configuration.rb +3 -3
  94. data/lib/grape/validations/attributes_doc.rb +38 -36
  95. data/lib/grape/validations/attributes_iterator.rb +1 -0
  96. data/lib/grape/validations/contract_scope.rb +34 -0
  97. data/lib/grape/validations/params_scope.rb +36 -32
  98. data/lib/grape/validations/types/array_coercer.rb +0 -2
  99. data/lib/grape/validations/types/dry_type_coercer.rb +9 -15
  100. data/lib/grape/validations/types/json.rb +0 -2
  101. data/lib/grape/validations/types/primitive_coercer.rb +0 -2
  102. data/lib/grape/validations/types/set_coercer.rb +0 -3
  103. data/lib/grape/validations/types.rb +0 -3
  104. data/lib/grape/validations/validator_factory.rb +2 -2
  105. data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
  106. data/lib/grape/validations/validators/base.rb +8 -11
  107. data/lib/grape/validations/validators/coerce_validator.rb +1 -1
  108. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  109. data/lib/grape/validations/validators/default_validator.rb +6 -2
  110. data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
  111. data/lib/grape/validations/validators/except_values_validator.rb +2 -2
  112. data/lib/grape/validations/validators/length_validator.rb +49 -0
  113. data/lib/grape/validations/validators/presence_validator.rb +1 -1
  114. data/lib/grape/validations/validators/regexp_validator.rb +2 -2
  115. data/lib/grape/validations/validators/values_validator.rb +20 -57
  116. data/lib/grape/validations.rb +8 -21
  117. data/lib/grape/version.rb +1 -1
  118. data/lib/grape/{util/xml.rb → xml.rb} +1 -1
  119. data/lib/grape.rb +42 -274
  120. metadata +45 -44
  121. data/lib/grape/eager_load.rb +0 -20
  122. data/lib/grape/http/headers.rb +0 -71
  123. data/lib/grape/middleware/helpers.rb +0 -12
  124. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  125. data/lib/grape/router/attribute_translator.rb +0 -63
  126. data/lib/grape/util/lazy_block.rb +0 -27
  127. data/lib/grape/util/lazy_object.rb +0 -43
  128. data/lib/grape/util/lazy_value.rb +0 -91
  129. data/lib/grape/util/registrable.rb +0 -15
  130. data/lib/grape/validations/types/build_coercer.rb +0 -94
data/UPGRADING.md CHANGED
@@ -1,6 +1,281 @@
1
1
  Upgrading Grape
2
2
  ===============
3
3
 
4
+ ### Upgrading to >= 2.4.0
5
+
6
+ #### Grape::Middleware::Auth::Base
7
+ `type` is now validated at compile time and will raise a `Grape::Exceptions::UnknownAuthStrategy` if unknown.
8
+
9
+ #### Grape::Middleware::Base
10
+
11
+ - 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.
12
+ - `Grape::Middleware::Helpers` has been removed. The equivalent method `context` is now part of `Grape::Middleware::Base`.
13
+
14
+ #### Grape::Http::Headers, Grape::Util::Lazy::Object
15
+
16
+ Both have been removed. See [2554](https://github.com/ruby-grape/grape/pull/2554).
17
+ Here are the notable changes:
18
+
19
+ - Constants like `HTTP_ACCEPT` have been replaced by their literal value.
20
+ - `SUPPORTED_METHODS` has been moved to `Grape` module.
21
+ - `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.
22
+ - `SUPPORTED_METHODS_WITHOUT_OPTIONS` and `find_supported_method` have been removed.
23
+
24
+ #### Grape::Middleware::Base
25
+
26
+ - Constant `TEXT_HTML` has been removed in favor of using literal string 'text/html'.
27
+ - `rack_request` and `query_params` have been added. Feel free to call these in your middlewares.
28
+
29
+ #### Params Builder
30
+
31
+ - 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).
32
+ - Including Grape's extensions like `Grape::Extensions::Hashie::Mash::ParamBuilder` has been deprecated in favor of using `build_with` at the route level.
33
+
34
+ #### Accept Header Negotiation Harmonized
35
+
36
+ [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.
37
+
38
+ 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:
39
+
40
+ ##### Invalid or missing quality ranking
41
+ The following used to yield `application/xml` and now will yield `application/json` as the preferred media type:
42
+ - `application/json;q=invalid,application/xml;q=0.5`
43
+ - `application/json,application/xml;q=1.0`
44
+
45
+ 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.
46
+
47
+ 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.
48
+
49
+ ##### Considering the closest generic when vendor tree
50
+ 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.
51
+ You can find the official vendor tree registrations on [IANA](https://www.iana.org/assignments/media-types/media-types.xhtml)
52
+
53
+ #### Custom Validators
54
+
55
+ 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.
56
+
57
+ See [2533](https://github.com/ruby-grape/grape/issues/2533) for more information.
58
+
59
+ ### Upgrading to >= 2.3.0
60
+
61
+ ### `content_type` vs `api.format` inside API
62
+
63
+ 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.
64
+ In addition, the function `api_format` has been added. Instead of setting `env['api.format']` directly, you can call `api_format`.
65
+ See [#2506](https://github.com/ruby-grape/grape/pull/2506) for more information.
66
+
67
+ #### Remove Deprecated Methods and Options
68
+
69
+ - Deprecated `file` method has been removed. Use `send_file` or `stream`.
70
+ See [#2500](https://github.com/ruby-grape/grape/pull/2500) for more information.
71
+
72
+ - The `except` and `proc` options have been removed from the `values` validator. Use `except_values` validator or assign `proc` directly to `values`.
73
+ See [#2501](https://github.com/ruby-grape/grape/pull/2501) for more information.
74
+
75
+ - `Passing an options hash and a block to 'desc'` deprecation has been removed. Move all hash options to block instead.
76
+ See [#2502](https://github.com/ruby-grape/grape/pull/2502) for more information.
77
+
78
+ ### Upgrading to >= 2.2.0
79
+
80
+ ### `Length` validator
81
+
82
+ After Grape 2.2.0, `length` validator will only take effect for parameters with types that support `#length` method, will not throw `ArgumentError` exception.
83
+
84
+ See [#2464](https://github.com/ruby-grape/grape/pull/2464) for more information.
85
+
86
+ ### Upgrading to >= 2.1.0
87
+
88
+ #### Optional Builder
89
+
90
+ 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`.
91
+
92
+ See [#2445](https://github.com/ruby-grape/grape/pull/2445) for more information.
93
+
94
+ #### Deep Merging of Parameter Attributes
95
+
96
+ 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.
97
+ With deep merge, attributes are now combined, allowing for more detailed and nuanced API specifications.
98
+
99
+ For example:
100
+
101
+ ```ruby
102
+ with(documentation: { in: 'body' }) do
103
+ optional :vault, documentation: { default: 33 }
104
+ end
105
+ ```
106
+
107
+ Before it was equivalent to:
108
+
109
+ ```ruby
110
+ optional :vault, documentation: { default: 33 }
111
+ ```
112
+
113
+ After it is an equivalent of:
114
+
115
+ ```ruby
116
+ optional :vault, documentation: { in: 'body', default: 33 }
117
+ ```
118
+
119
+ See [#2432](https://github.com/ruby-grape/grape/pull/2432) for more information.
120
+
121
+ #### Zeitwerk
122
+
123
+ Grape's autoloader has been updated and it's now based on [Zeitwerk](https://github.com/fxn/zeitwerk).
124
+ 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.
125
+
126
+ See [#2363](https://github.com/ruby-grape/grape/pull/2363) for more information.
127
+
128
+ #### Changes in rescue_from
129
+
130
+ The `rack_response` method has been deprecated and the `error_response` method has been removed. Use `error!` instead.
131
+
132
+ See [#2414](https://github.com/ruby-grape/grape/pull/2414) for more information.
133
+
134
+ #### Change in parameters precedence
135
+
136
+ 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.
137
+
138
+ This was a regression introduced by [#2326](https://github.com/ruby-grape/grape/pull/2326) in Grape v1.8.0.
139
+
140
+ ```ruby
141
+ Grape.configure do |config|
142
+ config.param_builder = Grape::Extensions::Hash::ParamBuilder
143
+ end
144
+
145
+ params do
146
+ requires :foo, type: String
147
+ end
148
+ route_param :foo do
149
+ get do
150
+ { value: params[:foo] }
151
+ end
152
+ end
153
+ ```
154
+
155
+ Request:
156
+
157
+ ```bash
158
+ curl -X POST -H "Content-Type: application/json" localhost:9292/bar -d '{"foo": "baz"}'
159
+ ```
160
+
161
+ Response prior to v1.8.0:
162
+
163
+ ```json
164
+ {
165
+ "value": "bar"
166
+ }
167
+ ```
168
+
169
+ v1.8.0..v2.0.0:
170
+
171
+ ```json
172
+ {
173
+ "value": "baz"
174
+ }
175
+ ```
176
+
177
+ v2.1.0+:
178
+
179
+ ```json
180
+ {
181
+ "value": "bar"
182
+ }
183
+ ```
184
+
185
+ See [#2378](https://github.com/ruby-grape/grape/pull/2378) for details.
186
+
187
+ #### Grape::Router::Route.route_xxx methods have been removed
188
+
189
+ - `route_method` is accessible through `request_method`
190
+ - `route_path` is accessible through `path`
191
+ - Any other `route_xyz` are accessible through `options[xyz]`
192
+
193
+ #### Instance variables scope
194
+
195
+ 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`.
196
+
197
+ 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.
198
+
199
+ Before:
200
+ ```ruby
201
+ class TwitterAPI < Grape::API
202
+ before do
203
+ @var = 1
204
+ end
205
+
206
+ get '/' do
207
+ puts @var # => 1
208
+ raise
209
+ end
210
+
211
+ rescue_from :all do
212
+ puts @var # => nil
213
+ end
214
+ end
215
+ ```
216
+
217
+ After:
218
+ ```ruby
219
+ class TwitterAPI < Grape::API
220
+ before do
221
+ @var = 1
222
+ end
223
+
224
+ get '/' do
225
+ puts @var # => 1
226
+ raise
227
+ end
228
+
229
+ rescue_from :all do
230
+ puts @var # => 1
231
+ end
232
+ end
233
+ ```
234
+
235
+ #### Recognizing Path
236
+
237
+ Grape now considers the types of the configured `route_params` in order to determine the endpoint that matches with the performed request.
238
+
239
+ So taking into account this `Grape::API` class
240
+
241
+ ```ruby
242
+ class Books < Grape::API
243
+ resource :books do
244
+ route_param :id, type: Integer do
245
+ # GET /books/:id
246
+ get do
247
+ #...
248
+ end
249
+ end
250
+
251
+ resource :share do
252
+ # POST /books/share
253
+ post do
254
+ # ....
255
+ end
256
+ end
257
+ end
258
+ end
259
+ ```
260
+
261
+ Before:
262
+ ```ruby
263
+ API.recognize_path '/books/1' # => /books/:id
264
+ API.recognize_path '/books/share' # => /books/:id
265
+ API.recognize_path '/books/other' # => /books/:id
266
+ ```
267
+
268
+ After:
269
+ ```ruby
270
+ API.recognize_path '/books/1' # => /books/:id
271
+ API.recognize_path '/books/share' # => /books/share
272
+ API.recognize_path '/books/other' # => nil
273
+ ```
274
+
275
+ 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`.
276
+
277
+ See [#2379](https://github.com/ruby-grape/grape/pull/2379) for more information.
278
+
4
279
  ### Upgrading to >= 2.0.0
5
280
 
6
281
  #### Headers
@@ -19,7 +294,7 @@ If you are using Rack 3 in your application then the headers will be set to:
19
294
  { "content-type" => "application/json", "secret-password" => "foo"}
20
295
  ```
21
296
 
22
- This means if you are checking for header values in your application, you would need to change your code to use downcased keys.
297
+ This means if you are checking for header values in your application, you would need to change your code to use downcased keys.
23
298
 
24
299
  ```ruby
25
300
  get do
@@ -474,8 +749,7 @@ end
474
749
 
475
750
  ##### `name` (and other caveats) of the mounted API
476
751
 
477
- After the patch, the mounted API is no longer a Named class inheriting from `Grape::API`, it is an anonymous class
478
- which inherit from `Grape::API::Instance`.
752
+ 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`.
479
753
 
480
754
  What this means in practice, is:
481
755
 
@@ -855,8 +1129,7 @@ See [#1114](https://github.com/ruby-grape/grape/pull/1114) for more information.
855
1129
 
856
1130
  #### Bypasses formatters when status code indicates no content
857
1131
 
858
- To be consistent with rack and it's handling of standard responses associated with no content, both default and custom formatters will now
859
- be bypassed when processing responses for status codes defined [by rack](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567)
1132
+ 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)
860
1133
 
861
1134
  See [#1190](https://github.com/ruby-grape/grape/pull/1190) for more information.
862
1135
 
@@ -1297,8 +1570,7 @@ As replacement can be used
1297
1570
  * `Grape::Middleware::Auth::Digest` => [`Rack::Auth::Digest::MD5`](https://github.com/rack/rack/blob/master/lib/rack/auth/digest/md5.rb)
1298
1571
  * `Grape::Middleware::Auth::OAuth2` => [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2](https://github.com/nov/rack-oauth2)
1299
1572
 
1300
- 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)
1301
- and host these files within your application
1573
+ 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
1302
1574
 
1303
1575
  See [#703](https://github.com/ruby-grape/Grape/pull/703) for more information.
1304
1576
 
data/grape.gemspec CHANGED
@@ -17,17 +17,17 @@ Gem::Specification.new do |s|
17
17
  'bug_tracker_uri' => 'https://github.com/ruby-grape/grape/issues',
18
18
  'changelog_uri' => "https://github.com/ruby-grape/grape/blob/v#{s.version}/CHANGELOG.md",
19
19
  'documentation_uri' => "https://www.rubydoc.info/gems/grape/#{s.version}",
20
- 'source_code_uri' => "https://github.com/ruby-grape/grape/tree/v#{s.version}"
20
+ 'source_code_uri' => "https://github.com/ruby-grape/grape/tree/v#{s.version}",
21
+ 'rubygems_mfa_required' => 'true'
21
22
  }
22
23
 
23
- s.add_runtime_dependency 'activesupport', '>= 5'
24
- s.add_runtime_dependency 'builder'
25
- s.add_runtime_dependency 'dry-types', '>= 1.1'
26
- s.add_runtime_dependency 'mustermann-grape', '~> 1.0.0'
27
- s.add_runtime_dependency 'rack', '>= 1.3.0'
28
- s.add_runtime_dependency 'rack-accept'
24
+ s.add_dependency 'activesupport', '>= 6.1'
25
+ s.add_dependency 'dry-types', '>= 1.1'
26
+ s.add_dependency 'mustermann-grape', '~> 1.1.0'
27
+ s.add_dependency 'rack', '>= 2'
28
+ s.add_dependency 'zeitwerk'
29
29
 
30
30
  s.files = Dir['lib/**/*', 'CHANGELOG.md', 'CONTRIBUTING.md', 'README.md', 'grape.png', 'UPGRADING.md', 'LICENSE', 'grape.gemspec']
31
31
  s.require_paths = ['lib']
32
- s.required_ruby_version = '>= 2.6.0'
32
+ s.required_ruby_version = '>= 2.7.0'
33
33
  end
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/router'
4
-
5
3
  module Grape
6
4
  class API
7
5
  # The API Instance class, is the engine behind Grape::API. Each class that inherits
8
6
  # from this will represent a different API instance
9
7
  class Instance
8
+ extend Grape::Middleware::Auth::DSL
10
9
  include Grape::DSL::API
11
10
 
12
11
  class << self
@@ -48,7 +47,7 @@ module Grape
48
47
  # Parses the API's definition and compiles it into an instance of
49
48
  # Grape::API.
50
49
  def compile
51
- @instance ||= new
50
+ @instance ||= new # rubocop:disable Naming/MemoizedInstanceVariableName
52
51
  end
53
52
 
54
53
  # Wipe the compiled API so we can recompile after changes were made.
@@ -112,9 +111,9 @@ module Grape
112
111
  end
113
112
 
114
113
  def evaluate_as_instance_with_configuration(block, lazy: false)
115
- lazy_block = Grape::Util::LazyBlock.new do |configuration|
114
+ lazy_block = Grape::Util::Lazy::Block.new do |configuration|
116
115
  value_for_configuration = configuration
117
- self.configuration = value_for_configuration.evaluate if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
116
+ self.configuration = value_for_configuration.evaluate if value_for_configuration.try(:lazy?)
118
117
  response = instance_eval(&block)
119
118
  self.configuration = value_for_configuration
120
119
  response
@@ -127,6 +126,7 @@ module Grape
127
126
  end
128
127
 
129
128
  def inherited(subclass)
129
+ super
130
130
  subclass.reset!
131
131
  subclass.logger = logger.clone
132
132
  end
@@ -162,9 +162,13 @@ module Grape
162
162
 
163
163
  # Handle a request. See Rack documentation for what `env` is.
164
164
  def call(env)
165
- result = @router.call(env)
166
- result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade?
167
- result
165
+ status, headers, response = @router.call(env)
166
+ unless cascade?
167
+ headers = Grape::Util::Header.new.merge(headers)
168
+ headers.delete('X-Cascade')
169
+ end
170
+
171
+ [status, headers, response]
168
172
  end
169
173
 
170
174
  # Some requests may return a HTTP 404 error if grape cannot find a matching
@@ -191,88 +195,52 @@ module Grape
191
195
  # will return an HTTP 405 response for any HTTP method that the resource
192
196
  # cannot handle.
193
197
  def add_head_not_allowed_methods_and_options_methods
194
- versioned_route_configs = collect_route_config_per_pattern
195
198
  # The paths we collected are prepared (cf. Path#prepare), so they
196
199
  # contain already versioning information when using path versioning.
200
+ all_routes = self.class.endpoints.map(&:routes).flatten
201
+
197
202
  # Disable versioning so adding a route won't prepend versioning
198
203
  # informations again.
199
- without_root_prefix do
200
- without_versioning do
201
- versioned_route_configs.each do |config|
202
- next if config[:options][:matching_wildchar]
203
-
204
- allowed_methods = config[:methods].dup
205
-
206
- allowed_methods |= [Grape::Http::Headers::HEAD] if !self.class.namespace_inheritable(:do_not_route_head) && allowed_methods.include?(Grape::Http::Headers::GET)
204
+ without_root_prefix_and_versioning { collect_route_config_per_pattern(all_routes) }
205
+ end
207
206
 
208
- allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods)
207
+ def collect_route_config_per_pattern(all_routes)
208
+ routes_by_regexp = all_routes.group_by(&:pattern_regexp)
209
209
 
210
- config[:endpoint].options[:options_route_enabled] = true unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
210
+ # Build the configuration based on the first endpoint and the collection of methods supported.
211
+ routes_by_regexp.each_value do |routes|
212
+ last_route = routes.last # Most of the configuration is taken from the last endpoint
213
+ next if routes.any? { |route| route.request_method == '*' }
211
214
 
212
- attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
213
- generate_not_allowed_method(config[:pattern], **attributes)
214
- end
215
- end
216
- end
217
- end
215
+ allowed_methods = routes.map(&:request_method)
216
+ allowed_methods |= [Rack::HEAD] if !self.class.namespace_inheritable(:do_not_route_head) && allowed_methods.include?(Rack::GET)
218
217
 
219
- def collect_route_config_per_pattern
220
- all_routes = self.class.endpoints.map(&:routes).flatten
221
- routes_by_regexp = all_routes.group_by { |route| route.pattern.to_regexp }
218
+ allow_header = self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Rack::OPTIONS] | allowed_methods
219
+ last_route.app.options[:options_route_enabled] = true unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Rack::OPTIONS)
222
220
 
223
- # Build the configuration based on the first endpoint and the collection of methods supported.
224
- routes_by_regexp.values.map do |routes|
225
- last_route = routes.last # Most of the configuration is taken from the last endpoint
226
- matching_wildchar = routes.any? { |route| route.request_method == '*' }
227
- {
228
- options: { matching_wildchar: matching_wildchar },
229
- pattern: last_route.pattern,
230
- requirements: last_route.requirements,
231
- path: last_route.origin,
232
- endpoint: last_route.app,
233
- methods: matching_wildchar ? Grape::Http::Headers::SUPPORTED_METHODS : routes.map(&:request_method)
234
- }
221
+ @router.associate_routes(last_route.pattern, {
222
+ endpoint: last_route.app,
223
+ allow_header: allow_header
224
+ })
235
225
  end
236
226
  end
237
227
 
238
- # Generate a route that returns an HTTP 405 response for a user defined
239
- # path on methods not specified
240
- def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
241
- supported_methods =
242
- if self.class.namespace_inheritable(:do_not_route_options)
243
- Grape::Http::Headers::SUPPORTED_METHODS
244
- else
245
- Grape::Http::Headers::SUPPORTED_METHODS_WITHOUT_OPTIONS
246
- end
247
- not_allowed_methods = supported_methods - allowed_methods
248
- @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
249
- end
250
-
251
228
  # Allows definition of endpoints that ignore the versioning configuration
252
229
  # used by the rest of your API.
253
- def without_versioning(&_block)
230
+ def without_root_prefix_and_versioning
254
231
  old_version = self.class.namespace_inheritable(:version)
255
232
  old_version_options = self.class.namespace_inheritable(:version_options)
233
+ old_root_prefix = self.class.namespace_inheritable(:root_prefix)
256
234
 
257
235
  self.class.namespace_inheritable_to_nil(:version)
258
236
  self.class.namespace_inheritable_to_nil(:version_options)
237
+ self.class.namespace_inheritable_to_nil(:root_prefix)
259
238
 
260
239
  yield
261
240
 
262
241
  self.class.namespace_inheritable(:version, old_version)
263
242
  self.class.namespace_inheritable(:version_options, old_version_options)
264
- end
265
-
266
- # Allows definition of endpoints that ignore the root prefix used by the
267
- # rest of your API.
268
- def without_root_prefix(&_block)
269
- old_prefix = self.class.namespace_inheritable(:root_prefix)
270
-
271
- self.class.namespace_inheritable_to_nil(:root_prefix)
272
-
273
- yield
274
-
275
- self.class.namespace_inheritable(:root_prefix, old_prefix)
243
+ self.class.namespace_inheritable(:root_prefix, old_root_prefix)
276
244
  end
277
245
  end
278
246
  end