grape 1.3.1 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/LICENSE +1 -1
  4. data/README.md +19 -6
  5. data/UPGRADING.md +120 -16
  6. data/lib/grape/api/instance.rb +12 -7
  7. data/lib/grape/dsl/inside_route.rb +37 -14
  8. data/lib/grape/http/headers.rb +1 -0
  9. data/lib/grape/middleware/versioner/header.rb +1 -1
  10. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  11. data/lib/grape/path.rb +2 -2
  12. data/lib/grape/router/attribute_translator.rb +23 -2
  13. data/lib/grape/router/route.rb +3 -22
  14. data/lib/grape/router.rb +6 -14
  15. data/lib/grape/util/base_inheritable.rb +9 -6
  16. data/lib/grape/util/reverse_stackable_values.rb +3 -1
  17. data/lib/grape/util/stackable_values.rb +3 -1
  18. data/lib/grape/validations/types/array_coercer.rb +14 -5
  19. data/lib/grape/validations/types/build_coercer.rb +5 -8
  20. data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
  21. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  22. data/lib/grape/validations/types/file.rb +15 -13
  23. data/lib/grape/validations/types/json.rb +40 -36
  24. data/lib/grape/validations/types/primitive_coercer.rb +11 -4
  25. data/lib/grape/validations/types/set_coercer.rb +6 -4
  26. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  27. data/lib/grape/validations/types.rb +6 -5
  28. data/lib/grape/validations/validators/coerce.rb +3 -10
  29. data/lib/grape/validations/validators/default.rb +0 -1
  30. data/lib/grape/validations/validators/regexp.rb +1 -1
  31. data/lib/grape/version.rb +1 -1
  32. data/spec/grape/api/instance_spec.rb +50 -0
  33. data/spec/grape/endpoint_spec.rb +18 -5
  34. data/spec/grape/path_spec.rb +4 -4
  35. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  36. data/spec/grape/validations/types/primitive_coercer_spec.rb +5 -1
  37. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  38. data/spec/grape/validations/types_spec.rb +1 -1
  39. data/spec/grape/validations/validators/coerce_spec.rb +207 -29
  40. data/spec/grape/validations/validators/default_spec.rb +121 -0
  41. data/spec/grape/validations/validators/values_spec.rb +1 -1
  42. data/spec/grape/validations_spec.rb +5 -5
  43. metadata +9 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1711dd2fb0f0c86757c7e73d780907efa87e3da54e0a999a4b45c276b5eec92c
4
- data.tar.gz: f5ba49001d1816d92130f70fb1b63ae061d7d9250142605f706d75c207880ad9
3
+ metadata.gz: d63fb79e412ead32064ad4994e171e65962131a04afb20d12957942c744f4df8
4
+ data.tar.gz: 9a9f4fb654e346eabb8a0b902d5e49ae54dc567797789c98c76f2a2fe2e2daa9
5
5
  SHA512:
6
- metadata.gz: 8b2dcf4d4903e3923dd10086a3371258404756cf294cd42c6fb64f3d2520fe7243c0accfe1b68f2e02fbaa85f29396c161d830cde4c5fdedd660b31e8d223a7f
7
- data.tar.gz: 88a4d5e9740495430cd28ed1e213d6aafd92895c1b7966bd6358dd6aeb5b2e1f16eac2e14834d63e7791a6d66e16475ef30cdef5e37d10bca0f38f1d1a22c1e4
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-2019 Michael Bleigh, Intridea Inc. and Contributors.
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.1**.
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
- { 'test' => 'password1' }[username] == password
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-2019 Michael Bleigh, Intridea Inc. and Contributors.
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 [dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter coercion. If your project depends on Virtus, explicitly add it to your `Gemfile`. Also, if Virtus is used for defining custom types
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
- class User
16
- include Virtus.model
79
+ # Legacy Grape parser
80
+ class SecureUriType < Virtus::Attribute
81
+ def coerce(input)
82
+ URI.parse value
83
+ end
17
84
 
18
- attribute :id, Integer
19
- attribute :name, String
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 :user, type: User
91
+ requires :secure_uri, type: SecureUri
25
92
  end
26
93
  ```
27
94
 
28
- Add a class-level `parse` method to the model:
95
+ To use dry-types, we need to:
29
96
 
30
- ```ruby
31
- class User
32
- include Virtus.model
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
- attribute :id, Integer
35
- attribute :name, String
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.parse(attrs)
38
- new(attrs)
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
- Custom types which don't depend on Virtus don't require any changes.
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
 
@@ -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] << route.request_method
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
- not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods
247
- not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
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(declared_parent_param, passed_children_params) do
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(declared_param, passed_children_params, &_block)
81
- should_be_empty_array?(declared_param, passed_children_params) ? [] : yield
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?(declared_param, passed_children_params)
85
- declared_param_is_array?(declared_param) && passed_children_params.empty?
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?(declared_param)
89
- key = declared_param.to_s
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
@@ -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'
@@ -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 =~ h.sub('application/', '')
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.to_s.match(%r{^([a-z*]+)\/([a-z0-9*\&\^\-_#\$!.+]+)(?:;([a-z0-9=;]+))?$})
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 && namespace.to_s =~ /^\S/ && namespace != '/'
45
+ namespace&.match?(/^\S/) && namespace != '/'
46
46
  end
47
47
 
48
48
  def path?
49
- raw_path && raw_path.to_s =~ /^\S/ && 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
- @request_method = attributes[:request_method]
12
- @requirements = attributes[:requirements]
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
@@ -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, :regexp, :options
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(@neutral_map.map(&:regexp))
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
- route.regexp = Regexp.new("(?<_#{index}>#{route.pattern.to_regexp})")
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
- regexp = Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}")
66
- @neutral_map << Any.new(pattern, regexp, @neutral_map.length, **options)
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
- combined = inherited_values.keys
30
- combined.concat(new_values.keys)
31
- combined.uniq!
32
- combined
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) if new_value
14
+ value.concat(new_value)
13
15
  value.concat(inherited_value)
14
16
  end
15
17
  end