grape 1.3.3 → 1.6.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 +111 -2
- data/CONTRIBUTING.md +2 -1
- data/README.md +135 -23
- data/UPGRADING.md +237 -46
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +34 -42
- data/lib/grape/api.rb +21 -16
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/desc.rb +3 -5
- data/lib/grape/dsl/headers.rb +5 -2
- data/lib/grape/dsl/helpers.rb +8 -5
- data/lib/grape/dsl/inside_route.rb +72 -53
- data/lib/grape/dsl/middleware.rb +4 -4
- data/lib/grape/dsl/parameters.rb +11 -7
- data/lib/grape/dsl/request_response.rb +9 -6
- data/lib/grape/dsl/routing.rb +8 -9
- data/lib/grape/dsl/settings.rb +5 -5
- data/lib/grape/dsl/validations.rb +18 -1
- data/lib/grape/eager_load.rb +1 -1
- data/lib/grape/endpoint.rb +29 -42
- data/lib/grape/error_formatter/json.rb +2 -6
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/empty_message_body.rb +11 -0
- data/lib/grape/exceptions/validation.rb +2 -3
- data/lib/grape/exceptions/validation_errors.rb +1 -1
- data/lib/grape/formatter/json.rb +1 -0
- data/lib/grape/formatter/serializable_hash.rb +2 -1
- data/lib/grape/formatter/xml.rb +1 -0
- data/lib/grape/locale/en.yml +1 -1
- data/lib/grape/middleware/auth/base.rb +3 -3
- data/lib/grape/middleware/auth/dsl.rb +7 -1
- data/lib/grape/middleware/base.rb +6 -3
- data/lib/grape/middleware/error.rb +11 -13
- data/lib/grape/middleware/formatter.rb +7 -7
- data/lib/grape/middleware/stack.rb +10 -3
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
- data/lib/grape/middleware/versioner/header.rb +6 -4
- data/lib/grape/middleware/versioner/param.rb +1 -0
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +2 -0
- data/lib/grape/parser/json.rb +1 -1
- data/lib/grape/parser/xml.rb +1 -1
- data/lib/grape/path.rb +1 -0
- data/lib/grape/request.rb +4 -1
- data/lib/grape/router/attribute_translator.rb +3 -3
- data/lib/grape/router/pattern.rb +1 -1
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/router.rb +31 -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 +2 -2
- data/lib/grape/util/inheritable_setting.rb +1 -3
- data/lib/grape/util/lazy_value.rb +4 -2
- data/lib/grape/util/strict_hash_configuration.rb +1 -1
- 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 +97 -62
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types/custom_type_coercer.rb +16 -3
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +4 -5
- data/lib/grape/validations/types.rb +1 -4
- data/lib/grape/validations/validator_factory.rb +1 -1
- data/lib/grape/validations/validators/all_or_none.rb +8 -5
- data/lib/grape/validations/validators/allow_blank.rb +9 -7
- data/lib/grape/validations/validators/as.rb +6 -8
- data/lib/grape/validations/validators/at_least_one_of.rb +7 -4
- data/lib/grape/validations/validators/base.rb +74 -69
- data/lib/grape/validations/validators/coerce.rb +63 -76
- data/lib/grape/validations/validators/default.rb +36 -34
- data/lib/grape/validations/validators/exactly_one_of.rb +9 -6
- data/lib/grape/validations/validators/except_values.rb +13 -11
- data/lib/grape/validations/validators/multiple_params_base.rb +24 -19
- data/lib/grape/validations/validators/mutual_exclusion.rb +8 -5
- data/lib/grape/validations/validators/presence.rb +7 -4
- data/lib/grape/validations/validators/regexp.rb +8 -5
- data/lib/grape/validations/validators/same_as.rb +18 -15
- data/lib/grape/validations/validators/values.rb +61 -56
- data/lib/grape/validations.rb +6 -0
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +7 -3
- data/spec/grape/api/custom_validations_spec.rb +77 -45
- data/spec/grape/api/deeply_included_options_spec.rb +3 -3
- data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -1
- data/spec/grape/api/invalid_format_spec.rb +2 -0
- data/spec/grape/api/recognize_path_spec.rb +1 -1
- data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -15
- data/spec/grape/api_remount_spec.rb +25 -19
- data/spec/grape/api_spec.rb +576 -211
- data/spec/grape/dsl/callbacks_spec.rb +2 -1
- data/spec/grape/dsl/headers_spec.rb +39 -9
- data/spec/grape/dsl/helpers_spec.rb +3 -2
- data/spec/grape/dsl/inside_route_spec.rb +185 -34
- data/spec/grape/dsl/logger_spec.rb +16 -18
- data/spec/grape/dsl/middleware_spec.rb +2 -1
- data/spec/grape/dsl/parameters_spec.rb +2 -0
- data/spec/grape/dsl/request_response_spec.rb +1 -0
- data/spec/grape/dsl/routing_spec.rb +10 -7
- data/spec/grape/endpoint/declared_spec.rb +848 -0
- data/spec/grape/endpoint_spec.rb +77 -589
- data/spec/grape/entity_spec.rb +29 -23
- data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -0
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +61 -22
- data/spec/grape/exceptions/validation_errors_spec.rb +13 -10
- data/spec/grape/exceptions/validation_spec.rb +5 -3
- data/spec/grape/extensions/param_builders/hash_spec.rb +7 -7
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -8
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -8
- data/spec/grape/integration/rack_sendfile_spec.rb +13 -9
- data/spec/grape/loading_spec.rb +8 -8
- data/spec/grape/middleware/auth/dsl_spec.rb +15 -6
- data/spec/grape/middleware/auth/strategies_spec.rb +61 -21
- data/spec/grape/middleware/base_spec.rb +24 -15
- data/spec/grape/middleware/error_spec.rb +3 -3
- data/spec/grape/middleware/exception_spec.rb +111 -161
- data/spec/grape/middleware/formatter_spec.rb +28 -7
- data/spec/grape/middleware/globals_spec.rb +7 -4
- data/spec/grape/middleware/stack_spec.rb +15 -12
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -1
- data/spec/grape/middleware/versioner/header_spec.rb +14 -13
- data/spec/grape/middleware/versioner/param_spec.rb +7 -1
- data/spec/grape/middleware/versioner/path_spec.rb +5 -1
- data/spec/grape/middleware/versioner_spec.rb +1 -1
- data/spec/grape/parser_spec.rb +4 -0
- data/spec/grape/path_spec.rb +52 -52
- data/spec/grape/presenters/presenter_spec.rb +7 -6
- data/spec/grape/request_spec.rb +6 -4
- data/spec/grape/util/inheritable_setting_spec.rb +7 -7
- data/spec/grape/util/inheritable_values_spec.rb +3 -2
- data/spec/grape/util/reverse_stackable_values_spec.rb +3 -1
- data/spec/grape/util/stackable_values_spec.rb +7 -5
- data/spec/grape/validations/instance_behaivour_spec.rb +9 -10
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +14 -3
- data/spec/grape/validations/params_scope_spec.rb +72 -10
- data/spec/grape/validations/single_attribute_iterator_spec.rb +18 -6
- data/spec/grape/validations/types/primitive_coercer_spec.rb +63 -7
- data/spec/grape/validations/types_spec.rb +8 -8
- data/spec/grape/validations/validators/all_or_none_spec.rb +50 -56
- data/spec/grape/validations/validators/allow_blank_spec.rb +136 -140
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -56
- data/spec/grape/validations/validators/coerce_spec.rb +248 -33
- data/spec/grape/validations/validators/default_spec.rb +121 -78
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -77
- data/spec/grape/validations/validators/except_values_spec.rb +4 -3
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -77
- data/spec/grape/validations/validators/presence_spec.rb +16 -1
- data/spec/grape/validations/validators/regexp_spec.rb +25 -31
- data/spec/grape/validations/validators/same_as_spec.rb +14 -20
- data/spec/grape/validations/validators/values_spec.rb +183 -178
- data/spec/grape/validations_spec.rb +342 -29
- data/spec/integration/eager_load/eager_load_spec.rb +15 -0
- data/spec/integration/multi_json/json_spec.rb +1 -1
- data/spec/integration/multi_xml/xml_spec.rb +1 -1
- data/spec/shared/versioning_examples.rb +32 -29
- data/spec/spec_helper.rb +12 -12
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/chunks.rb +14 -0
- data/spec/support/versioned_helpers.rb +4 -6
- metadata +110 -102
data/UPGRADING.md
CHANGED
@@ -1,16 +1,209 @@
|
|
1
1
|
Upgrading Grape
|
2
2
|
===============
|
3
3
|
|
4
|
+
### Upgrading to >= 1.6.0
|
5
|
+
|
6
|
+
#### Parameter renaming with :as
|
7
|
+
|
8
|
+
Prior to 1.6.0 the [parameter renaming](https://github.com/ruby-grape/grape#renaming) with `:as` was directly touching the request payload ([`#params`](https://github.com/ruby-grape/grape#parameters)) while duplicating the old and the new key to be both available in the hash. This allowed clients to bypass any validation in case they knew the internal name of the parameter. Unfortunately, in combination with [grape-swagger](https://github.com/ruby-grape/grape-swagger) the internal name (name set with `:as`) of the parameters were documented.
|
9
|
+
|
10
|
+
This behavior was fixed. Parameter renaming is now done when using the [`#declared(params)`](https://github.com/ruby-grape/grape#declared) parameters helper. This stops confusing validation/coercion behavior.
|
11
|
+
|
12
|
+
Here comes an illustration of the old and new behaviour as code:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
# (1) Rename a to b, while client sends +a+
|
16
|
+
optional :a, type: Integer, as: :b
|
17
|
+
params = { a: 1 }
|
18
|
+
declared(params, include_missing: false)
|
19
|
+
# expected => { b: 1 }
|
20
|
+
# actual => { b: 1 }
|
21
|
+
|
22
|
+
# (2) Rename a to b, while client sends +b+
|
23
|
+
optional :a, type: Integer, as: :b, values: [1, 2, 3]
|
24
|
+
params = { b: '5' }
|
25
|
+
declared(params, include_missing: false)
|
26
|
+
# expected => { } (>= 1.6.0)
|
27
|
+
# actual => { b: '5' } (uncasted, unvalidated, <= 1.5.3)
|
28
|
+
```
|
29
|
+
|
30
|
+
Another implication of this change is the dependent parameter resolution. Prior to 1.6.0 the following code produced a `Grape::Exceptions::UnknownParameter` because `:a` was replaced by `:b`:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
params do
|
34
|
+
optional :a, as: :b
|
35
|
+
given :a do # (<= 1.5.3 you had to reference +:b+ here to make it work)
|
36
|
+
requires :c
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
This code now works without any errors, as the renaming is just an internal behaviour of the `#declared(params)` parameter helper.
|
42
|
+
|
43
|
+
See [#2189](https://github.com/ruby-grape/grape/pull/2189) for more information.
|
44
|
+
|
45
|
+
### Upgrading to >= 1.5.3
|
46
|
+
|
47
|
+
#### Nil value and coercion
|
48
|
+
|
49
|
+
Prior to 1.2.5 version passing a `nil` value for a parameter with a custom coercer would invoke the coercer, and not passing a parameter would not invoke it.
|
50
|
+
This behavior was not tested or documented. Version 1.3.0 quietly changed this behavior, in that `nil` values skipped the coercion. Version 1.5.3 fixes and documents this as follows:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
class Api < Grape::API
|
54
|
+
params do
|
55
|
+
optional :value, type: Integer, coerce_with: ->(val) { val || 0 }
|
56
|
+
end
|
57
|
+
|
58
|
+
get 'example' do
|
59
|
+
params[:my_param]
|
60
|
+
end
|
61
|
+
get '/example', params: { value: nil }
|
62
|
+
# 1.5.2 = nil
|
63
|
+
# 1.5.3 = 0
|
64
|
+
get '/example', params: {}
|
65
|
+
# 1.5.2 = nil
|
66
|
+
# 1.5.3 = nil
|
67
|
+
end
|
68
|
+
```
|
69
|
+
See [#2164](https://github.com/ruby-grape/grape/pull/2164) for more information.
|
70
|
+
|
71
|
+
### Upgrading to >= 1.5.1
|
72
|
+
|
73
|
+
#### Dependent params
|
74
|
+
|
75
|
+
If you use [dependent params](https://github.com/ruby-grape/grape#dependent-parameters) with
|
76
|
+
`Grape::Extensions::Hash::ParamBuilder`, make sure a parameter to be dependent on is set as a Symbol.
|
77
|
+
If a String is given, a parameter that other parameters depend on won't be found even if it is present.
|
78
|
+
|
79
|
+
_Correct_:
|
80
|
+
```ruby
|
81
|
+
given :matrix do
|
82
|
+
# dependent params
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
_Wrong_:
|
87
|
+
```ruby
|
88
|
+
given 'matrix' do
|
89
|
+
# dependent params
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
### Upgrading to >= 1.5.0
|
94
|
+
|
95
|
+
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 `{}`.
|
96
|
+
|
97
|
+
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.
|
98
|
+
|
99
|
+
The following rules now apply to the `declared` helper when params are missing and `include_missing=true`:
|
100
|
+
|
101
|
+
* Hash params with children will resolve to a Hash with keys for each declared child.
|
102
|
+
* Hash params with no children will resolve to `{}`.
|
103
|
+
* Set params will resolve to `Set.new`.
|
104
|
+
* Array params will resolve to `[]`.
|
105
|
+
* All other params will resolve to `nil`.
|
106
|
+
|
107
|
+
#### Example
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
class Api < Grape::API
|
111
|
+
params do
|
112
|
+
optional :outer, type: Hash do
|
113
|
+
optional :inner, type: Hash do
|
114
|
+
optional :value, type: String
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
get 'example' do
|
119
|
+
declared(params, include_missing: true)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
```
|
125
|
+
get '/example'
|
126
|
+
# 1.3.3 = {}
|
127
|
+
# 1.5.0 = {outer: {inner: {value:null}}}
|
128
|
+
```
|
129
|
+
|
130
|
+
For more information see [#2103](https://github.com/ruby-grape/grape/pull/2103).
|
131
|
+
|
4
132
|
### Upgrading to >= 1.4.0
|
5
133
|
|
134
|
+
#### Reworking stream and file and un-deprecating stream like-objects
|
135
|
+
|
136
|
+
Previously in 0.16 stream-like objects were deprecated. This release restores their functionality for use-cases other than file streaming.
|
137
|
+
|
138
|
+
This release deprecated `file` in favor of `sendfile` to better document its purpose.
|
139
|
+
|
140
|
+
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).
|
141
|
+
```ruby
|
142
|
+
class API < Grape::API
|
143
|
+
get '/' do
|
144
|
+
sendfile '/path/to/file'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
Use `stream` to stream file content in chunks.
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
class API < Grape::API
|
153
|
+
get '/' do
|
154
|
+
stream '/path/to/file'
|
155
|
+
end
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
Or use `stream` to stream other kinds of content. In the following example a streamer class
|
160
|
+
streams paginated data from a database.
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
class MyObject
|
164
|
+
attr_accessor :result
|
165
|
+
|
166
|
+
def initialize(query)
|
167
|
+
@result = query
|
168
|
+
end
|
169
|
+
|
170
|
+
def each
|
171
|
+
yield '['
|
172
|
+
# Do paginated DB fetches and return each page formatted
|
173
|
+
first = false
|
174
|
+
result.find_in_batches do |records|
|
175
|
+
yield process_records(records, first)
|
176
|
+
first = false
|
177
|
+
end
|
178
|
+
yield ']'
|
179
|
+
end
|
180
|
+
|
181
|
+
def process_records(records, first)
|
182
|
+
buffer = +''
|
183
|
+
buffer << ',' unless first
|
184
|
+
buffer << records.map(&:to_json).join(',')
|
185
|
+
buffer
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
class API < Grape::API
|
190
|
+
get '/' do
|
191
|
+
stream MyObject.new(Sprocket.all)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
### Upgrading to >= 1.3.3
|
197
|
+
|
6
198
|
#### Nil values for structures
|
7
199
|
|
8
|
-
Nil values always been a special case when dealing with types especially with the following structures:
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
200
|
+
Nil values have always been a special case when dealing with types, especially with the following structures:
|
201
|
+
|
202
|
+
- Array
|
203
|
+
- Hash
|
204
|
+
- Set
|
205
|
+
|
206
|
+
The behavior for these structures has changed throughout the latest releases. For example:
|
14
207
|
|
15
208
|
```ruby
|
16
209
|
class Api < Grape::API
|
@@ -26,9 +219,10 @@ class Api < Grape::API
|
|
26
219
|
# 1.3.2 = nil
|
27
220
|
end
|
28
221
|
```
|
222
|
+
|
29
223
|
For now on, `nil` values stay `nil` values for all types, including arrays, sets and hashes.
|
30
224
|
|
31
|
-
If you want to have the same behavior as 1.3.1, apply a `default` validator
|
225
|
+
If you want to have the same behavior as 1.3.1, apply a `default` validator:
|
32
226
|
|
33
227
|
```ruby
|
34
228
|
class Api < Grape::API
|
@@ -62,16 +256,15 @@ end
|
|
62
256
|
|
63
257
|
### Upgrading to >= 1.3.0
|
64
258
|
|
259
|
+
You will need to upgrade to this version if you depend on `rack >= 2.1.0`.
|
260
|
+
|
65
261
|
#### Ruby
|
66
262
|
|
67
263
|
After adding dry-types, Ruby 2.4 or newer is required.
|
68
264
|
|
69
265
|
#### Coercion
|
70
266
|
|
71
|
-
[Virtus](https://github.com/solnic/virtus) has been replaced by
|
72
|
-
[dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter
|
73
|
-
coercion. If your project depends on Virtus outside of Grape, explicitly
|
74
|
-
add it to your `Gemfile`.
|
267
|
+
[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`.
|
75
268
|
|
76
269
|
Here's an example of how to migrate a custom type from Virtus to dry-types:
|
77
270
|
|
@@ -98,10 +291,7 @@ To use dry-types, we need to:
|
|
98
291
|
1. Rename `coerce` to `self.parse`
|
99
292
|
1. Rename `value_coerced?` to `self.parsed?`
|
100
293
|
|
101
|
-
The custom type must have a class-level `parse` method to the model. A
|
102
|
-
class-level `parsed?` is needed if the parsed type differs from the
|
103
|
-
defined type. In the example below, since `SecureUri` is not the same
|
104
|
-
as `URI::HTTPS`, `self.parsed?` is needed:
|
294
|
+
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:
|
105
295
|
|
106
296
|
```ruby
|
107
297
|
# New dry-types parser
|
@@ -120,21 +310,31 @@ params do
|
|
120
310
|
end
|
121
311
|
```
|
122
312
|
|
313
|
+
#### Coercing to `FalseClass` or `TrueClass` no longer works
|
314
|
+
|
315
|
+
Previous Grape versions allowed this, though it wasn't documented:
|
316
|
+
|
317
|
+
```ruby
|
318
|
+
requires :true_value, type: TrueClass
|
319
|
+
requires :bool_value, types: [FalseClass, TrueClass]
|
320
|
+
```
|
321
|
+
|
322
|
+
This is no longer supported, if you do this, your values will never be valid. Instead you should do this:
|
323
|
+
|
324
|
+
```ruby
|
325
|
+
requires :true_value, type: Boolean # in your endpoint you should validate if this is actually `true`
|
326
|
+
requires :bool_value, type: Boolean
|
327
|
+
```
|
328
|
+
|
123
329
|
#### Ensure that Array types have explicit coercions
|
124
330
|
|
125
|
-
Unlike Virtus, dry-types does not perform any implict coercions. If you
|
126
|
-
have any uses of `Array[String]`, `Array[Integer]`, etc. be sure they
|
127
|
-
use a `coerce_with` block. For example:
|
331
|
+
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:
|
128
332
|
|
129
333
|
```ruby
|
130
334
|
requires :values, type: Array[String]
|
131
335
|
```
|
132
336
|
|
133
|
-
It's quite common to pass a comma-separated list, such as `tag1,tag2` as
|
134
|
-
`values`. Previously Virtus would implicitly coerce this to
|
135
|
-
`Array(values)` so that `["tag1,tag2"]` would pass the type checks, but
|
136
|
-
with `dry-types` the values are no longer coerced for you. To fix this,
|
137
|
-
you might do:
|
337
|
+
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:
|
138
338
|
|
139
339
|
```ruby
|
140
340
|
requires :values, type: Array[String], coerce_with: ->(val) { val.split(',').map(&:strip) }
|
@@ -201,12 +401,9 @@ In order to make obtaining the name of a mounted class simpler, we've delegated
|
|
201
401
|
|
202
402
|
##### Patching the class
|
203
403
|
|
204
|
-
In an effort to make APIs re-mountable, The class `Grape::API` no longer refers to an API instance,
|
205
|
-
rather, what used to be `Grape::API` is now `Grape::API::Instance` and `Grape::API` was replaced
|
206
|
-
with a class that can contain several instances of `Grape::API`.
|
404
|
+
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`.
|
207
405
|
|
208
|
-
This changes were done in such a way that no code-changes should be required.
|
209
|
-
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`.
|
406
|
+
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`.
|
210
407
|
|
211
408
|
Note, this is particularly relevant if you are opening the class `Grape::API` for modification.
|
212
409
|
|
@@ -229,15 +426,20 @@ end
|
|
229
426
|
|
230
427
|
After the patch, the mounted API is no longer a Named class inheriting from `Grape::API`, it is an anonymous class
|
231
428
|
which inherit from `Grape::API::Instance`.
|
429
|
+
|
232
430
|
What this means in practice, is:
|
431
|
+
|
233
432
|
- Generally: you can access the named class from the instance calling the getter `base`.
|
234
|
-
- In particular: If you need the `name`, you can use `base`.`name
|
433
|
+
- In particular: If you need the `name`, you can use `base`.`name`.
|
235
434
|
|
236
435
|
**Deprecated**
|
436
|
+
|
237
437
|
```ruby
|
238
438
|
payload[:endpoint].options[:for].name
|
239
439
|
```
|
440
|
+
|
240
441
|
**New**
|
442
|
+
|
241
443
|
```ruby
|
242
444
|
payload[:endpoint].options[:for].base.name
|
243
445
|
```
|
@@ -328,8 +530,7 @@ See [#1610](https://github.com/ruby-grape/grape/pull/1610) for more information.
|
|
328
530
|
|
329
531
|
#### The `except`, `except_message`, and `proc` options of the `values` validator are deprecated.
|
330
532
|
|
331
|
-
The new `except_values` validator should be used in place of the `except` and `except_message` options of
|
332
|
-
the `values` validator.
|
533
|
+
The new `except_values` validator should be used in place of the `except` and `except_message` options of the `values` validator.
|
333
534
|
|
334
535
|
Arity one Procs may now be used directly as the `values` option to explicitly test param values.
|
335
536
|
|
@@ -405,9 +606,7 @@ get '/example' #=> before: 405, after: 404
|
|
405
606
|
|
406
607
|
#### Removed param processing from built-in OPTIONS handler
|
407
608
|
|
408
|
-
When a request is made to the built-in `OPTIONS` handler, only the `before` and `after`
|
409
|
-
callbacks associated with the resource will be run. The `before_validation` and
|
410
|
-
`after_validation` callbacks and parameter validations will be skipped.
|
609
|
+
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.
|
411
610
|
|
412
611
|
See [#1505](https://github.com/ruby-grape/grape/pull/1505) for more information.
|
413
612
|
|
@@ -428,8 +627,7 @@ See [#1510](https://github.com/ruby-grape/grape/pull/1510) for more information.
|
|
428
627
|
|
429
628
|
#### The default status code for DELETE is now 204 instead of 200.
|
430
629
|
|
431
|
-
Breaking change: Sets the default response status code for a delete request to 204.
|
432
|
-
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.
|
630
|
+
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.
|
433
631
|
|
434
632
|
To achieve the old behavior, one has to set it explicitly:
|
435
633
|
```ruby
|
@@ -607,18 +805,14 @@ See [#1114](https://github.com/ruby-grape/grape/pull/1114) for more information.
|
|
607
805
|
|
608
806
|
#### Bypasses formatters when status code indicates no content
|
609
807
|
|
610
|
-
To be consistent with rack and it's handling of standard responses
|
611
|
-
associated with no content, both default and custom formatters will now
|
808
|
+
To be consistent with rack and it's handling of standard responses associated with no content, both default and custom formatters will now
|
612
809
|
be bypassed when processing responses for status codes defined [by rack](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567)
|
613
810
|
|
614
811
|
See [#1190](https://github.com/ruby-grape/grape/pull/1190) for more information.
|
615
812
|
|
616
813
|
#### Redirects respond as plain text with message
|
617
814
|
|
618
|
-
`#redirect` now uses `text/plain` regardless of whether that format has
|
619
|
-
been enabled. This prevents formatters from attempting to serialize the
|
620
|
-
message body and allows for a descriptive message body to be provided - and
|
621
|
-
optionally overridden - that better fulfills the theme of the HTTP spec.
|
815
|
+
`#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.
|
622
816
|
|
623
817
|
See [#1194](https://github.com/ruby-grape/grape/pull/1194) for more information.
|
624
818
|
|
@@ -652,10 +846,7 @@ end
|
|
652
846
|
|
653
847
|
See [#1029](https://github.com/ruby-grape/grape/pull/1029) for more information.
|
654
848
|
|
655
|
-
There is a known issue because of this change. When Grape is used with an older
|
656
|
-
than 1.2.4 version of [warden](https://github.com/hassox/warden) there may be raised
|
657
|
-
the following exception having the [rack-mount](https://github.com/jm/rack-mount) gem's
|
658
|
-
lines as last ones in the backtrace:
|
849
|
+
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:
|
659
850
|
|
660
851
|
```
|
661
852
|
NoMethodError: undefined method `[]' for nil:NilClass
|
data/grape.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
$LOAD_PATH.unshift File.expand_path('
|
3
|
+
$LOAD_PATH.unshift File.expand_path('lib', __dir__)
|
4
4
|
require 'grape/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
@@ -14,10 +14,10 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.description = 'A Ruby framework for rapid API development with great conventions.'
|
15
15
|
s.license = 'MIT'
|
16
16
|
s.metadata = {
|
17
|
-
'bug_tracker_uri'
|
18
|
-
'changelog_uri'
|
17
|
+
'bug_tracker_uri' => 'https://github.com/ruby-grape/grape/issues',
|
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'
|
20
|
+
'source_code_uri' => "https://github.com/ruby-grape/grape/tree/v#{s.version}"
|
21
21
|
}
|
22
22
|
|
23
23
|
s.add_runtime_dependency 'activesupport'
|
@@ -32,5 +32,5 @@ Gem::Specification.new do |s|
|
|
32
32
|
s.files += Dir['lib/**/*']
|
33
33
|
s.test_files = Dir['spec/**/*']
|
34
34
|
s.require_paths = ['lib']
|
35
|
-
s.required_ruby_version = '>= 2.
|
35
|
+
s.required_ruby_version = '>= 2.5.0'
|
36
36
|
end
|
data/lib/grape/api/instance.rb
CHANGED
@@ -10,12 +10,11 @@ module Grape
|
|
10
10
|
include Grape::DSL::API
|
11
11
|
|
12
12
|
class << self
|
13
|
-
attr_reader :instance
|
14
|
-
attr_reader :base
|
13
|
+
attr_reader :instance, :base
|
15
14
|
attr_accessor :configuration
|
16
15
|
|
17
16
|
def given(conditional_option, &block)
|
18
|
-
evaluate_as_instance_with_configuration(block, lazy: true) if conditional_option &&
|
17
|
+
evaluate_as_instance_with_configuration(block, lazy: true) if conditional_option && block
|
19
18
|
end
|
20
19
|
|
21
20
|
def mounted(&block)
|
@@ -28,7 +27,7 @@ module Grape
|
|
28
27
|
end
|
29
28
|
|
30
29
|
def to_s
|
31
|
-
|
30
|
+
base&.to_s || super
|
32
31
|
end
|
33
32
|
|
34
33
|
def base_instance?
|
@@ -82,6 +81,7 @@ module Grape
|
|
82
81
|
|
83
82
|
def compile!
|
84
83
|
return if instance
|
84
|
+
|
85
85
|
LOCK.synchronize { compile unless instance }
|
86
86
|
end
|
87
87
|
|
@@ -103,7 +103,7 @@ module Grape
|
|
103
103
|
def nest(*blocks, &block)
|
104
104
|
blocks.reject!(&:nil?)
|
105
105
|
if blocks.any?
|
106
|
-
evaluate_as_instance_with_configuration(block) if
|
106
|
+
evaluate_as_instance_with_configuration(block) if block
|
107
107
|
blocks.each { |b| evaluate_as_instance_with_configuration(b) }
|
108
108
|
reset_validations!
|
109
109
|
else
|
@@ -114,9 +114,7 @@ module Grape
|
|
114
114
|
def evaluate_as_instance_with_configuration(block, lazy: false)
|
115
115
|
lazy_block = Grape::Util::LazyBlock.new do |configuration|
|
116
116
|
value_for_configuration = configuration
|
117
|
-
if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
|
118
|
-
self.configuration = value_for_configuration.evaluate
|
119
|
-
end
|
117
|
+
self.configuration = value_for_configuration.evaluate if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
|
120
118
|
response = instance_eval(&block)
|
121
119
|
self.configuration = value_for_configuration
|
122
120
|
response
|
@@ -179,7 +177,8 @@ module Grape
|
|
179
177
|
# X-Cascade. Default :cascade is true.
|
180
178
|
def cascade?
|
181
179
|
return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.key?(:cascade)
|
182
|
-
return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options)
|
180
|
+
return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options)&.key?(:cascade)
|
181
|
+
|
183
182
|
true
|
184
183
|
end
|
185
184
|
|
@@ -192,47 +191,23 @@ module Grape
|
|
192
191
|
# will return an HTTP 405 response for any HTTP method that the resource
|
193
192
|
# cannot handle.
|
194
193
|
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
|
-
if route.request_method == '*' || route_settings[:methods].include?('*')
|
209
|
-
route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS
|
210
|
-
else
|
211
|
-
route_settings[:methods] << route.request_method
|
212
|
-
end
|
213
|
-
route_settings[:endpoint] = route.app
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
194
|
+
versioned_route_configs = collect_route_config_per_pattern
|
217
195
|
# The paths we collected are prepared (cf. Path#prepare), so they
|
218
196
|
# contain already versioning information when using path versioning.
|
219
197
|
# Disable versioning so adding a route won't prepend versioning
|
220
198
|
# informations again.
|
221
199
|
without_root_prefix do
|
222
200
|
without_versioning do
|
223
|
-
|
224
|
-
|
225
|
-
allowed_methods = methods.dup
|
201
|
+
versioned_route_configs.each do |config|
|
202
|
+
next if config[:options][:matching_wildchar]
|
226
203
|
|
227
|
-
|
228
|
-
|
229
|
-
|
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)
|
230
207
|
|
231
208
|
allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods)
|
232
209
|
|
233
|
-
unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
|
234
|
-
config[:endpoint].options[:options_route_enabled] = true
|
235
|
-
end
|
210
|
+
config[:endpoint].options[:options_route_enabled] = true unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
|
236
211
|
|
237
212
|
attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
|
238
213
|
generate_not_allowed_method(config[:pattern], **attributes)
|
@@ -241,6 +216,25 @@ module Grape
|
|
241
216
|
end
|
242
217
|
end
|
243
218
|
|
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 }
|
222
|
+
|
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
|
+
}
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
244
238
|
# Generate a route that returns an HTTP 405 response for a user defined
|
245
239
|
# path on methods not specified
|
246
240
|
def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
|
@@ -251,8 +245,6 @@ module Grape
|
|
251
245
|
Grape::Http::Headers::SUPPORTED_METHODS_WITHOUT_OPTIONS
|
252
246
|
end
|
253
247
|
not_allowed_methods = supported_methods - allowed_methods
|
254
|
-
return if not_allowed_methods.empty?
|
255
|
-
|
256
248
|
@router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
|
257
249
|
end
|
258
250
|
|
data/lib/grape/api.rb
CHANGED
@@ -10,6 +10,18 @@ module Grape
|
|
10
10
|
# Class methods that we want to call on the API rather than on the API object
|
11
11
|
NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile! inherited]).freeze
|
12
12
|
|
13
|
+
class Boolean
|
14
|
+
def self.build(val)
|
15
|
+
return nil if val != true && val != false
|
16
|
+
|
17
|
+
new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Instance
|
22
|
+
Boolean = Grape::API::Boolean
|
23
|
+
end
|
24
|
+
|
13
25
|
class << self
|
14
26
|
attr_accessor :base_instance, :instances
|
15
27
|
|
@@ -20,17 +32,18 @@ module Grape
|
|
20
32
|
|
21
33
|
# When inherited, will create a list of all instances (times the API was mounted)
|
22
34
|
# It will listen to the setup required to mount that endpoint, and replicate it on any new instance
|
23
|
-
def inherited(api
|
24
|
-
|
35
|
+
def inherited(api)
|
36
|
+
super
|
37
|
+
|
38
|
+
api.initial_setup(Grape::API == self ? Grape::API::Instance : @base_instance)
|
25
39
|
api.override_all_methods!
|
26
|
-
make_inheritable(api)
|
27
40
|
end
|
28
41
|
|
29
42
|
# Initialize the instance variables on the remountable class, and the base_instance
|
30
43
|
# an instance that will be used to create the set up but will not be mounted
|
31
44
|
def initial_setup(base_instance_parent)
|
32
45
|
@instances = []
|
33
|
-
@setup =
|
46
|
+
@setup = Set.new
|
34
47
|
@base_parent = base_instance_parent
|
35
48
|
@base_instance = mount_instance
|
36
49
|
end
|
@@ -68,15 +81,6 @@ module Grape
|
|
68
81
|
instance_for_rack.call(*args, &block)
|
69
82
|
end
|
70
83
|
|
71
|
-
# Allows an API to itself be inheritable:
|
72
|
-
def make_inheritable(api)
|
73
|
-
# When a child API inherits from a parent API.
|
74
|
-
def api.inherited(child_api)
|
75
|
-
# The instances of the child API inherit from the instances of the parent API
|
76
|
-
Grape::API.inherited(child_api, base_instance)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
84
|
# Alleviates problems with autoloading by tring to search for the constant
|
81
85
|
def const_missing(*args)
|
82
86
|
if base_instance.const_defined?(*args)
|
@@ -87,10 +91,10 @@ module Grape
|
|
87
91
|
end
|
88
92
|
|
89
93
|
# The remountable class can have a configuration hash to provide some dynamic class-level variables.
|
90
|
-
# For instance, a
|
94
|
+
# For instance, a description could be done using: `desc configuration[:description]` if it may vary
|
91
95
|
# depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
|
92
96
|
# too much, you may actually want to provide a new API rather than remount it.
|
93
|
-
def mount_instance(opts
|
97
|
+
def mount_instance(**opts)
|
94
98
|
instance = Class.new(@base_parent)
|
95
99
|
instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {})
|
96
100
|
instance.base = self
|
@@ -141,7 +145,7 @@ module Grape
|
|
141
145
|
# Adds a new stage to the set up require to get a Grape::API up and running
|
142
146
|
def add_setup(method, *args, &block)
|
143
147
|
setup_step = { method: method, args: args, block: block }
|
144
|
-
@setup
|
148
|
+
@setup += [setup_step]
|
145
149
|
last_response = nil
|
146
150
|
@instances.each do |instance|
|
147
151
|
last_response = replay_step_on(instance, setup_step)
|
@@ -151,6 +155,7 @@ module Grape
|
|
151
155
|
|
152
156
|
def replay_step_on(instance, setup_step)
|
153
157
|
return if skip_immediate_run?(instance, setup_step[:args])
|
158
|
+
|
154
159
|
args = evaluate_arguments(instance.configuration, *setup_step[:args])
|
155
160
|
response = instance.send(setup_step[:method], *args, &setup_step[:block])
|
156
161
|
if skip_immediate_run?(instance, [response])
|
data/lib/grape/cookies.rb
CHANGED
@@ -33,9 +33,11 @@ module Grape
|
|
33
33
|
@cookies.each(&block)
|
34
34
|
end
|
35
35
|
|
36
|
+
# rubocop:disable Layout/SpaceBeforeBrackets
|
36
37
|
def delete(name, **opts)
|
37
38
|
options = opts.merge(value: 'deleted', expires: Time.at(0))
|
38
39
|
self.[]=(name, options)
|
39
40
|
end
|
41
|
+
# rubocop:enable Layout/SpaceBeforeBrackets
|
40
42
|
end
|
41
43
|
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)
|