grape 2.4.0 → 3.2.1
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 +109 -0
- data/CONTRIBUTING.md +2 -10
- data/README.md +137 -181
- data/UPGRADING.md +154 -0
- data/grape.gemspec +4 -4
- data/lib/grape/api/instance.rb +51 -104
- data/lib/grape/api.rb +29 -42
- data/lib/grape/content_types.rb +1 -4
- data/lib/grape/declared_params_handler.rb +118 -0
- data/lib/grape/dry_types.rb +48 -4
- data/lib/grape/dsl/callbacks.rb +8 -58
- data/lib/grape/dsl/declared.rb +35 -0
- data/lib/grape/dsl/desc.rb +8 -67
- data/lib/grape/dsl/helpers.rb +59 -64
- data/lib/grape/dsl/inside_route.rb +28 -189
- data/lib/grape/dsl/logger.rb +3 -6
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +24 -51
- data/lib/grape/dsl/request_response.rb +136 -139
- data/lib/grape/dsl/routing.rb +240 -200
- data/lib/grape/dsl/settings.rb +23 -135
- data/lib/grape/dsl/validations.rb +38 -44
- data/lib/grape/endpoint.rb +169 -205
- data/lib/grape/error_formatter/base.rb +4 -2
- data/lib/grape/exceptions/base.rb +19 -45
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_message_body.rb +1 -1
- data/lib/grape/exceptions/invalid_version_header.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/method_not_allowed.rb +1 -1
- data/lib/grape/exceptions/missing_group_type.rb +0 -2
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/request_error.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +1 -1
- data/lib/grape/exceptions/unknown_parameter.rb +1 -1
- data/lib/grape/exceptions/unknown_params_builder.rb +1 -1
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
- data/lib/grape/exceptions/validation.rb +7 -4
- data/lib/grape/exceptions/validation_errors.rb +13 -7
- data/lib/grape/locale/en.yml +0 -5
- data/lib/grape/middleware/auth/base.rb +2 -0
- data/lib/grape/middleware/auth/dsl.rb +9 -10
- data/lib/grape/middleware/base.rb +6 -4
- data/lib/grape/middleware/error.rb +4 -14
- data/lib/grape/middleware/formatter.rb +11 -7
- data/lib/grape/middleware/stack.rb +3 -2
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
- data/lib/grape/middleware/versioner/base.rb +42 -40
- data/lib/grape/middleware/versioner/header.rb +2 -18
- data/lib/grape/middleware/versioner/param.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +2 -2
- data/lib/grape/namespace.rb +15 -8
- data/lib/grape/params_builder/base.rb +2 -0
- data/lib/grape/params_builder.rb +2 -19
- data/lib/grape/request.rb +2 -10
- 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 +42 -65
- data/lib/grape/util/api_description.rb +58 -0
- data/lib/grape/util/base_inheritable.rb +5 -2
- data/lib/grape/util/cache.rb +1 -0
- data/lib/grape/util/deep_freeze.rb +35 -0
- data/lib/grape/util/inheritable_setting.rb +8 -1
- data/lib/grape/util/media_type.rb +2 -2
- data/lib/grape/util/registry.rb +1 -1
- data/lib/grape/util/translation.rb +42 -0
- data/lib/grape/validations/attributes_iterator.rb +35 -20
- data/lib/grape/validations/contract_scope.rb +2 -8
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/param_scope_tracker.rb +57 -0
- data/lib/grape/validations/params_documentation.rb +50 -0
- data/lib/grape/validations/params_scope.rb +147 -156
- data/lib/grape/validations/single_attribute_iterator.rb +2 -2
- data/lib/grape/validations/types/array_coercer.rb +2 -3
- data/lib/grape/validations/types/dry_type_coercer.rb +4 -11
- data/lib/grape/validations/types/primitive_coercer.rb +1 -28
- data/lib/grape/validations/types.rb +10 -25
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
- data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
- data/lib/grape/validations/validators/base.rb +95 -25
- data/lib/grape/validations/validators/coerce_validator.rb +15 -35
- data/lib/grape/validations/validators/contract_scope_validator.rb +9 -5
- data/lib/grape/validations/validators/default_validator.rb +12 -18
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
- data/lib/grape/validations/validators/except_values_validator.rb +13 -4
- data/lib/grape/validations/validators/length_validator.rb +21 -22
- data/lib/grape/validations/validators/multiple_params_base.rb +5 -5
- data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
- data/lib/grape/validations/validators/presence_validator.rb +4 -2
- data/lib/grape/validations/validators/regexp_validator.rb +11 -3
- data/lib/grape/validations/validators/same_as_validator.rb +6 -15
- data/lib/grape/validations/validators/values_validator.rb +29 -21
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +29 -23
- metadata +31 -26
- data/lib/grape/api/helpers.rb +0 -9
- data/lib/grape/dsl/api.rb +0 -17
- data/lib/grape/dsl/configuration.rb +0 -15
- data/lib/grape/exceptions/conflicting_types.rb +0 -11
- data/lib/grape/exceptions/empty_message_body.rb +0 -11
- data/lib/grape/exceptions/invalid_parameters.rb +0 -11
- data/lib/grape/exceptions/missing_option.rb +0 -11
- data/lib/grape/exceptions/too_deep_parameters.rb +0 -11
- data/lib/grape/exceptions/too_many_multipart_files.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
- data/lib/grape/types/invalid_value.rb +0 -8
- data/lib/grape/util/strict_hash_configuration.rb +0 -108
- data/lib/grape/validations/attributes_doc.rb +0 -60
- data/lib/grape/validations/validator_factory.rb +0 -15
data/UPGRADING.md
CHANGED
|
@@ -1,6 +1,160 @@
|
|
|
1
1
|
Upgrading Grape
|
|
2
2
|
===============
|
|
3
3
|
|
|
4
|
+
### Upgrading to >= 3.2
|
|
5
|
+
|
|
6
|
+
#### Rack parameter parsing errors now raise `Grape::Exceptions::RequestError`
|
|
7
|
+
|
|
8
|
+
Rack errors raised during parameter parsing (malformed multipart, parameter type conflicts, encoding issues, etc.) are now wrapped in `Grape::Exceptions::RequestError` instead of their previous specific exception classes (`Grape::Exceptions::EmptyMessageBody`, `Grape::Exceptions::TooManyMultipartFiles`, `Grape::Exceptions::TooDeepParameters`, `Grape::Exceptions::ConflictingTypes`, `Grape::Exceptions::InvalidParameters`). Those classes have been removed.
|
|
9
|
+
|
|
10
|
+
If you rescue any of these specific exceptions, update your rescue clauses to use `Grape::Exceptions::RequestError`:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
# Before
|
|
14
|
+
rescue Grape::Exceptions::ConflictingTypes, Grape::Exceptions::TooDeepParameters => e
|
|
15
|
+
# ...
|
|
16
|
+
|
|
17
|
+
# After
|
|
18
|
+
rescue Grape::Exceptions::RequestError => e
|
|
19
|
+
# ...
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The error message is now forwarded directly from Rack rather than translated through Grape's locale system. On Rack 3, all Rack bad-request errors share the `Rack::BadRequest` marker module and are covered by a single rescue.
|
|
23
|
+
|
|
24
|
+
#### `endpoint_run_validators.grape` notification no longer fired when there are no validators
|
|
25
|
+
|
|
26
|
+
`ActiveSupport::Notifications` subscribers listening to `endpoint_run_validators.grape` will no longer receive an event for endpoints that have no validators. If you rely on this notification to measure every request, subscribe to `endpoint_run.grape` instead, which always fires.
|
|
27
|
+
|
|
28
|
+
#### Custom validators: use `default_message_key` and `validation_error!`
|
|
29
|
+
|
|
30
|
+
Validators are now instantiated once at definition time and frozen. Any setup should happen in `initialize`, not in `validate_param!`.
|
|
31
|
+
|
|
32
|
+
If your custom validator did work in `validate_param!` that only depends on the validator's options (not the param value), move it to `initialize`. A common case is compiling a value derived from options — for example, building a `Regexp`. Previously this may have been cached back into `@options`, which now raises `FrozenError` since `@options` and its nested values are deep-frozen by the base class:
|
|
33
|
+
|
|
34
|
+
**Before:**
|
|
35
|
+
```ruby
|
|
36
|
+
class MyValidator < Grape::Validations::Validators::Base
|
|
37
|
+
def validate_param!(attr_name, params)
|
|
38
|
+
# raises FrozenError: @options is frozen, cannot store compiled pattern back into it
|
|
39
|
+
@options[:compiled] ||= Regexp.new(@options[:pattern])
|
|
40
|
+
validation_error!(attr_name) unless params[attr_name].match?(@options[:compiled])
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**After:**
|
|
46
|
+
```ruby
|
|
47
|
+
class MyValidator < Grape::Validations::Validators::Base
|
|
48
|
+
def initialize(attrs, options, required, scope, opts)
|
|
49
|
+
super
|
|
50
|
+
@pattern = Regexp.new(@options[:pattern]).freeze
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def validate_param!(attr_name, params)
|
|
54
|
+
validation_error!(attr_name) unless params[attr_name].match?(@pattern)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Any Array or Hash derived from options and stored in an ivar should be frozen, since the validator instance is shared across requests. `@options` itself (and any nested Hash/Array/String values within it) is deep-frozen by the base class, so mutations like `@options[:values] << 'extra'` will also raise a `FrozenError`.
|
|
60
|
+
|
|
61
|
+
#### Custom validators: rename `@option` to `@options`
|
|
62
|
+
|
|
63
|
+
The instance variable holding the validator's option value has been renamed from `@option` to `@options`. `@option` remains as an alias for backwards compatibility but will be removed in the next major release. Update any custom validators to use `@options` instead.
|
|
64
|
+
|
|
65
|
+
Several new helpers are available — see [Available helpers](README.md#available-helpers) in the README for full documentation and examples.
|
|
66
|
+
|
|
67
|
+
#### `with` now uses keyword arguments
|
|
68
|
+
|
|
69
|
+
The `with` DSL method now uses `**opts` instead of a positional hash. Calls using bare keyword syntax are unaffected:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
# still works
|
|
73
|
+
with(type: String, documentation: { in: 'body' }) { ... }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
However, passing an explicit hash literal will now raise an `ArgumentError`:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
# raises ArgumentError
|
|
80
|
+
with({ type: String }) { ... }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
See [#2663](https://github.com/ruby-grape/grape/pull/2663) for more information.
|
|
84
|
+
|
|
85
|
+
#### Custom validators: use `translate` instead of `I18n` directly
|
|
86
|
+
|
|
87
|
+
`Grape::Util::Translation` is now included in `Grape::Validations::Validators::Base`. Custom validators that previously called `I18n.t` or `I18n.translate` directly should switch to the `translate`, which provides the same `:en` fallback logic used by all built-in validators.
|
|
88
|
+
|
|
89
|
+
Key points:
|
|
90
|
+
- `scope` defaults to `'grape.errors.messages'` — no need to specify it for standard error message keys.
|
|
91
|
+
- Interpolation variables are passed directly to I18n.
|
|
92
|
+
- `format` is no longer needed — `translate` returns the fully interpolated string.
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
# Before
|
|
96
|
+
raise Grape::Exceptions::Validation.new(
|
|
97
|
+
params: [@scope.full_name(attr_name)],
|
|
98
|
+
message: format(I18n.t(:my_key, scope: 'grape.errors.messages'), min: 2, max: 10)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# After
|
|
102
|
+
raise Grape::Exceptions::Validation.new(
|
|
103
|
+
params: [@scope.full_name(attr_name)],
|
|
104
|
+
message: translate(:my_key, min: 2, max: 10)
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
See [#2662](https://github.com/ruby-grape/grape/pull/2662) for more information.
|
|
109
|
+
|
|
110
|
+
### Upgrading to >= 3.1
|
|
111
|
+
|
|
112
|
+
#### Explicit kwargs for `namespace` and `route_param`
|
|
113
|
+
|
|
114
|
+
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.
|
|
115
|
+
|
|
116
|
+
#### ParamsBuilder Grape::Extensions
|
|
117
|
+
|
|
118
|
+
Deprecated [ParamsBuilder's extensions](https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#params-builder) have been removed.
|
|
119
|
+
|
|
120
|
+
#### Enhanced API compile!
|
|
121
|
+
|
|
122
|
+
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.
|
|
123
|
+
|
|
124
|
+
### Upgrading to >= 3.0.0
|
|
125
|
+
|
|
126
|
+
#### Ruby 3+ Argument Delegation Modernization
|
|
127
|
+
|
|
128
|
+
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.
|
|
129
|
+
|
|
130
|
+
- All DSL methods now use explicit keyword arguments (`**kwargs`) instead of extracting options from mixed argument lists
|
|
131
|
+
- Method signatures are now more explicit and follow Ruby 3+ best practices
|
|
132
|
+
- The `active_support/core_ext/array/extract_options` dependency has been removed
|
|
133
|
+
|
|
134
|
+
This is a modernization effort that improves code quality while maintaining full backward compatibility.
|
|
135
|
+
|
|
136
|
+
See [#2618](https://github.com/ruby-grape/grape/pull/2618) for more information.
|
|
137
|
+
|
|
138
|
+
#### Configuration API Migration from ActiveSupport::Configurable to Dry::Configurable
|
|
139
|
+
|
|
140
|
+
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).
|
|
141
|
+
|
|
142
|
+
See [#2617](https://github.com/ruby-grape/grape/pull/2617) for more information.
|
|
143
|
+
|
|
144
|
+
#### Endpoint execution simplified and `return` deprecated
|
|
145
|
+
|
|
146
|
+
Executing a endpoint's block has been simplified and calling `return` in it has been deprecated. Use `next` instead.
|
|
147
|
+
|
|
148
|
+
See [#2577](https://github.com/ruby-grape/grape/pull/2577) for more information.
|
|
149
|
+
|
|
150
|
+
#### Old Deprecations Clean Up
|
|
151
|
+
|
|
152
|
+
- `rack_response` has been removed in favor of using `error!`.
|
|
153
|
+
- `Grape::Exceptions::MissingGroupType` and `Grape::Exceptions::UnsupportedGroupType` aliases `MissingGroupTypeError and `UnsupportedGroupType` have been removed.
|
|
154
|
+
- `Grape::Validations::Base` has been removed in favor of `Grape::Validations::Validators::Base`.
|
|
155
|
+
|
|
156
|
+
See [2573](https://github.com/ruby-grape/grape/pull/2573) for more information.
|
|
157
|
+
|
|
4
158
|
### Upgrading to >= 2.4.0
|
|
5
159
|
|
|
6
160
|
#### Grape::Middleware::Auth::Base
|
data/grape.gemspec
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
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', '>=
|
|
23
|
+
s.add_dependency 'activesupport', '>= 7.2'
|
|
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
|
|
32
|
+
s.required_ruby_version = '>= 3.2'
|
|
33
33
|
end
|
data/lib/grape/api/instance.rb
CHANGED
|
@@ -5,32 +5,33 @@ 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
|
-
|
|
18
|
+
|
|
19
|
+
Boolean = Grape::API::Boolean
|
|
10
20
|
|
|
11
21
|
class << self
|
|
12
|
-
|
|
22
|
+
extend Forwardable
|
|
23
|
+
|
|
13
24
|
attr_accessor :configuration
|
|
14
25
|
|
|
15
|
-
|
|
16
|
-
evaluate_as_instance_with_configuration(block, lazy: true) if conditional_option && block
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def mounted(&block)
|
|
20
|
-
evaluate_as_instance_with_configuration(block, lazy: true)
|
|
21
|
-
end
|
|
26
|
+
def_delegators :@base, :to_s
|
|
22
27
|
|
|
23
28
|
def base=(grape_api)
|
|
24
29
|
@base = grape_api
|
|
25
30
|
grape_api.instances << self
|
|
26
31
|
end
|
|
27
32
|
|
|
28
|
-
def to_s
|
|
29
|
-
base&.to_s || super
|
|
30
|
-
end
|
|
31
|
-
|
|
32
33
|
def base_instance?
|
|
33
|
-
self == base.base_instance
|
|
34
|
+
self == @base.base_instance
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
# A class-level lock to ensure the API is not compiled by multiple
|
|
@@ -44,92 +45,33 @@ module Grape
|
|
|
44
45
|
reset_validations!
|
|
45
46
|
end
|
|
46
47
|
|
|
47
|
-
# Parses the API's definition and compiles it into an instance of
|
|
48
|
-
# Grape::API.
|
|
49
|
-
def compile
|
|
50
|
-
@instance ||= new # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Wipe the compiled API so we can recompile after changes were made.
|
|
54
|
-
def change!
|
|
55
|
-
@instance = nil
|
|
56
|
-
end
|
|
57
|
-
|
|
58
48
|
# This is the interface point between Rack and Grape; it accepts a request
|
|
59
49
|
# from Rack and ultimately returns an array of three values: the status,
|
|
60
50
|
# the headers, and the body. See [the rack specification]
|
|
61
51
|
# (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
|
|
62
52
|
def call(env)
|
|
63
53
|
compile!
|
|
64
|
-
call
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# A non-synchronized version of ::call.
|
|
68
|
-
def call!(env)
|
|
69
|
-
instance.call(env)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# (see #cascade?)
|
|
73
|
-
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
|
|
54
|
+
@instance.call(env)
|
|
79
55
|
end
|
|
80
56
|
|
|
81
57
|
def compile!
|
|
82
|
-
return if instance
|
|
58
|
+
return if @instance
|
|
83
59
|
|
|
84
|
-
LOCK.synchronize {
|
|
60
|
+
LOCK.synchronize { @instance ||= new }
|
|
85
61
|
end
|
|
86
62
|
|
|
87
63
|
# see Grape::Router#recognize_path
|
|
88
64
|
def recognize_path(path)
|
|
89
65
|
compile!
|
|
90
|
-
instance.router.recognize_path(path)
|
|
66
|
+
@instance.router.recognize_path(path)
|
|
91
67
|
end
|
|
92
68
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
69
|
+
# Wipe the compiled API so we can recompile after changes were made.
|
|
70
|
+
def change!
|
|
71
|
+
@instance = nil
|
|
126
72
|
end
|
|
127
73
|
|
|
128
|
-
|
|
129
|
-
super
|
|
130
|
-
subclass.reset!
|
|
131
|
-
subclass.logger = logger.clone
|
|
132
|
-
end
|
|
74
|
+
protected
|
|
133
75
|
|
|
134
76
|
def inherit_settings(other_settings)
|
|
135
77
|
top_level_setting.inherit_from other_settings.point_in_time_copy
|
|
@@ -143,6 +85,14 @@ module Grape
|
|
|
143
85
|
|
|
144
86
|
reset_routes!
|
|
145
87
|
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def inherited(subclass)
|
|
92
|
+
super
|
|
93
|
+
subclass.reset!
|
|
94
|
+
subclass.logger logger.clone
|
|
95
|
+
end
|
|
146
96
|
end
|
|
147
97
|
|
|
148
98
|
attr_reader :router
|
|
@@ -180,8 +130,9 @@ module Grape
|
|
|
180
130
|
# errors from reaching upstream. This is effectivelly done by unsetting
|
|
181
131
|
# X-Cascade. Default :cascade is true.
|
|
182
132
|
def cascade?
|
|
183
|
-
|
|
184
|
-
return
|
|
133
|
+
namespace_inheritable = self.class.inheritable_setting.namespace_inheritable
|
|
134
|
+
return namespace_inheritable[:cascade] if namespace_inheritable.key?(:cascade)
|
|
135
|
+
return namespace_inheritable[:version_options][:cascade] if namespace_inheritable[:version_options]&.key?(:cascade)
|
|
185
136
|
|
|
186
137
|
true
|
|
187
138
|
end
|
|
@@ -197,7 +148,7 @@ module Grape
|
|
|
197
148
|
def add_head_not_allowed_methods_and_options_methods
|
|
198
149
|
# The paths we collected are prepared (cf. Path#prepare), so they
|
|
199
150
|
# contain already versioning information when using path versioning.
|
|
200
|
-
all_routes = self.class.endpoints.
|
|
151
|
+
all_routes = self.class.endpoints.flat_map(&:routes)
|
|
201
152
|
|
|
202
153
|
# Disable versioning so adding a route won't prepend versioning
|
|
203
154
|
# informations again.
|
|
@@ -206,41 +157,37 @@ module Grape
|
|
|
206
157
|
|
|
207
158
|
def collect_route_config_per_pattern(all_routes)
|
|
208
159
|
routes_by_regexp = all_routes.group_by(&:pattern_regexp)
|
|
160
|
+
namespace_inheritable = self.class.inheritable_setting.namespace_inheritable
|
|
209
161
|
|
|
210
162
|
# Build the configuration based on the first endpoint and the collection of methods supported.
|
|
211
163
|
routes_by_regexp.each_value do |routes|
|
|
212
|
-
last_route = routes.last # Most of the configuration is taken from the last endpoint
|
|
213
164
|
next if routes.any? { |route| route.request_method == '*' }
|
|
214
165
|
|
|
166
|
+
last_route = routes.last # Most of the configuration is taken from the last endpoint
|
|
215
167
|
allowed_methods = routes.map(&:request_method)
|
|
216
|
-
allowed_methods |= [Rack::HEAD] if !
|
|
168
|
+
allowed_methods |= [Rack::HEAD] if !namespace_inheritable[:do_not_route_head] && allowed_methods.include?(Rack::GET)
|
|
217
169
|
|
|
218
|
-
allow_header =
|
|
219
|
-
last_route.app.options[:options_route_enabled] = true unless
|
|
170
|
+
allow_header = namespace_inheritable[:do_not_route_options] ? allowed_methods : [Rack::OPTIONS] | allowed_methods
|
|
171
|
+
last_route.app.options[:options_route_enabled] = true unless namespace_inheritable[:do_not_route_options] || allowed_methods.include?(Rack::OPTIONS)
|
|
220
172
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
allow_header: allow_header
|
|
224
|
-
})
|
|
173
|
+
greedy_route = Grape::Router::GreedyRoute.new(last_route.pattern, endpoint: last_route.app, allow_header:)
|
|
174
|
+
@router.associate_routes(greedy_route)
|
|
225
175
|
end
|
|
226
176
|
end
|
|
227
177
|
|
|
178
|
+
ROOT_PREFIX_VERSIONING_KEY = %i[version version_options root_prefix].freeze
|
|
179
|
+
private_constant :ROOT_PREFIX_VERSIONING_KEY
|
|
180
|
+
|
|
228
181
|
# Allows definition of endpoints that ignore the versioning configuration
|
|
229
182
|
# used by the rest of your API.
|
|
230
183
|
def without_root_prefix_and_versioning
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
184
|
+
inheritable_setting = self.class.inheritable_setting
|
|
185
|
+
deleted_values = inheritable_setting.namespace_inheritable.delete(*ROOT_PREFIX_VERSIONING_KEY)
|
|
239
186
|
yield
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
187
|
+
ensure
|
|
188
|
+
ROOT_PREFIX_VERSIONING_KEY.each_with_index do |key, index|
|
|
189
|
+
inheritable_setting.namespace_inheritable[key] = deleted_values[index]
|
|
190
|
+
end
|
|
244
191
|
end
|
|
245
192
|
end
|
|
246
193
|
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
|
|
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
|
+
|
|
10
|
+
Helpers = Grape::DSL::Helpers::BaseHelper
|
|
9
11
|
|
|
10
12
|
class Boolean
|
|
11
13
|
def self.build(val)
|
|
@@ -15,32 +17,19 @@ 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
|
|
22
|
+
|
|
24
23
|
attr_accessor :base_instance, :instances
|
|
25
24
|
|
|
26
25
|
delegate_missing_to :base_instance
|
|
27
|
-
def_delegators :base_instance, :new, :configuration
|
|
28
26
|
|
|
29
27
|
# This is the interface point between Rack and Grape; it accepts a request
|
|
30
28
|
# from Rack and ultimately returns an array of three values: the status,
|
|
31
29
|
# the headers, and the body. See [the rack specification]
|
|
32
|
-
# (
|
|
30
|
+
# (https://github.com/rack/rack/blob/main/SPEC.rdoc) for more.
|
|
33
31
|
# NOTE: This will only be called on an API directly mounted on RACK
|
|
34
|
-
def_delegators :
|
|
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
|
|
32
|
+
def_delegators :base_instance, :new, :configuration, :call, :change!, :compile!, :recognize_path, :routes
|
|
44
33
|
|
|
45
34
|
# Initialize the instance variables on the remountable class, and the base_instance
|
|
46
35
|
# an instance that will be used to create the set up but will not be mounted
|
|
@@ -54,8 +43,8 @@ module Grape
|
|
|
54
43
|
# Redefines all methods so that are forwarded to add_setup and be recorded
|
|
55
44
|
def override_all_methods!
|
|
56
45
|
(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
|
|
46
|
+
define_singleton_method(method_override) do |*args, **kwargs, &block|
|
|
47
|
+
add_setup(method: method_override, args:, kwargs:, block:)
|
|
59
48
|
end
|
|
60
49
|
end
|
|
61
50
|
end
|
|
@@ -89,6 +78,15 @@ module Grape
|
|
|
89
78
|
|
|
90
79
|
private
|
|
91
80
|
|
|
81
|
+
# When inherited, will create a list of all instances (times the API was mounted)
|
|
82
|
+
# It will listen to the setup required to mount that endpoint, and replicate it on any new instance
|
|
83
|
+
def inherited(api)
|
|
84
|
+
super
|
|
85
|
+
|
|
86
|
+
api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)
|
|
87
|
+
api.override_all_methods!
|
|
88
|
+
end
|
|
89
|
+
|
|
92
90
|
# Replays the set up to produce an API as defined in this class, can be called
|
|
93
91
|
# on classes that inherit from Grape::API
|
|
94
92
|
def replay_setup_on(instance)
|
|
@@ -97,16 +95,8 @@ module Grape
|
|
|
97
95
|
end
|
|
98
96
|
end
|
|
99
97
|
|
|
100
|
-
def instance_for_rack
|
|
101
|
-
if never_mounted?
|
|
102
|
-
base_instance
|
|
103
|
-
else
|
|
104
|
-
mounted_instances.first
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
98
|
# Adds a new stage to the set up require to get a Grape::API up and running
|
|
109
|
-
def add_setup(step)
|
|
99
|
+
def add_setup(**step)
|
|
110
100
|
@setup << step
|
|
111
101
|
last_response = nil
|
|
112
102
|
@instances.each do |instance|
|
|
@@ -130,12 +120,13 @@ module Grape
|
|
|
130
120
|
end
|
|
131
121
|
end
|
|
132
122
|
|
|
133
|
-
def replay_step_on(instance, method:, args:, block:)
|
|
134
|
-
return if skip_immediate_run?(instance, args)
|
|
123
|
+
def replay_step_on(instance, method:, args:, kwargs:, block:)
|
|
124
|
+
return if skip_immediate_run?(instance, args, kwargs)
|
|
135
125
|
|
|
136
126
|
eval_args = evaluate_arguments(instance.configuration, *args)
|
|
137
|
-
|
|
138
|
-
|
|
127
|
+
eval_kwargs = kwargs.deep_transform_values { |v| evaluate_arguments(instance.configuration, v).first }
|
|
128
|
+
response = instance.__send__(method, *eval_args, **eval_kwargs, &block)
|
|
129
|
+
if skip_immediate_run?(instance, [response], kwargs)
|
|
139
130
|
response
|
|
140
131
|
else
|
|
141
132
|
evaluate_arguments(instance.configuration, response).first
|
|
@@ -143,18 +134,18 @@ module Grape
|
|
|
143
134
|
end
|
|
144
135
|
|
|
145
136
|
# Skips steps that contain arguments to be lazily executed (on re-mount time)
|
|
146
|
-
def skip_immediate_run?(instance, args)
|
|
137
|
+
def skip_immediate_run?(instance, args, kwargs)
|
|
147
138
|
instance.base_instance? &&
|
|
148
|
-
(any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) })
|
|
139
|
+
(any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) } || any_lazy?(kwargs.values))
|
|
149
140
|
end
|
|
150
141
|
|
|
151
142
|
def any_lazy?(args)
|
|
152
|
-
args.any? { |argument| argument
|
|
143
|
+
args.any? { |argument| argument_lazy?(argument) }
|
|
153
144
|
end
|
|
154
145
|
|
|
155
146
|
def evaluate_arguments(configuration, *args)
|
|
156
147
|
args.map do |argument|
|
|
157
|
-
if argument
|
|
148
|
+
if argument_lazy?(argument)
|
|
158
149
|
argument.evaluate_from(configuration)
|
|
159
150
|
elsif argument.is_a?(Hash)
|
|
160
151
|
argument.transform_values { |value| evaluate_arguments(configuration, value).first }
|
|
@@ -166,12 +157,8 @@ module Grape
|
|
|
166
157
|
end
|
|
167
158
|
end
|
|
168
159
|
|
|
169
|
-
def
|
|
170
|
-
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def mounted_instances
|
|
174
|
-
instances - [base_instance]
|
|
160
|
+
def argument_lazy?(argument)
|
|
161
|
+
argument.respond_to?(:lazy?) && argument.lazy?
|
|
175
162
|
end
|
|
176
163
|
end
|
|
177
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
|