grape 2.0.0 → 2.1.1
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 +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
|