grape 3.0.1 → 3.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/CONTRIBUTING.md +2 -2
- data/README.md +2 -2
- data/UPGRADING.md +14 -0
- data/grape.gemspec +2 -2
- data/lib/grape/api/instance.rb +16 -47
- data/lib/grape/api.rb +6 -10
- data/lib/grape/content_types.rb +1 -4
- data/lib/grape/declared_params_handler.rb +118 -0
- data/lib/grape/dsl/declared.rb +35 -0
- data/lib/grape/dsl/helpers.rb +2 -2
- data/lib/grape/dsl/inside_route.rb +3 -141
- data/lib/grape/dsl/parameters.rb +8 -26
- data/lib/grape/dsl/routing.rb +41 -29
- data/lib/grape/dsl/settings.rb +1 -1
- data/lib/grape/dsl/validations.rb +22 -20
- data/lib/grape/endpoint.rb +84 -83
- data/lib/grape/exceptions/base.rb +1 -1
- data/lib/grape/middleware/auth/dsl.rb +4 -4
- data/lib/grape/middleware/base.rb +4 -0
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/formatter.rb +6 -4
- data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
- data/lib/grape/middleware/versioner/base.rb +20 -0
- data/lib/grape/middleware/versioner/header.rb +1 -17
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +5 -9
- data/lib/grape/params_builder.rb +2 -19
- data/lib/grape/router/base_route.rb +14 -5
- data/lib/grape/router/greedy_route.rb +11 -5
- data/lib/grape/router/pattern.rb +6 -20
- data/lib/grape/router/route.rb +7 -11
- data/lib/grape/router.rb +35 -61
- data/lib/grape/util/api_description.rb +10 -8
- data/lib/grape/validations/attributes_iterator.rb +2 -2
- data/lib/grape/validations/params_scope.rb +12 -10
- data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
- data/lib/grape/validations/validators/base.rb +2 -2
- data/lib/grape/validations/validators/except_values_validator.rb +1 -1
- data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +1 -1
- data/lib/grape/validations/validators/presence_validator.rb +1 -1
- data/lib/grape/validations/validators/regexp_validator.rb +12 -2
- data/lib/grape/validations/validators/values_validator.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +5 -13
- metadata +11 -14
- data/lib/grape/exceptions/missing_option.rb +0 -11
- data/lib/grape/exceptions/unknown_options.rb +0 -11
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -24
- data/lib/grape/extensions/hash.rb +0 -27
- data/lib/grape/extensions/hashie/mash.rb +0 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a5d4ecfd73b009cc3c4bcfd0ed50048d70d15a20c48e5282571ef5d9dc717316
|
|
4
|
+
data.tar.gz: 60de796e7e411513df39ac636750d9ce22ac5f43bec24dce48627d672d63a9d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8cedc8302caa250302cde57047e45fb20d6dcd7f03cc71aa0f5cc1544e548a9d13745264f11c6c1dbd17dc82c8cf80165a4a803877a1c8fce4e96912bfdb3c06
|
|
7
|
+
data.tar.gz: 25f407f60587fc6ebed6e10fc91ea74f5bf3e23846b52a20f188a755fa0caa9f224b7d03b07542855f7894b99e078441bc0556d9ed41bc89ffde0c042dc8cd50
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
### 3.1.0 (2026-01-25)
|
|
2
|
+
|
|
3
|
+
#### Features
|
|
4
|
+
|
|
5
|
+
* [#2629](https://github.com/ruby-grape/grape/pull/2629): Refactor Router Architecture - [@ericproulx](https://github.com/ericproulx).
|
|
6
|
+
* [#2633](https://github.com/ruby-grape/grape/pull/2633): Refactor API::Instance and reorganize DSL modules - [@ericproulx](https://github.com/ericproulx).
|
|
7
|
+
* [#2636](https://github.com/ruby-grape/grape/pull/2636): Refactor router to simplify method signatures and reduce duplication - [@ericproulx](https://github.com/ericproulx).
|
|
8
|
+
* [#2640](https://github.com/ruby-grape/grape/pull/2640): Compute available_media_types once - [@ericproulx](https://github.com/ericproulx).
|
|
9
|
+
* [#2637](https://github.com/ruby-grape/grape/pull/2637): Refactor declared method - [@ericproulx](https://github.com/ericproulx).
|
|
10
|
+
* [#2639](https://github.com/ruby-grape/grape/pull/2639): Refactor mime_types_for - [@ericproulx](https://github.com/ericproulx).
|
|
11
|
+
* [#2638](https://github.com/ruby-grape/grape/pull/2638): Remove unnecessary path string duplication - [@ericproulx](https://github.com/ericproulx).
|
|
12
|
+
* [#2643](https://github.com/ruby-grape/grape/pull/2638): Remove `try` method in codebase - [@ericproulx](https://github.com/ericproulx).
|
|
13
|
+
* [#2646](https://github.com/ruby-grape/grape/pull/2646): Call `valid_encoding?` before scrub - [@ericproulx](https://github.com/ericproulx).
|
|
14
|
+
* [#2644](https://github.com/ruby-grape/grape/pull/2644): Clean useless/not valuable dependencies - [@ericproulx](https://github.com/ericproulx).
|
|
15
|
+
* [#2649](https://github.com/ruby-grape/grape/pull/2644): Drop support Ruby 3.0 and ActiveSupport 7.0 - [@ericproulx](https://github.com/ericproulx).
|
|
16
|
+
* [#2648](https://github.com/ruby-grape/grape/pull/2648): Remove deprecated ParamsBuilders extensions - [@ericproulx](https://github.com/ericproulx).
|
|
17
|
+
* [#2645](https://github.com/ruby-grape/grape/pull/2645): Endpoints are compiled when API is compiled - [@ericproulx](https://github.com/ericproulx).
|
|
18
|
+
* [#2647](https://github.com/ruby-grape/grape/pull/2647): Explicit kwargs for `namespace` and `route_param` - [@ericproulx](https://github.com/ericproulx).
|
|
19
|
+
* [#2651](https://github.com/ruby-grape/grape/pull/2651): Migrate Danger to use danger-pr-comment workflow - [@dblock](https://github.com/dblock).
|
|
20
|
+
|
|
21
|
+
#### Fixes
|
|
22
|
+
|
|
23
|
+
* [#2633](https://github.com/ruby-grape/grape/pull/2633): Fix cascade reading - [@ericproulx](https://github.com/ericproulx).
|
|
24
|
+
* [#2641](https://github.com/ruby-grape/grape/pull/2641): Restore support for `return` in endpoint blocks - [@ericproulx](https://github.com/ericproulx).
|
|
25
|
+
* [#2642](https://github.com/ruby-grape/grape/pull/2642): Fix array allocation in base_route.rb - [@ericproulx](https://github.com/ericproulx).
|
|
26
|
+
|
|
1
27
|
### 3.0.1 (2025-11-24)
|
|
2
28
|
|
|
3
29
|
#### Features
|
data/CONTRIBUTING.md
CHANGED
|
@@ -48,8 +48,8 @@ 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 3.
|
|
52
|
-
- running specs on a specific gemfile (e.g
|
|
51
|
+
- running all specs on a specific ruby version (e.g 3.4) `RUBY_VERSION=3.4 docker-compose run --rm --build grape rspec`
|
|
52
|
+
- running specs on a specific gemfile (e.g rails_8_1.gemfile) `docker-compose run -e GEMFILE=rails_8_1 --rm --build grape rspec`
|
|
53
53
|
|
|
54
54
|
#### Bundle Install and Test
|
|
55
55
|
|
data/README.md
CHANGED
|
@@ -160,7 +160,7 @@ Grape is a REST-like API framework for Ruby. It's designed to run on Rack or com
|
|
|
160
160
|
|
|
161
161
|
## Stable Release
|
|
162
162
|
|
|
163
|
-
You're reading the documentation for the stable release of Grape, 3.0.
|
|
163
|
+
You're reading the documentation for the stable release of Grape, 3.1.0.
|
|
164
164
|
|
|
165
165
|
## Project Resources
|
|
166
166
|
|
|
@@ -177,7 +177,7 @@ The maintainers of Grape are working with Tidelift to deliver commercial support
|
|
|
177
177
|
|
|
178
178
|
## Installation
|
|
179
179
|
|
|
180
|
-
Ruby 3.
|
|
180
|
+
Ruby 3.1 or newer is required.
|
|
181
181
|
|
|
182
182
|
Grape is available as a gem, to install it run:
|
|
183
183
|
|
data/UPGRADING.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
Upgrading Grape
|
|
2
2
|
===============
|
|
3
3
|
|
|
4
|
+
### Upgrading to >= 3.1
|
|
5
|
+
|
|
6
|
+
#### Explicit kwargs for `namespace` and `route_param`
|
|
7
|
+
|
|
8
|
+
The `API#namespace` and `route_param` methods are now defined with `**options` instead of `options = {}`. In addtion, `requirements` in explicitly defined so it's not in `options` anymore. You can still call `requirements` like before but `options[:requirements]` will be empty. For `route_param`, `type` is also an explicit parameter so it's not in `options` anymore. See [#2647](https://github.com/ruby-grape/grape/pull/2647) for more information.
|
|
9
|
+
|
|
10
|
+
#### ParamsBuilder Grape::Extensions
|
|
11
|
+
|
|
12
|
+
Deprecated [ParamsBuilder's extensions](https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#params-builder) have been removed.
|
|
13
|
+
|
|
14
|
+
#### Enhanced API compile!
|
|
15
|
+
|
|
16
|
+
Endpoints are now "compiled" instead of lazy loaded. Historically, when calling `YourAPI.compile!` in `config.ru` (or just receiving the first API call), only routing was compiled see [Grape::Router#compile!](https://github.com/ruby-grape/grape/blob/bf90e95c3b17c415c944363b1c07eb9727089ee7/lib/grape/router.rb#L41-L54) and endpoints were lazy loaded. Now, it's part of the API compilation. See [#2645](https://github.com/ruby-grape/grape/pull/2645) for more information.
|
|
17
|
+
|
|
4
18
|
### Upgrading to >= 3.0.0
|
|
5
19
|
|
|
6
20
|
#### Ruby 3+ Argument Delegation Modernization
|
data/grape.gemspec
CHANGED
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
|
|
|
20
20
|
'rubygems_mfa_required' => 'true'
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
s.add_dependency 'activesupport', '>= 7.
|
|
23
|
+
s.add_dependency 'activesupport', '>= 7.1'
|
|
24
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'
|
|
@@ -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 = '>= 3.
|
|
32
|
+
s.required_ruby_version = '>= 3.1'
|
|
33
33
|
end
|
data/lib/grape/api/instance.rb
CHANGED
|
@@ -21,20 +21,9 @@ module Grape
|
|
|
21
21
|
class << self
|
|
22
22
|
extend Forwardable
|
|
23
23
|
|
|
24
|
-
attr_reader :instance, :base
|
|
25
24
|
attr_accessor :configuration
|
|
26
25
|
|
|
27
|
-
def_delegators
|
|
28
|
-
|
|
29
|
-
def given(conditional_option, &block)
|
|
30
|
-
return unless conditional_option
|
|
31
|
-
|
|
32
|
-
mounted(&block)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def mounted(&block)
|
|
36
|
-
evaluate_as_instance_with_configuration(block, lazy: true)
|
|
37
|
-
end
|
|
26
|
+
def_delegators :@base, :to_s
|
|
38
27
|
|
|
39
28
|
def base=(grape_api)
|
|
40
29
|
@base = grape_api
|
|
@@ -42,7 +31,7 @@ module Grape
|
|
|
42
31
|
end
|
|
43
32
|
|
|
44
33
|
def base_instance?
|
|
45
|
-
self == base.base_instance
|
|
34
|
+
self == @base.base_instance
|
|
46
35
|
end
|
|
47
36
|
|
|
48
37
|
# A class-level lock to ensure the API is not compiled by multiple
|
|
@@ -56,43 +45,30 @@ module Grape
|
|
|
56
45
|
reset_validations!
|
|
57
46
|
end
|
|
58
47
|
|
|
59
|
-
# Parses the API's definition and compiles it into an instance of
|
|
60
|
-
# Grape::API.
|
|
61
|
-
def compile
|
|
62
|
-
@instance ||= new # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
63
|
-
end
|
|
64
|
-
|
|
65
48
|
# This is the interface point between Rack and Grape; it accepts a request
|
|
66
49
|
# from Rack and ultimately returns an array of three values: the status,
|
|
67
50
|
# the headers, and the body. See [the rack specification]
|
|
68
51
|
# (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
|
|
69
52
|
def call(env)
|
|
70
53
|
compile!
|
|
71
|
-
call
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# A non-synchronized version of ::call.
|
|
75
|
-
def call!(env)
|
|
76
|
-
instance.call(env)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# (see #cascade?)
|
|
80
|
-
def cascade(value = nil)
|
|
81
|
-
return inheritable_setting.namespace_inheritable.key?(:cascade) ? !inheritable_setting.namespace_inheritable(:cascade).nil? : true if value.nil?
|
|
82
|
-
|
|
83
|
-
inheritable_setting.namespace_inheritable[:cascade] = value
|
|
54
|
+
@instance.call(env)
|
|
84
55
|
end
|
|
85
56
|
|
|
86
57
|
def compile!
|
|
87
|
-
return if instance
|
|
58
|
+
return if @instance
|
|
88
59
|
|
|
89
|
-
LOCK.synchronize {
|
|
60
|
+
LOCK.synchronize { @instance ||= new }
|
|
90
61
|
end
|
|
91
62
|
|
|
92
63
|
# see Grape::Router#recognize_path
|
|
93
64
|
def recognize_path(path)
|
|
94
65
|
compile!
|
|
95
|
-
instance.router.recognize_path(path)
|
|
66
|
+
@instance.router.recognize_path(path)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Wipe the compiled API so we can recompile after changes were made.
|
|
70
|
+
def change!
|
|
71
|
+
@instance = nil
|
|
96
72
|
end
|
|
97
73
|
|
|
98
74
|
protected
|
|
@@ -110,11 +86,6 @@ module Grape
|
|
|
110
86
|
reset_routes!
|
|
111
87
|
end
|
|
112
88
|
|
|
113
|
-
# Wipe the compiled API so we can recompile after changes were made.
|
|
114
|
-
def change!
|
|
115
|
-
@instance = nil
|
|
116
|
-
end
|
|
117
|
-
|
|
118
89
|
private
|
|
119
90
|
|
|
120
91
|
def inherited(subclass)
|
|
@@ -177,7 +148,7 @@ module Grape
|
|
|
177
148
|
def add_head_not_allowed_methods_and_options_methods
|
|
178
149
|
# The paths we collected are prepared (cf. Path#prepare), so they
|
|
179
150
|
# contain already versioning information when using path versioning.
|
|
180
|
-
all_routes = self.class.endpoints.
|
|
151
|
+
all_routes = self.class.endpoints.flat_map(&:routes)
|
|
181
152
|
|
|
182
153
|
# Disable versioning so adding a route won't prepend versioning
|
|
183
154
|
# informations again.
|
|
@@ -186,23 +157,21 @@ module Grape
|
|
|
186
157
|
|
|
187
158
|
def collect_route_config_per_pattern(all_routes)
|
|
188
159
|
routes_by_regexp = all_routes.group_by(&:pattern_regexp)
|
|
160
|
+
namespace_inheritable = self.class.inheritable_setting.namespace_inheritable
|
|
189
161
|
|
|
190
162
|
# Build the configuration based on the first endpoint and the collection of methods supported.
|
|
191
163
|
routes_by_regexp.each_value do |routes|
|
|
192
|
-
last_route = routes.last # Most of the configuration is taken from the last endpoint
|
|
193
164
|
next if routes.any? { |route| route.request_method == '*' }
|
|
194
165
|
|
|
195
|
-
|
|
166
|
+
last_route = routes.last # Most of the configuration is taken from the last endpoint
|
|
196
167
|
allowed_methods = routes.map(&:request_method)
|
|
197
168
|
allowed_methods |= [Rack::HEAD] if !namespace_inheritable[:do_not_route_head] && allowed_methods.include?(Rack::GET)
|
|
198
169
|
|
|
199
170
|
allow_header = namespace_inheritable[:do_not_route_options] ? allowed_methods : [Rack::OPTIONS] | allowed_methods
|
|
200
171
|
last_route.app.options[:options_route_enabled] = true unless namespace_inheritable[:do_not_route_options] || allowed_methods.include?(Rack::OPTIONS)
|
|
201
172
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
allow_header: allow_header
|
|
205
|
-
})
|
|
173
|
+
greedy_route = Grape::Router::GreedyRoute.new(last_route.pattern, endpoint: last_route.app, allow_header: allow_header)
|
|
174
|
+
@router.associate_routes(greedy_route)
|
|
206
175
|
end
|
|
207
176
|
end
|
|
208
177
|
|
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
|
|
8
|
+
NON_OVERRIDABLE = %i[base= base_instance? call change! configuration compile! inherit_settings recognize_path reset! routes top_level_setting= top_level_setting].freeze
|
|
9
9
|
|
|
10
10
|
Helpers = Grape::DSL::Helpers::BaseHelper
|
|
11
11
|
|
|
@@ -29,7 +29,7 @@ module Grape
|
|
|
29
29
|
# the headers, and the body. See [the rack specification]
|
|
30
30
|
# (https://github.com/rack/rack/blob/main/SPEC.rdoc) for more.
|
|
31
31
|
# NOTE: This will only be called on an API directly mounted on RACK
|
|
32
|
-
def_delegators :base_instance, :new, :configuration, :call, :compile
|
|
32
|
+
def_delegators :base_instance, :new, :configuration, :call, :change!, :compile!, :recognize_path, :routes
|
|
33
33
|
|
|
34
34
|
# Initialize the instance variables on the remountable class, and the base_instance
|
|
35
35
|
# an instance that will be used to create the set up but will not be mounted
|
|
@@ -140,12 +140,12 @@ module Grape
|
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
def any_lazy?(args)
|
|
143
|
-
args.any? { |argument| argument
|
|
143
|
+
args.any? { |argument| argument_lazy?(argument) }
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
def evaluate_arguments(configuration, *args)
|
|
147
147
|
args.map do |argument|
|
|
148
|
-
if argument
|
|
148
|
+
if argument_lazy?(argument)
|
|
149
149
|
argument.evaluate_from(configuration)
|
|
150
150
|
elsif argument.is_a?(Hash)
|
|
151
151
|
argument.transform_values { |value| evaluate_arguments(configuration, value).first }
|
|
@@ -157,12 +157,8 @@ module Grape
|
|
|
157
157
|
end
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
-
def
|
|
161
|
-
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def mounted_instances
|
|
165
|
-
instances - [base_instance]
|
|
160
|
+
def argument_lazy?(argument)
|
|
161
|
+
argument.respond_to?(:lazy?) && argument.lazy?
|
|
166
162
|
end
|
|
167
163
|
end
|
|
168
164
|
end
|
data/lib/grape/content_types.rb
CHANGED
|
@@ -22,10 +22,7 @@ module Grape
|
|
|
22
22
|
def mime_types_for(from_settings)
|
|
23
23
|
return MIME_TYPES if from_settings == Grape::ContentTypes::DEFAULTS
|
|
24
24
|
|
|
25
|
-
from_settings.
|
|
26
|
-
# remove optional parameter
|
|
27
|
-
types_without_params[v.split(';', 2).first] = k
|
|
28
|
-
end
|
|
25
|
+
from_settings.invert.transform_keys! { |k| k.include?(';') ? k.split(';', 2).first : k }
|
|
29
26
|
end
|
|
30
27
|
end
|
|
31
28
|
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
class DeclaredParamsHandler
|
|
5
|
+
def initialize(include_missing: true, evaluate_given: false, stringify: false, contract_key_map: nil)
|
|
6
|
+
@include_missing = include_missing
|
|
7
|
+
@evaluate_given = evaluate_given
|
|
8
|
+
@stringify = stringify
|
|
9
|
+
@contract_key_map = contract_key_map
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(passed_params, declared_params, route_params, renamed_params)
|
|
13
|
+
recursive_declared(
|
|
14
|
+
passed_params,
|
|
15
|
+
declared_params: declared_params,
|
|
16
|
+
route_params: route_params,
|
|
17
|
+
renamed_params: renamed_params
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def recursive_declared(passed_params, declared_params:, route_params:, renamed_params:, params_nested_path: [])
|
|
24
|
+
res = if passed_params.is_a?(Array)
|
|
25
|
+
passed_params.map do |passed_param|
|
|
26
|
+
recursive_declared(passed_param, declared_params:, params_nested_path:, renamed_params:, route_params:)
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
declared_hash(passed_params, declared_params:, params_nested_path:, renamed_params:, route_params:)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@contract_key_map&.each { |key_map| key_map.write(passed_params, res) }
|
|
33
|
+
|
|
34
|
+
res
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def declared_hash(passed_params, declared_params:, params_nested_path:, renamed_params:, route_params:)
|
|
38
|
+
declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|
|
|
39
|
+
next if @evaluate_given && !declared_param_attr.scope.attr_meets_dependency?(passed_params)
|
|
40
|
+
|
|
41
|
+
declared_hash_attr(
|
|
42
|
+
passed_params,
|
|
43
|
+
declared_param: declared_param_attr.key,
|
|
44
|
+
params_nested_path:,
|
|
45
|
+
memo:,
|
|
46
|
+
renamed_params:,
|
|
47
|
+
route_params:
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def declared_hash_attr(passed_params, declared_param:, params_nested_path:, memo:, renamed_params:, route_params:)
|
|
53
|
+
if declared_param.is_a?(Hash)
|
|
54
|
+
declared_param.each_pair do |declared_parent_param, declared_children_params|
|
|
55
|
+
next unless @include_missing || passed_params.key?(declared_parent_param)
|
|
56
|
+
|
|
57
|
+
memo_key = build_memo_key(params_nested_path, declared_parent_param, renamed_params)
|
|
58
|
+
passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
|
|
59
|
+
|
|
60
|
+
params_nested_path_dup = params_nested_path.dup
|
|
61
|
+
params_nested_path_dup << declared_parent_param.to_s
|
|
62
|
+
|
|
63
|
+
memo[memo_key] = handle_passed_param(params_nested_path_dup, route_params:, has_passed_children: passed_children_params.any?) do
|
|
64
|
+
recursive_declared(
|
|
65
|
+
passed_children_params,
|
|
66
|
+
declared_params: declared_children_params,
|
|
67
|
+
params_nested_path: params_nested_path_dup,
|
|
68
|
+
renamed_params:,
|
|
69
|
+
route_params:
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
else
|
|
74
|
+
# If it is not a Hash then it does not have children.
|
|
75
|
+
# Find its value or set it to nil.
|
|
76
|
+
return unless @include_missing || (passed_params.respond_to?(:key?) && passed_params.key?(declared_param))
|
|
77
|
+
|
|
78
|
+
memo_key = build_memo_key(params_nested_path, declared_param, renamed_params)
|
|
79
|
+
passed_param = passed_params[declared_param]
|
|
80
|
+
|
|
81
|
+
params_nested_path_dup = params_nested_path.dup
|
|
82
|
+
params_nested_path_dup << declared_param.to_s
|
|
83
|
+
|
|
84
|
+
memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup, route_params:) do
|
|
85
|
+
passed_param
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def build_memo_key(params_nested_path, declared_param, renamed_params)
|
|
91
|
+
rename_path = params_nested_path + [declared_param.to_s]
|
|
92
|
+
renamed_param_name = renamed_params[rename_path]
|
|
93
|
+
|
|
94
|
+
param = renamed_param_name || declared_param
|
|
95
|
+
@stringify ? param.to_s : param.to_sym
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def handle_passed_param(params_nested_path, route_params:, has_passed_children: false, &_block)
|
|
99
|
+
return yield if has_passed_children
|
|
100
|
+
|
|
101
|
+
key = params_nested_path[0]
|
|
102
|
+
key += "[#{params_nested_path[1..].join('][')}]" if params_nested_path.size > 1
|
|
103
|
+
|
|
104
|
+
type = route_params.dig(key, :type)
|
|
105
|
+
has_children = route_params.keys.any? { |k| k != key && k.start_with?("#{key}[") }
|
|
106
|
+
|
|
107
|
+
if type == 'Hash' && !has_children
|
|
108
|
+
{}
|
|
109
|
+
elsif type == 'Array' || (type&.start_with?('[') && !type.include?(','))
|
|
110
|
+
[]
|
|
111
|
+
elsif type == 'Set' || type&.start_with?('#<Set', 'Set')
|
|
112
|
+
Set.new
|
|
113
|
+
else
|
|
114
|
+
yield
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module DSL
|
|
5
|
+
module Declared
|
|
6
|
+
# Denotes a situation where a DSL method has been invoked in a
|
|
7
|
+
# filter which it should not yet be available in
|
|
8
|
+
class MethodNotYetAvailable < StandardError
|
|
9
|
+
def initialize(msg = '#declared is not available prior to parameter validation')
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# A filtering method that will return a hash
|
|
15
|
+
# consisting only of keys that have been declared by a
|
|
16
|
+
# `params` statement against the current/target endpoint or parent
|
|
17
|
+
# namespaces.
|
|
18
|
+
# @param params [Hash] The initial hash to filter. Usually this will just be `params`
|
|
19
|
+
# @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
|
|
20
|
+
# options. `:include_parent_namespaces` defaults to true, hence must be set to false if
|
|
21
|
+
# you want only to return params declared against the current/target endpoint.
|
|
22
|
+
def declared(passed_params, include_parent_namespaces: true, include_missing: true, evaluate_given: false, stringify: false)
|
|
23
|
+
raise MethodNotYetAvailable unless before_filter_passed
|
|
24
|
+
|
|
25
|
+
contract_key_map = inheritable_setting.namespace_stackable[:contract_key_map]
|
|
26
|
+
handler = DeclaredParamsHandler.new(include_missing:, evaluate_given:, stringify:, contract_key_map: contract_key_map)
|
|
27
|
+
declared_params = include_parent_namespaces ? inheritable_setting.route[:declared_params] : (inheritable_setting.namespace_stackable[:declared_params].last || [])
|
|
28
|
+
renamed_params = inheritable_setting.route[:renamed_params] || {}
|
|
29
|
+
route_params = options.dig(:route_options, :params) || {} # options = endpoint's option
|
|
30
|
+
|
|
31
|
+
handler.call(passed_params, declared_params, route_params, renamed_params)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/grape/dsl/helpers.rb
CHANGED
|
@@ -50,9 +50,9 @@ module Grape
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
def make_inclusion(mod, &
|
|
53
|
+
def make_inclusion(mod, &)
|
|
54
54
|
define_boolean_in_mod(mod)
|
|
55
|
-
inject_api_helpers_to_mod(mod, &
|
|
55
|
+
inject_api_helpers_to_mod(mod, &)
|
|
56
56
|
inheritable_setting.namespace_stackable[:helpers] = mod
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -3,148 +3,10 @@
|
|
|
3
3
|
module Grape
|
|
4
4
|
module DSL
|
|
5
5
|
module InsideRoute
|
|
6
|
-
|
|
7
|
-
# filter which it should not yet be available in
|
|
8
|
-
class MethodNotYetAvailable < StandardError; end
|
|
6
|
+
include Declared
|
|
9
7
|
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
# @return [Module] A module containing method overrides suitable for the
|
|
13
|
-
# position in the filter evaluation sequence denoted by +type+. This
|
|
14
|
-
# defaults to an empty module if no overrides are defined for the given
|
|
15
|
-
# filter +type+.
|
|
16
|
-
def self.post_filter_methods(type)
|
|
17
|
-
@post_filter_modules ||= { before: PostBeforeFilter }
|
|
18
|
-
@post_filter_modules[type]
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Methods which should not be available in filters until the before filter
|
|
22
|
-
# has completed
|
|
23
|
-
module PostBeforeFilter
|
|
24
|
-
def declared(passed_params, options = {}, declared_params = nil, params_nested_path = [])
|
|
25
|
-
options.reverse_merge!(include_missing: true, include_parent_namespaces: true, evaluate_given: false)
|
|
26
|
-
declared_params ||= optioned_declared_params(options[:include_parent_namespaces])
|
|
27
|
-
|
|
28
|
-
res = if passed_params.is_a?(Array)
|
|
29
|
-
declared_array(passed_params, options, declared_params, params_nested_path)
|
|
30
|
-
else
|
|
31
|
-
declared_hash(passed_params, options, declared_params, params_nested_path)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
if (key_maps = inheritable_setting.namespace_stackable[:contract_key_map])
|
|
35
|
-
key_maps.each { |key_map| key_map.write(passed_params, res) }
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
res
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
def declared_array(passed_params, options, declared_params, params_nested_path)
|
|
44
|
-
passed_params.map do |passed_param|
|
|
45
|
-
declared(passed_param || {}, options, declared_params, params_nested_path)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def declared_hash(passed_params, options, declared_params, params_nested_path)
|
|
50
|
-
declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|
|
|
51
|
-
next if options[:evaluate_given] && !declared_param_attr.scope.attr_meets_dependency?(passed_params)
|
|
52
|
-
|
|
53
|
-
declared_hash_attr(passed_params, options, declared_param_attr.key, params_nested_path, memo)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def declared_hash_attr(passed_params, options, declared_param, params_nested_path, memo)
|
|
58
|
-
renamed_params = inheritable_setting.route[:renamed_params] || {}
|
|
59
|
-
if declared_param.is_a?(Hash)
|
|
60
|
-
declared_param.each_pair do |declared_parent_param, declared_children_params|
|
|
61
|
-
params_nested_path_dup = params_nested_path.dup
|
|
62
|
-
params_nested_path_dup << declared_parent_param.to_s
|
|
63
|
-
next unless options[:include_missing] || passed_params.key?(declared_parent_param)
|
|
64
|
-
|
|
65
|
-
rename_path = params_nested_path + [declared_parent_param.to_s]
|
|
66
|
-
renamed_param_name = renamed_params[rename_path]
|
|
67
|
-
|
|
68
|
-
memo_key = optioned_param_key(renamed_param_name || declared_parent_param, options)
|
|
69
|
-
passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
|
|
70
|
-
|
|
71
|
-
memo[memo_key] = handle_passed_param(params_nested_path_dup, passed_children_params.any?) do
|
|
72
|
-
declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
else
|
|
76
|
-
# If it is not a Hash then it does not have children.
|
|
77
|
-
# Find its value or set it to nil.
|
|
78
|
-
return unless options[:include_missing] || passed_params.try(:key?, declared_param)
|
|
79
|
-
|
|
80
|
-
rename_path = params_nested_path + [declared_param.to_s]
|
|
81
|
-
renamed_param_name = renamed_params[rename_path]
|
|
82
|
-
|
|
83
|
-
memo_key = optioned_param_key(renamed_param_name || declared_param, options)
|
|
84
|
-
passed_param = passed_params[declared_param]
|
|
85
|
-
|
|
86
|
-
params_nested_path_dup = params_nested_path.dup
|
|
87
|
-
params_nested_path_dup << declared_param.to_s
|
|
88
|
-
memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup) do
|
|
89
|
-
passed_param
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def handle_passed_param(params_nested_path, has_passed_children = false, &_block)
|
|
95
|
-
return yield if has_passed_children
|
|
96
|
-
|
|
97
|
-
key = params_nested_path[0]
|
|
98
|
-
key += "[#{params_nested_path[1..].join('][')}]" if params_nested_path.size > 1
|
|
99
|
-
|
|
100
|
-
route_options_params = options[:route_options][:params] || {}
|
|
101
|
-
type = route_options_params.dig(key, :type)
|
|
102
|
-
has_children = route_options_params.keys.any? { |k| k != key && k.start_with?("#{key}[") }
|
|
103
|
-
|
|
104
|
-
if type == 'Hash' && !has_children
|
|
105
|
-
{}
|
|
106
|
-
elsif type == 'Array' || (type&.start_with?('[') && type.exclude?(','))
|
|
107
|
-
[]
|
|
108
|
-
elsif type == 'Set' || type&.start_with?('#<Set')
|
|
109
|
-
Set.new
|
|
110
|
-
else
|
|
111
|
-
yield
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def optioned_param_key(declared_param, options)
|
|
116
|
-
options[:stringify] ? declared_param.to_s : declared_param.to_sym
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def optioned_declared_params(include_parent_namespaces)
|
|
120
|
-
declared_params = if include_parent_namespaces
|
|
121
|
-
# Declared params including parent namespaces
|
|
122
|
-
inheritable_setting.route[:declared_params]
|
|
123
|
-
else
|
|
124
|
-
# Declared params at current namespace
|
|
125
|
-
inheritable_setting.namespace_stackable[:declared_params].last || []
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
|
|
129
|
-
|
|
130
|
-
declared_params
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# A filtering method that will return a hash
|
|
135
|
-
# consisting only of keys that have been declared by a
|
|
136
|
-
# `params` statement against the current/target endpoint or parent
|
|
137
|
-
# namespaces.
|
|
138
|
-
#
|
|
139
|
-
# @see +PostBeforeFilter#declared+
|
|
140
|
-
#
|
|
141
|
-
# @param params [Hash] The initial hash to filter. Usually this will just be `params`
|
|
142
|
-
# @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
|
|
143
|
-
# options. `:include_parent_namespaces` defaults to true, hence must be set to false if
|
|
144
|
-
# you want only to return params declared against the current/target endpoint.
|
|
145
|
-
def declared(*)
|
|
146
|
-
raise MethodNotYetAvailable, '#declared is not available prior to parameter validation.'
|
|
147
|
-
end
|
|
8
|
+
# Backward compatibility: alias exception class to previous location
|
|
9
|
+
MethodNotYetAvailable = Declared::MethodNotYetAvailable
|
|
148
10
|
|
|
149
11
|
# The API version as specified in the URL.
|
|
150
12
|
def version
|