grape 1.3.0 → 1.5.2

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +119 -1
  3. data/LICENSE +1 -1
  4. data/README.md +123 -29
  5. data/UPGRADING.md +265 -39
  6. data/lib/grape/api/instance.rb +32 -31
  7. data/lib/grape/api.rb +5 -5
  8. data/lib/grape/content_types.rb +34 -0
  9. data/lib/grape/dsl/callbacks.rb +1 -1
  10. data/lib/grape/dsl/helpers.rb +2 -1
  11. data/lib/grape/dsl/inside_route.rb +77 -43
  12. data/lib/grape/dsl/parameters.rb +12 -8
  13. data/lib/grape/dsl/routing.rb +12 -11
  14. data/lib/grape/dsl/validations.rb +18 -1
  15. data/lib/grape/eager_load.rb +1 -1
  16. data/lib/grape/endpoint.rb +8 -6
  17. data/lib/grape/exceptions/base.rb +0 -4
  18. data/lib/grape/exceptions/validation.rb +1 -1
  19. data/lib/grape/exceptions/validation_errors.rb +12 -13
  20. data/lib/grape/http/headers.rb +26 -0
  21. data/lib/grape/middleware/auth/base.rb +3 -3
  22. data/lib/grape/middleware/base.rb +4 -5
  23. data/lib/grape/middleware/error.rb +11 -13
  24. data/lib/grape/middleware/formatter.rb +3 -3
  25. data/lib/grape/middleware/stack.rb +10 -2
  26. data/lib/grape/middleware/versioner/header.rb +4 -4
  27. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  28. data/lib/grape/middleware/versioner/path.rb +1 -1
  29. data/lib/grape/namespace.rb +12 -2
  30. data/lib/grape/path.rb +13 -3
  31. data/lib/grape/request.rb +13 -8
  32. data/lib/grape/router/attribute_translator.rb +26 -5
  33. data/lib/grape/router/pattern.rb +17 -16
  34. data/lib/grape/router/route.rb +5 -24
  35. data/lib/grape/router.rb +26 -30
  36. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  37. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  38. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  39. data/lib/grape/util/base_inheritable.rb +15 -8
  40. data/lib/grape/util/cache.rb +20 -0
  41. data/lib/grape/util/lazy_object.rb +43 -0
  42. data/lib/grape/util/lazy_value.rb +1 -0
  43. data/lib/grape/util/reverse_stackable_values.rb +2 -0
  44. data/lib/grape/util/stackable_values.rb +7 -20
  45. data/lib/grape/validations/attributes_iterator.rb +8 -0
  46. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  47. data/lib/grape/validations/params_scope.rb +10 -8
  48. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  49. data/lib/grape/validations/types/array_coercer.rb +14 -5
  50. data/lib/grape/validations/types/build_coercer.rb +5 -8
  51. data/lib/grape/validations/types/custom_type_coercer.rb +16 -2
  52. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  53. data/lib/grape/validations/types/file.rb +15 -12
  54. data/lib/grape/validations/types/invalid_value.rb +24 -0
  55. data/lib/grape/validations/types/json.rb +40 -36
  56. data/lib/grape/validations/types/primitive_coercer.rb +15 -6
  57. data/lib/grape/validations/types/set_coercer.rb +6 -4
  58. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  59. data/lib/grape/validations/types.rb +7 -9
  60. data/lib/grape/validations/validator_factory.rb +1 -1
  61. data/lib/grape/validations/validators/as.rb +1 -1
  62. data/lib/grape/validations/validators/base.rb +8 -8
  63. data/lib/grape/validations/validators/coerce.rb +11 -15
  64. data/lib/grape/validations/validators/default.rb +3 -5
  65. data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
  66. data/lib/grape/validations/validators/except_values.rb +1 -1
  67. data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
  68. data/lib/grape/validations/validators/regexp.rb +1 -1
  69. data/lib/grape/validations/validators/values.rb +1 -1
  70. data/lib/grape/version.rb +1 -1
  71. data/lib/grape.rb +5 -5
  72. data/spec/grape/api/instance_spec.rb +50 -0
  73. data/spec/grape/api_remount_spec.rb +9 -4
  74. data/spec/grape/api_spec.rb +82 -6
  75. data/spec/grape/dsl/inside_route_spec.rb +182 -33
  76. data/spec/grape/endpoint/declared_spec.rb +601 -0
  77. data/spec/grape/endpoint_spec.rb +0 -521
  78. data/spec/grape/entity_spec.rb +7 -1
  79. data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
  80. data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
  81. data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
  82. data/spec/grape/middleware/error_spec.rb +1 -1
  83. data/spec/grape/middleware/formatter_spec.rb +3 -3
  84. data/spec/grape/middleware/stack_spec.rb +10 -0
  85. data/spec/grape/path_spec.rb +4 -4
  86. data/spec/grape/request_spec.rb +1 -1
  87. data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
  88. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
  89. data/spec/grape/validations/params_scope_spec.rb +26 -0
  90. data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
  91. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  92. data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
  93. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  94. data/spec/grape/validations/types_spec.rb +1 -1
  95. data/spec/grape/validations/validators/coerce_spec.rb +366 -86
  96. data/spec/grape/validations/validators/default_spec.rb +170 -0
  97. data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
  98. data/spec/grape/validations/validators/except_values_spec.rb +1 -0
  99. data/spec/grape/validations/validators/values_spec.rb +1 -1
  100. data/spec/grape/validations_spec.rb +298 -30
  101. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  102. data/spec/shared/versioning_examples.rb +20 -20
  103. data/spec/spec_helper.rb +3 -10
  104. data/spec/support/chunks.rb +14 -0
  105. data/spec/support/eager_load.rb +19 -0
  106. data/spec/support/versioned_helpers.rb +4 -6
  107. metadata +27 -10
  108. data/lib/grape/util/content_types.rb +0 -28
