grape 2.4.0 → 3.0.0

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/CONTRIBUTING.md +1 -9
  4. data/README.md +72 -31
  5. data/UPGRADING.md +34 -0
  6. data/grape.gemspec +4 -4
  7. data/lib/grape/api/instance.rb +49 -72
  8. data/lib/grape/api.rb +24 -34
  9. data/lib/grape/dry_types.rb +48 -4
  10. data/lib/grape/dsl/callbacks.rb +8 -58
  11. data/lib/grape/dsl/desc.rb +8 -67
  12. data/lib/grape/dsl/helpers.rb +59 -64
  13. data/lib/grape/dsl/inside_route.rb +20 -43
  14. data/lib/grape/dsl/logger.rb +3 -6
  15. data/lib/grape/dsl/middleware.rb +22 -40
  16. data/lib/grape/dsl/parameters.rb +7 -16
  17. data/lib/grape/dsl/request_response.rb +136 -139
  18. data/lib/grape/dsl/routing.rb +229 -201
  19. data/lib/grape/dsl/settings.rb +22 -134
  20. data/lib/grape/dsl/validations.rb +37 -45
  21. data/lib/grape/endpoint.rb +64 -96
  22. data/lib/grape/error_formatter/base.rb +2 -0
  23. data/lib/grape/exceptions/base.rb +1 -1
  24. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  25. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  26. data/lib/grape/middleware/auth/dsl.rb +5 -6
  27. data/lib/grape/middleware/error.rb +1 -11
  28. data/lib/grape/middleware/formatter.rb +4 -2
  29. data/lib/grape/middleware/stack.rb +2 -2
  30. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
  31. data/lib/grape/middleware/versioner/base.rb +24 -42
  32. data/lib/grape/middleware/versioner/header.rb +1 -1
  33. data/lib/grape/middleware/versioner/param.rb +2 -2
  34. data/lib/grape/middleware/versioner/path.rb +1 -1
  35. data/lib/grape/namespace.rb +11 -0
  36. data/lib/grape/params_builder/base.rb +2 -0
  37. data/lib/grape/router.rb +4 -3
  38. data/lib/grape/util/api_description.rb +56 -0
  39. data/lib/grape/util/base_inheritable.rb +5 -2
  40. data/lib/grape/util/inheritable_setting.rb +7 -0
  41. data/lib/grape/util/media_type.rb +1 -1
  42. data/lib/grape/util/registry.rb +1 -1
  43. data/lib/grape/validations/contract_scope.rb +2 -2
  44. data/lib/grape/validations/params_documentation.rb +50 -0
  45. data/lib/grape/validations/params_scope.rb +38 -53
  46. data/lib/grape/validations/types/array_coercer.rb +2 -3
  47. data/lib/grape/validations/types/dry_type_coercer.rb +4 -11
  48. data/lib/grape/validations/types/primitive_coercer.rb +1 -28
  49. data/lib/grape/validations/types.rb +10 -25
  50. data/lib/grape/validations/validators/base.rb +0 -7
  51. data/lib/grape/version.rb +1 -1
  52. data/lib/grape.rb +7 -10
  53. metadata +24 -14
  54. data/lib/grape/api/helpers.rb +0 -9
  55. data/lib/grape/dsl/api.rb +0 -17
  56. data/lib/grape/dsl/configuration.rb +0 -15
  57. data/lib/grape/types/invalid_value.rb +0 -8
  58. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  59. data/lib/grape/validations/attributes_doc.rb +0 -60
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83e82bddb3698a0f659d7c58edcb20c2d7edd44cce6e78f31139a2cf3452da0e
4
- data.tar.gz: d47076d4e1b7445029b7695b5cfcb0ddebf72a1106743817eb583f79e636c8fc
3
+ metadata.gz: 919d93a2a8cd39c07b1a043fb342cbecdf5ce4a85b7696188d2de629f92b02a1
4
+ data.tar.gz: c33240607db8c00cba0fc1d5cf5d9c50809c561d3c29b10f246fe9e96423d277
5
5
  SHA512:
