grape 2.3.0 → 2.4.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +36 -14
  5. data/UPGRADING.md +56 -1
  6. data/grape.gemspec +1 -1
  7. data/lib/grape/api/instance.rb +3 -2
  8. data/lib/grape/api.rb +43 -66
  9. data/lib/grape/cookies.rb +31 -25
  10. data/lib/grape/dsl/api.rb +0 -2
  11. data/lib/grape/dsl/headers.rb +1 -1
  12. data/lib/grape/dsl/helpers.rb +1 -1
  13. data/lib/grape/dsl/inside_route.rb +6 -18
  14. data/lib/grape/dsl/parameters.rb +3 -3
  15. data/lib/grape/dsl/routing.rb +9 -1
  16. data/lib/grape/endpoint.rb +30 -33
  17. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  18. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  19. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  20. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  21. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  22. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
  23. data/lib/grape/extensions/hash.rb +2 -1
  24. data/lib/grape/extensions/hashie/mash.rb +3 -5
  25. data/lib/grape/locale/en.yml +44 -44
  26. data/lib/grape/middleware/auth/base.rb +11 -32
  27. data/lib/grape/middleware/auth/dsl.rb +23 -29
  28. data/lib/grape/middleware/base.rb +30 -11
  29. data/lib/grape/middleware/error.rb +16 -24
  30. data/lib/grape/middleware/formatter.rb +38 -72
  31. data/lib/grape/middleware/stack.rb +26 -36
  32. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -3
  33. data/lib/grape/middleware/versioner/base.rb +10 -18
  34. data/lib/grape/middleware/versioner/header.rb +1 -1
  35. data/lib/grape/middleware/versioner/param.rb +2 -3
  36. data/lib/grape/params_builder/base.rb +18 -0
  37. data/lib/grape/params_builder/hash.rb +11 -0
  38. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  39. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  40. data/lib/grape/params_builder.rb +32 -0
  41. data/lib/grape/request.rb +161 -22
  42. data/lib/grape/router/route.rb +1 -1
  43. data/lib/grape/router.rb +25 -7
  44. data/lib/grape/validations/params_scope.rb +8 -3
  45. data/lib/grape/validations/validators/base.rb +2 -2
  46. data/lib/grape/validations/validators/except_values_validator.rb +1 -1
  47. data/lib/grape/validations/validators/presence_validator.rb +1 -1
  48. data/lib/grape/validations/validators/regexp_validator.rb +1 -1
  49. data/lib/grape/version.rb +1 -1
  50. data/lib/grape.rb +13 -1
  51. metadata +18 -13
  52. data/lib/grape/error_formatter/jsonapi.rb +0 -7
  53. data/lib/grape/http/headers.rb +0 -56
  54. data/lib/grape/middleware/helpers.rb +0 -12
  55. data/lib/grape/parser/jsonapi.rb +0 -7
  56. data/lib/grape/util/lazy/object.rb +0 -45
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eefbc6eed618a448bcc9657c03ca73f5771f22b5f13b4c56cacb7ec7e05750ac
4
- data.tar.gz: 6506e73d4ce49cdf54c142acee7085217bd6f55211f86b74a50d23a83ae48c02
3
+ metadata.gz: 83e82bddb3698a0f659d7c58edcb20c2d7edd44cce6e78f31139a2cf3452da0e
4
+ data.tar.gz: d47076d4e1b7445029b7695b5cfcb0ddebf72a1106743817eb583f79e636c8fc
5
5
  SHA512:
