grape 1.3.1 → 1.3.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 +34 -0
- data/LICENSE +1 -1
- data/README.md +19 -6
- data/UPGRADING.md +120 -16
- data/lib/grape/api/instance.rb +12 -7
- data/lib/grape/dsl/inside_route.rb +37 -14
- data/lib/grape/http/headers.rb +1 -0
- data/lib/grape/middleware/versioner/header.rb +1 -1
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/path.rb +2 -2
- data/lib/grape/router/attribute_translator.rb +23 -2
- data/lib/grape/router/route.rb +3 -22
- data/lib/grape/router.rb +6 -14
- data/lib/grape/util/base_inheritable.rb +9 -6
- data/lib/grape/util/reverse_stackable_values.rb +3 -1
- data/lib/grape/util/stackable_values.rb +3 -1
- data/lib/grape/validations/types/array_coercer.rb +14 -5
- data/lib/grape/validations/types/build_coercer.rb +5 -8
- data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
- data/lib/grape/validations/types/file.rb +15 -13
- data/lib/grape/validations/types/json.rb +40 -36
- data/lib/grape/validations/types/primitive_coercer.rb +11 -4
- data/lib/grape/validations/types/set_coercer.rb +6 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
- data/lib/grape/validations/types.rb +6 -5
- data/lib/grape/validations/validators/coerce.rb +3 -10
- data/lib/grape/validations/validators/default.rb +0 -1
- data/lib/grape/validations/validators/regexp.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/instance_spec.rb +50 -0
- data/spec/grape/endpoint_spec.rb +18 -5
- data/spec/grape/path_spec.rb +4 -4
- data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
- data/spec/grape/validations/types/primitive_coercer_spec.rb +5 -1
- data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
- data/spec/grape/validations/types_spec.rb +1 -1
- data/spec/grape/validations/validators/coerce_spec.rb +207 -29
- data/spec/grape/validations/validators/default_spec.rb +121 -0
- data/spec/grape/validations/validators/values_spec.rb +1 -1
- data/spec/grape/validations_spec.rb +5 -5
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d63fb79e412ead32064ad4994e171e65962131a04afb20d12957942c744f4df8
|
4
|
+
data.tar.gz: 9a9f4fb654e346eabb8a0b902d5e49ae54dc567797789c98c76f2a2fe2e2daa9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b75afa355e6a2b8200d72a2c4407bc78646648832a94d576bff06bdebde90d54b1897df09a560665bd9f58e735f9832a110b63bbcaabe14a4e2ee3c97e743b57
|
7
|
+
data.tar.gz: 18a4ea057230ae0e6cfe16f92e5043339b66026a94d29bd8c897d2482577cb279ec4e202f41a643605a3ea09c748fa159fddc36977a3c4828b5b64daef080272
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
### 1.3.3 (2020/05/23)
|
2
|
+
|
3
|
+
#### Features
|
4
|
+
|
5
|
+
* [#2048](https://github.com/ruby-grape/grape/issues/2034): Grape Enterprise support is now available [via TideLift](https://tidelift.com/subscription/request-a-demo?utm_source=rubygems-grape&utm_medium=referral&utm_campaign=enterprise) - [@dblock](https://github.com/dblock).
|
6
|
+
* [#2039](https://github.com/ruby-grape/grape/pull/2039): Travis - update rails versions - [@ericproulx](https://github.com/ericproulx).
|
7
|
+
* [#2038](https://github.com/ruby-grape/grape/pull/2038): Travis - update ruby versions - [@ericproulx](https://github.com/ericproulx).
|
8
|
+
* [#2050](https://github.com/ruby-grape/grape/pull/2050): Refactor route public_send to AttributeTranslator - [@ericproulx](https://github.com/ericproulx).
|
9
|
+
|
10
|
+
#### Fixes
|
11
|
+
|
12
|
+
* [#2049](https://github.com/ruby-grape/grape/pull/2049): Coerce an empty string to nil in case of the bool type - [@dnesteryuk](https://github.com/dnesteryuk).
|
13
|
+
* [#2043](https://github.com/ruby-grape/grape/pull/2043): Modify declared for nested array and hash - [@kadotami](https://github.com/kadotami).
|
14
|
+
* [#2040](https://github.com/ruby-grape/grape/pull/2040): Fix a regression with Array of type nil - [@ericproulx](https://github.com/ericproulx).
|
15
|
+
* [#2054](https://github.com/ruby-grape/grape/pull/2054): Coercing of nested arrays - [@dnesteryuk](https://github.com/dnesteryuk).
|
16
|
+
* [#2050](https://github.com/ruby-grape/grape/pull/2053): Fix broken multiple mounts - [@Jack12816](https://github.com/Jack12816).
|
17
|
+
|
18
|
+
### 1.3.2 (2020/04/12)
|
19
|
+
|
20
|
+
#### Features
|
21
|
+
|
22
|
+
* [#2020](https://github.com/ruby-grape/grape/pull/2020): Reduce array allocation - [@ericproulx](https://github.com/ericproulx).
|
23
|
+
* [#2015](https://github.com/ruby-grape/grape/pull/2014): Reduce MatchData allocation - [@ericproulx](https://github.com/ericproulx).
|
24
|
+
* [#2014](https://github.com/ruby-grape/grape/pull/2014): Reduce total allocated arrays - [@ericproulx](https://github.com/ericproulx).
|
25
|
+
* [#2011](https://github.com/ruby-grape/grape/pull/2011): Reduce total retained regexes - [@ericproulx](https://github.com/ericproulx).
|
26
|
+
|
27
|
+
#### Fixes
|
28
|
+
|
29
|
+
* [#2033](https://github.com/ruby-grape/grape/pull/2033): Ensure `Float` params are correctly coerced to `BigDecimal` - [@tlconnor](https://github.com/tlconnor).
|
30
|
+
* [#2031](https://github.com/ruby-grape/grape/pull/2031): Fix a regression with an array of a custom type - [@dnesteryuk](https://github.com/dnesteryuk).
|
31
|
+
* [#2026](https://github.com/ruby-grape/grape/pull/2026): Fix a regression in `coerce_with` when coercion returns `nil` - [@misdoro](https://github.com/misdoro).
|
32
|
+
* [#2025](https://github.com/ruby-grape/grape/pull/2025): Fix Decimal type category - [@kdoya](https://github.com/kdoya).
|
33
|
+
* [#2019](https://github.com/ruby-grape/grape/pull/2019): Avoid coercing parameter with multiple types to an empty Array - [@stanhu](https://github.com/stanhu).
|
34
|
+
|
1
35
|
### 1.3.1 (2020/03/11)
|
2
36
|
|
3
37
|
#### Features
|
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2010-
|
1
|
+
Copyright (c) 2010-2020 Michael Bleigh, Intridea Inc. and Contributors.
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
a copy of this software and associated documentation files (the
|
data/README.md
CHANGED
@@ -12,6 +12,7 @@
|
|
12
12
|
- [What is Grape?](#what-is-grape)
|
13
13
|
- [Stable Release](#stable-release)
|
14
14
|
- [Project Resources](#project-resources)
|
15
|
+
- [Grape for Enterprise](#grape-for-enterprise)
|
15
16
|
- [Installation](#installation)
|
16
17
|
- [Basic Usage](#basic-usage)
|
17
18
|
- [Mounting](#mounting)
|
@@ -141,6 +142,7 @@
|
|
141
142
|
- [format_response.grape](#format_responsegrape)
|
142
143
|
- [Monitoring Products](#monitoring-products)
|
143
144
|
- [Contributing to Grape](#contributing-to-grape)
|
145
|
+
- [Security](#security)
|
144
146
|
- [License](#license)
|
145
147
|
- [Copyright](#copyright)
|
146
148
|
|
@@ -154,7 +156,7 @@ content negotiation, versioning and much more.
|
|
154
156
|
|
155
157
|
## Stable Release
|
156
158
|
|
157
|
-
You're reading the documentation for the stable release of Grape, **1.3.
|
159
|
+
You're reading the documentation for the stable release of Grape, **1.3.3**.
|
158
160
|
|
159
161
|
## Project Resources
|
160
162
|
|
@@ -163,6 +165,14 @@ You're reading the documentation for the stable release of Grape, **1.3.1**.
|
|
163
165
|
* Need help? Try [Grape Google Group](http://groups.google.com/group/ruby-grape) or [Gitter](https://gitter.im/ruby-grape/grape)
|
164
166
|
* [Follow us on Twitter](https://twitter.com/grapeframework)
|
165
167
|
|
168
|
+
## Grape for Enterprise
|
169
|
+
|
170
|
+
Available as part of the Tidelift Subscription.
|
171
|
+
|
172
|
+
The maintainers of Grape are working with Tidelift to deliver commercial support and maintenance. Save time, reduce risk, and improve code health, while paying the maintainers of Grape. Click [here](https://tidelift.com/subscription/request-a-demo?utm_source=rubygems-grape&utm_medium=referral&utm_campaign=enterprise) for more details.
|
173
|
+
|
174
|
+
In 2020, we plan to use the money towards gathering Grape contributors for dinner in New York City.
|
175
|
+
|
166
176
|
## Installation
|
167
177
|
|
168
178
|
Ruby 2.4 or newer is required.
|
@@ -3187,14 +3197,13 @@ applies to the current namespace and any children, but not parents.
|
|
3187
3197
|
```ruby
|
3188
3198
|
http_basic do |username, password|
|
3189
3199
|
# verify user's password here
|
3190
|
-
|
3200
|
+
# IMPORTANT: make sure you use a comparison method which isn't prone to a timing attack
|
3191
3201
|
end
|
3192
3202
|
```
|
3193
3203
|
|
3194
3204
|
```ruby
|
3195
3205
|
http_digest({ realm: 'Test Api', opaque: 'app secret' }) do |username|
|
3196
3206
|
# lookup the user's password here
|
3197
|
-
{ 'user1' => 'password1' }[username]
|
3198
3207
|
end
|
3199
3208
|
```
|
3200
3209
|
|
@@ -3851,7 +3860,7 @@ Grape integrates with following third-party tools:
|
|
3851
3860
|
* **Librato Metrics** - [grape-librato](https://github.com/seanmoon/grape-librato) gem
|
3852
3861
|
* **[Skylight](https://www.skylight.io/)** - [skylight](https://github.com/skylightio/skylight-ruby) gem, [documentation](https://docs.skylight.io/grape/)
|
3853
3862
|
* **[AppSignal](https://www.appsignal.com)** - [appsignal-ruby](https://github.com/appsignal/appsignal-ruby) gem, [documentation](http://docs.appsignal.com/getting-started/supported-frameworks.html#grape)
|
3854
|
-
* **[ElasticAPM](https://www.elastic.co/products/apm) - [elastic-apm](https://github.com/elastic/apm-agent-ruby) gem, [documentation](https://www.elastic.co/guide/en/apm/agent/ruby/3.x/getting-started-rack.html#getting-started-grape)
|
3863
|
+
* **[ElasticAPM](https://www.elastic.co/products/apm)** - [elastic-apm](https://github.com/elastic/apm-agent-ruby) gem, [documentation](https://www.elastic.co/guide/en/apm/agent/ruby/3.x/getting-started-rack.html#getting-started-grape)
|
3855
3864
|
|
3856
3865
|
## Contributing to Grape
|
3857
3866
|
|
@@ -3860,10 +3869,14 @@ features and discuss issues.
|
|
3860
3869
|
|
3861
3870
|
See [CONTRIBUTING](CONTRIBUTING.md).
|
3862
3871
|
|
3872
|
+
## Security
|
3873
|
+
|
3874
|
+
See [SECURITY](SECURITY.md) for details.
|
3875
|
+
|
3863
3876
|
## License
|
3864
3877
|
|
3865
|
-
MIT License. See LICENSE for details.
|
3878
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
3866
3879
|
|
3867
3880
|
## Copyright
|
3868
3881
|
|
3869
|
-
Copyright (c) 2010-
|
3882
|
+
Copyright (c) 2010-2020 Michael Bleigh, Intridea Inc. and Contributors.
|
data/UPGRADING.md
CHANGED
@@ -1,6 +1,65 @@
|
|
1
1
|
Upgrading Grape
|
2
2
|
===============
|
3
3
|
|
4
|
+
### Upgrading to >= 1.4.0
|
5
|
+
|
6
|
+
#### Nil values for structures
|
7
|
+
|
8
|
+
Nil values always been a special case when dealing with types especially with the following structures:
|
9
|
+
- Array
|
10
|
+
- Hash
|
11
|
+
- Set
|
12
|
+
|
13
|
+
The behaviour for these structures has change through out the latest releases. For instance:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class Api < Grape::API
|
17
|
+
params do
|
18
|
+
require :my_param, type: Array[Integer]
|
19
|
+
end
|
20
|
+
|
21
|
+
get 'example' do
|
22
|
+
params[:my_param]
|
23
|
+
end
|
24
|
+
get '/example', params: { my_param: nil }
|
25
|
+
# 1.3.1 = []
|
26
|
+
# 1.3.2 = nil
|
27
|
+
end
|
28
|
+
```
|
29
|
+
For now on, `nil` values stay `nil` values for all types, including arrays, sets and hashes.
|
30
|
+
|
31
|
+
If you want to have the same behavior as 1.3.1, apply a `default` validator
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class Api < Grape::API
|
35
|
+
params do
|
36
|
+
require :my_param, type: Array[Integer], default: []
|
37
|
+
end
|
38
|
+
|
39
|
+
get 'example' do
|
40
|
+
params[:my_param]
|
41
|
+
end
|
42
|
+
get '/example', params: { my_param: nil } # => []
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
#### Default validator
|
47
|
+
|
48
|
+
Default validator is now applied for `nil` values.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
class Api < Grape::API
|
52
|
+
params do
|
53
|
+
requires :my_param, type: Integer, default: 0
|
54
|
+
end
|
55
|
+
|
56
|
+
get 'example' do
|
57
|
+
params[:my_param]
|
58
|
+
end
|
59
|
+
get '/example', params: { my_param: nil } #=> before: nil, after: 0
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
4
63
|
### Upgrading to >= 1.3.0
|
5
64
|
|
6
65
|
#### Ruby
|
@@ -9,38 +68,83 @@ After adding dry-types, Ruby 2.4 or newer is required.
|
|
9
68
|
|
10
69
|
#### Coercion
|
11
70
|
|
12
|
-
[Virtus](https://github.com/solnic/virtus) has been replaced by
|
71
|
+
[Virtus](https://github.com/solnic/virtus) has been replaced by
|
72
|
+
[dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter
|
73
|
+
coercion. If your project depends on Virtus outside of Grape, explicitly
|
74
|
+
add it to your `Gemfile`.
|
75
|
+
|
76
|
+
Here's an example of how to migrate a custom type from Virtus to dry-types:
|
13
77
|
|
14
78
|
```ruby
|
15
|
-
|
16
|
-
|
79
|
+
# Legacy Grape parser
|
80
|
+
class SecureUriType < Virtus::Attribute
|
81
|
+
def coerce(input)
|
82
|
+
URI.parse value
|
83
|
+
end
|
17
84
|
|
18
|
-
|
19
|
-
|
85
|
+
def value_coerced?(input)
|
86
|
+
value.is_a? String
|
87
|
+
end
|
20
88
|
end
|
21
89
|
|
22
|
-
# somewhere in your API
|
23
90
|
params do
|
24
|
-
requires :
|
91
|
+
requires :secure_uri, type: SecureUri
|
25
92
|
end
|
26
93
|
```
|
27
94
|
|
28
|
-
|
95
|
+
To use dry-types, we need to:
|
29
96
|
|
30
|
-
|
31
|
-
|
32
|
-
|
97
|
+
1. Remove the inheritance of `Virtus::Attribute`
|
98
|
+
1. Rename `coerce` to `self.parse`
|
99
|
+
1. Rename `value_coerced?` to `self.parsed?`
|
100
|
+
|
101
|
+
The custom type must have a class-level `parse` method to the model. A
|
102
|
+
class-level `parsed?` is needed if the parsed type differs from the
|
103
|
+
defined type. In the example below, since `SecureUri` is not the same
|
104
|
+
as `URI::HTTPS`, `self.parsed?` is needed:
|
33
105
|
|
34
|
-
|
35
|
-
|
106
|
+
```ruby
|
107
|
+
# New dry-types parser
|
108
|
+
class SecureUri
|
109
|
+
def self.parse(value)
|
110
|
+
URI.parse value
|
111
|
+
end
|
36
112
|
|
37
|
-
def self.
|
38
|
-
|
113
|
+
def self.parsed?(value)
|
114
|
+
value.is_a? URI::HTTPS
|
39
115
|
end
|
40
116
|
end
|
117
|
+
|
118
|
+
params do
|
119
|
+
requires :secure_uri, type: SecureUri
|
120
|
+
end
|
41
121
|
```
|
42
122
|
|
43
|
-
|
123
|
+
#### Ensure that Array types have explicit coercions
|
124
|
+
|
125
|
+
Unlike Virtus, dry-types does not perform any implict coercions. If you
|
126
|
+
have any uses of `Array[String]`, `Array[Integer]`, etc. be sure they
|
127
|
+
use a `coerce_with` block. For example:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
requires :values, type: Array[String]
|
131
|
+
```
|
132
|
+
|
133
|
+
It's quite common to pass a comma-separated list, such as `tag1,tag2` as
|
134
|
+
`values`. Previously Virtus would implicitly coerce this to
|
135
|
+
`Array(values)` so that `["tag1,tag2"]` would pass the type checks, but
|
136
|
+
with `dry-types` the values are no longer coerced for you. To fix this,
|
137
|
+
you might do:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
requires :values, type: Array[String], coerce_with: ->(val) { val.split(',').map(&:strip) }
|
141
|
+
```
|
142
|
+
|
143
|
+
Likewise, for `Array[Integer]`, you might do:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
requires :values, type: Array[Integer], coerce_with: ->(val) { val.split(',').map(&:strip).map(&:to_i) }
|
147
|
+
```
|
44
148
|
|
45
149
|
For more information see [#1920](https://github.com/ruby-grape/grape/pull/1920).
|
46
150
|
|
data/lib/grape/api/instance.rb
CHANGED
@@ -205,11 +205,12 @@ module Grape
|
|
205
205
|
route_settings[:requirements] = route.requirements
|
206
206
|
route_settings[:path] = route.origin
|
207
207
|
route_settings[:methods] ||= []
|
208
|
-
route_settings[:methods]
|
208
|
+
if route.request_method == '*' || route_settings[:methods].include?('*')
|
209
|
+
route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS
|
210
|
+
else
|
211
|
+
route_settings[:methods] << route.request_method
|
212
|
+
end
|
209
213
|
route_settings[:endpoint] = route.app
|
210
|
-
|
211
|
-
# using the :any shorthand produces [nil] for route methods, substitute all manually
|
212
|
-
route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS if route_settings[:methods].include?('*')
|
213
214
|
end
|
214
215
|
end
|
215
216
|
|
@@ -243,9 +244,13 @@ module Grape
|
|
243
244
|
# Generate a route that returns an HTTP 405 response for a user defined
|
244
245
|
# path on methods not specified
|
245
246
|
def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
|
246
|
-
|
247
|
-
|
248
|
-
|
247
|
+
supported_methods =
|
248
|
+
if self.class.namespace_inheritable(:do_not_route_options)
|
249
|
+
Grape::Http::Headers::SUPPORTED_METHODS
|
250
|
+
else
|
251
|
+
Grape::Http::Headers::SUPPORTED_METHODS_WITHOUT_OPTIONS
|
252
|
+
end
|
253
|
+
not_allowed_methods = supported_methods - allowed_methods
|
249
254
|
return if not_allowed_methods.empty?
|
250
255
|
|
251
256
|
@router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
|
@@ -28,36 +28,38 @@ module Grape
|
|
28
28
|
# Methods which should not be available in filters until the before filter
|
29
29
|
# has completed
|
30
30
|
module PostBeforeFilter
|
31
|
-
def declared(passed_params, options = {}, declared_params = nil)
|
31
|
+
def declared(passed_params, options = {}, declared_params = nil, params_nested_path = [])
|
32
32
|
options = options.reverse_merge(include_missing: true, include_parent_namespaces: true)
|
33
33
|
declared_params ||= optioned_declared_params(**options)
|
34
34
|
|
35
35
|
if passed_params.is_a?(Array)
|
36
|
-
declared_array(passed_params, options, declared_params)
|
36
|
+
declared_array(passed_params, options, declared_params, params_nested_path)
|
37
37
|
else
|
38
|
-
declared_hash(passed_params, options, declared_params)
|
38
|
+
declared_hash(passed_params, options, declared_params, params_nested_path)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
private
|
43
43
|
|
44
|
-
def declared_array(passed_params, options, declared_params)
|
44
|
+
def declared_array(passed_params, options, declared_params, params_nested_path)
|
45
45
|
passed_params.map do |passed_param|
|
46
|
-
declared(passed_param || {}, options, declared_params)
|
46
|
+
declared(passed_param || {}, options, declared_params, params_nested_path)
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
def declared_hash(passed_params, options, declared_params)
|
50
|
+
def declared_hash(passed_params, options, declared_params, params_nested_path)
|
51
51
|
declared_params.each_with_object(passed_params.class.new) do |declared_param, memo|
|
52
52
|
if declared_param.is_a?(Hash)
|
53
53
|
declared_param.each_pair do |declared_parent_param, declared_children_params|
|
54
|
+
params_nested_path_dup = params_nested_path.dup
|
55
|
+
params_nested_path_dup << declared_parent_param.to_s
|
54
56
|
next unless options[:include_missing] || passed_params.key?(declared_parent_param)
|
55
57
|
|
56
58
|
passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
|
57
59
|
memo_key = optioned_param_key(declared_parent_param, options)
|
58
60
|
|
59
|
-
memo[memo_key] = handle_passed_param(
|
60
|
-
declared(passed_children_params, options, declared_children_params)
|
61
|
+
memo[memo_key] = handle_passed_param(passed_children_params, params_nested_path_dup) do
|
62
|
+
declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
|
61
63
|
end
|
62
64
|
end
|
63
65
|
else
|
@@ -77,19 +79,34 @@ module Grape
|
|
77
79
|
end
|
78
80
|
end
|
79
81
|
|
80
|
-
def handle_passed_param(
|
81
|
-
|
82
|
+
def handle_passed_param(passed_children_params, params_nested_path, &_block)
|
83
|
+
if should_be_empty_hash?(passed_children_params, params_nested_path)
|
84
|
+
{}
|
85
|
+
elsif should_be_empty_array?(passed_children_params, params_nested_path)
|
86
|
+
[]
|
87
|
+
else
|
88
|
+
yield
|
89
|
+
end
|
82
90
|
end
|
83
91
|
|
84
|
-
def should_be_empty_array?(
|
85
|
-
|
92
|
+
def should_be_empty_array?(passed_children_params, params_nested_path)
|
93
|
+
passed_children_params.empty? && declared_param_is_array?(params_nested_path)
|
86
94
|
end
|
87
95
|
|
88
|
-
def declared_param_is_array?(
|
89
|
-
key =
|
96
|
+
def declared_param_is_array?(params_nested_path)
|
97
|
+
key = route_options_params_key(params_nested_path)
|
90
98
|
route_options_params[key] && route_options_params[key][:type] == 'Array'
|
91
99
|
end
|
92
100
|
|
101
|
+
def should_be_empty_hash?(passed_children_params, params_nested_path)
|
102
|
+
passed_children_params.empty? && declared_param_is_hash?(params_nested_path)
|
103
|
+
end
|
104
|
+
|
105
|
+
def declared_param_is_hash?(params_nested_path)
|
106
|
+
key = route_options_params_key(params_nested_path)
|
107
|
+
route_options_params[key] && route_options_params[key][:type] == 'Hash'
|
108
|
+
end
|
109
|
+
|
93
110
|
def route_options_params
|
94
111
|
options[:route_options][:params] || {}
|
95
112
|
end
|
@@ -98,6 +115,12 @@ module Grape
|
|
98
115
|
options[:stringify] ? declared_param.to_s : declared_param.to_sym
|
99
116
|
end
|
100
117
|
|
118
|
+
def route_options_params_key(params_nested_path)
|
119
|
+
key = params_nested_path[0]
|
120
|
+
key += '[' + params_nested_path[1..-1].join('][') + ']' if params_nested_path.size > 1
|
121
|
+
key
|
122
|
+
end
|
123
|
+
|
101
124
|
def optioned_declared_params(**options)
|
102
125
|
declared_params = if options[:include_parent_namespaces]
|
103
126
|
# Declared params including parent namespaces
|
data/lib/grape/http/headers.rb
CHANGED
@@ -21,6 +21,7 @@ module Grape
|
|
21
21
|
OPTIONS = 'OPTIONS'
|
22
22
|
|
23
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 }
|
24
25
|
|
25
26
|
HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION'
|
26
27
|
X_CASCADE = 'X-Cascade'
|
@@ -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
|
data/lib/grape/path.rb
CHANGED
@@ -42,11 +42,11 @@ module Grape
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def namespace?
|
45
|
-
namespace
|
45
|
+
namespace&.match?(/^\S/) && namespace != '/'
|
46
46
|
end
|
47
47
|
|
48
48
|
def path?
|
49
|
-
raw_path
|
49
|
+
raw_path&.match?(/^\S/) && raw_path != '/'
|
50
50
|
end
|
51
51
|
|
52
52
|
def suffix
|
@@ -6,10 +6,31 @@ module Grape
|
|
6
6
|
class AttributeTranslator
|
7
7
|
attr_reader :attributes, :request_method, :requirements
|
8
8
|
|
9
|
+
ROUTE_ATTRIBUTES = %i[
|
10
|
+
prefix
|
11
|
+
version
|
12
|
+
settings
|
13
|
+
format
|
14
|
+
description
|
15
|
+
http_codes
|
16
|
+
headers
|
17
|
+
entity
|
18
|
+
details
|
19
|
+
requirements
|
20
|
+
request_method
|
21
|
+
namespace
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
ROUTER_ATTRIBUTES = %i[pattern index].freeze
|
25
|
+
|
9
26
|
def initialize(attributes = {})
|
10
27
|
@attributes = attributes
|
11
|
-
|
12
|
-
|
28
|
+
end
|
29
|
+
|
30
|
+
(ROUTER_ATTRIBUTES + ROUTE_ATTRIBUTES).each do |attr|
|
31
|
+
define_method attr do
|
32
|
+
attributes[attr]
|
33
|
+
end
|
13
34
|
end
|
14
35
|
|
15
36
|
def to_h
|
data/lib/grape/router/route.rb
CHANGED
@@ -12,12 +12,13 @@ module Grape
|
|
12
12
|
SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/.freeze
|
13
13
|
FIXED_NAMED_CAPTURES = %w[format version].freeze
|
14
14
|
|
15
|
-
attr_accessor :pattern, :translator, :app, :index, :
|
15
|
+
attr_accessor :pattern, :translator, :app, :index, :options
|
16
16
|
|
17
17
|
alias attributes translator
|
18
18
|
|
19
19
|
extend Forwardable
|
20
20
|
def_delegators :pattern, :path, :origin
|
21
|
+
delegate Grape::Router::AttributeTranslator::ROUTE_ATTRIBUTES => :attributes
|
21
22
|
|
22
23
|
def method_missing(method_id, *arguments)
|
23
24
|
match = ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s)
|
@@ -31,26 +32,7 @@ module Grape
|
|
31
32
|
end
|
32
33
|
|
33
34
|
def respond_to_missing?(method_id, _)
|
34
|
-
ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s)
|
35
|
-
end
|
36
|
-
|
37
|
-
%i[
|
38
|
-
prefix
|
39
|
-
version
|
40
|
-
settings
|
41
|
-
format
|
42
|
-
description
|
43
|
-
http_codes
|
44
|
-
headers
|
45
|
-
entity
|
46
|
-
details
|
47
|
-
requirements
|
48
|
-
request_method
|
49
|
-
namespace
|
50
|
-
].each do |method_name|
|
51
|
-
define_method method_name do
|
52
|
-
attributes.public_send method_name
|
53
|
-
end
|
35
|
+
ROUTE_ATTRIBUTE_REGEXP.match?(method_id.to_s)
|
54
36
|
end
|
55
37
|
|
56
38
|
def route_method
|
@@ -67,7 +49,6 @@ module Grape
|
|
67
49
|
method_s = method.to_s
|
68
50
|
method_upcase = Grape::Http::Headers.find_supported_method(method_s) || method_s.upcase
|
69
51
|
|
70
|
-
@suffix = options[:suffix]
|
71
52
|
@options = options.merge(method: method_upcase)
|
72
53
|
@pattern = Pattern.new(pattern, **options)
|
73
54
|
@translator = AttributeTranslator.new(**options, request_method: method_upcase)
|
data/lib/grape/router.rb
CHANGED
@@ -7,16 +7,6 @@ module Grape
|
|
7
7
|
class Router
|
8
8
|
attr_reader :map, :compiled
|
9
9
|
|
10
|
-
class Any < AttributeTranslator
|
11
|
-
attr_reader :pattern, :regexp, :index
|
12
|
-
def initialize(pattern, regexp, index, **attributes)
|
13
|
-
@pattern = pattern
|
14
|
-
@regexp = regexp
|
15
|
-
@index = index
|
16
|
-
super(attributes)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
10
|
class NormalizePathCache < Grape::Util::Cache
|
21
11
|
def initialize
|
22
12
|
@cache = Hash.new do |h, path|
|
@@ -39,18 +29,20 @@ module Grape
|
|
39
29
|
|
40
30
|
def initialize
|
41
31
|
@neutral_map = []
|
32
|
+
@neutral_regexes = []
|
42
33
|
@map = Hash.new { |hash, key| hash[key] = [] }
|
43
34
|
@optimized_map = Hash.new { |hash, key| hash[key] = // }
|
44
35
|
end
|
45
36
|
|
46
37
|
def compile!
|
47
38
|
return if compiled
|
48
|
-
@union = Regexp.union(@
|
39
|
+
@union = Regexp.union(@neutral_regexes)
|
40
|
+
@neutral_regexes = nil
|
49
41
|
self.class.supported_methods.each do |method|
|
50
42
|
routes = map[method]
|
51
43
|
@optimized_map[method] = routes.map.with_index do |route, index|
|
52
44
|
route.index = index
|
53
|
-
|
45
|
+
Regexp.new("(?<_#{index}>#{route.pattern.to_regexp})")
|
54
46
|
end
|
55
47
|
@optimized_map[method] = Regexp.union(@optimized_map[method])
|
56
48
|
end
|
@@ -62,8 +54,8 @@ module Grape
|
|
62
54
|
end
|
63
55
|
|
64
56
|
def associate_routes(pattern, **options)
|
65
|
-
|
66
|
-
@neutral_map <<
|
57
|
+
@neutral_regexes << Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}")
|
58
|
+
@neutral_map << Grape::Router::AttributeTranslator.new(options.merge(pattern: pattern, index: @neutral_map.length))
|
67
59
|
end
|
68
60
|
|
69
61
|
def call(env)
|
@@ -5,8 +5,7 @@ module Grape
|
|
5
5
|
# Base for classes which need to operate with own values kept
|
6
6
|
# in the hash and inherited values kept in a Hash-like object.
|
7
7
|
class BaseInheritable
|
8
|
-
attr_accessor :inherited_values
|
9
|
-
attr_accessor :new_values
|
8
|
+
attr_accessor :inherited_values, :new_values
|
10
9
|
|
11
10
|
# @param inherited_values [Object] An object implementing an interface
|
12
11
|
# of the Hash class.
|
@@ -26,10 +25,14 @@ module Grape
|
|
26
25
|
end
|
27
26
|
|
28
27
|
def keys
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
if new_values.any?
|
29
|
+
combined = inherited_values.keys
|
30
|
+
combined.concat(new_values.keys)
|
31
|
+
combined.uniq!
|
32
|
+
combined
|
33
|
+
else
|
34
|
+
inherited_values.keys
|
35
|
+
end
|
33
36
|
end
|
34
37
|
|
35
38
|
def key?(name)
|
@@ -8,8 +8,10 @@ module Grape
|
|
8
8
|
protected
|
9
9
|
|
10
10
|
def concat_values(inherited_value, new_value)
|
11
|
+
return inherited_value unless new_value
|
12
|
+
|
11
13
|
[].tap do |value|
|
12
|
-
value.concat(new_value)
|
14
|
+
value.concat(new_value)
|
13
15
|
value.concat(inherited_value)
|
14
16
|
end
|
15
17
|
end
|