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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +119 -1
- data/LICENSE +1 -1
- data/README.md +123 -29
- data/UPGRADING.md +265 -39
- data/lib/grape/api/instance.rb +32 -31
- data/lib/grape/api.rb +5 -5
- data/lib/grape/content_types.rb +34 -0
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/helpers.rb +2 -1
- data/lib/grape/dsl/inside_route.rb +77 -43
- data/lib/grape/dsl/parameters.rb +12 -8
- data/lib/grape/dsl/routing.rb +12 -11
- data/lib/grape/dsl/validations.rb +18 -1
- data/lib/grape/eager_load.rb +1 -1
- data/lib/grape/endpoint.rb +8 -6
- data/lib/grape/exceptions/base.rb +0 -4
- data/lib/grape/exceptions/validation.rb +1 -1
- data/lib/grape/exceptions/validation_errors.rb +12 -13
- data/lib/grape/http/headers.rb +26 -0
- data/lib/grape/middleware/auth/base.rb +3 -3
- data/lib/grape/middleware/base.rb +4 -5
- data/lib/grape/middleware/error.rb +11 -13
- data/lib/grape/middleware/formatter.rb +3 -3
- data/lib/grape/middleware/stack.rb +10 -2
- data/lib/grape/middleware/versioner/header.rb +4 -4
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +12 -2
- data/lib/grape/path.rb +13 -3
- data/lib/grape/request.rb +13 -8
- data/lib/grape/router/attribute_translator.rb +26 -5
- data/lib/grape/router/pattern.rb +17 -16
- data/lib/grape/router/route.rb +5 -24
- data/lib/grape/router.rb +26 -30
- data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
- data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
- data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
- data/lib/grape/util/base_inheritable.rb +15 -8
- data/lib/grape/util/cache.rb +20 -0
- data/lib/grape/util/lazy_object.rb +43 -0
- data/lib/grape/util/lazy_value.rb +1 -0
- data/lib/grape/util/reverse_stackable_values.rb +2 -0
- data/lib/grape/util/stackable_values.rb +7 -20
- data/lib/grape/validations/attributes_iterator.rb +8 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/params_scope.rb +10 -8
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types/array_coercer.rb +14 -5
- data/lib/grape/validations/types/build_coercer.rb +5 -8
- data/lib/grape/validations/types/custom_type_coercer.rb +16 -2
- data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
- data/lib/grape/validations/types/file.rb +15 -12
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +40 -36
- data/lib/grape/validations/types/primitive_coercer.rb +15 -6
- data/lib/grape/validations/types/set_coercer.rb +6 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
- data/lib/grape/validations/types.rb +7 -9
- data/lib/grape/validations/validator_factory.rb +1 -1
- data/lib/grape/validations/validators/as.rb +1 -1
- data/lib/grape/validations/validators/base.rb +8 -8
- data/lib/grape/validations/validators/coerce.rb +11 -15
- data/lib/grape/validations/validators/default.rb +3 -5
- data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
- data/lib/grape/validations/validators/except_values.rb +1 -1
- data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
- data/lib/grape/validations/validators/regexp.rb +1 -1
- data/lib/grape/validations/validators/values.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +5 -5
- data/spec/grape/api/instance_spec.rb +50 -0
- data/spec/grape/api_remount_spec.rb +9 -4
- data/spec/grape/api_spec.rb +82 -6
- data/spec/grape/dsl/inside_route_spec.rb +182 -33
- data/spec/grape/endpoint/declared_spec.rb +601 -0
- data/spec/grape/endpoint_spec.rb +0 -521
- data/spec/grape/entity_spec.rb +7 -1
- data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
- data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
- data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
- data/spec/grape/middleware/error_spec.rb +1 -1
- data/spec/grape/middleware/formatter_spec.rb +3 -3
- data/spec/grape/middleware/stack_spec.rb +10 -0
- data/spec/grape/path_spec.rb +4 -4
- data/spec/grape/request_spec.rb +1 -1
- data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
- data/spec/grape/validations/params_scope_spec.rb +26 -0
- data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
- data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
- data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
- data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
- data/spec/grape/validations/types_spec.rb +1 -1
- data/spec/grape/validations/validators/coerce_spec.rb +366 -86
- data/spec/grape/validations/validators/default_spec.rb +170 -0
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
- data/spec/grape/validations/validators/except_values_spec.rb +1 -0
- data/spec/grape/validations/validators/values_spec.rb +1 -1
- data/spec/grape/validations_spec.rb +298 -30
- data/spec/integration/eager_load/eager_load_spec.rb +15 -0
- data/spec/shared/versioning_examples.rb +20 -20
- data/spec/spec_helper.rb +3 -10
- data/spec/support/chunks.rb +14 -0
- data/spec/support/eager_load.rb +19 -0
- data/spec/support/versioned_helpers.rb +4 -6
- metadata +27 -10
- 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`.
|
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
|
-
|
16
|
-
|
203
|
+
# Legacy Grape parser
|
204
|
+
class SecureUriType < Virtus::Attribute
|
205
|
+
def coerce(input)
|
206
|
+
URI.parse value
|
207
|
+
end
|
17
208
|
|
18
|
-
|
19
|
-
|
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 :
|
215
|
+
requires :secure_uri, type: SecureUri
|
25
216
|
end
|
26
217
|
```
|
27
218
|
|
28
|
-
|
219
|
+
To use dry-types, we need to:
|
29
220
|
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
38
|
-
|
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
|
-
|
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
|
data/lib/grape/api/instance.rb
CHANGED
@@ -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.
|
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.
|
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
|
-
|
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
|
-
|
223
|
-
|
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)
|
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
|
-
|
247
|
-
|
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
|
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.
|
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
|
data/lib/grape/dsl/callbacks.rb
CHANGED
@@ -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
|
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)
|
data/lib/grape/dsl/helpers.rb
CHANGED
@@ -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
|