6
- metadata.gz: 6e5dab9502a1484f267e9881b8a368cfe809f47eaf9870d14db2002c63c48863f92bbd561c54dd81b0c4e4ff416a7fa7abc121c0052576dc0d7cb088d425d282
7
- data.tar.gz: e78c3fc83ae2a908ef510a387cd31060a13eeb23d7d209c842f2d7e64286ca21e506472fd7ca9f397e1169f7e74fc439a097c376d4ba6682b1c091009fc52acd
6
+ metadata.gz: a22fbee812a61d12aa8ca151cc53835df146d840173fc92d9bb4ae049b5b14a70f6d399fcd06440167312af7bd60116e8382f21424a7c8b494b0251a82e7cea5
7
+ data.tar.gz: cd3d6d737305696db1cb703639dcdc3d7a6a4e32c02a25fd3942bef3e91529a8acaf11bb5cd4039b108ef2c908461c233a54207d2c4f528959fd944afeac0b22
data/CHANGELOG.md CHANGED
@@ -1,3 +1,32 @@
1
+ ### 2.4.0 (2025-06-18)
2
+
3
+ #### Features
4
+
5
+ * [#2532](https://github.com/ruby-grape/grape/pull/2532): Update RuboCop 1.71.2 - [@ericproulx](https://github.com/ericproulx).
6
+ * [#2535](https://github.com/ruby-grape/grape/pull/2535): Delegate calls to inner objects - [@ericproulx](https://github.com/ericproulx).
7
+ * [#2537](https://github.com/ruby-grape/grape/pull/2537): Use activesupport `try` pattern - [@ericproulx](https://github.com/ericproulx).
8
+ * [#2536](https://github.com/ruby-grape/grape/pull/2536): Update normalize_path like Rails - [@ericproulx](https://github.com/ericproulx).
9
+ * [#2540](https://github.com/ruby-grape/grape/pull/2540): Introduce params builder with symbolized short name - [@ericproulx](https://github.com/ericproulx).
10
+ * [#2550](https://github.com/ruby-grape/grape/pull/2550): Drop ActiveSupport 6.0 - [@ericproulx](https://github.com/ericproulx).
11
+ * [#2549](https://github.com/ruby-grape/grape/pull/2549): Delegate cookies management to `Grape::Request` - [@ericproulx](https://github.com/ericproulx).
12
+ * [#2554](https://github.com/ruby-grape/grape/pull/2554): Remove `Grape::Http::Headers` and `Grape::Util::Lazy::Object` - [@ericproulx](https://github.com/ericproulx).
13
+ * [#2556](https://github.com/ruby-grape/grape/pull/2556): Remove unused `Grape::Request::DEFAULT_PARAMS_BUILDER` constant - [@eriklovmo](https://github.com/eriklovmo).
14
+ * [#2558](https://github.com/ruby-grape/grape/pull/2558): Add Ruby's option `enable_frozen_string_literal` in CI - [@ericproulx](https://github.com/ericproulx).
15
+ * [#2557](https://github.com/ruby-grape/grape/pull/2557): Add `lint!` - [@ericproulx](https://github.com/ericproulx).
16
+ * [#2561](https://github.com/ruby-grape/grape/pull/2561): Optimize hash alloc for middleware's default options - [@ericproulx](https://github.com/ericproulx).
17
+ * [#2563](https://github.com/ruby-grape/grape/pull/2563): Update `Grape::Middleware::Auth::Base` - [@ericproulx](https://github.com/ericproulx).
18
+ * [#2571](https://github.com/ruby-grape/grape/pull/2571): Update RuboCop 1.75.8 - [@pieterocp](https://github.com/pieterocp).
19
+
20
+ #### Fixes
21
+
22
+ * [#2538](https://github.com/ruby-grape/grape/pull/2538): Fix validating nested json array params - [@mohammednasser-32](https://github.com/mohammednasser-32).
23
+ * [#2543](https://github.com/ruby-grape/grape/pull/2543): Fix array allocation on mount - [@ericproulx](https://github.com/ericproulx).
24
+ * [#2546](https://github.com/ruby-grape/grape/pull/2546): Fix middleware with keywords - [@ericproulx](https://github.com/ericproulx).
25
+ * [#2547](https://github.com/ruby-grape/grape/pull/2547): Remove jsonapi related code - [@ericproulx](https://github.com/ericproulx).
26
+ * [#2548](https://github.com/ruby-grape/grape/pull/2548): Formatting from header acts like versioning from header - [@ericproulx](https://github.com/ericproulx).
27
+ * [#2552](https://github.com/ruby-grape/grape/pull/2552): Fix declared params optional array - [@ericproulx](https://github.com/ericproulx).
28
+ * [#2553](https://github.com/ruby-grape/grape/pull/2553): Improve performance of query params parsing - [@ericproulx](https://github.com/ericproulx).
29
+
1
30
  ### 2.3.0 (2025-02-08)
2
31
 
3
32
  #### Features
data/CONTRIBUTING.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Contributing to Grape
2
2
  =====================
3
3
 
4
- Grape is work of [hundreds of contributors](https://github.com/ruby-grape/grape/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/ruby-grape/grape/pulls), [propose features and discuss issues](https://github.com/ruby-grape/grape/issues). When in doubt, ask a question in the [Grape Google Group](http://groups.google.com/group/ruby-grape).
4
+ Grape is work of [hundreds of contributors](https://github.com/ruby-grape/grape/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/ruby-grape/grape/pulls), [propose features and discuss issues](https://github.com/ruby-grape/grape/issues).
5
5
 
6
6
  #### Fork the Project
7
7
 
data/README.md CHANGED
@@ -1,11 +1,8 @@
1
1
  ![grape logo](grape.png)
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/grape.svg)](http://badge.fury.io/rb/grape)
4
- [![Build Status](https://github.com/ruby-grape/grape/workflows/test/badge.svg?branch=master)](https://github.com/ruby-grape/grape/actions)
5
- [![Code Climate](https://codeclimate.com/github/ruby-grape/grape.svg)](https://codeclimate.com/github/ruby-grape/grape)
4
+ [![test](https://github.com/ruby-grape/grape/actions/workflows/test.yml/badge.svg)](https://github.com/ruby-grape/grape/actions/workflows/test.yml)
6
5
  [![Coverage Status](https://coveralls.io/repos/github/ruby-grape/grape/badge.svg?branch=master)](https://coveralls.io/github/ruby-grape/grape?branch=master)
7
- [![Inline docs](https://inch-ci.org/github/ruby-grape/grape.svg)](https://inch-ci.org/github/ruby-grape/grape)
8
- [![Join the chat at https://gitter.im/ruby-grape/grape](https://badges.gitter.im/ruby-grape/grape.svg)](https://gitter.im/ruby-grape/grape?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
9
6
 
10
7
  ## Table of Contents
11
8
 
@@ -31,6 +28,8 @@
31
28
  - [Header](#header)
32
29
  - [Accept-Version Header](#accept-version-header)
33
30
  - [Param](#param)
31
+ - [Linting](#linting)
32
+ - [Bug in Rack::ETag under Rack 3.X](#bug-in-racketag-under-rack-3x)
34
33
  - [Describing Methods](#describing-methods)
35
34
  - [Configuration](#configuration)
36
35
  - [Parameters](#parameters)
@@ -157,14 +156,14 @@ Grape is a REST-like API framework for Ruby. It's designed to run on Rack or com
157
156
 
158
157
  ## Stable Release
159
158
 
160
- You're reading the documentation for the next release of Grape, 2.3.0.
161
- Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
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.
162
161
 
163
162
  ## Project Resources
164
163
 
165
164
  * [Grape Website](http://www.ruby-grape.org)
166
165
  * [Documentation](http://www.rubydoc.info/gems/grape)
167
- * Need help? Try [Grape Google Group](http://groups.google.com/group/ruby-grape) or [Gitter](https://gitter.im/ruby-grape/grape)
166
+ * Need help? [Open an Issue](https://github.com/ruby-grape/grape/issues)
168
167
  * [Follow us on Twitter](https://twitter.com/grapeframework)
169
168
 
170
169
  ## Grape for Enterprise
@@ -272,7 +271,7 @@ Grape's [deprecator](https://api.rubyonrails.org/v7.1.0/classes/ActiveSupport/De
272
271
  ### All
273
272
 
274
273
 
275
- By default Grape will compile the routes on the first route, it is possible to pre-load routes using the `compile!` method.
274
+ By default Grape will compile the routes on the first route, but it is possible to pre-load routes using the `compile!` method.
276
275
 
277
276
  ```ruby
278
277
  Twitter::API.compile!
@@ -653,6 +652,27 @@ version 'v1', using: :param, parameter: 'v'
653
652
  curl http://localhost:9292/statuses/public_timeline?v=v1
654
653
 
655
654
 
655
+ ## Linting
656
+
657
+ You can check whether your API is in conformance with the [Rack's specification](https://github.com/rack/rack/blob/main/SPEC.rdoc) by calling `lint!` at the API level or through [configuration](#configuration).
658
+
659
+ ```ruby
660
+ class Api < Grape::API
661
+ lint!
662
+ end
663
+ ```
664
+ ```ruby
665
+ Grape.configure do |config|
666
+ config.lint = true
667
+ end
668
+ ```
669
+ ```ruby
670
+ Grape.config.lint = true
671
+ ```
672
+
673
+ ### Bug in Rack::ETag under Rack 3.X
674
+ If you're using Rack 3.X and the `Rack::Etag` middleware (used by [Rails](https://guides.rubyonrails.org/rails_on_rack.html#inspecting-middleware-stack)), a [bug](https://github.com/rack/rack/pull/2324) related to linting has been fixed in [3.1.13](https://github.com/rack/rack/blob/v3.1.13/CHANGELOG.md#3113---2025-04-13) and [3.0.15](https://github.com/rack/rack/blob/v3.1.13/CHANGELOG.md#3015---2025-04-13) respectively.
675
+
656
676
  ## Describing Methods
657
677
 
658
678
  You can add a description to API methods and namespaces. The description would be used by [grape-swagger][grape-swagger] to generate swagger compliant documentation.
@@ -719,10 +739,13 @@ For example, for the `param_builder`, the following code could run in an initial
719
739
 
720
740
  ```ruby
721
741
  Grape.configure do |config|
722
- config.param_builder = Grape::Extensions::Hashie::Mash::ParamBuilder
742
+ config.param_builder = :hashie_mash
723
743
  end
724
744
  ```
725
745
 
746
+ Available parameter builders are `:hash`, `:hash_with_indifferent_access`, and `:hashie_mash`.
747
+ See [params_builder](lib/grape/params_builder).
748
+
726
749
  You can also configure a single API:
727
750
 
728
751
  ```ruby
@@ -789,7 +812,7 @@ By default parameters are available as `ActiveSupport::HashWithIndifferentAccess
789
812
 
790
813
  ```ruby
791
814
  class API < Grape::API
792
- include Grape::Extensions::Hashie::Mash::ParamBuilder
815
+ build_with :hashie_mash
793
816
 
794
817
  params do
795
818
  optional :color, type: String
@@ -803,16 +826,15 @@ The class can also be overridden on individual parameter blocks using `build_wit
803
826
 
804
827
  ```ruby
805
828
  params do
806
- build_with Grape::Extensions::Hash::ParamBuilder
829
+ build_with :hash
807
830
  optional :color, type: String
808
831
  end
809
832
  ```
810
833
 
811
- Or globally with the [Configuration](#configuration) `Grape.configure.param_builder`.
812
-
813
834
  In the example above, `params["color"]` will return `nil` since `params` is a plain `Hash`.
814
835
 
815
- Available parameter builders are `Grape::Extensions::Hash::ParamBuilder`, `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder` and `Grape::Extensions::Hashie::Mash::ParamBuilder`.
836
+ Available parameter builders are `:hash`, `:hash_with_indifferent_access`, and `:hashie_mash`.
837
+ See [params_builder](lib/grape/params_builder).
816
838
 
817
839
  ### Declared
818
840
 
data/UPGRADING.md CHANGED
@@ -1,6 +1,61 @@
1
1
  Upgrading Grape
2
2
  ===============
3
3
 
4
+ ### Upgrading to >= 2.4.0
5
+
6
+ #### Grape::Middleware::Auth::Base
7
+ `type` is now validated at compile time and will raise a `Grape::Exceptions::UnknownAuthStrategy` if unknown.
8
+
9
+ #### Grape::Middleware::Base
10
+
11
+ - Second argument `options` is now a double splat (**) instead of single splat (*). If you're redefining `initialize` in your middleware and/or calling `super` in it, you might have to adapt the signature and the `super` call. Also, you might have to remove `{}` if you're pass `options` as a literal `Hash` or add `**` if you're using a variable.
12
+ - `Grape::Middleware::Helpers` has been removed. The equivalent method `context` is now part of `Grape::Middleware::Base`.
13
+
14
+ #### Grape::Http::Headers, Grape::Util::Lazy::Object
15
+
16
+ Both have been removed. See [2554](https://github.com/ruby-grape/grape/pull/2554).
17
+ Here are the notable changes:
18
+
19
+ - Constants like `HTTP_ACCEPT` have been replaced by their literal value.
20
+ - `SUPPORTED_METHODS` has been moved to `Grape` module.
21
+ - `HTTP_HEADERS` has been moved to `Grape::Request` and renamed `KNOWN_HEADERS`. The last has been refreshed with new headers, and it's not lazy anymore.
22
+ - `SUPPORTED_METHODS_WITHOUT_OPTIONS` and `find_supported_method` have been removed.
23
+
24
+ #### Grape::Middleware::Base
25
+
26
+ - Constant `TEXT_HTML` has been removed in favor of using literal string 'text/html'.
27
+ - `rack_request` and `query_params` have been added. Feel free to call these in your middlewares.
28
+
29
+ #### Params Builder
30
+
31
+ - Passing a class to `build_with` or `Grape.config.param_builder` has been deprecated in favor of a symbolized short_name. See `SHORTNAME_LOOKUP` in [params_builder](lib/grape/params_builder.rb).
32
+ - Including Grape's extensions like `Grape::Extensions::Hashie::Mash::ParamBuilder` has been deprecated in favor of using `build_with` at the route level.
33
+
34
+ #### Accept Header Negotiation Harmonized
35
+
36
+ [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept) header is now fully interpreted through `Rack::Utils.best_q_match` which is following [RFC2616 14.1](https://datatracker.ietf.org/doc/html/rfc2616#section-14.1). Since [Grape 2.1.0](https://github.com/ruby-grape/grape/blob/master/CHANGELOG.md#210-20240615), the [header versioning strategy](https://github.com/ruby-grape/grape?tab=readme-ov-file#header) was adhering to it, but `Grape::Middleware::Formatter` never did.
37
+
38
+ Your API might act differently since it will strictly follow the [RFC2616 14.1](https://datatracker.ietf.org/doc/html/rfc2616#section-14.1) when interpreting the `Accept` header. Here are the differences:
39
+
40
+ ##### Invalid or missing quality ranking
41
+ The following used to yield `application/xml` and now will yield `application/json` as the preferred media type:
42
+ - `application/json;q=invalid,application/xml;q=0.5`
43
+ - `application/json,application/xml;q=1.0`
44
+
45
+ For the invalid case, the value `invalid` was automatically `to_f` and `invalid.to_f` equals `0.0`. Now, since it doesn't match [Rack's regex](https://github.com/rack/rack/blob/3-1-stable/lib/rack/utils.rb#L138), its interpreted as non provided and its quality ranking equals 1.0.
46
+
47
+ For the non provided case, 1.0 was automatically assigned and in a case of multiple best matches, the first was returned based on Ruby's sort_by `quality`. Now, 1.0 is still assigned and the last is returned in case of multiple best matches. See [Rack's implementation](https://github.com/rack/rack/blob/e8f47608668d507e0f231a932fa37c9ca551c0a5/lib/rack/utils.rb#L167) of the RFC.
48
+
49
+ ##### Considering the closest generic when vendor tree
50
+ Excluding the [header versioning strategy](https://github.com/ruby-grape/grape?tab=readme-ov-file#header), whenever a media type with the [vendor tree](https://datatracker.ietf.org/doc/html/rfc6838#section-3.2) leading facet `vnd.` like `application/vnd.api+json` was provided, Grape would also consider its closest generic when negotiating. In that case, `application/json` was added to the negotiation. Now, it will just consider the provided media types without considering any closest generics, and you'll need to [register](https://github.com/ruby-grape/grape?tab=readme-ov-file#api-formats) it.
51
+ You can find the official vendor tree registrations on [IANA](https://www.iana.org/assignments/media-types/media-types.xhtml)
52
+
53
+ #### Custom Validators
54
+
55
+ If you now receive an error of `'Grape::Validations.require_validator': unknown validator: your_custom_validation (Grape::Exceptions::UnknownValidator)` after upgrading to 2.4.0 then you will need to ensure that you require the `your_custom_validation` file before your Grape API code is loaded.
56
+
57
+ See [2533](https://github.com/ruby-grape/grape/issues/2533) for more information.
58
+
4
59
  ### Upgrading to >= 2.3.0
5
60
 
6
61
  ### `content_type` vs `api.format` inside API
@@ -83,7 +138,7 @@ When using together with `Grape::Extensions::Hash::ParamBuilder`, `route_param`
83
138
  This was a regression introduced by [#2326](https://github.com/ruby-grape/grape/pull/2326) in Grape v1.8.0.
84
139
 
85
140
  ```ruby
86
- grape.configure do |config|
141
+ Grape.configure do |config|
87
142
  config.param_builder = Grape::Extensions::Hash::ParamBuilder
88
143
  end
89
144
 
data/grape.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  'rubygems_mfa_required' => 'true'
22
22
  }
23
23
 
24
- s.add_dependency 'activesupport', '>= 6'
24
+ s.add_dependency 'activesupport', '>= 6.1'
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'
@@ -5,6 +5,7 @@ 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::Middleware::Auth::DSL
8
9
  include Grape::DSL::API
9
10
 
10
11
  class << self
@@ -112,7 +113,7 @@ module Grape
112
113
  def evaluate_as_instance_with_configuration(block, lazy: false)
113
114
  lazy_block = Grape::Util::Lazy::Block.new do |configuration|
114
115
  value_for_configuration = configuration
115
- self.configuration = value_for_configuration.evaluate if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
116
+ self.configuration = value_for_configuration.evaluate if value_for_configuration.try(:lazy?)
116
117
  response = instance_eval(&block)
117
118
  self.configuration = value_for_configuration
118
119
  response
@@ -164,7 +165,7 @@ module Grape
164
165
  status, headers, response = @router.call(env)
165
166
  unless cascade?
166
167
  headers = Grape::Util::Header.new.merge(headers)
167
- headers.delete(Grape::Http::Headers::X_CASCADE)
168
+ headers.delete('X-Cascade')
168
169
  end
169
170
 
170
171
  [status, headers, response]
data/lib/grape/api.rb CHANGED
@@ -5,7 +5,7 @@ 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].freeze
8
+ NON_OVERRIDABLE = %i[call call! configuration compile! inherited recognize_path].freeze
9
9
 
10
10
  class Boolean
11
11
  def self.build(val)
@@ -20,12 +20,18 @@ module Grape
20
20
  end
21
21
 
22
22
  class << self
23
+ extend Forwardable
23
24
  attr_accessor :base_instance, :instances
24
25
 
25
- # Rather than initializing an object of type Grape::API, create an object of type Instance
26
- def new(...)
27
- base_instance.new(...)
28
- end
26
+ delegate_missing_to :base_instance
27
+ def_delegators :base_instance, :new, :configuration
28
+
29
+ # This is the interface point between Rack and Grape; it accepts a request
30
+ # from Rack and ultimately returns an array of three values: the status,
31
+ # the headers, and the body. See [the rack specification]
32
+ # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
33
+ # NOTE: This will only be called on an API directly mounted on RACK
34
+ def_delegators :instance_for_rack, :call, :compile!
29
35
 
30
36
  # When inherited, will create a list of all instances (times the API was mounted)
31
37
  # It will listen to the setup required to mount that endpoint, and replicate it on any new instance
@@ -49,7 +55,7 @@ module Grape
49
55
  def override_all_methods!
50
56
  (base_instance.methods - Class.methods - NON_OVERRIDABLE).each do |method_override|
51
57
  define_singleton_method(method_override) do |*args, &block|
52
- add_setup(method_override, *args, &block)
58
+ add_setup(method: method_override, args: args, block: block)
53
59
  end
54
60
  end
55
61
  end
@@ -69,58 +75,28 @@ module Grape
69
75
  end
70
76
  end
71
77
 
72
- # This is the interface point between Rack and Grape; it accepts a request
73
- # from Rack and ultimately returns an array of three values: the status,
74
- # the headers, and the body. See [the rack specification]
75
- # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
76
- # NOTE: This will only be called on an API directly mounted on RACK
77
- def call(...)
78
- instance_for_rack.call(...)
79
- end
80
-
81
78
  # The remountable class can have a configuration hash to provide some dynamic class-level variables.
82
79
  # For instance, a description could be done using: `desc configuration[:description]` if it may vary
83
80
  # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
84
81
  # too much, you may actually want to provide a new API rather than remount it.
85
- def mount_instance(opts = {})
86
- instance = Class.new(@base_parent)
87
- instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {})
88
- instance.base = self
89
- replay_setup_on(instance)
90
- instance
82
+ def mount_instance(configuration: nil)
83
+ Class.new(@base_parent).tap do |instance|
84
+ instance.configuration = Grape::Util::EndpointConfiguration.new(configuration || {})
85
+ instance.base = self
86
+ replay_setup_on(instance)
87
+ end
91
88
  end
92
89
 
90
+ private
91
+
93
92
  # Replays the set up to produce an API as defined in this class, can be called
94
93
  # on classes that inherit from Grape::API
95
94
  def replay_setup_on(instance)
96
95
  @setup.each do |setup_step|
97
- replay_step_on(instance, setup_step)
98
- end
99
- end
100
-
101
- def respond_to?(method, include_private = false)
102
- super || base_instance.respond_to?(method, include_private)
103
- end
104
-
105
- def respond_to_missing?(method, include_private = false)
106
- base_instance.respond_to?(method, include_private)
107
- end
108
-
109
- def method_missing(method, *args, &block)
110
- # If there's a missing method, it may be defined on the base_instance instead.
111
- if respond_to_missing?(method)
112
- base_instance.send(method, *args, &block)
113
- else
114
- super
96
+ replay_step_on(instance, **setup_step)
115
97
  end
116
98
  end
117
99
 
118
- def compile!
119
- instance_for_rack.compile! # See API::Instance.compile!
120
- end
121
-
122
- private
123
-
124
100
  def instance_for_rack
125
101
  if never_mounted?
126
102
  base_instance
@@ -130,34 +106,35 @@ module Grape
130
106
  end
131
107
 
132
108
  # Adds a new stage to the set up require to get a Grape::API up and running
133
- def add_setup(method, *args, &block)
134
- setup_step = { method: method, args: args, block: block }
135
- @setup += [setup_step]
109
+ def add_setup(step)
110
+ @setup << step
136
111
  last_response = nil
137
112
  @instances.each do |instance|
138
- last_response = replay_step_on(instance, setup_step)
113
+ last_response = replay_step_on(instance, **step)
139
114
  end
140
115
 
141
- # Updating all previously mounted classes in the case that new methods have been executed.
142
- if method != :mount && @setup.any?
143
- previous_mount_steps = @setup.select { |step| step[:method] == :mount }
144
- previous_mount_steps.each do |mount_step|
145
- refresh_mount_step = mount_step.merge(method: :refresh_mounted_api)
146
- @setup += [refresh_mount_step]
147
- @instances.each do |instance|
148
- replay_step_on(instance, refresh_mount_step)
149
- end
116
+ refresh_mount_step if step[:method] != :mount
117
+ last_response
118
+ end
119
+
120
+ # Updating all previously mounted classes in the case that new methods have been executed.
121
+ def refresh_mount_step
122
+ @setup.each do |setup_step|
123
+ next if setup_step[:method] != :mount
124
+
125
+ refresh_mount_step = setup_step.merge(method: :refresh_mounted_api)
126
+ @setup << refresh_mount_step
127
+ @instances.each do |instance|
128
+ replay_step_on(instance, **refresh_mount_step)
150
129
  end
151
130
  end
152
-
153
- last_response
154
131
  end
155
132
 
156
- def replay_step_on(instance, setup_step)
157
- return if skip_immediate_run?(instance, setup_step[:args])
133
+ def replay_step_on(instance, method:, args:, block:)
134
+ return if skip_immediate_run?(instance, args)
158
135
 
159
- args = evaluate_arguments(instance.configuration, *setup_step[:args])
160
- response = instance.send(setup_step[:method], *args, &setup_step[:block])
136
+ eval_args = evaluate_arguments(instance.configuration, *args)
137
+ response = instance.send(method, *eval_args, &block)
161
138
  if skip_immediate_run?(instance, [response])
162
139
  response
163
140
  else
@@ -172,12 +149,12 @@ module Grape
172
149
  end
173
150
 
174
151
  def any_lazy?(args)
175
- args.any? { |argument| argument.respond_to?(:lazy?) && argument.lazy? }
152
+ args.any? { |argument| argument.try(:lazy?) }
176
153
  end
177
154
 
178
155
  def evaluate_arguments(configuration, *args)
179
156
  args.map do |argument|
180
- if argument.respond_to?(:lazy?) && argument.lazy?
157
+ if argument.try(:lazy?)
181
158
  argument.evaluate_from(configuration)
182
159
  elsif argument.is_a?(Hash)
183
160
  argument.transform_values { |value| evaluate_arguments(configuration, value).first }
data/lib/grape/cookies.rb CHANGED
@@ -2,43 +2,49 @@
2
2
 
3
3
  module Grape
4
4
  class Cookies
5
- def initialize
6
- @cookies = {}
7
- @send_cookies = {}
8
- end
5
+ extend Forwardable
9
6
 
10
- def read(request)
11
- request.cookies.each do |name, value|
12
- @cookies[name.to_s] = value
13
- end
7
+ DELETED_COOKIES_ATTRS = {
8
+ max_age: '0',
9
+ value: '',
10
+ expires: Time.at(0)
11
+ }.freeze
12
+
13
+ def_delegators :cookies, :[], :each
14
+
15
+ def initialize(rack_cookies)
16
+ @cookies = rack_cookies
17
+ @send_cookies = nil
14
18
  end
15
19
 
16
- def write(header)
17
- @cookies.select { |key, _value| @send_cookies[key] == true }.each do |name, value|
18
- cookie_value = value.is_a?(Hash) ? value : { value: value }
19
- Rack::Utils.set_cookie_header! header, name, cookie_value
20
+ def response_cookies
21
+ return unless @send_cookies
22
+
23
+ send_cookies.each do |name|
24
+ yield name, cookies[name]
20
25
  end
21
26
  end
22
27
 
23
- def [](name)
24
- @cookies[name.to_s]
28
+ def []=(name, value)
29
+ cookies[name] = value
30
+ send_cookies << name
25
31
  end
26
32
 
27
- def []=(name, value)
28
- @cookies[name.to_s] = value
29
- @send_cookies[name.to_s] = true
33
+ # see https://github.com/rack/rack/blob/main/lib/rack/utils.rb#L338-L340
34
+ def delete(name, **opts)
35
+ self.[]=(name, opts.merge(DELETED_COOKIES_ATTRS))
30
36
  end
31
37
 
32
- def each(&block)
33
- @cookies.each(&block)
38
+ private
39
+
40
+ def cookies
41
+ return @cookies unless @cookies.is_a?(Proc)
42
+
43
+ @cookies = @cookies.call.with_indifferent_access
34
44
  end
35
45
 
36
- # see https://github.com/rack/rack/blob/main/lib/rack/utils.rb#L338-L340
37
- # rubocop:disable Layout/SpaceBeforeBrackets
38
- def delete(name, **opts)
39
- options = opts.merge(max_age: '0', value: '', expires: Time.at(0))
40
- self.[]=(name, options)
46
+ def send_cookies
47
+ @send_cookies ||= Set.new
41
48
  end
42
- # rubocop:enable Layout/SpaceBeforeBrackets
43
49
  end
44
50
  end
data/lib/grape/dsl/api.rb CHANGED
@@ -5,8 +5,6 @@ module Grape
5
5
  module API
6
6
  extend ActiveSupport::Concern
7
7
 
8
- include Grape::Middleware::Auth::DSL
9
-
10
8
  include Grape::DSL::Validations
11
9
  include Grape::DSL::Callbacks
12
10
  include Grape::DSL::Configuration
@@ -10,7 +10,7 @@ module Grape
10
10
  # 4. Delete a specifc header key-value pair
11
11
  def header(key = nil, val = nil)
12
12
  if key
13
- val ? header[key.to_s] = val : header.delete(key.to_s)
13
+ val ? header[key] = val : header.delete(key)
14
14
  else
15
15
  @header ||= Grape::Util::Header.new
16
16
  end
@@ -98,7 +98,7 @@ module Grape
98
98
  protected
99
99
 
100
100
  def process_named_params
101
- return unless instance_variable_defined?(:@named_params) && @named_params && @named_params.any?
101
+ return if @named_params.blank?
102
102
 
103
103
  api.namespace_stackable(:named_params, @named_params)
104
104
  end
@@ -79,7 +79,7 @@ module Grape
79
79
  else
80
80
  # If it is not a Hash then it does not have children.
81
81
  # Find its value or set it to nil.
82
- return unless options[:include_missing] || passed_params.key?(declared_param)
82
+ return unless options[:include_missing] || passed_params.try(:key?, declared_param)
83
83
 
84
84
  rename_path = params_nested_path + [declared_param.to_s]
85
85
  renamed_param_name = renamed_params[rename_path]
@@ -107,7 +107,7 @@ module Grape
107
107
 
108
108
  if type == 'Hash' && !has_children
109
109
  {}
110
- elsif type == 'Array' || (type&.start_with?('[') && type&.exclude?(','))
110
+ elsif type == 'Array' || (type&.start_with?('[') && type.exclude?(','))
111
111
  []
112
112
  elsif type == 'Set' || type&.start_with?('#<Set')
113
113
  Set.new
@@ -213,7 +213,7 @@ module Grape
213
213
  status 302
214
214
  body_message ||= "This resource has been moved temporarily to #{url}."
215
215
  end
216
- header Grape::Http::Headers::LOCATION, url
216
+ header 'Location', url
217
217
  content_type 'text/plain'
218
218
  body body_message
219
219
  end
@@ -257,18 +257,6 @@ module Grape
257
257
  end
258
258
  end
259
259
 
260
- # Set or get a cookie
261
- #
262
- # @example
263
- # cookies[:mycookie] = 'mycookie val'
264
- # cookies['mycookie-string'] = 'mycookie string val'
265
- # cookies[:more] = { value: '123', expires: Time.at(0) }
266
- # cookies.delete :more
267
- #
268
- def cookies
269
- @cookies ||= Cookies.new
270
- end
271
-
272
260
  # Allows you to define the response body as something other than the
273
261
  # return value.
274
262
  #
@@ -342,7 +330,7 @@ module Grape
342
330
  return if value.nil? && @stream.nil?
343
331
 
344
332
  header Rack::CONTENT_LENGTH, nil
345
- header Grape::Http::Headers::TRANSFER_ENCODING, nil
333
+ header 'Transfer-Encoding', nil
346
334
  header Rack::CACHE_CONTROL, 'no-cache' # Skips ETag generation (reading the response up front)
347
335
  if value.is_a?(String)
348
336
  file_body = Grape::ServeStream::FileBody.new(value)
@@ -447,11 +435,11 @@ module Grape
447
435
  def entity_representation_for(entity_class, object, options)
448
436
  embeds = { env: env }
449
437
  embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
450
- entity_class.represent(object, **embeds.merge(options))
438
+ entity_class.represent(object, **embeds, **options)
451
439
  end
452
440
 
453
441
  def http_version
454
- env.fetch(Grape::Http::Headers::HTTP_VERSION) { env[Rack::SERVER_PROTOCOL] }
442
+ env.fetch('HTTP_VERSION') { env[Rack::SERVER_PROTOCOL] }
455
443
  end
456
444
 
457
445
  def api_format(format)