data/UPGRADING.md CHANGED
@@ -1,6 +1,192 @@
1
1
  Upgrading Grape
2
2
  ===============
3
3
 
4
+ ### Upgrading to >= 1.5.1
5
+
6
+ #### Dependent params
7
+
8
+ If you use [dependent params](https://github.com/ruby-grape/grape#dependent-parameters) with
9
+ `Grape::Extensions::Hash::ParamBuilder`, make sure a parameter to be dependent on is set as a Symbol.
10
+ If a String is given, a parameter that other parameters depend on won't be found even if it is present.
11
+
12
+ _Correct_:
13
+ ```ruby
14
+ given :matrix do
15
+ # dependent params
16
+ end
17
+ ```
18
+
19
+ _Wrong_:
20
+ ```ruby
21
+ given 'matrix' do
22
+ # dependent params
23
+ end
24
+ ```
25
+
26
+ ### Upgrading to >= 1.5.0
27
+
28
+ Prior to 1.3.3, the `declared` helper would always return the complete params structure if `include_missing=true` was set. In 1.3.3 a regression was introduced such that a missing Hash with or without nested parameters would always resolve to `{}`.
29
+
30
+ In 1.5.0 this behavior is reverted, so the whole params structure will always be available via `declared`, regardless of whether any params are passed.
31
+
32
+ The following rules now apply to the `declared` helper when params are missing and `include_missing=true`:
33
+
34
+ * Hash params with children will resolve to a Hash with keys for each declared child.
35
+ * Hash params with no children will resolve to `{}`.
36
+ * Set params will resolve to `Set.new`.
37
+ * Array params will resolve to `[]`.
38
+ * All other params will resolve to `nil`.
39
+
40
+ #### Example
41
+
42
+ ```ruby
43
+ class Api < Grape::API
44
+ params do
45
+ optional :outer, type: Hash do
46
+ optional :inner, type: Hash do
47
+ optional :value, type: String
48
+ end
49
+ end
50
+ end
51
+ get 'example' do
52
+ declared(params, include_missing: true)
53
+ end
54
+ end
55
+ ```
56
+
57
+ ```
58
+ get '/example'
59
+ # 1.3.3 = {}
60
+ # 1.5.0 = {outer: {inner: {value:null}}}
61
+ ```
62
+
63
+ For more information see [#2103](https://github.com/ruby-grape/grape/pull/2103).
64
+
65
+ ### Upgrading to >= 1.4.0
66
+
67
+ #### Reworking stream and file and un-deprecating stream like-objects
68
+
69
+ Previously in 0.16 stream-like objects were deprecated. This release restores their functionality for use-cases other than file streaming.
70
+
71
+ This release deprecated `file` in favor of `sendfile` to better document its purpose.
72
+
73
+ To deliver a file via the Sendfile support in your web server and have the Rack::Sendfile middleware enabled. See [`Rack::Sendfile`](https://www.rubydoc.info/gems/rack/Rack/Sendfile).
74
+ ```ruby
75
+ class API < Grape::API
76
+ get '/' do
77
+ sendfile '/path/to/file'
78
+ end
79
+ end
80
+ ```
81
+
82
+ Use `stream` to stream file content in chunks.
83
+
84
+ ```ruby
85
+ class API < Grape::API
86
+ get '/' do
87
+ stream '/path/to/file'
88
+ end
89
+ end
90
+ ```
91
+
92
+ Or use `stream` to stream other kinds of content. In the following example a streamer class
93
+ streams paginated data from a database.
94
+
95
+ ```ruby
96
+ class MyObject
97
+ attr_accessor :result
98
+
99
+ def initialize(query)
100
+ @result = query
101
+ end
102
+
103
+ def each
104
+ yield '['
105
+ # Do paginated DB fetches and return each page formatted
106
+ first = false
107
+ result.find_in_batches do |records|
108
+ yield process_records(records, first)
109
+ first = false
110
+ end
111
+ yield ']'
112
+ end
113
+
114
+ def process_records(records, first)
115
+ buffer = +''
116
+ buffer << ',' unless first
117
+ buffer << records.map(&:to_json).join(',')
118
+ buffer
119
+ end
120
+ end
121
+
122
+ class API < Grape::API
123
+ get '/' do
124
+ stream MyObject.new(Sprocket.all)
125
+ end
126
+ end
127
+ ```
128
+
129
+ ### Upgrading to >= 1.3.3
130
+
131
+ #### Nil values for structures
132
+
133
+ Nil values always been a special case when dealing with types especially with the following structures:
134
+
135
+ - Array
136
+ - Hash
137
+ - Set
138
+
139
+ The behavior for these structures has change through out the latest releases. For example:
140
+
141
+ ```ruby
142
+ class Api < Grape::API
143
+ params do
144
+ require :my_param, type: Array[Integer]
145
+ end
146
+
147
+ get 'example' do
148
+ params[:my_param]
149
+ end
150
+ get '/example', params: { my_param: nil }
151
+ # 1.3.1 = []
152
+ # 1.3.2 = nil
153
+ end
154
+ ```
155
+
156
+ For now on, `nil` values stay `nil` values for all types, including arrays, sets and hashes.
157
+
158
+ If you want to have the same behavior as 1.3.1, apply a `default` validator:
159
+
160
+ ```ruby
161
+ class Api < Grape::API
162
+ params do
163
+ require :my_param, type: Array[Integer], default: []
164
+ end
165
+
166
+ get 'example' do
167
+ params[:my_param]
168
+ end
169
+ get '/example', params: { my_param: nil } # => []
170
+ end
171
+ ```
172
+
173
+ #### Default validator
174
+
175
+ Default validator is now applied for `nil` values.
176
+
177
+ ```ruby
178
+ class Api < Grape::API
179
+ params do
180
+ requires :my_param, type: Integer, default: 0
181
+ end
182
+
183
+ get 'example' do
184
+ params[:my_param]
185
+ end
186
+ get '/example', params: { my_param: nil } #=> before: nil, after: 0
187
+ end
188
+ ```
189
+
4
190
  ### Upgrading to >= 1.3.0
5
191
 
6
192
  #### Ruby
@@ -9,38 +195,87 @@ After adding dry-types, Ruby 2.4 or newer is required.
9
195
 
10
196
  #### Coercion
11
197
 
12
- [Virtus](https://github.com/solnic/virtus) has been replaced by [dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter coercion. If your project depends on Virtus, explicitly add it to your `Gemfile`. Also, if Virtus is used for defining custom types
198
+ [Virtus](https://github.com/solnic/virtus) has been replaced by [dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter coercion. If your project depends on Virtus outside of Grape, explicitly add it to your `Gemfile`.
199
+
200
+ Here's an example of how to migrate a custom type from Virtus to dry-types:
13
201
 
14
202
  ```ruby
15
- class User
16
- include Virtus.model
203
+ # Legacy Grape parser
204
+ class SecureUriType < Virtus::Attribute
205
+ def coerce(input)
206
+ URI.parse value
207
+ end
17
208
 
18
- attribute :id, Integer
19
- attribute :name, String
209
+ def value_coerced?(input)
210
+ value.is_a? String
211
+ end
20
212
  end
21
213
 
22
- # somewhere in your API
23
214
  params do
24
- requires :user, type: User
215
+ requires :secure_uri, type: SecureUri
25
216
  end
26
217
  ```
27
218
 
28
- Add a class-level `parse` method to the model:
219
+ To use dry-types, we need to:
29
220
 
30
- ```ruby
31
- class User
32
- include Virtus.model
221
+ 1. Remove the inheritance of `Virtus::Attribute`
222
+ 1. Rename `coerce` to `self.parse`
223
+ 1. Rename `value_coerced?` to `self.parsed?`
33
224
 
34
- attribute :id, Integer
35
- attribute :name, String
225
+ The custom type must have a class-level `parse` method to the model. A class-level `parsed?` is needed if the parsed type differs from the defined type. In the example below, since `SecureUri` is not the same as `URI::HTTPS`, `self.parsed?` is needed:
36
226
 
37
- def self.parse(attrs)
38
- new(attrs)
227
+ ```ruby
228
+ # New dry-types parser
229
+ class SecureUri
230
+ def self.parse(value)
231
+ URI.parse value
232
+ end
233
+
234
+ def self.parsed?(value)
235
+ value.is_a? URI::HTTPS
39
236
  end
40
237
  end
238
+
239
+ params do
240
+ requires :secure_uri, type: SecureUri
241
+ end
41
242
  ```
42
243
 
43
- Custom types which don't depend on Virtus don't require any changes.
244
+ #### Coercing to `FalseClass` or `TrueClass` no longer works
245
+
246
+ Previous Grape versions allowed this, though it wasn't documented:
247
+
248
+ ```ruby
249
+ requires :true_value, type: TrueClass
250
+ requires :bool_value, types: [FalseClass, TrueClass]
251
+ ```
252
+
253
+ This is no longer supported, if you do this, your values will never be valid. Instead you should do this:
254
+
255
+ ```ruby
256
+ requires :true_value, type: Boolean # in your endpoint you should validate if this is actually `true`
257
+ requires :bool_value, type: Boolean
258
+ ```
259
+
260
+ #### Ensure that Array types have explicit coercions
261
+
262
+ Unlike Virtus, dry-types does not perform any implict coercions. If you have any uses of `Array[String]`, `Array[Integer]`, etc. be sure they use a `coerce_with` block. For example:
263
+
264
+ ```ruby
265
+ requires :values, type: Array[String]
266
+ ```
267
+
268
+ It's quite common to pass a comma-separated list, such as `tag1,tag2` as `values`. Previously Virtus would implicitly coerce this to `Array(values)` so that `["tag1,tag2"]` would pass the type checks, but with `dry-types` the values are no longer coerced for you. To fix this, you might do:
269
+
270
+ ```ruby
271
+ requires :values, type: Array[String], coerce_with: ->(val) { val.split(',').map(&:strip) }
272
+ ```
273
+
274
+ Likewise, for `Array[Integer]`, you might do:
275
+
276
+ ```ruby
277
+ requires :values, type: Array[Integer], coerce_with: ->(val) { val.split(',').map(&:strip).map(&:to_i) }
278
+ ```
44
279
 
45
280
  For more information see [#1920](https://github.com/ruby-grape/grape/pull/1920).
46
281
 
@@ -97,12 +332,9 @@ In order to make obtaining the name of a mounted class simpler, we've delegated
97
332
 
98
333
  ##### Patching the class
99
334
 
100
- In an effort to make APIs re-mountable, The class `Grape::API` no longer refers to an API instance,
101
- rather, what used to be `Grape::API` is now `Grape::API::Instance` and `Grape::API` was replaced
102
- with a class that can contain several instances of `Grape::API`.
335
+ In an effort to make APIs re-mountable, The class `Grape::API` no longer refers to an API instance, rather, what used to be `Grape::API` is now `Grape::API::Instance` and `Grape::API` was replaced with a class that can contain several instances of `Grape::API`.
103
336
 
104
- This changes were done in such a way that no code-changes should be required.
105
- However, if experiencing problems, or relying on private methods and internal behaviour too deeply, it is possible to restore the prior behaviour by replacing the references from `Grape::API` to `Grape::API::Instance`.
337
+ This changes were done in such a way that no code-changes should be required. However, if experiencing problems, or relying on private methods and internal behaviour too deeply, it is possible to restore the prior behaviour by replacing the references from `Grape::API` to `Grape::API::Instance`.
106
338
 
107
339
  Note, this is particularly relevant if you are opening the class `Grape::API` for modification.
108
340
 
@@ -125,15 +357,20 @@ end
125
357
 
126
358
  After the patch, the mounted API is no longer a Named class inheriting from `Grape::API`, it is an anonymous class
127
359
  which inherit from `Grape::API::Instance`.
360
+
128
361
  What this means in practice, is:
362
+
129
363
  - Generally: you can access the named class from the instance calling the getter `base`.
130
- - In particular: If you need the `name`, you can use `base`.`name`
364
+ - In particular: If you need the `name`, you can use `base`.`name`.
131
365
 
132
366
  **Deprecated**
367
+
133
368
  ```ruby
134
369
  payload[:endpoint].options[:for].name
135
370
  ```
371
+
136
372
  **New**
373
+
137
374
  ```ruby
138
375
  payload[:endpoint].options[:for].base.name
139
376
  ```
@@ -224,8 +461,7 @@ See [#1610](https://github.com/ruby-grape/grape/pull/1610) for more information.
224
461
 
225
462
  #### The `except`, `except_message`, and `proc` options of the `values` validator are deprecated.
226
463
 
227
- The new `except_values` validator should be used in place of the `except` and `except_message` options of
228
- the `values` validator.
464
+ The new `except_values` validator should be used in place of the `except` and `except_message` options of the `values` validator.
229
465
 
230
466
  Arity one Procs may now be used directly as the `values` option to explicitly test param values.
231
467
 
@@ -301,9 +537,7 @@ get '/example' #=> before: 405, after: 404
301
537
 
302
538
  #### Removed param processing from built-in OPTIONS handler
303
539
 
304
- When a request is made to the built-in `OPTIONS` handler, only the `before` and `after`
305
- callbacks associated with the resource will be run. The `before_validation` and
306
- `after_validation` callbacks and parameter validations will be skipped.
540
+ When a request is made to the built-in `OPTIONS` handler, only the `before` and `after` callbacks associated with the resource will be run. The `before_validation` and `after_validation` callbacks and parameter validations will be skipped.
307
541
 
308
542
  See [#1505](https://github.com/ruby-grape/grape/pull/1505) for more information.
309
543
 
@@ -324,8 +558,7 @@ See [#1510](https://github.com/ruby-grape/grape/pull/1510) for more information.
324
558
 
325
559
  #### The default status code for DELETE is now 204 instead of 200.
326
560
 
327
- Breaking change: Sets the default response status code for a delete request to 204.
328
- A status of 204 makes the response more distinguishable and therefore easier to handle on the client side, particularly because a DELETE request typically returns an empty body as the resource was deleted or voided.
561
+ Breaking change: Sets the default response status code for a delete request to 204. A status of 204 makes the response more distinguishable and therefore easier to handle on the client side, particularly because a DELETE request typically returns an empty body as the resource was deleted or voided.
329
562
 
330
563
  To achieve the old behavior, one has to set it explicitly:
331
564
  ```ruby
@@ -503,18 +736,14 @@ See [#1114](https://github.com/ruby-grape/grape/pull/1114) for more information.
503
736
 
504
737
  #### Bypasses formatters when status code indicates no content
505
738
 
506
- To be consistent with rack and it's handling of standard responses
507
- associated with no content, both default and custom formatters will now
739
+ To be consistent with rack and it's handling of standard responses associated with no content, both default and custom formatters will now
508
740
  be bypassed when processing responses for status codes defined [by rack](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567)
509
741
 
510
742
  See [#1190](https://github.com/ruby-grape/grape/pull/1190) for more information.
511
743
 
512
744
  #### Redirects respond as plain text with message
513
745
 
514
- `#redirect` now uses `text/plain` regardless of whether that format has
515
- been enabled. This prevents formatters from attempting to serialize the
516
- message body and allows for a descriptive message body to be provided - and
517
- optionally overridden - that better fulfills the theme of the HTTP spec.
746
+ `#redirect` now uses `text/plain` regardless of whether that format has been enabled. This prevents formatters from attempting to serialize the message body and allows for a descriptive message body to be provided - and optionally overridden - that better fulfills the theme of the HTTP spec.
518
747
 
519
748
  See [#1194](https://github.com/ruby-grape/grape/pull/1194) for more information.
520
749
 
@@ -548,10 +777,7 @@ end
548
777
 
549
778
  See [#1029](https://github.com/ruby-grape/grape/pull/1029) for more information.
550
779
 
551
- There is a known issue because of this change. When Grape is used with an older
552
- than 1.2.4 version of [warden](https://github.com/hassox/warden) there may be raised
553
- the following exception having the [rack-mount](https://github.com/jm/rack-mount) gem's
554
- lines as last ones in the backtrace:
780
+ There is a known issue because of this change. When Grape is used with an older than 1.2.4 version of [warden](https://github.com/hassox/warden) there may be raised the following exception having the [rack-mount](https://github.com/jm/rack-mount) gem's lines as last ones in the backtrace:
555
781
 
556
782
  ```
557
783
  NoMethodError: undefined method `[]' for nil:NilClass
@@ -74,7 +74,7 @@ module Grape
74
74
  # (see #cascade?)
75
75
  def cascade(value = nil)
76
76
  if value.nil?
77
- inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
77
+ inheritable_setting.namespace_inheritable.key?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
78
78
  else
79
79
  namespace_inheritable(:cascade, value)
80
80
  end
@@ -178,7 +178,7 @@ module Grape
178
178
  # errors from reaching upstream. This is effectivelly done by unsetting
179
179
  # X-Cascade. Default :cascade is true.
180
180
  def cascade?
181
- return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
181
+ return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.key?(:cascade)
182
182
  return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
183
183
  true
184
184
  end
@@ -192,42 +192,21 @@ module Grape
192
192
  # will return an HTTP 405 response for any HTTP method that the resource
193
193
  # cannot handle.
194
194
  def add_head_not_allowed_methods_and_options_methods
195
- routes_map = {}
196
-
197
- self.class.endpoints.each do |endpoint|
198
- routes = endpoint.routes
199
- routes.each do |route|
200
- # using the :any shorthand produces [nil] for route methods, substitute all manually
201
- route_key = route.pattern.to_regexp
202
- routes_map[route_key] ||= {}
203
- route_settings = routes_map[route_key]
204
- route_settings[:pattern] = route.pattern
205
- route_settings[:requirements] = route.requirements
206
- route_settings[:path] = route.origin
207
- route_settings[:methods] ||= []
208
- route_settings[:methods] << route.request_method
209
- route_settings[:endpoint] = route.app
210
-
211
- # using the :any shorthand produces [nil] for route methods, substitute all manually
212
- route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*')
213
- end
214
- end
215
-
195
+ versioned_route_configs = collect_route_config_per_pattern
216
196
  # The paths we collected are prepared (cf. Path#prepare), so they
217
197
  # contain already versioning information when using path versioning.
218
198
  # Disable versioning so adding a route won't prepend versioning
219
199
  # informations again.
220
200
  without_root_prefix do
221
201
  without_versioning do
222
- routes_map.each_value do |config|
223
- methods = config[:methods]
224
- allowed_methods = methods.dup
202
+ versioned_route_configs.each do |config|
203
+ allowed_methods = config[:methods].dup
225
204
 
226
205
  unless self.class.namespace_inheritable(:do_not_route_head)
227
206
  allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
228
207
  end
229
208
 
230
- allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
209
+ allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods)
231
210
 
232
211
  unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
233
212
  config[:endpoint].options[:options_route_enabled] = true
@@ -240,14 +219,36 @@ module Grape
240
219
  end
241
220
  end
242
221
 
222
+ def collect_route_config_per_pattern
223
+ all_routes = self.class.endpoints.map(&:routes).flatten
224
+ routes_by_regexp = all_routes.group_by { |route| route.pattern.to_regexp }
225
+
226
+ # Build the configuration based on the first endpoint and the collection of methods supported.
227
+ routes_by_regexp.values.map do |routes|
228
+ last_route = routes.last # Most of the configuration is taken from the last endpoint
229
+ matching_wildchar = routes.any? { |route| route.request_method == '*' }
230
+ {
231
+ options: {},
232
+ pattern: last_route.pattern,
233
+ requirements: last_route.requirements,
234
+ path: last_route.origin,
235
+ endpoint: last_route.app,
236
+ methods: matching_wildchar ? Grape::Http::Headers::SUPPORTED_METHODS : routes.map(&:request_method)
237
+ }
238
+ end
239
+ end
240
+
243
241
  # Generate a route that returns an HTTP 405 response for a user defined
244
242
  # path on methods not specified
245
243
  def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
246
- not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods
247
- not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
248
-
244
+ supported_methods =
245
+ if self.class.namespace_inheritable(:do_not_route_options)
246
+ Grape::Http::Headers::SUPPORTED_METHODS
247
+ else
248
+ Grape::Http::Headers::SUPPORTED_METHODS_WITHOUT_OPTIONS
249
+ end
250
+ not_allowed_methods = supported_methods - allowed_methods
249
251
  return if not_allowed_methods.empty?
250
-
251
252
  @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
252
253
  end
253
254
 
data/lib/grape/api.rb CHANGED
@@ -8,7 +8,7 @@ module Grape
8
8
  # should subclass this class in order to build an API.
9
9
  class API
10
10
  # Class methods that we want to call on the API rather than on the API object
11
- NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile!]).freeze
11
+ NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile! inherited]).freeze
12
12
 
13
13
  class << self
14
14
  attr_accessor :base_instance, :instances
@@ -30,7 +30,7 @@ module Grape
30
30
  # an instance that will be used to create the set up but will not be mounted
31
31
  def initial_setup(base_instance_parent)
32
32
  @instances = []
33
- @setup = []
33
+ @setup = Set.new
34
34
  @base_parent = base_instance_parent
35
35
  @base_instance = mount_instance
36
36
  end
@@ -87,10 +87,10 @@ module Grape
87
87
  end
88
88
 
89
89
  # The remountable class can have a configuration hash to provide some dynamic class-level variables.
90
- # For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary
90
+ # For instance, a description could be done using: `desc configuration[:description]` if it may vary
91
91
  # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
92
92
  # too much, you may actually want to provide a new API rather than remount it.
93
- def mount_instance(opts = {})
93
+ def mount_instance(**opts)
94
94
  instance = Class.new(@base_parent)
95
95
  instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {})
96
96
  instance.base = self
@@ -175,7 +175,7 @@ module Grape
175
175
  if argument.respond_to?(:lazy?) && argument.lazy?
176
176
  argument.evaluate_from(configuration)
177
177
  elsif argument.is_a?(Hash)
178
- argument.map { |key, value| [key, evaluate_arguments(configuration, value).first] }.to_h
178
+ argument.transform_values { |value| evaluate_arguments(configuration, value).first }
179
179
  elsif argument.is_a?(Array)
180
180
  evaluate_arguments(configuration, *argument)
181
181
  else
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'grape/util/registrable'
4
+
5
+ module Grape
6
+ module ContentTypes
7
+ extend Util::Registrable
8
+
9
+ # Content types are listed in order of preference.
10
+ CONTENT_TYPES = {
11
+ xml: 'application/xml',
12
+ serializable_hash: 'application/json',
13
+ json: 'application/json',
14
+ binary: 'application/octet-stream',
15
+ txt: 'text/plain'
16
+ }.freeze
17
+
18
+ class << self
19
+ def content_types_for_settings(settings)
20
+ return if settings.blank?
21
+
22
+ settings.each_with_object({}) { |value, result| result.merge!(value) }
23
+ end
24
+
25
+ def content_types_for(from_settings)
26
+ if from_settings.present?
27
+ from_settings
28
+ else
29
+ Grape::ContentTypes::CONTENT_TYPES.merge(default_elements)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -59,7 +59,7 @@ module Grape
59
59
  # end
60
60
  # end
61
61
  #
62
- # This will make sure that the ApiLogger is opened and close around every
62
+ # This will make sure that the ApiLogger is opened and closed around every
63
63
  # request
64
64
  # @param ensured_block [Proc] The block to be executed after every api_call
65
65
  def finally(&block)
@@ -81,6 +81,7 @@ module Grape
81
81
  # to provide some API-specific functionality.
82
82
  module BaseHelper
83
83
  attr_accessor :api
84
+
84
85
  def params(name, &block)
85
86
  @named_params ||= {}
86
87
  @named_params[name] = block
@@ -94,7 +95,7 @@ module Grape
94
95
  protected
95
96
 
96
97
  def process_named_params
97
- return unless @named_params && @named_params.any?
98
+ return unless instance_variable_defined?(:@named_params) && @named_params && @named_params.any?
98
99
  api.namespace_stackable(:named_params, @named_params)
99
100
  end
100
101
  end