grape 2.0.0 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -1
- data/README.md +362 -316
- data/UPGRADING.md +197 -7
- data/grape.gemspec +5 -6
- data/lib/grape/api/instance.rb +13 -10
- data/lib/grape/api.rb +17 -8
- data/lib/grape/content_types.rb +0 -2
- data/lib/grape/cookies.rb +2 -1
- data/lib/grape/dry_types.rb +0 -2
- data/lib/grape/dsl/desc.rb +22 -20
- data/lib/grape/dsl/headers.rb +1 -1
- data/lib/grape/dsl/inside_route.rb +42 -13
- data/lib/grape/dsl/parameters.rb +4 -3
- data/lib/grape/dsl/routing.rb +20 -4
- data/lib/grape/dsl/validations.rb +13 -0
- data/lib/grape/endpoint.rb +12 -15
- data/lib/grape/{util/env.rb → env.rb} +0 -5
- data/lib/grape/error_formatter/txt.rb +11 -10
- data/lib/grape/exceptions/base.rb +3 -3
- data/lib/grape/exceptions/validation.rb +0 -2
- data/lib/grape/exceptions/validation_array_errors.rb +1 -0
- data/lib/grape/exceptions/validation_errors.rb +1 -3
- data/lib/grape/extensions/hash.rb +5 -1
- data/lib/grape/http/headers.rb +18 -34
- data/lib/grape/{util/json.rb → json.rb} +1 -3
- data/lib/grape/locale/en.yml +3 -0
- data/lib/grape/middleware/auth/base.rb +0 -2
- data/lib/grape/middleware/auth/dsl.rb +0 -2
- data/lib/grape/middleware/base.rb +0 -2
- data/lib/grape/middleware/error.rb +55 -50
- data/lib/grape/middleware/formatter.rb +16 -13
- data/lib/grape/middleware/globals.rb +1 -3
- data/lib/grape/middleware/stack.rb +2 -3
- data/lib/grape/middleware/versioner/accept_version_header.rb +0 -2
- data/lib/grape/middleware/versioner/header.rb +17 -163
- data/lib/grape/middleware/versioner/param.rb +2 -4
- data/lib/grape/middleware/versioner/path.rb +1 -3
- data/lib/grape/namespace.rb +3 -4
- data/lib/grape/path.rb +24 -29
- data/lib/grape/request.rb +4 -12
- data/lib/grape/router/base_route.rb +39 -0
- data/lib/grape/router/greedy_route.rb +20 -0
- data/lib/grape/router/pattern.rb +39 -30
- data/lib/grape/router/route.rb +22 -59
- data/lib/grape/router.rb +32 -37
- data/lib/grape/util/accept/header.rb +19 -0
- data/lib/grape/util/accept_header_handler.rb +105 -0
- data/lib/grape/util/base_inheritable.rb +4 -4
- data/lib/grape/util/cache.rb +0 -3
- data/lib/grape/util/endpoint_configuration.rb +1 -1
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_values.rb +0 -2
- data/lib/grape/util/lazy/block.rb +29 -0
- data/lib/grape/util/lazy/object.rb +45 -0
- data/lib/grape/util/lazy/value.rb +38 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +34 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +70 -0
- data/lib/grape/util/reverse_stackable_values.rb +1 -6
- data/lib/grape/util/stackable_values.rb +1 -6
- data/lib/grape/util/strict_hash_configuration.rb +3 -3
- data/lib/grape/validations/attributes_doc.rb +38 -36
- data/lib/grape/validations/contract_scope.rb +71 -0
- data/lib/grape/validations/params_scope.rb +10 -9
- data/lib/grape/validations/types/array_coercer.rb +0 -2
- data/lib/grape/validations/types/build_coercer.rb +69 -71
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
- data/lib/grape/validations/types/json.rb +0 -2
- data/lib/grape/validations/types/primitive_coercer.rb +0 -2
- data/lib/grape/validations/types/set_coercer.rb +0 -3
- data/lib/grape/validations/types.rb +0 -3
- data/lib/grape/validations/validators/base.rb +1 -0
- data/lib/grape/validations/validators/default_validator.rb +5 -1
- data/lib/grape/validations/validators/length_validator.rb +42 -0
- data/lib/grape/validations/validators/values_validator.rb +6 -1
- data/lib/grape/validations.rb +3 -7
- data/lib/grape/version.rb +1 -1
- data/lib/grape/{util/xml.rb → xml.rb} +1 -1
- data/lib/grape.rb +30 -274
- metadata +31 -37
- data/lib/grape/eager_load.rb +0 -20
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
- data/lib/grape/router/attribute_translator.rb +0 -63
- data/lib/grape/util/lazy_block.rb +0 -27
- data/lib/grape/util/lazy_object.rb +0 -43
- data/lib/grape/util/lazy_value.rb +0 -91
data/UPGRADING.md
CHANGED
@@ -1,6 +1,199 @@
|
|
1
1
|
Upgrading Grape
|
2
2
|
===============
|
3
3
|
|
4
|
+
### Upgrading to >= 2.1.0
|
5
|
+
|
6
|
+
#### Optional Builder
|
7
|
+
|
8
|
+
The `builder` gem dependency has been made optional as it's only used when generating XML. If your code does, add `builder` to your `Gemfile`.
|
9
|
+
|
10
|
+
See [#2445](https://github.com/ruby-grape/grape/pull/2445) for more information.
|
11
|
+
|
12
|
+
#### Deep Merging of Parameter Attributes
|
13
|
+
|
14
|
+
Grape now uses `deep_merge` to combine parameter attributes within the `with` method. Previously, attributes defined at the parameter level would override those defined at the group level.
|
15
|
+
With deep merge, attributes are now combined, allowing for more detailed and nuanced API specifications.
|
16
|
+
|
17
|
+
For example:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
with(documentation: { in: 'body' }) do
|
21
|
+
optional :vault, documentation: { default: 33 }
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
Before it was equivalent to:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
optional :vault, documentation: { default: 33 }
|
29
|
+
```
|
30
|
+
|
31
|
+
After it is an equivalent of:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
optional :vault, documentation: { in: 'body', default: 33 }
|
35
|
+
```
|
36
|
+
|
37
|
+
See [#2432](https://github.com/ruby-grape/grape/pull/2432) for more information.
|
38
|
+
|
39
|
+
#### Zeitwerk
|
40
|
+
|
41
|
+
Grape's autoloader has been updated and it's now based on [Zeitwerk](https://github.com/fxn/zeitwerk).
|
42
|
+
If you MP (Monkey Patch) some files and you're not following the [file structure](https://github.com/fxn/zeitwerk?tab=readme-ov-file#file-structure), you might end up with a Zeitwerk error.
|
43
|
+
|
44
|
+
See [#2363](https://github.com/ruby-grape/grape/pull/2363) for more information.
|
45
|
+
|
46
|
+
#### Changes in rescue_from
|
47
|
+
|
48
|
+
The `rack_response` method has been deprecated and the `error_response` method has been removed. Use `error!` instead.
|
49
|
+
|
50
|
+
See [#2414](https://github.com/ruby-grape/grape/pull/2414) for more information.
|
51
|
+
|
52
|
+
#### Change in parameters precedence
|
53
|
+
|
54
|
+
When using together with `Grape::Extensions::Hash::ParamBuilder`, `route_param` takes higher precedence over a regular parameter defined with same name, which now matches the default param builder behavior.
|
55
|
+
|
56
|
+
This was a regression introduced by [#2326](https://github.com/ruby-grape/grape/pull/2326) in Grape v1.8.0.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
grape.configure do |config|
|
60
|
+
config.param_builder = Grape::Extensions::Hash::ParamBuilder
|
61
|
+
end
|
62
|
+
|
63
|
+
params do
|
64
|
+
requires :foo, type: String
|
65
|
+
end
|
66
|
+
route_param :foo do
|
67
|
+
get do
|
68
|
+
{ value: params[:foo] }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
Request:
|
74
|
+
|
75
|
+
```bash
|
76
|
+
curl -X POST -H "Content-Type: application/json" localhost:9292/bar -d '{"foo": "baz"}'
|
77
|
+
```
|
78
|
+
|
79
|
+
Response prior to v1.8.0:
|
80
|
+
|
81
|
+
```json
|
82
|
+
{
|
83
|
+
"value": "bar"
|
84
|
+
}
|
85
|
+
```
|
86
|
+
|
87
|
+
v1.8.0..v2.0.0:
|
88
|
+
|
89
|
+
```json
|
90
|
+
{
|
91
|
+
"value": "baz"
|
92
|
+
}
|
93
|
+
```
|
94
|
+
|
95
|
+
v2.1.0+:
|
96
|
+
|
97
|
+
```json
|
98
|
+
{
|
99
|
+
"value": "bar"
|
100
|
+
}
|
101
|
+
```
|
102
|
+
|
103
|
+
See [#2378](https://github.com/ruby-grape/grape/pull/2378) for details.
|
104
|
+
|
105
|
+
#### Grape::Router::Route.route_xxx methods have been removed
|
106
|
+
|
107
|
+
- `route_method` is accessible through `request_method`
|
108
|
+
- `route_path` is accessible through `path`
|
109
|
+
- Any other `route_xyz` are accessible through `options[xyz]`
|
110
|
+
|
111
|
+
#### Instance variables scope
|
112
|
+
|
113
|
+
Due to the changes done in [#2377](https://github.com/ruby-grape/grape/pull/2377), the instance variables defined inside each of the endpoints (or inside a `before` validator) are now accessible inside the `rescue_from`. The behavior of the instance variables was undefined until `2.1.0`.
|
114
|
+
|
115
|
+
If you were using the same variable name defined inside an endpoint or `before` validator inside a `rescue_from` handler, you need to take in mind that you can start getting different values or you can be overriding values.
|
116
|
+
|
117
|
+
Before:
|
118
|
+
```ruby
|
119
|
+
class TwitterAPI < Grape::API
|
120
|
+
before do
|
121
|
+
@var = 1
|
122
|
+
end
|
123
|
+
|
124
|
+
get '/' do
|
125
|
+
puts @var # => 1
|
126
|
+
raise
|
127
|
+
end
|
128
|
+
|
129
|
+
rescue_from :all do
|
130
|
+
puts @var # => nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
After:
|
136
|
+
```ruby
|
137
|
+
class TwitterAPI < Grape::API
|
138
|
+
before do
|
139
|
+
@var = 1
|
140
|
+
end
|
141
|
+
|
142
|
+
get '/' do
|
143
|
+
puts @var # => 1
|
144
|
+
raise
|
145
|
+
end
|
146
|
+
|
147
|
+
rescue_from :all do
|
148
|
+
puts @var # => 1
|
149
|
+
end
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
#### Recognizing Path
|
154
|
+
|
155
|
+
Grape now considers the types of the configured `route_params` in order to determine the endpoint that matches with the performed request.
|
156
|
+
|
157
|
+
So taking into account this `Grape::API` class
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
class Books < Grape::API
|
161
|
+
resource :books do
|
162
|
+
route_param :id, type: Integer do
|
163
|
+
# GET /books/:id
|
164
|
+
get do
|
165
|
+
#...
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
resource :share do
|
170
|
+
# POST /books/share
|
171
|
+
post do
|
172
|
+
# ....
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
Before:
|
180
|
+
```ruby
|
181
|
+
API.recognize_path '/books/1' # => /books/:id
|
182
|
+
API.recognize_path '/books/share' # => /books/:id
|
183
|
+
API.recognize_path '/books/other' # => /books/:id
|
184
|
+
```
|
185
|
+
|
186
|
+
After:
|
187
|
+
```ruby
|
188
|
+
API.recognize_path '/books/1' # => /books/:id
|
189
|
+
API.recognize_path '/books/share' # => /books/share
|
190
|
+
API.recognize_path '/books/other' # => nil
|
191
|
+
```
|
192
|
+
|
193
|
+
This implies that before this changes, when you performed `/books/other` and it matched with the `/books/:id` endpoint, you get a `400 Bad Request` response because the type of the provided `:id` param was not an `Integer`. However, after upgrading to version `2.1.0` you will get a `404 Not Found` response, because there is not a defined endpoint that matches with `/books/other`.
|
194
|
+
|
195
|
+
See [#2379](https://github.com/ruby-grape/grape/pull/2379) for more information.
|
196
|
+
|
4
197
|
### Upgrading to >= 2.0.0
|
5
198
|
|
6
199
|
#### Headers
|
@@ -19,7 +212,7 @@ If you are using Rack 3 in your application then the headers will be set to:
|
|
19
212
|
{ "content-type" => "application/json", "secret-password" => "foo"}
|
20
213
|
```
|
21
214
|
|
22
|
-
This means if you are checking for header values in your application, you would need to change your code to use downcased keys.
|
215
|
+
This means if you are checking for header values in your application, you would need to change your code to use downcased keys.
|
23
216
|
|
24
217
|
```ruby
|
25
218
|
get do
|
@@ -474,8 +667,7 @@ end
|
|
474
667
|
|
475
668
|
##### `name` (and other caveats) of the mounted API
|
476
669
|
|
477
|
-
After the patch, the mounted API is no longer a Named class inheriting from `Grape::API`, it is an anonymous class
|
478
|
-
which inherit from `Grape::API::Instance`.
|
670
|
+
After the patch, the mounted API is no longer a Named class inheriting from `Grape::API`, it is an anonymous class which inherit from `Grape::API::Instance`.
|
479
671
|
|
480
672
|
What this means in practice, is:
|
481
673
|
|
@@ -855,8 +1047,7 @@ See [#1114](https://github.com/ruby-grape/grape/pull/1114) for more information.
|
|
855
1047
|
|
856
1048
|
#### Bypasses formatters when status code indicates no content
|
857
1049
|
|
858
|
-
To be consistent with rack and it's handling of standard responses associated with no content, both default and custom formatters will now
|
859
|
-
be bypassed when processing responses for status codes defined [by rack](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567)
|
1050
|
+
To be consistent with rack and it's handling of standard responses associated with no content, both default and custom formatters will now be bypassed when processing responses for status codes defined [by rack](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567)
|
860
1051
|
|
861
1052
|
See [#1190](https://github.com/ruby-grape/grape/pull/1190) for more information.
|
862
1053
|
|
@@ -1297,8 +1488,7 @@ As replacement can be used
|
|
1297
1488
|
* `Grape::Middleware::Auth::Digest` => [`Rack::Auth::Digest::MD5`](https://github.com/rack/rack/blob/master/lib/rack/auth/digest/md5.rb)
|
1298
1489
|
* `Grape::Middleware::Auth::OAuth2` => [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2](https://github.com/nov/rack-oauth2)
|
1299
1490
|
|
1300
|
-
If this is not possible you can extract the middleware files from [grape v0.7.0](https://github.com/ruby-grape/grape/tree/v0.7.0/lib/grape/middleware/auth)
|
1301
|
-
and host these files within your application
|
1491
|
+
If this is not possible you can extract the middleware files from [grape v0.7.0](https://github.com/ruby-grape/grape/tree/v0.7.0/lib/grape/middleware/auth) and host these files within your application
|
1302
1492
|
|
1303
1493
|
See [#703](https://github.com/ruby-grape/Grape/pull/703) for more information.
|
1304
1494
|
|
data/grape.gemspec
CHANGED
@@ -20,14 +20,13 @@ Gem::Specification.new do |s|
|
|
20
20
|
'source_code_uri' => "https://github.com/ruby-grape/grape/tree/v#{s.version}"
|
21
21
|
}
|
22
22
|
|
23
|
-
s.add_runtime_dependency 'activesupport', '>=
|
24
|
-
s.add_runtime_dependency 'builder'
|
23
|
+
s.add_runtime_dependency 'activesupport', '>= 6'
|
25
24
|
s.add_runtime_dependency 'dry-types', '>= 1.1'
|
26
|
-
s.add_runtime_dependency 'mustermann-grape', '~> 1.
|
27
|
-
s.add_runtime_dependency 'rack', '>=
|
28
|
-
s.add_runtime_dependency '
|
25
|
+
s.add_runtime_dependency 'mustermann-grape', '~> 1.1.0'
|
26
|
+
s.add_runtime_dependency 'rack', '>= 2'
|
27
|
+
s.add_runtime_dependency 'zeitwerk'
|
29
28
|
|
30
29
|
s.files = Dir['lib/**/*', 'CHANGELOG.md', 'CONTRIBUTING.md', 'README.md', 'grape.png', 'UPGRADING.md', 'LICENSE', 'grape.gemspec']
|
31
30
|
s.require_paths = ['lib']
|
32
|
-
s.required_ruby_version = '>= 2.
|
31
|
+
s.required_ruby_version = '>= 2.7.0'
|
33
32
|
end
|
data/lib/grape/api/instance.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/router'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
class API
|
7
5
|
# The API Instance class, is the engine behind Grape::API. Each class that inherits
|
@@ -112,7 +110,7 @@ module Grape
|
|
112
110
|
end
|
113
111
|
|
114
112
|
def evaluate_as_instance_with_configuration(block, lazy: false)
|
115
|
-
lazy_block = Grape::Util::
|
113
|
+
lazy_block = Grape::Util::Lazy::Block.new do |configuration|
|
116
114
|
value_for_configuration = configuration
|
117
115
|
self.configuration = value_for_configuration.evaluate if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
|
118
116
|
response = instance_eval(&block)
|
@@ -127,6 +125,7 @@ module Grape
|
|
127
125
|
end
|
128
126
|
|
129
127
|
def inherited(subclass)
|
128
|
+
super
|
130
129
|
subclass.reset!
|
131
130
|
subclass.logger = logger.clone
|
132
131
|
end
|
@@ -162,9 +161,13 @@ module Grape
|
|
162
161
|
|
163
162
|
# Handle a request. See Rack documentation for what `env` is.
|
164
163
|
def call(env)
|
165
|
-
|
166
|
-
|
167
|
-
|
164
|
+
status, headers, response = @router.call(env)
|
165
|
+
unless cascade?
|
166
|
+
headers = Grape::Util::Header.new.merge(headers)
|
167
|
+
headers.delete(Grape::Http::Headers::X_CASCADE)
|
168
|
+
end
|
169
|
+
|
170
|
+
[status, headers, response]
|
168
171
|
end
|
169
172
|
|
170
173
|
# Some requests may return a HTTP 404 error if grape cannot find a matching
|
@@ -203,11 +206,11 @@ module Grape
|
|
203
206
|
|
204
207
|
allowed_methods = config[:methods].dup
|
205
208
|
|
206
|
-
allowed_methods |= [
|
209
|
+
allowed_methods |= [Rack::HEAD] if !self.class.namespace_inheritable(:do_not_route_head) && allowed_methods.include?(Rack::GET)
|
207
210
|
|
208
|
-
allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [
|
211
|
+
allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Rack::OPTIONS] | allowed_methods)
|
209
212
|
|
210
|
-
config[:endpoint].options[:options_route_enabled] = true unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(
|
213
|
+
config[:endpoint].options[:options_route_enabled] = true unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Rack::OPTIONS)
|
211
214
|
|
212
215
|
attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
|
213
216
|
generate_not_allowed_method(config[:pattern], **attributes)
|
@@ -218,7 +221,7 @@ module Grape
|
|
218
221
|
|
219
222
|
def collect_route_config_per_pattern
|
220
223
|
all_routes = self.class.endpoints.map(&:routes).flatten
|
221
|
-
routes_by_regexp = all_routes.group_by
|
224
|
+
routes_by_regexp = all_routes.group_by(&:pattern_regexp)
|
222
225
|
|
223
226
|
# Build the configuration based on the first endpoint and the collection of methods supported.
|
224
227
|
routes_by_regexp.values.map do |routes|
|
data/lib/grape/api.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/router'
|
4
|
-
require 'grape/api/instance'
|
5
|
-
|
6
3
|
module Grape
|
7
4
|
# The API class is the primary entry point for creating Grape APIs. Users
|
8
5
|
# should subclass this class in order to build an API.
|
@@ -26,8 +23,8 @@ module Grape
|
|
26
23
|
attr_accessor :base_instance, :instances
|
27
24
|
|
28
25
|
# Rather than initializing an object of type Grape::API, create an object of type Instance
|
29
|
-
def new(
|
30
|
-
base_instance.new(
|
26
|
+
def new(...)
|
27
|
+
base_instance.new(...)
|
31
28
|
end
|
32
29
|
|
33
30
|
# When inherited, will create a list of all instances (times the API was mounted)
|
@@ -77,8 +74,8 @@ module Grape
|
|
77
74
|
# the headers, and the body. See [the rack specification]
|
78
75
|
# (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
|
79
76
|
# NOTE: This will only be called on an API directly mounted on RACK
|
80
|
-
def call(
|
81
|
-
instance_for_rack.call(
|
77
|
+
def call(...)
|
78
|
+
instance_for_rack.call(...)
|
82
79
|
end
|
83
80
|
|
84
81
|
# Alleviates problems with autoloading by tring to search for the constant
|
@@ -128,7 +125,6 @@ module Grape
|
|
128
125
|
end
|
129
126
|
|
130
127
|
def compile!
|
131
|
-
require 'grape/eager_load'
|
132
128
|
instance_for_rack.compile! # See API::Instance.compile!
|
133
129
|
end
|
134
130
|
|
@@ -150,6 +146,19 @@ module Grape
|
|
150
146
|
@instances.each do |instance|
|
151
147
|
last_response = replay_step_on(instance, setup_step)
|
152
148
|
end
|
149
|
+
|
150
|
+
# Updating all previously mounted classes in the case that new methods have been executed.
|
151
|
+
if method != :mount && @setup.any?
|
152
|
+
previous_mount_steps = @setup.select { |step| step[:method] == :mount }
|
153
|
+
previous_mount_steps.each do |mount_step|
|
154
|
+
refresh_mount_step = mount_step.merge(method: :refresh_mounted_api)
|
155
|
+
@setup += [refresh_mount_step]
|
156
|
+
@instances.each do |instance|
|
157
|
+
replay_step_on(instance, refresh_mount_step)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
153
162
|
last_response
|
154
163
|
end
|
155
164
|
|
data/lib/grape/content_types.rb
CHANGED
data/lib/grape/cookies.rb
CHANGED
@@ -33,9 +33,10 @@ module Grape
|
|
33
33
|
@cookies.each(&block)
|
34
34
|
end
|
35
35
|
|
36
|
+
# see https://github.com/rack/rack/blob/main/lib/rack/utils.rb#L338-L340
|
36
37
|
# rubocop:disable Layout/SpaceBeforeBrackets
|
37
38
|
def delete(name, **opts)
|
38
|
-
options = opts.merge(value: '
|
39
|
+
options = opts.merge(max_age: '0', value: '', expires: Time.at(0))
|
39
40
|
self.[]=(name, options)
|
40
41
|
end
|
41
42
|
# rubocop:enable Layout/SpaceBeforeBrackets
|
data/lib/grape/dry_types.rb
CHANGED
data/lib/grape/dsl/desc.rb
CHANGED
@@ -5,6 +5,27 @@ module Grape
|
|
5
5
|
module Desc
|
6
6
|
include Grape::DSL::Settings
|
7
7
|
|
8
|
+
ROUTE_ATTRIBUTES = %i[
|
9
|
+
body_name
|
10
|
+
consumes
|
11
|
+
default
|
12
|
+
deprecated
|
13
|
+
description
|
14
|
+
detail
|
15
|
+
entity
|
16
|
+
headers
|
17
|
+
hidden
|
18
|
+
http_codes
|
19
|
+
is_array
|
20
|
+
named
|
21
|
+
nickname
|
22
|
+
params
|
23
|
+
produces
|
24
|
+
security
|
25
|
+
summary
|
26
|
+
tags
|
27
|
+
].freeze
|
28
|
+
|
8
29
|
# Add a description to the next namespace or function.
|
9
30
|
# @param description [String] descriptive string for this endpoint
|
10
31
|
# or namespace
|
@@ -81,26 +102,7 @@ module Grape
|
|
81
102
|
# Returns an object which configures itself via an instance-context DSL.
|
82
103
|
def desc_container(endpoint_configuration)
|
83
104
|
Module.new do
|
84
|
-
include Grape::Util::StrictHashConfiguration.module(
|
85
|
-
:summary,
|
86
|
-
:description,
|
87
|
-
:detail,
|
88
|
-
:params,
|
89
|
-
:entity,
|
90
|
-
:http_codes,
|
91
|
-
:named,
|
92
|
-
:body_name,
|
93
|
-
:headers,
|
94
|
-
:hidden,
|
95
|
-
:deprecated,
|
96
|
-
:is_array,
|
97
|
-
:nickname,
|
98
|
-
:produces,
|
99
|
-
:consumes,
|
100
|
-
:security,
|
101
|
-
:tags,
|
102
|
-
:default
|
103
|
-
)
|
105
|
+
include Grape::Util::StrictHashConfiguration.module(*ROUTE_ATTRIBUTES)
|
104
106
|
config_context.define_singleton_method(:configuration) do
|
105
107
|
endpoint_configuration
|
106
108
|
end
|
data/lib/grape/dsl/headers.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/dsl/headers'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module DSL
|
7
5
|
module InsideRoute
|
@@ -31,11 +29,17 @@ module Grape
|
|
31
29
|
options = options.reverse_merge(include_missing: true, include_parent_namespaces: true, evaluate_given: false)
|
32
30
|
declared_params ||= optioned_declared_params(**options)
|
33
31
|
|
34
|
-
if passed_params.is_a?(Array)
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
res = if passed_params.is_a?(Array)
|
33
|
+
declared_array(passed_params, options, declared_params, params_nested_path)
|
34
|
+
else
|
35
|
+
declared_hash(passed_params, options, declared_params, params_nested_path)
|
36
|
+
end
|
37
|
+
|
38
|
+
if (key_maps = namespace_stackable(:contract_key_map))
|
39
|
+
key_maps.each { |key_map| key_map.write(passed_params, res) }
|
38
40
|
end
|
41
|
+
|
42
|
+
res
|
39
43
|
end
|
40
44
|
|
41
45
|
private
|
@@ -99,7 +103,7 @@ module Grape
|
|
99
103
|
|
100
104
|
route_options_params = options[:route_options][:params] || {}
|
101
105
|
type = route_options_params.dig(key, :type)
|
102
|
-
has_children = route_options_params.keys.any? { |k| k != key && k.start_with?(key) }
|
106
|
+
has_children = route_options_params.keys.any? { |k| k != key && k.start_with?("#{key}[") }
|
103
107
|
|
104
108
|
if type == 'Hash' && !has_children
|
105
109
|
{}
|
@@ -162,9 +166,27 @@ module Grape
|
|
162
166
|
# @param status [Integer] the HTTP Status Code. Defaults to default_error_status, 500 if not set.
|
163
167
|
# @param additional_headers [Hash] Addtional headers for the response.
|
164
168
|
def error!(message, status = nil, additional_headers = nil)
|
165
|
-
self.status(status || namespace_inheritable(:default_error_status))
|
169
|
+
status = self.status(status || namespace_inheritable(:default_error_status))
|
166
170
|
headers = additional_headers.present? ? header.merge(additional_headers) : header
|
167
|
-
throw :error, message: message, status:
|
171
|
+
throw :error, message: message, status: status, headers: headers
|
172
|
+
end
|
173
|
+
|
174
|
+
# Creates a Rack response based on the provided message, status, and headers.
|
175
|
+
# The content type in the headers is set to the default content type unless provided.
|
176
|
+
# The message is HTML-escaped if the content type is 'text/html'.
|
177
|
+
#
|
178
|
+
# @param message [String] The content of the response.
|
179
|
+
# @param status [Integer] The HTTP status code.
|
180
|
+
# @params headers [Hash] (optional) Headers for the response
|
181
|
+
# (default: {Rack::CONTENT_TYPE => content_type}).
|
182
|
+
#
|
183
|
+
# Returns:
|
184
|
+
# A Rack::Response object containing the specified message, status, and headers.
|
185
|
+
#
|
186
|
+
def rack_response(message, status = 200, headers = { Rack::CONTENT_TYPE => content_type })
|
187
|
+
Grape.deprecator.warn('The rack_response method has been deprecated, use error! instead.')
|
188
|
+
message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == 'text/html'
|
189
|
+
Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), headers)
|
168
190
|
end
|
169
191
|
|
170
192
|
# Redirect to a new url.
|
@@ -178,7 +200,7 @@ module Grape
|
|
178
200
|
if permanent
|
179
201
|
status 301
|
180
202
|
body_message ||= "This resource has been moved permanently to #{url}."
|
181
|
-
elsif
|
203
|
+
elsif http_version == 'HTTP/1.1' && !request.get?
|
182
204
|
status 303
|
183
205
|
body_message ||= "An alternate resource is located at #{url}."
|
184
206
|
else
|
@@ -204,10 +226,9 @@ module Grape
|
|
204
226
|
when nil
|
205
227
|
return @status if instance_variable_defined?(:@status) && @status
|
206
228
|
|
207
|
-
|
208
|
-
when Grape::Http::Headers::POST
|
229
|
+
if request.post?
|
209
230
|
201
|
210
|
-
|
231
|
+
elsif request.delete?
|
211
232
|
if instance_variable_defined?(:@body) && @body.present?
|
212
233
|
200
|
213
234
|
else
|
@@ -436,6 +457,14 @@ module Grape
|
|
436
457
|
embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
|
437
458
|
entity_class.represent(object, **embeds.merge(options))
|
438
459
|
end
|
460
|
+
|
461
|
+
def http_version
|
462
|
+
env['HTTP_VERSION'] || env[Rack::SERVER_PROTOCOL]
|
463
|
+
end
|
464
|
+
|
465
|
+
def context
|
466
|
+
self
|
467
|
+
end
|
439
468
|
end
|
440
469
|
end
|
441
470
|
end
|
data/lib/grape/dsl/parameters.rb
CHANGED
@@ -130,7 +130,7 @@ module Grape
|
|
130
130
|
|
131
131
|
opts = attrs.extract_options!.clone
|
132
132
|
opts[:presence] = { value: true, message: opts[:message] }
|
133
|
-
opts = @group.
|
133
|
+
opts = @group.deep_merge(opts) if instance_variable_defined?(:@group) && @group
|
134
134
|
|
135
135
|
if opts[:using]
|
136
136
|
require_required_and_optional_fields(attrs.first, opts)
|
@@ -149,7 +149,7 @@ module Grape
|
|
149
149
|
|
150
150
|
opts = attrs.extract_options!.clone
|
151
151
|
type = opts[:type]
|
152
|
-
opts = @group.
|
152
|
+
opts = @group.deep_merge(opts) if instance_variable_defined?(:@group) && @group
|
153
153
|
|
154
154
|
# check type for optional parameter group
|
155
155
|
if attrs && block
|
@@ -170,7 +170,8 @@ module Grape
|
|
170
170
|
# @param (see #requires)
|
171
171
|
# @option (see #requires)
|
172
172
|
def with(*attrs, &block)
|
173
|
-
|
173
|
+
new_group_attrs = [@group, attrs.clone.first].compact.reduce(&:deep_merge)
|
174
|
+
new_group_scope([new_group_attrs], &block)
|
174
175
|
end
|
175
176
|
|
176
177
|
# Disallow the given parameters to be present in the same request.
|
data/lib/grape/dsl/routing.rb
CHANGED
@@ -30,7 +30,7 @@ module Grape
|
|
30
30
|
if args.any?
|
31
31
|
options = args.extract_options!
|
32
32
|
options = options.reverse_merge(using: :path)
|
33
|
-
requested_versions = args.flatten
|
33
|
+
requested_versions = args.flatten.map(&:to_s)
|
34
34
|
|
35
35
|
raise Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
|
36
36
|
|
@@ -54,7 +54,7 @@ module Grape
|
|
54
54
|
|
55
55
|
# Define a root URL prefix for your entire API.
|
56
56
|
def prefix(prefix = nil)
|
57
|
-
namespace_inheritable(:root_prefix, prefix)
|
57
|
+
namespace_inheritable(:root_prefix, prefix&.to_s)
|
58
58
|
end
|
59
59
|
|
60
60
|
# Create a scope without affecting the URL.
|
@@ -85,8 +85,8 @@ module Grape
|
|
85
85
|
mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
|
86
86
|
mounts.each_pair do |app, path|
|
87
87
|
if app.respond_to?(:mount_instance)
|
88
|
-
opts_with = opts.any? ? opts.
|
89
|
-
mount({ app.mount_instance(configuration: opts_with) => path })
|
88
|
+
opts_with = opts.any? ? opts.first[:with] : {}
|
89
|
+
mount({ app.mount_instance(configuration: opts_with) => path }, *opts)
|
90
90
|
next
|
91
91
|
end
|
92
92
|
in_setting = inheritable_setting
|
@@ -103,6 +103,15 @@ module Grape
|
|
103
103
|
change!
|
104
104
|
end
|
105
105
|
|
106
|
+
# When trying to mount multiple times the same endpoint, remove the previous ones
|
107
|
+
# from the list of endpoints if refresh_already_mounted parameter is true
|
108
|
+
refresh_already_mounted = opts.any? ? opts.first[:refresh_already_mounted] : false
|
109
|
+
if refresh_already_mounted && !endpoints.empty?
|
110
|
+
endpoints.delete_if do |endpoint|
|
111
|
+
endpoint.options[:app].to_s == app.to_s
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
106
115
|
endpoints << Grape::Endpoint.new(
|
107
116
|
in_setting,
|
108
117
|
method: :any,
|
@@ -225,6 +234,13 @@ module Grape
|
|
225
234
|
def versions
|
226
235
|
@versions ||= []
|
227
236
|
end
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
def refresh_mounted_api(mounts, *opts)
|
241
|
+
opts << { refresh_already_mounted: true }
|
242
|
+
mount(mounts, *opts)
|
243
|
+
end
|
228
244
|
end
|
229
245
|
end
|
230
246
|
end
|