6
- metadata.gz: a22fbee812a61d12aa8ca151cc53835df146d840173fc92d9bb4ae049b5b14a70f6d399fcd06440167312af7bd60116e8382f21424a7c8b494b0251a82e7cea5
7
- data.tar.gz: cd3d6d737305696db1cb703639dcdc3d7a6a4e32c02a25fd3942bef3e91529a8acaf11bb5cd4039b108ef2c908461c233a54207d2c4f528959fd944afeac0b22
6
+ metadata.gz: baf576a03f0a56114032b12d842964d8fb5587de44bdded21c2faecca9a2da3bf7f8eb36aa7a8cec4b4b9b4904ab92ab9763045688452e88f891fc7bf781e527
7
+ data.tar.gz: 48dcc1cab168fd88fd7b839da6c0bbaecc8108c6eac62cc9e1f0f2c09c3e9fd5db2756fa246fce2fb2822c92eff5dc8ef6e2b694edf9b08e244b7c0749fb6633
data/CHANGELOG.md CHANGED
@@ -1,3 +1,42 @@
1
+ ### 3.0.0 (2025-11-15)
2
+
3
+ #### Features
4
+
5
+ * [#2572](https://github.com/ruby-grape/grape/pull/2572): Drop support ruby 2.7 and active_support 6.1 - [@ericproulx](https://github.com/ericproulx).
6
+ * [#2573](https://github.com/ruby-grape/grape/pull/2573): Clean up deprecated code - [@ericproulx](https://github.com/ericproulx).
7
+ * [#2575](https://github.com/ruby-grape/grape/pull/2575): Refactor Api description class - [@ericproulx](https://github.com/ericproulx).
8
+ * [#2577](https://github.com/ruby-grape/grape/pull/2577): Deprecate `return` in endpoint execution - [@ericproulx](https://github.com/ericproulx).
9
+ * [#2580](https://github.com/ruby-grape/grape/pull/2580): Refactor endpoint helpers and error middleware integration - [@ericproulx](https://github.com/ericproulx).
10
+ * [#2581](https://github.com/ruby-grape/grape/pull/2581): Delegate `to_s` in Grape::API::Instance - [@ericproulx](https://github.com/ericproulx).
11
+ * [#2582](https://github.com/ruby-grape/grape/pull/2582): Fix leaky slash when normalizing - [@ericproulx](https://github.com/ericproulx).
12
+ * [#2583](https://github.com/ruby-grape/grape/pull/2583): Optimize api parameter documentation and memory usage - [@ericproulx](https://github.com/ericproulx).
13
+ * [#2589](https://github.com/ruby-grape/grape/pull/2589): Replace `send` by `__send__` in codebase - [@ericproulx](https://github.com/ericproulx).
14
+ * [#2598](https://github.com/ruby-grape/grape/pull/2598): Refactor settings DSL to use explicit methods instead of dynamic generation - [@ericproulx](https://github.com/ericproulx).
15
+ * [#2599](https://github.com/ruby-grape/grape/pull/2599): Simplify settings DSL get_or_set method and optimize logger implementation - [@ericproulx](https://github.com/ericproulx).
16
+ * [#2600](https://github.com/ruby-grape/grape/pull/2600): Refactor versioner middleware: simplify base class and improve consistency - [@ericproulx](https://github.com/ericproulx).
17
+ * [#2601](https://github.com/ruby-grape/grape/pull/2601): Refactor route_setting internal usage to use inheritable_setting.route for improved consistency and performance - [@ericproulx](https://github.com/ericproulx).
18
+ * [#2602](https://github.com/ruby-grape/grape/pull/2602): Remove `namespace_reverse_stackable` from public DSL interface and use direct inheritable_setting access - [@ericproulx](https://github.com/ericproulx).
19
+ * [#2603](https://github.com/ruby-grape/grape/pull/2603): Remove `namespace_stackable_with_hash` from public interface and move to internal InheritableSetting - [@ericproulx](https://github.com/ericproulx).
20
+ * [#2604](https://github.com/ruby-grape/grape/pull/2604): Enable branch coverage - [@ericproulx](https://github.com/ericproulx).
21
+ * [#2605](https://github.com/ruby-grape/grape/pull/2605): Add Rack 3.2 support with new gemfile and CI integration - [@ericproulx](https://github.com/ericproulx).
22
+ * [#2607](https://github.com/ruby-grape/grape/pull/2607): Remove namespace_stackable and namespace_inheritable from public API - [@ericproulx](https://github.com/ericproulx).
23
+ * [#2615](https://github.com/ruby-grape/grape/pull/2615): Remove manual toc and tod danger check - [@alexanderadam](https://github.com/alexanderadam).
24
+ * [#2612](https://github.com/ruby-grape/grape/pull/2612): Avoid multiple mount pollution - [@alexanderadam](https://github.com/alexanderadam).
25
+ * [#2617](https://github.com/ruby-grape/grape/pull/2617): Migrate from `ActiveSupport::Configurable` to `Dry::Configurable` - [@ericproulx](https://github.com/ericproulx).
26
+ * [#2618](https://github.com/ruby-grape/grape/pull/2618): Modernize argument delegation for Ruby 3+ compatibility - [@ericproulx](https://github.com/ericproulx).
27
+ * [#2623](https://github.com/ruby-grape/grape/pull/2623): Refactor coercer caching to use `Grape::Util::Cache` - [@ericproulx](https://github.com/ericproulx).
28
+
29
+ #### Fixes
30
+
31
+ * [#2586](https://github.com/ruby-grape/grape/pull/2586): Limit helpers DSL public scope - [@ericproulx](https://github.com/ericproulx).
32
+ * [#2588](https://github.com/ruby-grape/grape/pull/2588): Fix defaut format regression on */* - [@ericproulx](https://github.com/ericproulx).
33
+ * [#2593](https://github.com/ruby-grape/grape/pull/2593): Fix warning message when overriding global registry key - [@ericproulx](https://github.com/ericproulx).
34
+ * [#2594](https://github.com/ruby-grape/grape/pull/2594): Fix routes memoization - [@ericproulx](https://github.com/ericproulx).
35
+ * [#2595](https://github.com/ruby-grape/grape/pull/2595): Keep `within_namespace` as part of our internal api - [@ericproulx](https://github.com/ericproulx).
36
+ * [#2596](https://github.com/ruby-grape/grape/pull/2596): Remove `namespace_reverse_stackable_with_hash` from public scope - [@ericproulx](https://github.com/ericproulx).
37
+ * [#2621](https://github.com/ruby-grape/grape/pull/2621): Update upgrading notes regarding `return` usage and simplify endpoint execution - [@ericproulx](https://github.com/ericproulx).
38
+ * [#2622](https://github.com/ruby-grape/grape/pull/2622): Use `require_relative` instead of `$LOAD_PATH` in gemspec - [@ericproulx](https://github.com/ericproulx).
39
+
1
40
  ### 2.4.0 (2025-06-18)
2
41
 
3
42
  #### Features
@@ -1165,3 +1204,4 @@
1165
1204
  ### 0.1.0 (2010/11/13)
1166
1205
 
1167
1206
  * Initial public release - [@mbleigh](https://github.com/mbleigh).
1207
+
data/CONTRIBUTING.md CHANGED
@@ -48,7 +48,7 @@ Here are some examples:
48
48
  - running rspec on a specific file `docker-compose run --rm --build grape rspec spec/:file_path`
49
49
  - running task `docker-compose run --rm --build grape rake <task_name>`
50
50
  - running rubocop `docker-compose run --rm --build grape rubocop`
51
- - running all specs on a specific ruby version (e.g 2.7.7) `RUBY_VERSION=2.7.7 docker-compose run --rm --build grape rspec`
51
+ - running all specs on a specific ruby version (e.g 3.0) `RUBY_VERSION=3.0 docker-compose run --rm --build grape rspec`
52
52
  - running specs on a specific gemfile (e.g rails_7_0.gemfile) `docker-compose run -e GEMFILE=rails_7_0 --rm --build grape rspec`
53
53
 
54
54
  #### Bundle Install and Test
@@ -60,14 +60,6 @@ bundle install
60
60
  bundle exec rake
61
61
  ```
62
62
 
63
- Run tests against all supported versions of Rails.
64
-
65
- ```
66
- gem install appraisal
67
- appraisal install
68
- appraisal rake spec
69
- ```
70
-
71
63
  #### Write Tests
72
64
 
73
65
  Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [spec/grape](spec/grape).
data/README.md CHANGED
@@ -137,13 +137,17 @@
137
137
  - [Reloading API Changes in Development](#reloading-api-changes-in-development)
138
138
  - [Reloading in Rack Applications](#reloading-in-rack-applications)
139
139
  - [Reloading in Rails Applications](#reloading-in-rails-applications)
140
+ - [Rails 7+ (Zeitwerk)](#rails-7-zeitwerk)
141
+ - [Rails 6 and Earlier](#rails-6-and-earlier)
140
142
  - [Performance Monitoring](#performance-monitoring)
141
143
  - [Active Support Instrumentation](#active-support-instrumentation)
142
- - [endpoint_run.grape](#endpoint_rungrape)
143
- - [endpoint_render.grape](#endpoint_rendergrape)
144
- - [endpoint_run_filters.grape](#endpoint_run_filtersgrape)
145
- - [endpoint_run_validators.grape](#endpoint_run_validatorsgrape)
146
- - [format_response.grape](#format_responsegrape)
144
+ - [Hook Points](#hook-points)
145
+ - [endpoint_run.grape](#endpoint_rungrape)
146
+ - [endpoint_render.grape](#endpoint_rendergrape)
147
+ - [endpoint_run_filters.grape](#endpoint_run_filtersgrape)
148
+ - [endpoint_run_validators.grape](#endpoint_run_validatorsgrape)
149
+ - [format_response.grape](#format_responsegrape)
150
+ - [Subscribe to Hooks](#subscribe-to-hooks)
147
151
  - [Monitoring Products](#monitoring-products)
148
152
  - [Contributing to Grape](#contributing-to-grape)
149
153
  - [Security](#security)
@@ -156,8 +160,7 @@ Grape is a REST-like API framework for Ruby. It's designed to run on Rack or com
156
160
 
157
161
  ## Stable Release
158
162
 
159
- You're reading the documentation for the stable release of Grape, 2.4.0.
160
- Please read [UPGRADING](https://github.com/ruby-grape/grape/blob/v2.4.0/UPGRADING.md) when upgrading from a previous version.
163
+ You're reading the documentation for the stable release of Grape, 3.0.0.
161
164
 
162
165
  ## Project Resources
163
166
 
@@ -174,7 +177,7 @@ The maintainers of Grape are working with Tidelift to deliver commercial support
174
177
 
175
178
  ## Installation
176
179
 
177
- Ruby 2.7 or newer is required.
180
+ Ruby 3.0 or newer is required.
178
181
 
179
182
  Grape is available as a gem, to install it run:
180
183
 
@@ -4077,6 +4080,25 @@ Use [grape-reload](https://github.com/AlexYankee/grape-reload).
4077
4080
 
4078
4081
  ### Reloading in Rails Applications
4079
4082
 
4083
+ #### Rails 7+ (Zeitwerk)
4084
+
4085
+ Rails 7+ uses [Zeitwerk](https://github.com/fxn/zeitwerk) as the default autoloader, which automatically handles reloading of code in development mode without any additional configuration.
4086
+
4087
+ If your API files are in `app/api`, Zeitwerk will automatically autoload and reload them. No additional configuration is needed.
4088
+
4089
+ If you encounter issues with reloading, ensure that:
4090
+
4091
+ 1. Your API files follow Zeitwerk naming conventions (file names should match class names).
4092
+ 2. The `config.enable_reloading` is set to `true` in `config/environments/development.rb` (this is the default).
4093
+
4094
+ For troubleshooting autoloading issues, have a look at the [Rails documentation](https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#troubleshooting).
4095
+
4096
+ See the [Rails Autoloading and Reloading Constants guide](https://guides.rubyonrails.org/autoloading_and_reloading_constants.html) for more information.
4097
+
4098
+ #### Rails 6 and Earlier
4099
+
4100
+ For Rails versions before 7, you need to configure reloading manually.
4101
+
4080
4102
  Add API paths to `config/application.rb`.
4081
4103
 
4082
4104
  ```ruby
@@ -4095,28 +4117,12 @@ if Rails.env.development?
4095
4117
  api_reloader = ActiveSupport::FileUpdateChecker.new(api_files) do
4096
4118
  Rails.application.reload_routes!
4097
4119
  end
4098
- ActionDispatch::Callbacks.to_prepare do
4120
+ ActiveSupport::Reloader.to_prepare do
4099
4121
  api_reloader.execute_if_updated
4100
4122
  end
4101
4123
  end
4102
4124
  ```
4103
4125
 
4104
- For Rails >= 5.1.4, change this:
4105
-
4106
- ```ruby
4107
- ActionDispatch::Callbacks.to_prepare do
4108
- api_reloader.execute_if_updated
4109
- end
4110
- ```
4111
-
4112
- to this:
4113
-
4114
- ```ruby
4115
- ActiveSupport::Reloader.to_prepare do
4116
- api_reloader.execute_if_updated
4117
- end
4118
- ```
4119
-
4120
4126
  See [StackOverflow #3282655](http://stackoverflow.com/questions/3282655/ruby-on-rails-3-reload-lib-directory-for-each-request/4368838#4368838) for more information.
4121
4127
 
4122
4128
  ## Performance Monitoring
@@ -4125,27 +4131,30 @@ See [StackOverflow #3282655](http://stackoverflow.com/questions/3282655/ruby-on-
4125
4131
 
4126
4132
  Grape has built-in support for [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) which provides simple hook points to instrument key parts of your application.
4127
4133
 
4128
- The following are currently supported:
4129
4134
 
4130
- #### endpoint_run.grape
4135
+ #### Hook Points
4136
+
4137
+ The following hook points are currently supported:
4138
+
4139
+ ##### endpoint_run.grape
4131
4140
 
4132
4141
  The main execution of an endpoint, includes filters and rendering.
4133
4142
 
4134
4143
  * *endpoint* - The endpoint instance
4135
4144
 
4136
- #### endpoint_render.grape
4145
+ ##### endpoint_render.grape
4137
4146
 
4138
4147
  The execution of the main content block of the endpoint.
4139
4148
 
4140
4149
  * *endpoint* - The endpoint instance
4141
4150
 
4142
- #### endpoint_run_filters.grape
4151
+ ##### endpoint_run_filters.grape
4143
4152
 
4144
4153
  * *endpoint* - The endpoint instance
4145
4154
  * *filters* - The filters being executed
4146
4155
  * *type* - The type of filters (before, before_validation, after_validation, after)
4147
4156
 
4148
- #### endpoint_run_validators.grape
4157
+ ##### endpoint_run_validators.grape
4149
4158
 
4150
4159
  The execution of validators.
4151
4160
 
@@ -4153,7 +4162,7 @@ The execution of validators.
4153
4162
  * *validators* - The validators being executed
4154
4163
  * *request* - The request being validated
4155
4164
 
4156
- #### format_response.grape
4165
+ ##### format_response.grape
4157
4166
 
4158
4167
  Serialization or template rendering.
4159
4168
 
@@ -4162,12 +4171,44 @@ Serialization or template rendering.
4162
4171
 
4163
4172
  See the [ActiveSupport::Notifications documentation](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) for information on how to subscribe to these events.
4164
4173
 
4174
+ #### Subscribe to Hooks
4175
+
4176
+ Once subscribed to the instrumentation, you can intercept the events reported above.
4177
+
4178
+ ```ruby
4179
+ ActiveSupport::Notifications.subscribe(/<api_path>/) do |name, start, finish, id, payload|
4180
+ # your code to intercept the notification
4181
+ end
4182
+ ```
4183
+
4184
+ The request data, the API’s internal data, and the response can be retrieved from the payload.
4185
+
4186
+ You can use `payload.fetch(:endpoint)` or directly `payload[:endpoint]`.
4187
+
4188
+ The `:endpoint` contains the data currently being processed, and access to attributes such as `body`, `request`, `params`, `headers`, `cookies` and `response_cookies`
4189
+
4190
+ For example, `payload[:endpoint].body` provides the current state of the response.
4191
+
4192
+ ```ruby
4193
+ ActiveSupport::Notifications.subscribe(/v1/) do |name, start, finish, id, payload|
4194
+ hook_record = {
4195
+ hook: name
4196
+ status: payload[:env]&.dig("api.endpoint")&.status
4197
+ format: payload[:env]&.dig("api.format")
4198
+ body: payload[:endpoint]&.body
4199
+ duration: (finish - start) * 1000
4200
+ }
4201
+ # your code to save the notification
4202
+ end
4203
+ ```
4204
+
4165
4205
  ### Monitoring Products
4166
4206
 
4167
4207
  Grape integrates with following third-party tools:
4168
4208
 
4169
4209
  * **New Relic** - [built-in support](https://docs.newrelic.com/docs/agents/ruby-agent/frameworks/grape-instrumentation) from v3.10.0 of the official [newrelic_rpm](https://github.com/newrelic/rpm) gem, also [newrelic-grape](https://github.com/xinminlabs/newrelic-grape) gem
4170
4210
  * **Librato Metrics** - [grape-librato](https://github.com/seanmoon/grape-librato) gem
4211
+ * **Rails Performance** - [rails_performance](https://github.com/igorkasyanchuk/rails_performance) gem
4171
4212
  * **[Skylight](https://www.skylight.io/)** - [skylight](https://github.com/skylightio/skylight-ruby) gem, [documentation](https://docs.skylight.io/grape/)
4172
4213
  * **[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)
4173
4214
  * **[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)
data/UPGRADING.md CHANGED
@@ -1,6 +1,40 @@
1
1
  Upgrading Grape
2
2
  ===============
3
3
 
4
+ ### Upgrading to >= 3.0.0
5
+
6
+ #### Ruby 3+ Argument Delegation Modernization
7
+
8
+ Grape has been modernized to use Ruby 3+'s preferred argument delegation patterns. This change replaces `args.extract_options!` with explicit `**kwargs` parameters throughout the codebase.
9
+
10
+ - All DSL methods now use explicit keyword arguments (`**kwargs`) instead of extracting options from mixed argument lists
11
+ - Method signatures are now more explicit and follow Ruby 3+ best practices
12
+ - The `active_support/core_ext/array/extract_options` dependency has been removed
13
+
14
+ This is a modernization effort that improves code quality while maintaining full backward compatibility.
15
+
16
+ See [#2618](https://github.com/ruby-grape/grape/pull/2618) for more information.
17
+
18
+ #### Configuration API Migration from ActiveSupport::Configurable to Dry::Configurable
19
+
20
+ Grape has migrated from `ActiveSupport::Configurable` to `Dry::Configurable` for its configuration system since its [deprecated](https://github.com/rails/rails/blob/1cdd190a25e483b65f1f25bbd0f13a25d696b461/activesupport/lib/active_support/configurable.rb#L3-L7).
21
+
22
+ See [#2617](https://github.com/ruby-grape/grape/pull/2617) for more information.
23
+
24
+ #### Endpoint execution simplified and `return` deprecated
25
+
26
+ Executing a endpoint's block has been simplified and calling `return` in it has been deprecated. Use `next` instead.
27
+
28
+ See [#2577](https://github.com/ruby-grape/grape/pull/2577) for more information.
29
+
30
+ #### Old Deprecations Clean Up
31
+
32
+ - `rack_response` has been removed in favor of using `error!`.
33
+ - `Grape::Exceptions::MissingGroupType` and `Grape::Exceptions::UnsupportedGroupType` aliases `MissingGroupTypeError and `UnsupportedGroupType` have been removed.
34
+ - `Grape::Validations::Base` has been removed in favor of `Grape::Validations::Validators::Base`.
35
+
36
+ See [2573](https://github.com/ruby-grape/grape/pull/2573) for more information.
37
+
4
38
  ### Upgrading to >= 2.4.0
5
39
 
6
40
  #### Grape::Middleware::Auth::Base
data/grape.gemspec CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- $LOAD_PATH.unshift File.expand_path('lib', __dir__)
4
- require 'grape/version'
3
+ require_relative 'lib/grape/version'
5
4
 
6
5
  Gem::Specification.new do |s|
7
6
  s.name = 'grape'
@@ -21,7 +20,8 @@ Gem::Specification.new do |s|
21
20
  'rubygems_mfa_required' => 'true'
22
21
  }
23
22
 
24
- s.add_dependency 'activesupport', '>= 6.1'
23
+ s.add_dependency 'activesupport', '>= 7.0'
24
+ s.add_dependency 'dry-configurable'
25
25
  s.add_dependency 'dry-types', '>= 1.1'
26
26
  s.add_dependency 'mustermann-grape', '~> 1.1.0'
27
27
  s.add_dependency 'rack', '>= 2'
@@ -29,5 +29,5 @@ Gem::Specification.new do |s|
29
29
 
30
30
  s.files = Dir['lib/**/*', 'CHANGELOG.md', 'CONTRIBUTING.md', 'README.md', 'grape.png', 'UPGRADING.md', 'LICENSE', 'grape.gemspec']
31
31
  s.require_paths = ['lib']
32
- s.required_ruby_version = '>= 2.7.0'
32
+ s.required_ruby_version = '>= 3.0'
33
33
  end
@@ -5,15 +5,30 @@ module Grape
5
5
  # The API Instance class, is the engine behind Grape::API. Each class that inherits
6
6
  # from this will represent a different API instance
7
7
  class Instance
8
+ extend Grape::DSL::Settings
9
+ extend Grape::DSL::Desc
10
+ extend Grape::DSL::Validations
11
+ extend Grape::DSL::Callbacks
12
+ extend Grape::DSL::Logger
13
+ extend Grape::DSL::Middleware
14
+ extend Grape::DSL::RequestResponse
15
+ extend Grape::DSL::Routing
16
+ extend Grape::DSL::Helpers
8
17
  extend Grape::Middleware::Auth::DSL
9
- include Grape::DSL::API
18
+
19
+ Boolean = Grape::API::Boolean
10
20
 
11
21
  class << self
22
+ extend Forwardable
12
23
  attr_reader :instance, :base
13
24
  attr_accessor :configuration
14
25
 
26
+ def_delegators :base, :to_s
27
+
15
28
  def given(conditional_option, &block)
16
- evaluate_as_instance_with_configuration(block, lazy: true) if conditional_option && block
29
+ return unless conditional_option
30
+
31
+ mounted(&block)
17
32
  end
18
33
 
19
34
  def mounted(&block)
@@ -25,10 +40,6 @@ module Grape
25
40
  grape_api.instances << self
26
41
  end
27
42
 
28
- def to_s
29
- base&.to_s || super
30
- end
31
-
32
43
  def base_instance?
33
44
  self == base.base_instance
34
45
  end
@@ -50,11 +61,6 @@ module Grape
50
61
  @instance ||= new # rubocop:disable Naming/MemoizedInstanceVariableName
51
62
  end
52
63
 
53
- # Wipe the compiled API so we can recompile after changes were made.
54
- def change!
55
- @instance = nil
56
- end
57
-
58
64
  # This is the interface point between Rack and Grape; it accepts a request
59
65
  # from Rack and ultimately returns an array of three values: the status,
60
66
  # the headers, and the body. See [the rack specification]
@@ -71,11 +77,9 @@ module Grape
71
77
 
72
78
  # (see #cascade?)
73
79
  def cascade(value = nil)
74
- if value.nil?
75
- inheritable_setting.namespace_inheritable.key?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
76
- else
77
- namespace_inheritable(:cascade, value)
78
- end
80
+ return inheritable_setting.namespace_inheritable.key?(:cascade) ? !inheritable_setting.namespace_inheritable(:cascade).nil? : true if value.nil?
81
+
82
+ inheritable_setting.namespace_inheritable[:cascade] = value
79
83
  end
80
84
 
81
85
  def compile!
@@ -92,45 +96,6 @@ module Grape
92
96
 
93
97
  protected
94
98
 
95
- def prepare_routes
96
- endpoints.map(&:routes).flatten
97
- end
98
-
99
- # Execute first the provided block, then each of the
100
- # block passed in. Allows for simple 'before' setups
101
- # of settings stack pushes.
102
- def nest(*blocks, &block)
103
- blocks.compact!
104
- if blocks.any?
105
- evaluate_as_instance_with_configuration(block) if block
106
- blocks.each { |b| evaluate_as_instance_with_configuration(b) }
107
- reset_validations!
108
- else
109
- instance_eval(&block)
110
- end
111
- end
112
-
113
- def evaluate_as_instance_with_configuration(block, lazy: false)
114
- lazy_block = Grape::Util::Lazy::Block.new do |configuration|
115
- value_for_configuration = configuration
116
- self.configuration = value_for_configuration.evaluate if value_for_configuration.try(:lazy?)
117
- response = instance_eval(&block)
118
- self.configuration = value_for_configuration
119
- response
120
- end
121
- if base && base_instance? && lazy
122
- lazy_block
123
- else
124
- lazy_block.evaluate_from(configuration)
125
- end
126
- end
127
-
128
- def inherited(subclass)
129
- super
130
- subclass.reset!
131
- subclass.logger = logger.clone
132
- end
133
-
134
99
  def inherit_settings(other_settings)
135
100
  top_level_setting.inherit_from other_settings.point_in_time_copy
136
101
 
@@ -143,6 +108,19 @@ module Grape
143
108
 
144
109
  reset_routes!
145
110
  end
111
+
112
+ # Wipe the compiled API so we can recompile after changes were made.
113
+ def change!
114
+ @instance = nil
115
+ end
116
+
117
+ private
118
+
119
+ def inherited(subclass)
120
+ super
121
+ subclass.reset!
122
+ subclass.logger logger.clone
123
+ end
146
124
  end
147
125
 
148
126
  attr_reader :router
@@ -180,8 +158,9 @@ module Grape
180
158
  # errors from reaching upstream. This is effectivelly done by unsetting
181
159
  # X-Cascade. Default :cascade is true.
182
160
  def cascade?
183
- return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.key?(:cascade)
184
- return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options)&.key?(:cascade)
161
+ namespace_inheritable = self.class.inheritable_setting.namespace_inheritable
162
+ return namespace_inheritable[:cascade] if namespace_inheritable.key?(:cascade)
163
+ return namespace_inheritable[:version_options][:cascade] if namespace_inheritable[:version_options]&.key?(:cascade)
185
164
 
186
165
  true
187
166
  end
@@ -212,11 +191,12 @@ module Grape
212
191
  last_route = routes.last # Most of the configuration is taken from the last endpoint
213
192
  next if routes.any? { |route| route.request_method == '*' }
214
193
 
194
+ namespace_inheritable = self.class.inheritable_setting.namespace_inheritable
215
195
  allowed_methods = routes.map(&:request_method)
216
- allowed_methods |= [Rack::HEAD] if !self.class.namespace_inheritable(:do_not_route_head) && allowed_methods.include?(Rack::GET)
196
+ allowed_methods |= [Rack::HEAD] if !namespace_inheritable[:do_not_route_head] && allowed_methods.include?(Rack::GET)
217
197
 
218
- allow_header = self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Rack::OPTIONS] | allowed_methods
219
- last_route.app.options[:options_route_enabled] = true unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Rack::OPTIONS)
198
+ allow_header = namespace_inheritable[:do_not_route_options] ? allowed_methods : [Rack::OPTIONS] | allowed_methods
199
+ last_route.app.options[:options_route_enabled] = true unless namespace_inheritable[:do_not_route_options] || allowed_methods.include?(Rack::OPTIONS)
220
200
 
221
201
  @router.associate_routes(last_route.pattern, {
222
202
  endpoint: last_route.app,
@@ -225,22 +205,19 @@ module Grape
225
205
  end
226
206
  end
227
207
 
208
+ ROOT_PREFIX_VERSIONING_KEY = %i[version version_options root_prefix].freeze
209
+ private_constant :ROOT_PREFIX_VERSIONING_KEY
210
+
228
211
  # Allows definition of endpoints that ignore the versioning configuration
229
212
  # used by the rest of your API.
230
213
  def without_root_prefix_and_versioning
231
- old_version = self.class.namespace_inheritable(:version)
232
- old_version_options = self.class.namespace_inheritable(:version_options)
233
- old_root_prefix = self.class.namespace_inheritable(:root_prefix)
234
-
235
- self.class.namespace_inheritable_to_nil(:version)
236
- self.class.namespace_inheritable_to_nil(:version_options)
237
- self.class.namespace_inheritable_to_nil(:root_prefix)
238
-
214
+ inheritable_setting = self.class.inheritable_setting
215
+ deleted_values = inheritable_setting.namespace_inheritable.delete(*ROOT_PREFIX_VERSIONING_KEY)
239
216
  yield
240
-
241
- self.class.namespace_inheritable(:version, old_version)
242
- self.class.namespace_inheritable(:version_options, old_version_options)
243
- self.class.namespace_inheritable(:root_prefix, old_root_prefix)
217
+ ensure
218
+ ROOT_PREFIX_VERSIONING_KEY.each_with_index do |key, index|
219
+ inheritable_setting.namespace_inheritable[key] = deleted_values[index]
220
+ end
244
221
  end
245
222
  end
246
223
  end
data/lib/grape/api.rb CHANGED
@@ -5,7 +5,9 @@ module Grape
5
5
  # should subclass this class in order to build an API.
6
6
  class API
7
7
  # Class methods that we want to call on the API rather than on the API object
8
- NON_OVERRIDABLE = %i[call call! configuration compile! inherited recognize_path].freeze
8
+ NON_OVERRIDABLE = %i[call call! configuration compile! inherited recognize_path routes].freeze
9
+
10
+ Helpers = Grape::DSL::Helpers::BaseHelper
9
11
 
10
12
  class Boolean
11
13
  def self.build(val)
@@ -15,32 +17,18 @@ module Grape
15
17
  end
16
18
  end
17
19
 
18
- class Instance
19
- Boolean = Grape::API::Boolean
20
- end
21
-
22
20
  class << self
23
21
  extend Forwardable
24
22
  attr_accessor :base_instance, :instances
25
23
 
26
24
  delegate_missing_to :base_instance
27
- def_delegators :base_instance, :new, :configuration
28
25
 
29
26
  # This is the interface point between Rack and Grape; it accepts a request
30
27
  # from Rack and ultimately returns an array of three values: the status,
31
28
  # the headers, and the body. See [the rack specification]
32
- # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
29
+ # (https://github.com/rack/rack/blob/main/SPEC.rdoc) for more.
33
30
  # NOTE: This will only be called on an API directly mounted on RACK
34
- def_delegators :instance_for_rack, :call, :compile!
35
-
36
- # When inherited, will create a list of all instances (times the API was mounted)
37
- # It will listen to the setup required to mount that endpoint, and replicate it on any new instance
38
- def inherited(api)
39
- super
40
-
41
- api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)
42
- api.override_all_methods!
43
- end
31
+ def_delegators :base_instance, :new, :configuration, :call, :compile!
44
32
 
45
33
  # Initialize the instance variables on the remountable class, and the base_instance
46
34
  # an instance that will be used to create the set up but will not be mounted
@@ -54,8 +42,8 @@ module Grape
54
42
  # Redefines all methods so that are forwarded to add_setup and be recorded
55
43
  def override_all_methods!
56
44
  (base_instance.methods - Class.methods - NON_OVERRIDABLE).each do |method_override|
57
- define_singleton_method(method_override) do |*args, &block|
58
- add_setup(method: method_override, args: args, block: block)
45
+ define_singleton_method(method_override) do |*args, **kwargs, &block|
46
+ add_setup(method: method_override, args: args, kwargs: kwargs, block: block)
59
47
  end
60
48
  end
61
49
  end
@@ -89,6 +77,15 @@ module Grape
89
77
 
90
78
  private
91
79
 
80
+ # When inherited, will create a list of all instances (times the API was mounted)
81
+ # It will listen to the setup required to mount that endpoint, and replicate it on any new instance
82
+ def inherited(api)
83
+ super
84
+
85
+ api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)
86
+ api.override_all_methods!
87
+ end
88
+
92
89
  # Replays the set up to produce an API as defined in this class, can be called
93
90
  # on classes that inherit from Grape::API
94
91
  def replay_setup_on(instance)
@@ -97,16 +94,8 @@ module Grape
97
94
  end
98
95
  end
99
96
 
100
- def instance_for_rack
101
- if never_mounted?
102
- base_instance
103
- else
104
- mounted_instances.first
105
- end
106
- end
107
-
108
97
  # Adds a new stage to the set up require to get a Grape::API up and running
109
- def add_setup(step)
98
+ def add_setup(**step)
110
99
  @setup << step
111
100
  last_response = nil
112
101
  @instances.each do |instance|
@@ -130,12 +119,13 @@ module Grape
130
119
  end
131
120
  end
132
121
 
133
- def replay_step_on(instance, method:, args:, block:)
134
- return if skip_immediate_run?(instance, args)
122
+ def replay_step_on(instance, method:, args:, kwargs:, block:)
123
+ return if skip_immediate_run?(instance, args, kwargs)
135
124
 
136
125
  eval_args = evaluate_arguments(instance.configuration, *args)
137
- response = instance.send(method, *eval_args, &block)
138
- if skip_immediate_run?(instance, [response])
126
+ eval_kwargs = kwargs.deep_transform_values { |v| evaluate_arguments(instance.configuration, v).first }
127
+ response = instance.__send__(method, *eval_args, **eval_kwargs, &block)
128
+ if skip_immediate_run?(instance, [response], kwargs)
139
129
  response
140
130
  else
141
131
  evaluate_arguments(instance.configuration, response).first
@@ -143,9 +133,9 @@ module Grape
143
133
  end
144
134
 
145
135
  # Skips steps that contain arguments to be lazily executed (on re-mount time)
146
- def skip_immediate_run?(instance, args)
136
+ def skip_immediate_run?(instance, args, kwargs)
147
137
  instance.base_instance? &&
148
- (any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) })
138
+ (any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) } || any_lazy?(kwargs.values))
149
139
  end
150
140
 
151
141
  def any_lazy?(args)