grape 1.3.0 → 1.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/README.md +4 -6
- data/UPGRADING.md +61 -16
- data/lib/grape/api/instance.rb +11 -7
- data/lib/grape/api.rb +2 -2
- data/lib/grape/content_types.rb +34 -0
- data/lib/grape/dsl/helpers.rb +1 -1
- data/lib/grape/dsl/inside_route.rb +10 -9
- data/lib/grape/dsl/parameters.rb +4 -4
- data/lib/grape/dsl/routing.rb +6 -4
- data/lib/grape/exceptions/base.rb +0 -4
- data/lib/grape/exceptions/validation_errors.rb +11 -12
- data/lib/grape/http/headers.rb +26 -0
- data/lib/grape/middleware/base.rb +1 -3
- data/lib/grape/middleware/stack.rb +2 -1
- data/lib/grape/middleware/versioner/header.rb +4 -4
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +12 -2
- data/lib/grape/path.rb +13 -3
- data/lib/grape/request.rb +12 -7
- data/lib/grape/router/pattern.rb +17 -16
- data/lib/grape/router/route.rb +4 -5
- data/lib/grape/router.rb +24 -14
- data/lib/grape/util/base_inheritable.rb +13 -6
- data/lib/grape/util/cache.rb +20 -0
- data/lib/grape/util/lazy_object.rb +43 -0
- data/lib/grape/util/reverse_stackable_values.rb +2 -0
- data/lib/grape/util/stackable_values.rb +7 -20
- data/lib/grape/validations/params_scope.rb +1 -1
- data/lib/grape/validations/types/build_coercer.rb +4 -3
- data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
- data/lib/grape/validations/types/file.rb +15 -12
- data/lib/grape/validations/types/json.rb +40 -36
- data/lib/grape/validations/types/primitive_coercer.rb +6 -3
- data/lib/grape/validations/types.rb +6 -5
- data/lib/grape/validations/validators/coerce.rb +14 -12
- data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
- data/lib/grape/validations/validators/regexp.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +2 -3
- data/spec/grape/api_spec.rb +7 -6
- data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
- data/spec/grape/middleware/formatter_spec.rb +2 -2
- data/spec/grape/middleware/stack_spec.rb +9 -0
- data/spec/grape/path_spec.rb +4 -4
- data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
- data/spec/grape/validations/types/primitive_coercer_spec.rb +75 -0
- data/spec/grape/validations/types_spec.rb +1 -1
- data/spec/grape/validations/validators/coerce_spec.rb +160 -78
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
- data/spec/grape/validations_spec.rb +8 -12
- data/spec/spec_helper.rb +3 -0
- data/spec/support/eager_load.rb +19 -0
- metadata +12 -6
- data/lib/grape/util/content_types.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1ba205b72fd823b11e700753891784fdfbd6791f591b790a99ac3433cb8fe23
|
4
|
+
data.tar.gz: '09e64a0d93415b28fdb179fbb5d7ef0d03e00dcb976fd885c0c655d6ac041345'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 06fcd2157bcfcc6e71876df09b34a318c600761b79fb99ad77739521d056950ee8391dfd050efd43618f7f99f11990623e276b32d15a27e92015ec3a56c6c08b
|
7
|
+
data.tar.gz: 5463bbd0347950765c9d92e859965e90dd69d7d1f781fd188386cf39597c196f5ba2dc697dbee4f896a70f6498c1e43034a33db8460ea9a719218a7d5df46d6f
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,41 @@
|
|
1
|
+
### 1.3.2 (2020/04/12)
|
2
|
+
|
3
|
+
#### Features
|
4
|
+
* [#2020](https://github.com/ruby-grape/grape/pull/2020): Reduce array allocation - [@ericproulx](https://github.com/ericproulx).
|
5
|
+
* [#2015](https://github.com/ruby-grape/grape/pull/2014): Reduce MatchData allocation - [@ericproulx](https://github.com/ericproulx).
|
6
|
+
* [#2014](https://github.com/ruby-grape/grape/pull/2014): Reduce total allocated arrays - [@ericproulx](https://github.com/ericproulx).
|
7
|
+
* [#2011](https://github.com/ruby-grape/grape/pull/2011): Reduce total retained regexes - [@ericproulx](https://github.com/ericproulx).
|
8
|
+
|
9
|
+
#### Fixes
|
10
|
+
|
11
|
+
* [#2033](https://github.com/ruby-grape/grape/pull/2033): Ensure `Float` params are correctly coerced to `BigDecimal` - [@tlconnor](https://github.com/tlconnor).
|
12
|
+
* [#2031](https://github.com/ruby-grape/grape/pull/2031): Fix a regression with an array of a custom type - [@dnesteryuk](https://github.com/dnesteryuk).
|
13
|
+
* [#2026](https://github.com/ruby-grape/grape/pull/2026): Fix a regression in `coerce_with` when coercion returns `nil` - [@misdoro](https://github.com/misdoro).
|
14
|
+
* [#2025](https://github.com/ruby-grape/grape/pull/2025): Fix Decimal type category - [@kdoya](https://github.com/kdoya).
|
15
|
+
* [#2019](https://github.com/ruby-grape/grape/pull/2019): Avoid coercing parameter with multiple types to an empty Array - [@stanhu](https://github.com/stanhu).
|
16
|
+
|
17
|
+
### 1.3.1 (2020/03/11)
|
18
|
+
|
19
|
+
#### Features
|
20
|
+
|
21
|
+
* [#2005](https://github.com/ruby-grape/grape/pull/2005): Content types registrable - [@ericproulx](https://github.com/ericproulx).
|
22
|
+
* [#2003](https://github.com/ruby-grape/grape/pull/2003): Upgraded Rubocop to 0.80.1 - [@ericproulx](https://github.com/ericproulx).
|
23
|
+
* [#2002](https://github.com/ruby-grape/grape/pull/2002): Objects allocation optimization (lazy_lookup) - [@ericproulx](https://github.com/ericproulx).
|
24
|
+
|
25
|
+
#### Fixes
|
26
|
+
|
27
|
+
* [#2006](https://github.com/ruby-grape/grape/pull/2006): Fix explicit rescue StandardError - [@ericproulx](https://github.com/ericproulx).
|
28
|
+
* [#2004](https://github.com/ruby-grape/grape/pull/2004): Rubocop fixes - [@ericproulx](https://github.com/ericproulx).
|
29
|
+
* [#1995](https://github.com/ruby-grape/grape/pull/1995): Fix: "undefined instance variables" and "method redefined" warnings - [@nbeyer](https://github.com/nbeyer).
|
30
|
+
* [#1994](https://github.com/ruby-grape/grape/pull/1993): Fix typos in README - [@bellmyer](https://github.com/bellmyer).
|
31
|
+
* [#1993](https://github.com/ruby-grape/grape/pull/1993): Lazy join allow header - [@ericproulx](https://github.com/ericproulx).
|
32
|
+
* [#1987](https://github.com/ruby-grape/grape/pull/1987): Re-add exactly_one_of mutually exclusive error message - [@ZeroInputCtrl](https://github.com/ZeroInputCtrl).
|
33
|
+
* [#1977](https://github.com/ruby-grape/grape/pull/1977): Skip validation for a file if it is optional and nil - [@dnesteryuk](https://github.com/dnesteryuk).
|
34
|
+
* [#1976](https://github.com/ruby-grape/grape/pull/1976): Ensure classes/modules listed for autoload really exist - [@dnesteryuk](https://github.com/dnesteryuk).
|
35
|
+
* [#1971](https://github.com/ruby-grape/grape/pull/1971): Fix BigDecimal coercion - [@FlickStuart](https://github.com/FlickStuart).
|
36
|
+
* [#1968](https://github.com/ruby-grape/grape/pull/1968): Fix args forwarding in Grape::Middleware::Stack#merge_with for ruby 2.7.0 - [@dm1try](https://github.com/dm1try).
|
37
|
+
* [#1988](https://github.com/ruby-grape/grape/pull/1988): Refactored the full_messages method and stop overriding full_message - [@hosseintoussi](https://github.com/hosseintoussi).
|
38
|
+
|
1
39
|
### 1.3.0 (2020/01/11)
|
2
40
|
|
3
41
|
#### Features
|
data/README.md
CHANGED
@@ -154,8 +154,7 @@ content negotiation, versioning and much more.
|
|
154
154
|
|
155
155
|
## Stable Release
|
156
156
|
|
157
|
-
You're reading the documentation for the stable release of Grape,
|
158
|
-
Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
|
157
|
+
You're reading the documentation for the stable release of Grape, 1.3.2.
|
159
158
|
|
160
159
|
## Project Resources
|
161
160
|
|
@@ -1722,7 +1721,7 @@ params do
|
|
1722
1721
|
end
|
1723
1722
|
```
|
1724
1723
|
|
1725
|
-
Every validation will have
|
1724
|
+
Every validation will have its own instance of the validator, which means that the validator can have a state.
|
1726
1725
|
|
1727
1726
|
### Validation Errors
|
1728
1727
|
|
@@ -3188,14 +3187,13 @@ applies to the current namespace and any children, but not parents.
|
|
3188
3187
|
```ruby
|
3189
3188
|
http_basic do |username, password|
|
3190
3189
|
# verify user's password here
|
3191
|
-
|
3190
|
+
# IMPORTANT: make sure you use a comparison method which isn't prone to a timing attack
|
3192
3191
|
end
|
3193
3192
|
```
|
3194
3193
|
|
3195
3194
|
```ruby
|
3196
3195
|
http_digest({ realm: 'Test Api', opaque: 'app secret' }) do |username|
|
3197
3196
|
# lookup the user's password here
|
3198
|
-
{ 'user1' => 'password1' }[username]
|
3199
3197
|
end
|
3200
3198
|
```
|
3201
3199
|
|
@@ -3301,7 +3299,7 @@ end
|
|
3301
3299
|
|
3302
3300
|
Blocks can be executed before or after every API call, using `before`, `after`,
|
3303
3301
|
`before_validation` and `after_validation`.
|
3304
|
-
If the API fails the `after` call will not be
|
3302
|
+
If the API fails the `after` call will not be triggered, if you need code to execute for sure
|
3305
3303
|
use the `finally`.
|
3306
3304
|
|
3307
3305
|
Before and after callbacks execute in the following order:
|
data/UPGRADING.md
CHANGED
@@ -9,38 +9,83 @@ After adding dry-types, Ruby 2.4 or newer is required.
|
|
9
9
|
|
10
10
|
#### Coercion
|
11
11
|
|
12
|
-
[Virtus](https://github.com/solnic/virtus) has been replaced by
|
12
|
+
[Virtus](https://github.com/solnic/virtus) has been replaced by
|
13
|
+
[dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter
|
14
|
+
coercion. If your project depends on Virtus outside of Grape, explicitly
|
15
|
+
add it to your `Gemfile`.
|
16
|
+
|
17
|
+
Here's an example of how to migrate a custom type from Virtus to dry-types:
|
13
18
|
|
14
19
|
```ruby
|
15
|
-
|
16
|
-
|
20
|
+
# Legacy Grape parser
|
21
|
+
class SecureUriType < Virtus::Attribute
|
22
|
+
def coerce(input)
|
23
|
+
URI.parse value
|
24
|
+
end
|
17
25
|
|
18
|
-
|
19
|
-
|
26
|
+
def value_coerced?(input)
|
27
|
+
value.is_a? String
|
28
|
+
end
|
20
29
|
end
|
21
30
|
|
22
|
-
# somewhere in your API
|
23
31
|
params do
|
24
|
-
requires :
|
32
|
+
requires :secure_uri, type: SecureUri
|
25
33
|
end
|
26
34
|
```
|
27
35
|
|
28
|
-
|
36
|
+
To use dry-types, we need to:
|
29
37
|
|
30
|
-
|
31
|
-
|
32
|
-
|
38
|
+
1. Remove the inheritance of `Virtus::Attribute`
|
39
|
+
1. Rename `coerce` to `self.parse`
|
40
|
+
1. Rename `value_coerced?` to `self.parsed?`
|
33
41
|
|
34
|
-
|
35
|
-
|
42
|
+
The custom type must have a class-level `parse` method to the model. A
|
43
|
+
class-level `parsed?` is needed if the parsed type differs from the
|
44
|
+
defined type. In the example below, since `SecureUri` is not the same
|
45
|
+
as `URI::HTTPS`, `self.parsed?` is needed:
|
36
46
|
|
37
|
-
|
38
|
-
|
47
|
+
```ruby
|
48
|
+
# New dry-types parser
|
49
|
+
class SecureUri
|
50
|
+
def self.parse(value)
|
51
|
+
URI.parse value
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.parsed?(value)
|
55
|
+
value.is_a? URI::HTTPS
|
39
56
|
end
|
40
57
|
end
|
58
|
+
|
59
|
+
params do
|
60
|
+
requires :secure_uri, type: SecureUri
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
#### Ensure that Array types have explicit coercions
|
65
|
+
|
66
|
+
Unlike Virtus, dry-types does not perform any implict coercions. If you
|
67
|
+
have any uses of `Array[String]`, `Array[Integer]`, etc. be sure they
|
68
|
+
use a `coerce_with` block. For example:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
requires :values, type: Array[String]
|
41
72
|
```
|
42
73
|
|
43
|
-
|
74
|
+
It's quite common to pass a comma-separated list, such as `tag1,tag2` as
|
75
|
+
`values`. Previously Virtus would implicitly coerce this to
|
76
|
+
`Array(values)` so that `["tag1,tag2"]` would pass the type checks, but
|
77
|
+
with `dry-types` the values are no longer coerced for you. To fix this,
|
78
|
+
you might do:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
requires :values, type: Array[String], coerce_with: ->(val) { val.split(',').map(&:strip) }
|
82
|
+
```
|
83
|
+
|
84
|
+
Likewise, for `Array[Integer]`, you might do:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
requires :values, type: Array[Integer], coerce_with: ->(val) { val.split(',').map(&:strip).map(&:to_i) }
|
88
|
+
```
|
44
89
|
|
45
90
|
For more information see [#1920](https://github.com/ruby-grape/grape/pull/1920).
|
46
91
|
|
data/lib/grape/api/instance.rb
CHANGED
@@ -74,7 +74,7 @@ module Grape
|
|
74
74
|
# (see #cascade?)
|
75
75
|
def cascade(value = nil)
|
76
76
|
if value.nil?
|
77
|
-
inheritable_setting.namespace_inheritable.
|
77
|
+
inheritable_setting.namespace_inheritable.key?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
|
78
78
|
else
|
79
79
|
namespace_inheritable(:cascade, value)
|
80
80
|
end
|
@@ -178,7 +178,7 @@ module Grape
|
|
178
178
|
# errors from reaching upstream. This is effectivelly done by unsetting
|
179
179
|
# X-Cascade. Default :cascade is true.
|
180
180
|
def cascade?
|
181
|
-
return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.
|
181
|
+
return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.key?(:cascade)
|
182
182
|
return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
|
183
183
|
true
|
184
184
|
end
|
@@ -209,7 +209,7 @@ module Grape
|
|
209
209
|
route_settings[:endpoint] = route.app
|
210
210
|
|
211
211
|
# using the :any shorthand produces [nil] for route methods, substitute all manually
|
212
|
-
route_settings[:methods] =
|
212
|
+
route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS if route_settings[:methods].include?('*')
|
213
213
|
end
|
214
214
|
end
|
215
215
|
|
@@ -227,7 +227,7 @@ module Grape
|
|
227
227
|
allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
|
228
228
|
end
|
229
229
|
|
230
|
-
allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods)
|
230
|
+
allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods)
|
231
231
|
|
232
232
|
unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
|
233
233
|
config[:endpoint].options[:options_route_enabled] = true
|
@@ -243,9 +243,13 @@ module Grape
|
|
243
243
|
# Generate a route that returns an HTTP 405 response for a user defined
|
244
244
|
# path on methods not specified
|
245
245
|
def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
|
246
|
-
|
247
|
-
|
248
|
-
|
246
|
+
supported_methods =
|
247
|
+
if self.class.namespace_inheritable(:do_not_route_options)
|
248
|
+
Grape::Http::Headers::SUPPORTED_METHODS
|
249
|
+
else
|
250
|
+
Grape::Http::Headers::SUPPORTED_METHODS_WITHOUT_OPTIONS
|
251
|
+
end
|
252
|
+
not_allowed_methods = supported_methods - allowed_methods
|
249
253
|
return if not_allowed_methods.empty?
|
250
254
|
|
251
255
|
@router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
|
data/lib/grape/api.rb
CHANGED
@@ -8,7 +8,7 @@ module Grape
|
|
8
8
|
# should subclass this class in order to build an API.
|
9
9
|
class API
|
10
10
|
# Class methods that we want to call on the API rather than on the API object
|
11
|
-
NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile!]).freeze
|
11
|
+
NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile! inherited]).freeze
|
12
12
|
|
13
13
|
class << self
|
14
14
|
attr_accessor :base_instance, :instances
|
@@ -175,7 +175,7 @@ module Grape
|
|
175
175
|
if argument.respond_to?(:lazy?) && argument.lazy?
|
176
176
|
argument.evaluate_from(configuration)
|
177
177
|
elsif argument.is_a?(Hash)
|
178
|
-
argument.
|
178
|
+
argument.transform_values { |value| evaluate_arguments(configuration, value).first }
|
179
179
|
elsif argument.is_a?(Array)
|
180
180
|
evaluate_arguments(configuration, *argument)
|
181
181
|
else
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'grape/util/registrable'
|
4
|
+
|
5
|
+
module Grape
|
6
|
+
module ContentTypes
|
7
|
+
extend Util::Registrable
|
8
|
+
|
9
|
+
# Content types are listed in order of preference.
|
10
|
+
CONTENT_TYPES = {
|
11
|
+
xml: 'application/xml',
|
12
|
+
serializable_hash: 'application/json',
|
13
|
+
json: 'application/json',
|
14
|
+
binary: 'application/octet-stream',
|
15
|
+
txt: 'text/plain'
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def content_types_for_settings(settings)
|
20
|
+
return if settings.blank?
|
21
|
+
|
22
|
+
settings.each_with_object({}) { |value, result| result.merge!(value) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def content_types_for(from_settings)
|
26
|
+
if from_settings.present?
|
27
|
+
from_settings
|
28
|
+
else
|
29
|
+
Grape::ContentTypes::CONTENT_TYPES.merge(default_elements)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/grape/dsl/helpers.rb
CHANGED
@@ -94,7 +94,7 @@ module Grape
|
|
94
94
|
protected
|
95
95
|
|
96
96
|
def process_named_params
|
97
|
-
return unless @named_params && @named_params.any?
|
97
|
+
return unless instance_variable_defined?(:@named_params) && @named_params && @named_params.any?
|
98
98
|
api.namespace_stackable(:named_params, @named_params)
|
99
99
|
end
|
100
100
|
end
|
@@ -177,17 +177,17 @@ module Grape
|
|
177
177
|
def status(status = nil)
|
178
178
|
case status
|
179
179
|
when Symbol
|
180
|
-
raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.
|
180
|
+
raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(status)
|
181
181
|
@status = Rack::Utils.status_code(status)
|
182
182
|
when Integer
|
183
183
|
@status = status
|
184
184
|
when nil
|
185
|
-
return @status if @status
|
185
|
+
return @status if instance_variable_defined?(:@status) && @status
|
186
186
|
case request.request_method.to_s.upcase
|
187
187
|
when Grape::Http::Headers::POST
|
188
188
|
201
|
189
189
|
when Grape::Http::Headers::DELETE
|
190
|
-
if @body.present?
|
190
|
+
if instance_variable_defined?(:@body) && @body.present?
|
191
191
|
200
|
192
192
|
else
|
193
193
|
204
|
@@ -238,7 +238,7 @@ module Grape
|
|
238
238
|
@body = ''
|
239
239
|
status 204
|
240
240
|
else
|
241
|
-
@body
|
241
|
+
instance_variable_defined?(:@body) ? @body : nil
|
242
242
|
end
|
243
243
|
end
|
244
244
|
|
@@ -272,7 +272,7 @@ module Grape
|
|
272
272
|
warn '[DEPRECATION] Argument as FileStreamer-like object is deprecated. Use path to file instead.'
|
273
273
|
@file = Grape::ServeFile::FileResponse.new(value)
|
274
274
|
else
|
275
|
-
@file
|
275
|
+
instance_variable_defined?(:@file) ? @file : nil
|
276
276
|
end
|
277
277
|
end
|
278
278
|
|
@@ -331,11 +331,12 @@ module Grape
|
|
331
331
|
end
|
332
332
|
|
333
333
|
representation = { root => representation } if root
|
334
|
+
|
334
335
|
if key
|
335
|
-
representation = (
|
336
|
-
elsif entity_class.present? &&
|
336
|
+
representation = (body || {}).merge(key => representation)
|
337
|
+
elsif entity_class.present? && body
|
337
338
|
raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
|
338
|
-
representation =
|
339
|
+
representation = body.merge(representation)
|
339
340
|
end
|
340
341
|
|
341
342
|
body representation
|
@@ -387,7 +388,7 @@ module Grape
|
|
387
388
|
def entity_representation_for(entity_class, object, options)
|
388
389
|
embeds = { env: env }
|
389
390
|
embeds[:version] = env[Grape::Env::API_VERSION] if env[Grape::Env::API_VERSION]
|
390
|
-
entity_class.represent(object,
|
391
|
+
entity_class.represent(object, embeds.merge(options))
|
391
392
|
end
|
392
393
|
end
|
393
394
|
end
|
data/lib/grape/dsl/parameters.rb
CHANGED
@@ -127,7 +127,7 @@ module Grape
|
|
127
127
|
|
128
128
|
opts = attrs.extract_options!.clone
|
129
129
|
opts[:presence] = { value: true, message: opts[:message] }
|
130
|
-
opts = @group.merge(opts) if @group
|
130
|
+
opts = @group.merge(opts) if instance_variable_defined?(:@group) && @group
|
131
131
|
|
132
132
|
if opts[:using]
|
133
133
|
require_required_and_optional_fields(attrs.first, opts)
|
@@ -146,7 +146,7 @@ module Grape
|
|
146
146
|
|
147
147
|
opts = attrs.extract_options!.clone
|
148
148
|
type = opts[:type]
|
149
|
-
opts = @group.merge(opts) if @group
|
149
|
+
opts = @group.merge(opts) if instance_variable_defined?(:@group) && @group
|
150
150
|
|
151
151
|
# check type for optional parameter group
|
152
152
|
if attrs && block_given?
|
@@ -243,8 +243,8 @@ module Grape
|
|
243
243
|
# @return hash of parameters relevant for the current scope
|
244
244
|
# @api private
|
245
245
|
def params(params)
|
246
|
-
params = @parent.params(params) if @parent
|
247
|
-
params = map_params(params, @element) if @element
|
246
|
+
params = @parent.params(params) if instance_variable_defined?(:@parent) && @parent
|
247
|
+
params = map_params(params, @element) if instance_variable_defined?(:@element) && @element
|
248
248
|
params
|
249
249
|
end
|
250
250
|
|
data/lib/grape/dsl/routing.rb
CHANGED
@@ -51,7 +51,7 @@ module Grape
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
-
@versions.last
|
54
|
+
@versions.last if instance_variable_defined?(:@versions) && @versions
|
55
55
|
end
|
56
56
|
|
57
57
|
# Define a root URL prefix for your entire API.
|
@@ -142,11 +142,11 @@ module Grape
|
|
142
142
|
reset_validations!
|
143
143
|
end
|
144
144
|
|
145
|
-
|
146
|
-
define_method
|
145
|
+
Grape::Http::Headers::SUPPORTED_METHODS.each do |supported_method|
|
146
|
+
define_method supported_method.downcase do |*args, &block|
|
147
147
|
options = args.extract_options!
|
148
148
|
paths = args.first || ['/']
|
149
|
-
route(
|
149
|
+
route(supported_method, paths, options, &block)
|
150
150
|
end
|
151
151
|
end
|
152
152
|
|
@@ -163,6 +163,8 @@ module Grape
|
|
163
163
|
# end
|
164
164
|
# end
|
165
165
|
def namespace(space = nil, options = {}, &block)
|
166
|
+
@namespace_description = nil unless instance_variable_defined?(:@namespace_description) && @namespace_description
|
167
|
+
|
166
168
|
if space || block_given?
|
167
169
|
within_namespace do
|
168
170
|
previous_namespace_description = @namespace_description
|
@@ -5,6 +5,9 @@ require 'grape/exceptions/base'
|
|
5
5
|
module Grape
|
6
6
|
module Exceptions
|
7
7
|
class ValidationErrors < Grape::Exceptions::Base
|
8
|
+
ERRORS_FORMAT_KEY = 'grape.errors.format'
|
9
|
+
DEFAULT_ERRORS_FORMAT = '%{attributes} %{message}'
|
10
|
+
|
8
11
|
include Enumerable
|
9
12
|
|
10
13
|
attr_reader :errors
|
@@ -41,21 +44,17 @@ module Grape
|
|
41
44
|
end
|
42
45
|
|
43
46
|
def full_messages
|
44
|
-
messages = map
|
47
|
+
messages = map do |attributes, error|
|
48
|
+
I18n.t(
|
49
|
+
ERRORS_FORMAT_KEY,
|
50
|
+
default: DEFAULT_ERRORS_FORMAT,
|
51
|
+
attributes: translate_attributes(attributes),
|
52
|
+
message: error.message
|
53
|
+
)
|
54
|
+
end
|
45
55
|
messages.uniq!
|
46
56
|
messages
|
47
57
|
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def full_message(attributes, error)
|
52
|
-
I18n.t(
|
53
|
-
'grape.errors.format',
|
54
|
-
default: '%{attributes} %{message}',
|
55
|
-
attributes: attributes.count == 1 ? translate_attribute(attributes.first) : translate_attributes(attributes),
|
56
|
-
message: error.message
|
57
|
-
)
|
58
|
-
end
|
59
58
|
end
|
60
59
|
end
|
61
60
|
end
|
data/lib/grape/http/headers.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'grape/util/lazy_object'
|
4
|
+
|
3
5
|
module Grape
|
4
6
|
module Http
|
5
7
|
module Headers
|
@@ -19,6 +21,7 @@ module Grape
|
|
19
21
|
OPTIONS = 'OPTIONS'
|
20
22
|
|
21
23
|
SUPPORTED_METHODS = [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS].freeze
|
24
|
+
SUPPORTED_METHODS_WITHOUT_OPTIONS = Grape::Util::LazyObject.new { [GET, POST, PUT, PATCH, DELETE, HEAD].freeze }
|
22
25
|
|
23
26
|
HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION'
|
24
27
|
X_CASCADE = 'X-Cascade'
|
@@ -27,6 +30,29 @@ module Grape
|
|
27
30
|
|
28
31
|
FORMAT = 'format'
|
29
32
|
|
33
|
+
HTTP_HEADERS = Grape::Util::LazyObject.new do
|
34
|
+
common_http_headers = %w[
|
35
|
+
Version
|
36
|
+
Host
|
37
|
+
Connection
|
38
|
+
Cache-Control
|
39
|
+
Dnt
|
40
|
+
Upgrade-Insecure-Requests
|
41
|
+
User-Agent
|
42
|
+
Sec-Fetch-Dest
|
43
|
+
Accept
|
44
|
+
Sec-Fetch-Site
|
45
|
+
Sec-Fetch-Mode
|
46
|
+
Sec-Fetch-User
|
47
|
+
Accept-Encoding
|
48
|
+
Accept-Language
|
49
|
+
Cookie
|
50
|
+
].freeze
|
51
|
+
common_http_headers.each_with_object({}) do |header, response|
|
52
|
+
response["HTTP_#{header.upcase.tr('-', '_')}"] = header
|
53
|
+
end.freeze
|
54
|
+
end
|
55
|
+
|
30
56
|
def self.find_supported_method(route_method)
|
31
57
|
Grape::Http::Headers::SUPPORTED_METHODS.detect { |supported_method| supported_method.casecmp(route_method).zero? }
|
32
58
|
end
|
@@ -74,11 +74,9 @@ module Grape
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def mime_types
|
77
|
-
|
78
|
-
content_types.each_pair do |k, v|
|
77
|
+
@mime_type ||= content_types.each_pair.with_object({}) do |(k, v), types_without_params|
|
79
78
|
types_without_params[v.split(';').first] = k
|
80
79
|
end
|
81
|
-
types_without_params
|
82
80
|
end
|
83
81
|
|
84
82
|
private
|
@@ -78,7 +78,8 @@ module Grape
|
|
78
78
|
def merge_with(middleware_specs)
|
79
79
|
middleware_specs.each do |operation, *args|
|
80
80
|
if args.last.is_a?(Proc)
|
81
|
-
|
81
|
+
last_proc = args.pop
|
82
|
+
public_send(operation, *args, &last_proc)
|
82
83
|
else
|
83
84
|
public_send(operation, *args)
|
84
85
|
end
|
@@ -26,10 +26,10 @@ module Grape
|
|
26
26
|
# route.
|
27
27
|
class Header < Base
|
28
28
|
VENDOR_VERSION_HEADER_REGEX =
|
29
|
-
/\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z
|
29
|
+
/\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z/.freeze
|
30
30
|
|
31
|
-
HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#\$&\^]
|
32
|
-
HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))
|
31
|
+
HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#\$&\^]+/.freeze
|
32
|
+
HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))+/.freeze
|
33
33
|
|
34
34
|
def before
|
35
35
|
strict_header_checks if strict?
|
@@ -63,7 +63,7 @@ module Grape
|
|
63
63
|
|
64
64
|
def an_accept_header_with_version_and_vendor_is_present?
|
65
65
|
header.qvalues.keys.any? do |h|
|
66
|
-
VENDOR_VERSION_HEADER_REGEX
|
66
|
+
VENDOR_VERSION_HEADER_REGEX.match?(h.sub('application/', ''))
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
@@ -3,11 +3,12 @@
|
|
3
3
|
module Rack
|
4
4
|
module Accept
|
5
5
|
module Header
|
6
|
+
ALLOWED_CHARACTERS = %r{^([a-z*]+)\/([a-z0-9*\&\^\-_#\$!.+]+)(?:;([a-z0-9=;]+))?$}.freeze
|
6
7
|
class << self
|
7
8
|
# Corrected version of https://github.com/mjackson/rack-accept/blob/master/lib/rack/accept/header.rb#L40-L44
|
8
9
|
def parse_media_type(media_type)
|
9
10
|
# see http://tools.ietf.org/html/rfc6838#section-4.2 for allowed characters in media type names
|
10
|
-
m = media_type
|
11
|
+
m = media_type&.match(ALLOWED_CHARACTERS)
|
11
12
|
m ? [m[1], m[2], m[3] || ''] : []
|
12
13
|
end
|
13
14
|
end
|
@@ -36,7 +36,7 @@ module Grape
|
|
36
36
|
|
37
37
|
pieces = path.split('/')
|
38
38
|
potential_version = pieces[1]
|
39
|
-
return unless potential_version
|
39
|
+
return unless potential_version&.match?(options[:pattern])
|
40
40
|
throw :error, status: 404, message: '404 API Version Not Found' if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
|
41
41
|
env[Grape::Env::API_VERSION] = potential_version
|
42
42
|
end
|
data/lib/grape/namespace.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'grape/util/cache'
|
4
|
+
|
3
5
|
module Grape
|
4
6
|
# A container for endpoints or other namespaces, which allows for both
|
5
7
|
# logical grouping of endpoints as well as sharing common configuration.
|
@@ -25,13 +27,21 @@ module Grape
|
|
25
27
|
|
26
28
|
# (see ::joined_space_path)
|
27
29
|
def self.joined_space(settings)
|
28
|
-
|
30
|
+
settings&.map(&:space)
|
29
31
|
end
|
30
32
|
|
31
33
|
# Join the namespaces from a list of settings to create a path prefix.
|
32
34
|
# @param settings [Array] list of Grape::Util::InheritableSettings.
|
33
35
|
def self.joined_space_path(settings)
|
34
|
-
Grape::Router.normalize_path(joined_space(settings))
|
36
|
+
Grape::Router.normalize_path(JoinedSpaceCache[joined_space(settings)])
|
37
|
+
end
|
38
|
+
|
39
|
+
class JoinedSpaceCache < Grape::Util::Cache
|
40
|
+
def initialize
|
41
|
+
@cache = Hash.new do |h, joined_space|
|
42
|
+
h[joined_space] = -joined_space.join('/')
|
43
|
+
end
|
44
|
+
end
|
35
45
|
end
|
36
46
|
end
|
37
47
|
end
|