grape 1.5.2 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/CONTRIBUTING.md +2 -1
  4. data/README.md +33 -3
  5. data/UPGRADING.md +71 -2
  6. data/grape.gemspec +5 -5
  7. data/lib/grape/api/instance.rb +13 -17
  8. data/lib/grape/api.rb +18 -13
  9. data/lib/grape/cookies.rb +2 -0
  10. data/lib/grape/dsl/desc.rb +3 -5
  11. data/lib/grape/dsl/headers.rb +5 -2
  12. data/lib/grape/dsl/helpers.rb +7 -5
  13. data/lib/grape/dsl/inside_route.rb +17 -8
  14. data/lib/grape/dsl/middleware.rb +4 -4
  15. data/lib/grape/dsl/parameters.rb +3 -3
  16. data/lib/grape/dsl/request_response.rb +9 -6
  17. data/lib/grape/dsl/routing.rb +2 -2
  18. data/lib/grape/dsl/settings.rb +5 -5
  19. data/lib/grape/endpoint.rb +21 -36
  20. data/lib/grape/error_formatter/json.rb +2 -6
  21. data/lib/grape/error_formatter/xml.rb +2 -6
  22. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  23. data/lib/grape/exceptions/validation.rb +1 -2
  24. data/lib/grape/formatter/json.rb +1 -0
  25. data/lib/grape/formatter/serializable_hash.rb +2 -1
  26. data/lib/grape/formatter/xml.rb +1 -0
  27. data/lib/grape/locale/en.yml +1 -1
  28. data/lib/grape/middleware/auth/dsl.rb +7 -1
  29. data/lib/grape/middleware/base.rb +3 -1
  30. data/lib/grape/middleware/formatter.rb +4 -4
  31. data/lib/grape/middleware/stack.rb +2 -2
  32. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
  33. data/lib/grape/middleware/versioner/header.rb +6 -4
  34. data/lib/grape/middleware/versioner/param.rb +1 -0
  35. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  36. data/lib/grape/middleware/versioner/path.rb +2 -0
  37. data/lib/grape/parser/json.rb +1 -1
  38. data/lib/grape/parser/xml.rb +1 -1
  39. data/lib/grape/path.rb +1 -0
  40. data/lib/grape/request.rb +3 -0
  41. data/lib/grape/router/pattern.rb +1 -1
  42. data/lib/grape/router/route.rb +2 -2
  43. data/lib/grape/router.rb +6 -0
  44. data/lib/grape/util/inheritable_setting.rb +1 -3
  45. data/lib/grape/util/lazy_value.rb +3 -2
  46. data/lib/grape/util/strict_hash_configuration.rb +1 -1
  47. data/lib/grape/validations/params_scope.rb +88 -55
  48. data/lib/grape/validations/types/custom_type_coercer.rb +1 -2
  49. data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
  50. data/lib/grape/validations/types/json.rb +2 -1
  51. data/lib/grape/validations/types/primitive_coercer.rb +3 -3
  52. data/lib/grape/validations/validators/all_or_none.rb +8 -5
  53. data/lib/grape/validations/validators/allow_blank.rb +9 -7
  54. data/lib/grape/validations/validators/as.rb +6 -8
  55. data/lib/grape/validations/validators/at_least_one_of.rb +7 -4
  56. data/lib/grape/validations/validators/base.rb +75 -70
  57. data/lib/grape/validations/validators/coerce.rb +63 -79
  58. data/lib/grape/validations/validators/default.rb +37 -34
  59. data/lib/grape/validations/validators/exactly_one_of.rb +9 -6
  60. data/lib/grape/validations/validators/except_values.rb +13 -11
  61. data/lib/grape/validations/validators/multiple_params_base.rb +24 -20
  62. data/lib/grape/validations/validators/mutual_exclusion.rb +8 -5
  63. data/lib/grape/validations/validators/presence.rb +7 -4
  64. data/lib/grape/validations/validators/regexp.rb +8 -5
  65. data/lib/grape/validations/validators/same_as.rb +18 -15
  66. data/lib/grape/validations/validators/values.rb +61 -56
  67. data/lib/grape/validations.rb +6 -0
  68. data/lib/grape/version.rb +1 -1
  69. data/lib/grape.rb +4 -1
  70. data/spec/grape/api/custom_validations_spec.rb +77 -45
  71. data/spec/grape/api/deeply_included_options_spec.rb +3 -3
  72. data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -1
  73. data/spec/grape/api/invalid_format_spec.rb +2 -0
  74. data/spec/grape/api/recognize_path_spec.rb +1 -1
  75. data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
  76. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -15
  77. data/spec/grape/api_remount_spec.rb +16 -15
  78. data/spec/grape/api_spec.rb +510 -220
  79. data/spec/grape/dsl/callbacks_spec.rb +2 -1
  80. data/spec/grape/dsl/headers_spec.rb +39 -9
  81. data/spec/grape/dsl/helpers_spec.rb +3 -2
  82. data/spec/grape/dsl/inside_route_spec.rb +6 -4
  83. data/spec/grape/dsl/logger_spec.rb +16 -18
  84. data/spec/grape/dsl/middleware_spec.rb +2 -1
  85. data/spec/grape/dsl/parameters_spec.rb +2 -0
  86. data/spec/grape/dsl/request_response_spec.rb +1 -0
  87. data/spec/grape/dsl/routing_spec.rb +10 -7
  88. data/spec/grape/endpoint/declared_spec.rb +259 -12
  89. data/spec/grape/endpoint_spec.rb +77 -55
  90. data/spec/grape/entity_spec.rb +22 -22
  91. data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -0
  92. data/spec/grape/exceptions/invalid_accept_header_spec.rb +61 -22
  93. data/spec/grape/exceptions/validation_errors_spec.rb +13 -10
  94. data/spec/grape/exceptions/validation_spec.rb +5 -3
  95. data/spec/grape/extensions/param_builders/hash_spec.rb +7 -7
  96. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -8
  97. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -8
  98. data/spec/grape/integration/rack_sendfile_spec.rb +1 -1
  99. data/spec/grape/loading_spec.rb +8 -8
  100. data/spec/grape/middleware/auth/dsl_spec.rb +15 -6
  101. data/spec/grape/middleware/auth/strategies_spec.rb +60 -20
  102. data/spec/grape/middleware/base_spec.rb +24 -15
  103. data/spec/grape/middleware/error_spec.rb +2 -2
  104. data/spec/grape/middleware/exception_spec.rb +111 -161
  105. data/spec/grape/middleware/formatter_spec.rb +27 -6
  106. data/spec/grape/middleware/globals_spec.rb +7 -4
  107. data/spec/grape/middleware/stack_spec.rb +14 -12
  108. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -1
  109. data/spec/grape/middleware/versioner/header_spec.rb +14 -13
  110. data/spec/grape/middleware/versioner/param_spec.rb +7 -1
  111. data/spec/grape/middleware/versioner/path_spec.rb +5 -1
  112. data/spec/grape/middleware/versioner_spec.rb +1 -1
  113. data/spec/grape/parser_spec.rb +4 -0
  114. data/spec/grape/path_spec.rb +52 -52
  115. data/spec/grape/presenters/presenter_spec.rb +7 -6
  116. data/spec/grape/request_spec.rb +6 -4
  117. data/spec/grape/util/inheritable_setting_spec.rb +7 -7
  118. data/spec/grape/util/inheritable_values_spec.rb +3 -2
  119. data/spec/grape/util/reverse_stackable_values_spec.rb +3 -1
  120. data/spec/grape/util/stackable_values_spec.rb +7 -5
  121. data/spec/grape/validations/instance_behaivour_spec.rb +9 -10
  122. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +1 -0
  123. data/spec/grape/validations/params_scope_spec.rb +46 -10
  124. data/spec/grape/validations/single_attribute_iterator_spec.rb +2 -1
  125. data/spec/grape/validations/types/primitive_coercer_spec.rb +4 -4
  126. data/spec/grape/validations/types_spec.rb +8 -8
  127. data/spec/grape/validations/validators/all_or_none_spec.rb +50 -56
  128. data/spec/grape/validations/validators/allow_blank_spec.rb +136 -140
  129. data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -56
  130. data/spec/grape/validations/validators/coerce_spec.rb +99 -22
  131. data/spec/grape/validations/validators/default_spec.rb +72 -78
  132. data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -77
  133. data/spec/grape/validations/validators/except_values_spec.rb +3 -3
  134. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -77
  135. data/spec/grape/validations/validators/presence_spec.rb +16 -1
  136. data/spec/grape/validations/validators/regexp_spec.rb +25 -31
  137. data/spec/grape/validations/validators/same_as_spec.rb +14 -20
  138. data/spec/grape/validations/validators/values_spec.rb +183 -178
  139. data/spec/grape/validations_spec.rb +99 -58
  140. data/spec/integration/eager_load/eager_load_spec.rb +2 -2
  141. data/spec/integration/multi_json/json_spec.rb +1 -1
  142. data/spec/integration/multi_xml/xml_spec.rb +1 -1
  143. data/spec/shared/versioning_examples.rb +12 -9
  144. data/spec/spec_helper.rb +12 -2
  145. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  146. metadata +102 -101
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac9fdc749f4dcad72fa8baacf0b07fa0fa499552521c789dd8e84439554ce275
4
- data.tar.gz: 075b2bc7e75e7b0240973086cdae37cd2adf10ef4a4c419dc2cccd7a2741a3cd
3
+ metadata.gz: d182a6dfa2a571345d24a6a2b7286b4a2b34eeedc6570334ba2259fdda59faac
4
+ data.tar.gz: c365091e871e7ad78dd46dc0aab52cdd41d1e5ddb5a46503fcf0020af45f4324
5
5
  SHA512:
