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.
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