grape 2.0.0 → 2.1.3
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 +77 -1
- data/README.md +362 -316
- data/UPGRADING.md +197 -7
- data/grape.gemspec +5 -6
- data/lib/grape/api/instance.rb +14 -11
- data/lib/grape/api.rb +19 -10
- 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 +46 -15
- data/lib/grape/dsl/parameters.rb +5 -4
- data/lib/grape/dsl/routing.rb +20 -4
- data/lib/grape/dsl/validations.rb +13 -0
- data/lib/grape/endpoint.rb +14 -17
- 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 +2 -4
- 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 +1 -3
- 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 +4 -5
- 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_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/attributes_iterator.rb +1 -0
- data/lib/grape/validations/contract_scope.rb +71 -0
- data/lib/grape/validations/params_scope.rb +22 -19
- 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/exactly_one_of_validator.rb +1 -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 +30 -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
|
@@ -48,7 +46,7 @@ module Grape
|
|
48
46
|
# Parses the API's definition and compiles it into an instance of
|
49
47
|
# Grape::API.
|
50
48
|
def compile
|
51
|
-
@instance ||= new
|
49
|
+
@instance ||= new # rubocop:disable Naming/MemoizedInstanceVariableName
|
52
50
|
end
|
53
51
|
|
54
52
|
# Wipe the compiled API so we can recompile after changes were made.
|
@@ -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)
|
@@ -35,7 +32,7 @@ module Grape
|
|
35
32
|
def inherited(api)
|
36
33
|
super
|
37
34
|
|
38
|
-
api.initial_setup(Grape::API
|
35
|
+
api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)
|
39
36
|
api.override_all_methods!
|
40
37
|
end
|
41
38
|
|
@@ -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
|
@@ -111,7 +108,7 @@ module Grape
|
|
111
108
|
end
|
112
109
|
|
113
110
|
def respond_to?(method, include_private = false)
|
114
|
-
super
|
111
|
+
super || base_instance.respond_to?(method, include_private)
|
115
112
|
end
|
116
113
|
|
117
114
|
def respond_to_missing?(method, include_private = false)
|
@@ -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
|
{}
|
@@ -159,12 +163,32 @@ module Grape
|
|
159
163
|
# end user with the specified message.
|
160
164
|
#
|
161
165
|
# @param message [String] The message to display.
|
162
|
-
# @param status [Integer]
|
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
|
-
|
165
|
-
|
168
|
+
# @param backtrace [Array<String>] The backtrace of the exception that caused the error.
|
169
|
+
# @param original_exception [Exception] The original exception that caused the error.
|
170
|
+
def error!(message, status = nil, additional_headers = nil, backtrace = nil, original_exception = nil)
|
171
|
+
status = self.status(status || namespace_inheritable(:default_error_status))
|
166
172
|
headers = additional_headers.present? ? header.merge(additional_headers) : header
|
167
|
-
throw :error, message: message, status:
|
173
|
+
throw :error, message: message, status: status, headers: headers, backtrace: backtrace, original_exception: original_exception
|
174
|
+
end
|
175
|
+
|
176
|
+
# Creates a Rack response based on the provided message, status, and headers.
|
177
|
+
# The content type in the headers is set to the default content type unless provided.
|
178
|
+
# The message is HTML-escaped if the content type is 'text/html'.
|
179
|
+
#
|
180
|
+
# @param message [String] The content of the response.
|
181
|
+
# @param status [Integer] The HTTP status code.
|
182
|
+
# @params headers [Hash] (optional) Headers for the response
|
183
|
+
# (default: {Rack::CONTENT_TYPE => content_type}).
|
184
|
+
#
|
185
|
+
# Returns:
|
186
|
+
# A Rack::Response object containing the specified message, status, and headers.
|
187
|
+
#
|
188
|
+
def rack_response(message, status = 200, headers = { Rack::CONTENT_TYPE => content_type })
|
189
|
+
Grape.deprecator.warn('The rack_response method has been deprecated, use error! instead.')
|
190
|
+
message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == 'text/html'
|
191
|
+
Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), headers)
|
168
192
|
end
|
169
193
|
|
170
194
|
# Redirect to a new url.
|
@@ -178,7 +202,7 @@ module Grape
|
|
178
202
|
if permanent
|
179
203
|
status 301
|
180
204
|
body_message ||= "This resource has been moved permanently to #{url}."
|
181
|
-
elsif
|
205
|
+
elsif http_version == 'HTTP/1.1' && !request.get?
|
182
206
|
status 303
|
183
207
|
body_message ||= "An alternate resource is located at #{url}."
|
184
208
|
else
|
@@ -204,10 +228,9 @@ module Grape
|
|
204
228
|
when nil
|
205
229
|
return @status if instance_variable_defined?(:@status) && @status
|
206
230
|
|
207
|
-
|
208
|
-
when Grape::Http::Headers::POST
|
231
|
+
if request.post?
|
209
232
|
201
|
210
|
-
|
233
|
+
elsif request.delete?
|
211
234
|
if instance_variable_defined?(:@body) && @body.present?
|
212
235
|
200
|
213
236
|
else
|
@@ -436,6 +459,14 @@ module Grape
|
|
436
459
|
embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
|
437
460
|
entity_class.represent(object, **embeds.merge(options))
|
438
461
|
end
|
462
|
+
|
463
|
+
def http_version
|
464
|
+
env['HTTP_VERSION'] || env[Rack::SERVER_PROTOCOL]
|
465
|
+
end
|
466
|
+
|
467
|
+
def context
|
468
|
+
self
|
469
|
+
end
|
439
470
|
end
|
440
471
|
end
|
441
472
|
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.
|
@@ -230,7 +231,7 @@ module Grape
|
|
230
231
|
|
231
232
|
alias group requires
|
232
233
|
|
233
|
-
class EmptyOptionalValue; end
|
234
|
+
class EmptyOptionalValue; end # rubocop:disable Lint/EmptyClass
|
234
235
|
|
235
236
|
def map_params(params, element, is_array = false)
|
236
237
|
if params.is_a?(Array)
|