6
- metadata.gz: b845801e8c95ceee643853fd306c1513ce0611ebd535f3e2f058e95b4c491da08f58bcbd33b8d42d2cd8f1432d619dda203f8243fcc72e2b808ec4d0e15bc81c
7
- data.tar.gz: ccdf098f65e4331fc35eaab564fbaf8d7411d1ae6f394f4cf4e0fdfa4f4871092e60b23a8a3a60ffc95680e3287446cac343932b0ef2af6b0745b91422d1d0a4
6
+ metadata.gz: 539fdf761a5058dfcb3bab993a7fac3c7223ec06e0b828e102cb52095ac79f63668ebab1c1c4b47bf16984990c94f9a291537e7b2a1359626736b7abdd2c34ec
7
+ data.tar.gz: 2b30d941ab114aab53c4f4630742adedd037856ecc63b65f87b0df506349a13eed092964d9f53c003774006e74b9800043178b2ad2e6c55b0d5d42f6039a7e71
data/CHANGELOG.md CHANGED
@@ -1,3 +1,50 @@
1
+ ### 1.6.2 (2021/12/30)
2
+
3
+ #### Features
4
+
5
+ #### Fixes
6
+
7
+ * [#2219](https://github.com/ruby-grape/grape/pull/2219): Revert the changes for autoloading provided in 1.6.1 - [@dm1try](https://github.com/dm1try).
8
+
9
+ ### 1.6.1 (2021/12/28)
10
+
11
+ #### Features
12
+
13
+ * [#2196](https://github.com/ruby-grape/grape/pull/2196): Add support for `passwords_hashed` param for `digest_auth` - [@lHydra](https://github.com/lhydra).
14
+ * [#2208](https://github.com/ruby-grape/grape/pull/2208): Added Rails 7 support - [@ericproulx](https://github.com/ericproulx).
15
+
16
+ #### Fixes
17
+
18
+ * [#2206](https://github.com/ruby-grape/grape/pull/2206): Require main active_support lib before any of its extension definitions - [@annih](https://github.com/Annih).
19
+ * [#2193](https://github.com/ruby-grape/grape/pull/2193): Fixed the broken ruby-head NoMethodError spec - [@Jack12816](https://github.com/Jack12816).
20
+ * [#2192](https://github.com/ruby-grape/grape/pull/2192): Memoize the result of Grape::Middleware::Base#response - [@Jack12816](https://github.com/Jack12816).
21
+ * [#2200](https://github.com/ruby-grape/grape/pull/2200): Add validators module to all validators - [@ericproulx](https://github.com/ericproulx).
22
+ * [#2202](https://github.com/ruby-grape/grape/pull/2202): Fix random mock spec error - [@ericproulx](https://github.com/ericproulx).
23
+ * [#2203](https://github.com/ruby-grape/grape/pull/2203): Add rubocop-rspec - [@ericproulx](https://github.com/ericproulx).
24
+ * [#2207](https://github.com/ruby-grape/grape/pull/2207): Autoload Validations/Validators - [@ericproulx](https://github.com/ericproulx).
25
+ * [#2209](https://github.com/ruby-grape/grape/pull/2209): Autoload Validations/Types - [@ericproulx](https://github.com/ericproulx).
26
+
27
+ ### 1.6.0 (2021/10/04)
28
+
29
+ #### Features
30
+
31
+ * [#2190](https://github.com/ruby-grape/grape/pull/2190): Upgrade dev deps & drop Ruby 2.4.x support - [@dnesteryuk](https://github.com/dnesteryuk).
32
+
33
+ #### Fixes
34
+
35
+ * [#2176](https://github.com/ruby-grape/grape/pull/2176): Fix: OPTIONS fails if matching all routes - [@myxoh](https://github.com/myxoh).
36
+ * [#2177](https://github.com/ruby-grape/grape/pull/2177): Fix: `default` validator fails if preceded by `as` validator - [@Catsuko](https://github.com/Catsuko).
37
+ * [#2180](https://github.com/ruby-grape/grape/pull/2180): Call `super` in `API.inherited` - [@yogeshjain999](https://github.com/yogeshjain999).
38
+ * [#2189](https://github.com/ruby-grape/grape/pull/2189): Fix: rename parameters when using `:as` (behaviour and grape-swagger documentation) - [@Jack12816](https://github.com/Jack12816).
39
+
40
+ ### 1.5.3 (2021/03/07)
41
+
42
+ #### Fixes
43
+
44
+ * [#2161](https://github.com/ruby-grape/grape/pull/2157): Handle EOFError from Rack when given an empty multipart body - [@bschmeck](https://github.com/bschmeck).
45
+ * [#2162](https://github.com/ruby-grape/grape/pull/2162): Corrected a hash modification while iterating issue - [@Jack12816](https://github.com/Jack12816).
46
+ * [#2164](https://github.com/ruby-grape/grape/pull/2164): Fix: `coerce_with` is now called for params with `nil` value - [@braktar](https://github.com/braktar).
47
+
1
48
  ### 1.5.2 (2021/02/06)
2
49
 
3
50
  #### Features
data/CONTRIBUTING.md CHANGED
@@ -35,6 +35,7 @@ bundle exec rake
35
35
  Run tests against all supported versions of Rails.
36
36
 
37
37
  ```
38
+ gem install appraisal
38
39
  appraisal install
39
40
  appraisal rake spec
40
41
  ```
@@ -118,7 +119,7 @@ Go back to your pull request after a few minutes and see whether it passed muste
118
119
 
119
120
  #### Be Patient
120
121
 
121
- It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there!
122
+ It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang in there!
122
123
 
123
124
  #### Thank You
124
125
 
data/README.md CHANGED
@@ -158,7 +158,8 @@ content negotiation, versioning and much more.
158
158
 
159
159
  ## Stable Release
160
160
 
161
- You're reading the documentation for the stable release of Grape, 1.5.2.
161
+ You're reading the documentation for the stable release of Grape, **1.6.2**.
162
+ Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
162
163
 
163
164
  ## Project Resources
164
165
 
@@ -799,6 +800,7 @@ Grape allows you to access only the parameters that have been declared by your `
799
800
 
800
801
  * Filter out the params that have been passed, but are not allowed.
801
802
  * Include any optional params that are declared but not passed.
803
+ * Perform any parameter renaming on the resulting hash.
802
804
 
803
805
  Consider the following API endpoint:
804
806
 
@@ -993,8 +995,10 @@ curl -X POST -H "Content-Type: application/json" localhost:9292/users/signup -d
993
995
  ````json
994
996
  {
995
997
  "declared_params": {
996
- "first_name": "first name",
997
- "last_name": null
998
+ "user": {
999
+ "first_name": "first name",
1000
+ "last_name": null
1001
+ }
998
1002
  }
999
1003
  }
1000
1004
  ````
@@ -1226,6 +1230,7 @@ params do
1226
1230
  end
1227
1231
  end
1228
1232
  ```
1233
+ Note that, a `nil` value will call the custom coercion method, while a missing parameter will not.
1229
1234
 
1230
1235
  Example of use of `coerce_with` with a lambda (a class with a `parse` method could also have been used)
1231
1236
  It will parse a string and return an Array of Integers, matching the `Array[Integer]` `type`.
@@ -1527,6 +1532,14 @@ end
1527
1532
 
1528
1533
  While Procs are convenient for single cases, consider using [Custom Validators](#custom-validators) in cases where a validation is used more than once.
1529
1534
 
1535
+ Note that [allow_blank](#allow_blank) validator applies while using `:values`. In the following example the absence of `:allow_blank` does not prevent `:state` from receiving blank values because `:allow_blank` defaults to `true`.
1536
+
1537
+ ```ruby
1538
+ params do
1539
+ requires :state, type: Symbol, values: [:active, :inactive]
1540
+ end
1541
+ ```
1542
+
1530
1543
  #### `except_values`
1531
1544
 
1532
1545
  Parameters can be restricted from having a specific set of values with the `:except_values` option.
@@ -3283,12 +3296,20 @@ http_basic do |username, password|
3283
3296
  end
3284
3297
  ```
3285
3298
 
3299
+ Digest auth supports clear-text passwords and password hashes.
3300
+
3286
3301
  ```ruby
3287
3302
  http_digest({ realm: 'Test Api', opaque: 'app secret' }) do |username|
3288
3303
  # lookup the user's password here
3289
3304
  end
3290
3305
  ```
3291
3306
 
3307
+ ```ruby
3308
+ http_digest(realm: { realm: 'Test Api', opaque: 'app secret', passwords_hashed: true }) do |username|
3309
+ # lookup the user's password hash here
3310
+ end
3311
+ ```
3312
+
3292
3313
  ### Register custom middleware for authentication
3293
3314
 
3294
3315
  Grape can use custom Middleware for authentication. How to implement these
@@ -3639,6 +3660,14 @@ You can access the controller params, headers, and helpers through the context w
3639
3660
  Note that when you're using Grape mounted on Rails you don't have to use Rails middleware because it's already included into your middleware stack.
3640
3661
  You only have to implement the helpers to access the specific `env` variable.
3641
3662
 
3663
+ If you are using a custom application that is inherited from `Rails::Application` and need to insert a new middleware among the ones initiated via Rails, you will need to register it manually in your custom application class.
3664
+
3665
+ ```ruby
3666
+ class Company::Application < Rails::Application
3667
+ config.middleware.insert_before(Rack::Attack, Middleware::ApiLogger)
3668
+ end
3669
+ ```
3670
+
3642
3671
  ### Remote IP
3643
3672
 
3644
3673
  By default you can access remote IP with `request.ip`. This is the remote IP address implemented by Rack. Sometimes it is desirable to get the remote IP [Rails-style](http://stackoverflow.com/questions/10997005/whats-the-difference-between-request-remote-ip-and-request-ip-in-rails) with `ActionDispatch::RemoteIp`.
@@ -3943,6 +3972,7 @@ Grape integrates with following third-party tools:
3943
3972
  * **[Skylight](https://www.skylight.io/)** - [skylight](https://github.com/skylightio/skylight-ruby) gem, [documentation](https://docs.skylight.io/grape/)
3944
3973
  * **[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)
3945
3974
  * **[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)
3975
+ * **[Datadog APM](https://docs.datadoghq.com/tracing/)** - [ddtrace](https://github.com/datadog/dd-trace-rb) gem, [documentation](https://docs.datadoghq.com/tracing/setup_overview/setup/ruby/#grape)
3946
3976
 
3947
3977
  ## Contributing to Grape
3948
3978
 
data/UPGRADING.md CHANGED
@@ -1,6 +1,73 @@
1
1
  Upgrading Grape
2
2
  ===============
3
3
 
4
+ ### Upgrading to >= 1.6.0
5
+
6
+ #### Parameter renaming with :as
7
+
8
+ Prior to 1.6.0 the [parameter renaming](https://github.com/ruby-grape/grape#renaming) with `:as` was directly touching the request payload ([`#params`](https://github.com/ruby-grape/grape#parameters)) while duplicating the old and the new key to be both available in the hash. This allowed clients to bypass any validation in case they knew the internal name of the parameter. Unfortunately, in combination with [grape-swagger](https://github.com/ruby-grape/grape-swagger) the internal name (name set with `:as`) of the parameters were documented.
9
+
10
+ This behavior was fixed. Parameter renaming is now done when using the [`#declared(params)`](https://github.com/ruby-grape/grape#declared) parameters helper. This stops confusing validation/coercion behavior.
11
+
12
+ Here comes an illustration of the old and new behaviour as code:
13
+
14
+ ```ruby
15
+ # (1) Rename a to b, while client sends +a+
16
+ optional :a, type: Integer, as: :b
17
+ params = { a: 1 }
18
+ declared(params, include_missing: false)
19
+ # expected => { b: 1 }
20
+ # actual => { b: 1 }
21
+
22
+ # (2) Rename a to b, while client sends +b+
23
+ optional :a, type: Integer, as: :b, values: [1, 2, 3]
24
+ params = { b: '5' }
25
+ declared(params, include_missing: false)
26
+ # expected => { } (>= 1.6.0)
27
+ # actual => { b: '5' } (uncasted, unvalidated, <= 1.5.3)
28
+ ```
29
+
30
+ Another implication of this change is the dependent parameter resolution. Prior to 1.6.0 the following code produced a `Grape::Exceptions::UnknownParameter` because `:a` was replaced by `:b`:
31
+
32
+ ```ruby
33
+ params do
34
+ optional :a, as: :b
35
+ given :a do # (<= 1.5.3 you had to reference +:b+ here to make it work)
36
+ requires :c
37
+ end
38
+ end
39
+ ```
40
+
41
+ This code now works without any errors, as the renaming is just an internal behaviour of the `#declared(params)` parameter helper.
42
+
43
+ See [#2189](https://github.com/ruby-grape/grape/pull/2189) for more information.
44
+
45
+ ### Upgrading to >= 1.5.3
46
+
47
+ #### Nil value and coercion
48
+
49
+ Prior to 1.2.5 version passing a `nil` value for a parameter with a custom coercer would invoke the coercer, and not passing a parameter would not invoke it.
50
+ This behavior was not tested or documented. Version 1.3.0 quietly changed this behavior, in that `nil` values skipped the coercion. Version 1.5.3 fixes and documents this as follows:
51
+
52
+ ```ruby
53
+ class Api < Grape::API
54
+ params do
55
+ optional :value, type: Integer, coerce_with: ->(val) { val || 0 }
56
+ end
57
+
58
+ get 'example' do
59
+ params[:my_param]
60
+ end
61
+ get '/example', params: { value: nil }
62
+ # 1.5.2 = nil
63
+ # 1.5.3 = 0
64
+ get '/example', params: {}
65
+ # 1.5.2 = nil
66
+ # 1.5.3 = nil
67
+ end
68
+ ```
69
+ See [#2164](https://github.com/ruby-grape/grape/pull/2164) for more information.
70
+
4
71
  ### Upgrading to >= 1.5.1
5
72
 
6
73
  #### Dependent params
@@ -130,13 +197,13 @@ end
130
197
 
131
198
  #### Nil values for structures
132
199
 
133
- Nil values always been a special case when dealing with types especially with the following structures:
200
+ Nil values have always been a special case when dealing with types, especially with the following structures:
134
201
 
135
202
  - Array
136
203
  - Hash
137
204
  - Set
138
205
 
139
- The behavior for these structures has change through out the latest releases. For example:
206
+ The behavior for these structures has changed throughout the latest releases. For example:
140
207
 
141
208
  ```ruby
142
209
  class Api < Grape::API
@@ -189,6 +256,8 @@ end
189
256
 
190
257
  ### Upgrading to >= 1.3.0
191
258
 
259
+ You will need to upgrade to this version if you depend on `rack >= 2.1.0`.
260
+
192
261
  #### Ruby
193
262
 
194
263
  After adding dry-types, Ruby 2.4 or newer is required.
data/grape.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
4
4
  require 'grape/version'
5
5
 
6
6
  Gem::Specification.new do |s|
@@ -14,10 +14,10 @@ Gem::Specification.new do |s|
14
14
  s.description = 'A Ruby framework for rapid API development with great conventions.'
15
15
  s.license = 'MIT'
16
16
  s.metadata = {
17
- 'bug_tracker_uri' => 'https://github.com/ruby-grape/grape/issues',
18
- 'changelog_uri' => "https://github.com/ruby-grape/grape/blob/v#{s.version}/CHANGELOG.md",
17
+ 'bug_tracker_uri' => 'https://github.com/ruby-grape/grape/issues',
18
+ 'changelog_uri' => "https://github.com/ruby-grape/grape/blob/v#{s.version}/CHANGELOG.md",
19
19
  'documentation_uri' => "https://www.rubydoc.info/gems/grape/#{s.version}",
20
- 'source_code_uri' => "https://github.com/ruby-grape/grape/tree/v#{s.version}"
20
+ 'source_code_uri' => "https://github.com/ruby-grape/grape/tree/v#{s.version}"
21
21
  }
22
22
 
23
23
  s.add_runtime_dependency 'activesupport'
@@ -32,5 +32,5 @@ Gem::Specification.new do |s|
32
32
  s.files += Dir['lib/**/*']
33
33
  s.test_files = Dir['spec/**/*']
34
34
  s.require_paths = ['lib']
35
- s.required_ruby_version = '>= 2.4.0'
35
+ s.required_ruby_version = '>= 2.5.0'
36
36
  end
@@ -10,12 +10,11 @@ module Grape
10
10
  include Grape::DSL::API
11
11
 
12
12
  class << self
13
- attr_reader :instance
14
- attr_reader :base
13
+ attr_reader :instance, :base
15
14
  attr_accessor :configuration
16
15
 
17
16
  def given(conditional_option, &block)
18
- evaluate_as_instance_with_configuration(block, lazy: true) if conditional_option && block_given?
17
+ evaluate_as_instance_with_configuration(block, lazy: true) if conditional_option && block
19
18
  end
20
19
 
21
20
  def mounted(&block)
@@ -28,7 +27,7 @@ module Grape
28
27
  end
29
28
 
30
29
  def to_s
31
- (base && base.to_s) || super
30
+ base&.to_s || super
32
31
  end
33
32
 
34
33
  def base_instance?
@@ -82,6 +81,7 @@ module Grape
82
81
 
83
82
  def compile!
84
83
  return if instance
84
+
85
85
  LOCK.synchronize { compile unless instance }
86
86
  end
87
87
 
@@ -103,7 +103,7 @@ module Grape
103
103
  def nest(*blocks, &block)
104
104
  blocks.reject!(&:nil?)
105
105
  if blocks.any?
106
- evaluate_as_instance_with_configuration(block) if block_given?
106
+ evaluate_as_instance_with_configuration(block) if block
107
107
  blocks.each { |b| evaluate_as_instance_with_configuration(b) }
108
108
  reset_validations!
109
109
  else
@@ -114,9 +114,7 @@ module Grape
114
114
  def evaluate_as_instance_with_configuration(block, lazy: false)
115
115
  lazy_block = Grape::Util::LazyBlock.new do |configuration|
116
116
  value_for_configuration = configuration
117
- if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
118
- self.configuration = value_for_configuration.evaluate
119
- end
117
+ self.configuration = value_for_configuration.evaluate if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
120
118
  response = instance_eval(&block)
121
119
  self.configuration = value_for_configuration
122
120
  response
@@ -179,7 +177,8 @@ module Grape
179
177
  # X-Cascade. Default :cascade is true.
180
178
  def cascade?
181
179
  return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.key?(:cascade)
182
- return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
180
+ return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options)&.key?(:cascade)
181
+
183
182
  true
184
183
  end
185
184
 
@@ -200,17 +199,15 @@ module Grape
200
199
  without_root_prefix do
201
200
  without_versioning do
202
201
  versioned_route_configs.each do |config|
202
+ next if config[:options][:matching_wildchar]
203
+
203
204
  allowed_methods = config[:methods].dup
204
205
 
205
- unless self.class.namespace_inheritable(:do_not_route_head)
206
- allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
207
- end
206
+ allowed_methods |= [Grape::Http::Headers::HEAD] if !self.class.namespace_inheritable(:do_not_route_head) && allowed_methods.include?(Grape::Http::Headers::GET)
208
207
 
209
208
  allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods)
210
209
 
211
- unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
212
- config[:endpoint].options[:options_route_enabled] = true
213
- end
210
+ config[:endpoint].options[:options_route_enabled] = true unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
214
211
 
215
212
  attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
216
213
  generate_not_allowed_method(config[:pattern], **attributes)
@@ -228,7 +225,7 @@ module Grape
228
225
  last_route = routes.last # Most of the configuration is taken from the last endpoint
229
226
  matching_wildchar = routes.any? { |route| route.request_method == '*' }
230
227
  {
231
- options: {},
228
+ options: { matching_wildchar: matching_wildchar },
232
229
  pattern: last_route.pattern,
233
230
  requirements: last_route.requirements,
234
231
  path: last_route.origin,
@@ -248,7 +245,6 @@ module Grape
248
245
  Grape::Http::Headers::SUPPORTED_METHODS_WITHOUT_OPTIONS
249
246
  end
250
247
  not_allowed_methods = supported_methods - allowed_methods
251
- return if not_allowed_methods.empty?
252
248
  @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
253
249
  end
254
250
 
data/lib/grape/api.rb CHANGED
@@ -10,6 +10,18 @@ module Grape
10
10
  # Class methods that we want to call on the API rather than on the API object
11
11
  NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile! inherited]).freeze
12
12
 
13
+ class Boolean
14
+ def self.build(val)
15
+ return nil if val != true && val != false
16
+
17
+ new
18
+ end
19
+ end
20
+
21
+ class Instance
22
+ Boolean = Grape::API::Boolean
23
+ end
24
+
13
25
  class << self
14
26
  attr_accessor :base_instance, :instances
15
27
 
@@ -20,10 +32,11 @@ module Grape
20
32
 
21
33
  # When inherited, will create a list of all instances (times the API was mounted)
22
34
  # It will listen to the setup required to mount that endpoint, and replicate it on any new instance
23
- def inherited(api, base_instance_parent = Grape::API::Instance)
24
- api.initial_setup(base_instance_parent)
35
+ def inherited(api)
36
+ super
37
+
38
+ api.initial_setup(Grape::API == self ? Grape::API::Instance : @base_instance)
25
39
  api.override_all_methods!
26
- make_inheritable(api)
27
40
  end
28
41
 
29
42
  # Initialize the instance variables on the remountable class, and the base_instance
@@ -68,15 +81,6 @@ module Grape
68
81
  instance_for_rack.call(*args, &block)
69
82
  end
70
83
 
71
- # Allows an API to itself be inheritable:
72
- def make_inheritable(api)
73
- # When a child API inherits from a parent API.
74
- def api.inherited(child_api)
75
- # The instances of the child API inherit from the instances of the parent API
76
- Grape::API.inherited(child_api, base_instance)
77
- end
78
- end
79
-
80
84
  # Alleviates problems with autoloading by tring to search for the constant
81
85
  def const_missing(*args)
82
86
  if base_instance.const_defined?(*args)
@@ -141,7 +145,7 @@ module Grape
141
145
  # Adds a new stage to the set up require to get a Grape::API up and running
142
146
  def add_setup(method, *args, &block)
143
147
  setup_step = { method: method, args: args, block: block }
144
- @setup << setup_step
148
+ @setup += [setup_step]
145
149
  last_response = nil
146
150
  @instances.each do |instance|
147
151
  last_response = replay_step_on(instance, setup_step)
@@ -151,6 +155,7 @@ module Grape
151
155
 
152
156
  def replay_step_on(instance, setup_step)
153
157
  return if skip_immediate_run?(instance, setup_step[:args])
158
+
154
159
  args = evaluate_arguments(instance.configuration, *setup_step[:args])
155
160
  response = instance.send(setup_step[:method], *args, &setup_step[:block])
156
161
  if skip_immediate_run?(instance, [response])
data/lib/grape/cookies.rb CHANGED
@@ -33,9 +33,11 @@ module Grape
33
33
  @cookies.each(&block)
34
34
  end
35
35
 
36
+ # rubocop:disable Layout/SpaceBeforeBrackets
36
37
  def delete(name, **opts)
37
38
  options = opts.merge(value: 'deleted', expires: Time.at(0))
38
39
  self.[]=(name, options)
39
40
  end
41
+ # rubocop:enable Layout/SpaceBeforeBrackets
40
42
  end
41
43
  end
@@ -50,7 +50,7 @@ module Grape
50
50
  # end
51
51
  #
52
52
  def desc(description, options = {}, &config_block)
53
- if block_given?
53
+ if config_block
54
54
  endpoint_configuration = if defined?(configuration)
55
55
  # When the instance is mounted - the configuration is executed on mount time
56
56
  if configuration.respond_to?(:evaluate)
@@ -68,9 +68,7 @@ module Grape
68
68
  end
69
69
 
70
70
  config_class.configure(&config_block)
71
- unless options.empty?
72
- warn '[DEPRECATION] Passing a options hash and a block to `desc` is deprecated. Move all hash options to block.'
73
- end
71
+ warn '[DEPRECATION] Passing a options hash and a block to `desc` is deprecated. Move all hash options to block.' unless options.empty?
74
72
  options = config_class.settings
75
73
  else
76
74
  options = options.merge(description: description)
@@ -92,7 +90,7 @@ module Grape
92
90
 
93
91
  def unset_description_field(field)
94
92
  description = route_setting(:description)
95
- description.delete(field) if description
93
+ description&.delete(field)
96
94
  end
97
95
 
98
96
  # Returns an object which configures itself via an instance-context DSL.
@@ -3,8 +3,11 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module Headers
6
- # Set an individual header or retrieve
7
- # all headers that have been set.
6
+ # This method has four responsibilities:
7
+ # 1. Set a specifc header value by key
8
+ # 2. Retrieve a specifc header value by key
9
+ # 3. Retrieve all headers that have been set
10
+ # 4. Delete a specifc header key-value pair
8
11
  def header(key = nil, val = nil)
9
12
  if key
10
13
  val ? header[key.to_s] = val : header.delete(key.to_s)
@@ -36,8 +36,8 @@ module Grape
36
36
  #
37
37
  def helpers(*new_modules, &block)
38
38
  include_new_modules(new_modules) if new_modules.any?
39
- include_block(block) if block_given?
40
- include_all_in_scope if !block_given? && new_modules.empty?
39
+ include_block(block) if block
40
+ include_all_in_scope if !block && new_modules.empty?
41
41
  end
42
42
 
43
43
  protected
@@ -67,12 +67,13 @@ module Grape
67
67
 
68
68
  def define_boolean_in_mod(mod)
69
69
  return if defined? mod::Boolean
70
- mod.const_set('Boolean', Grape::API::Boolean)
70
+
71
+ mod.const_set(:Boolean, Grape::API::Boolean)
71
72
  end
72
73
 
73
- def inject_api_helpers_to_mod(mod, &_block)
74
+ def inject_api_helpers_to_mod(mod, &block)
74
75
  mod.extend(BaseHelper) unless mod.is_a?(BaseHelper)
75
- yield if block_given?
76
+ yield if block
76
77
  mod.api_changed(self)
77
78
  end
78
79
  end
@@ -96,6 +97,7 @@ module Grape
96
97
 
97
98
  def process_named_params
98
99
  return unless instance_variable_defined?(:@named_params) && @named_params && @named_params.any?
100
+
99
101
  api.namespace_stackable(:named_params, @named_params)
100
102
  end
101
103
  end
@@ -48,6 +48,8 @@ module Grape
48
48
  end
49
49
 
50
50
  def declared_hash(passed_params, options, declared_params, params_nested_path)
51
+ renamed_params = route_setting(:renamed_params) || {}
52
+
51
53
  declared_params.each_with_object(passed_params.class.new) do |declared_param, memo|
52
54
  if declared_param.is_a?(Hash)
53
55
  declared_param.each_pair do |declared_parent_param, declared_children_params|
@@ -55,8 +57,11 @@ module Grape
55
57
  params_nested_path_dup << declared_parent_param.to_s
56
58
  next unless options[:include_missing] || passed_params.key?(declared_parent_param)
57
59
 
60
+ rename_path = params_nested_path + [declared_parent_param.to_s]
61
+ renamed_param_name = renamed_params[rename_path]
62
+
63
+ memo_key = optioned_param_key(renamed_param_name || declared_parent_param, options)
58
64
  passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
59
- memo_key = optioned_param_key(declared_parent_param, options)
60
65
 
61
66
  memo[memo_key] = handle_passed_param(params_nested_path_dup, passed_children_params.any?) do
62
67
  declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
@@ -65,13 +70,13 @@ module Grape
65
70
  else
66
71
  # If it is not a Hash then it does not have children.
67
72
  # Find its value or set it to nil.
68
- has_renaming = route_setting(:renamed_params) && route_setting(:renamed_params).find { |current| current[declared_param] }
69
- param_renaming = has_renaming[declared_param] if has_renaming
73
+ next unless options[:include_missing] || passed_params.key?(declared_param)
70
74
 
71
- next unless options[:include_missing] || passed_params.key?(declared_param) || (param_renaming && passed_params.key?(param_renaming))
75
+ rename_path = params_nested_path + [declared_param.to_s]
76
+ renamed_param_name = renamed_params[rename_path]
72
77
 
73
- memo_key = optioned_param_key(param_renaming || declared_param, options)
74
- passed_param = passed_params[param_renaming || declared_param]
78
+ memo_key = optioned_param_key(renamed_param_name || declared_param, options)
79
+ passed_param = passed_params[declared_param]
75
80
 
76
81
  params_nested_path_dup = params_nested_path.dup
77
82
  params_nested_path_dup << declared_param.to_s
@@ -86,7 +91,7 @@ module Grape
86
91
  return yield if has_passed_children
87
92
 
88
93
  key = params_nested_path[0]
89
- key += '[' + params_nested_path[1..-1].join('][') + ']' if params_nested_path.size > 1
94
+ key += "[#{params_nested_path[1..-1].join('][')}]" if params_nested_path.size > 1
90
95
 
91
96
  route_options_params = options[:route_options][:params] || {}
92
97
  type = route_options_params.dig(key, :type)
@@ -94,7 +99,7 @@ module Grape
94
99
 
95
100
  if type == 'Hash' && !has_children
96
101
  {}
97
- elsif type == 'Array' || type&.start_with?('[') && !type&.include?(',')
102
+ elsif type == 'Array' || (type&.start_with?('[') && !type&.include?(','))
98
103
  []
99
104
  elsif type == 'Set' || type&.start_with?('#<Set')
100
105
  Set.new
@@ -117,6 +122,7 @@ module Grape
117
122
  end
118
123
 
119
124
  raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
125
+
120
126
  declared_params
121
127
  end
122
128
  end
@@ -187,11 +193,13 @@ module Grape
187
193
  case status
188
194
  when Symbol
189
195
  raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(status)
196
+
190
197
  @status = Rack::Utils.status_code(status)
191
198
  when Integer
192
199
  @status = status
193
200
  when nil
194
201
  return @status if instance_variable_defined?(:@status) && @status
202
+
195
203
  case request.request_method.to_s.upcase
196
204
  when Grape::Http::Headers::POST
197
205
  201
@@ -369,6 +377,7 @@ module Grape
369
377
  representation = (body || {}).merge(key => representation)
370
378
  elsif entity_class.present? && body
371
379
  raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
380
+
372
381
  representation = body.merge(representation)
373
382
  end
374
383