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
|
@@ -3,54 +3,48 @@
|
|
|
3
3
|
module Grape
|
|
4
4
|
module DSL
|
|
5
5
|
module Validations
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
# Opens a root-level ParamsScope, defining parameter coercions and
|
|
7
|
+
# validations for the endpoint.
|
|
8
|
+
# @yield instance context of the new scope
|
|
9
|
+
def params(&)
|
|
10
|
+
Grape::Validations::ParamsScope.new(api: self, type: Hash, &)
|
|
11
|
+
end
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# end
|
|
20
|
-
#
|
|
21
|
-
# # somewhere between them the reset_validations! method gets called
|
|
22
|
-
#
|
|
23
|
-
# params do
|
|
24
|
-
# # params for the endpoint below this block
|
|
25
|
-
# end
|
|
26
|
-
# post '/next' do
|
|
27
|
-
# # whatever
|
|
28
|
-
# end
|
|
29
|
-
def reset_validations!
|
|
30
|
-
unset_namespace_stackable :declared_params
|
|
31
|
-
unset_namespace_stackable :validations
|
|
32
|
-
unset_namespace_stackable :params
|
|
33
|
-
end
|
|
13
|
+
# Declare the contract to be used for the endpoint's parameters.
|
|
14
|
+
# @param contract [Class<Dry::Validation::Contract> | Dry::Schema::Processor]
|
|
15
|
+
# The contract or schema to be used for validation. Optional.
|
|
16
|
+
# @yield a block yielding a new instance of Dry::Schema::Params
|
|
17
|
+
# subclass, allowing to define the schema inline. When the
|
|
18
|
+
# +contract+ parameter is a schema, it will be used as a parent. Optional.
|
|
19
|
+
def contract(contract = nil, &block)
|
|
20
|
+
raise ArgumentError, 'Either contract or block must be provided' unless contract || block
|
|
21
|
+
raise ArgumentError, 'Cannot inherit from contract, only schema' if block && contract.respond_to?(:schema)
|
|
34
22
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# @yield instance context of the new scope
|
|
38
|
-
def params(&block)
|
|
39
|
-
Grape::Validations::ParamsScope.new(api: self, type: Hash, &block)
|
|
40
|
-
end
|
|
23
|
+
Grape::Validations::ContractScope.new(self, contract, &block)
|
|
24
|
+
end
|
|
41
25
|
|
|
42
|
-
|
|
43
|
-
# @param contract [Class<Dry::Validation::Contract> | Dry::Schema::Processor]
|
|
44
|
-
# The contract or schema to be used for validation. Optional.
|
|
45
|
-
# @yield a block yielding a new instance of Dry::Schema::Params
|
|
46
|
-
# subclass, allowing to define the schema inline. When the
|
|
47
|
-
# +contract+ parameter is a schema, it will be used as a parent. Optional.
|
|
48
|
-
def contract(contract = nil, &block)
|
|
49
|
-
raise ArgumentError, 'Either contract or block must be provided' unless contract || block
|
|
50
|
-
raise ArgumentError, 'Cannot inherit from contract, only schema' if block && contract.respond_to?(:schema)
|
|
26
|
+
private
|
|
51
27
|
|
|
52
|
-
|
|
53
|
-
|
|
28
|
+
# Clears all defined parameters and validations. The main purpose of it is to clean up
|
|
29
|
+
# settings, so next endpoint won't interfere with previous one.
|
|
30
|
+
#
|
|
31
|
+
# params do
|
|
32
|
+
# # params for the endpoint below this block
|
|
33
|
+
# end
|
|
34
|
+
# post '/current' do
|
|
35
|
+
# # whatever
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# # somewhere between them the reset_validations! method gets called
|
|
39
|
+
#
|
|
40
|
+
# params do
|
|
41
|
+
# # params for the endpoint below this block
|
|
42
|
+
# end
|
|
43
|
+
# post '/next' do
|
|
44
|
+
# # whatever
|
|
45
|
+
# end
|
|
46
|
+
def reset_validations!
|
|
47
|
+
inheritable_setting.namespace_stackable.delete(:declared_params, :params, :validations)
|
|
54
48
|
end
|
|
55
49
|
end
|
|
56
50
|
end
|
data/lib/grape/endpoint.rb
CHANGED
|
@@ -8,60 +8,40 @@ module Grape
|
|
|
8
8
|
class Endpoint
|
|
9
9
|
extend Forwardable
|
|
10
10
|
include Grape::DSL::Settings
|
|
11
|
+
include Grape::DSL::Headers
|
|
11
12
|
include Grape::DSL::InsideRoute
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
attr_reader :env, :request
|
|
14
|
+
attr_reader :env, :request, :source, :options, :endpoints
|
|
15
15
|
|
|
16
16
|
def_delegators :request, :params, :headers, :cookies
|
|
17
17
|
def_delegator :cookies, :response_cookies
|
|
18
18
|
|
|
19
19
|
class << self
|
|
20
|
-
def new(...)
|
|
21
|
-
self == Endpoint ? Class.new(Endpoint).new(...) : super
|
|
22
|
-
end
|
|
23
|
-
|
|
24
20
|
def before_each(new_setup = false, &block)
|
|
25
21
|
@before_each ||= []
|
|
26
22
|
if new_setup == false
|
|
27
23
|
return @before_each unless block
|
|
28
24
|
|
|
29
25
|
@before_each << block
|
|
30
|
-
|
|
26
|
+
elsif new_setup
|
|
31
27
|
@before_each = [new_setup]
|
|
28
|
+
else
|
|
29
|
+
@before_each.clear
|
|
32
30
|
end
|
|
33
31
|
end
|
|
34
32
|
|
|
35
33
|
def run_before_each(endpoint)
|
|
36
34
|
superclass.run_before_each(endpoint) unless self == Endpoint
|
|
37
|
-
before_each.each { |blk| blk.
|
|
35
|
+
before_each.each { |blk| blk.call(endpoint) }
|
|
38
36
|
end
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# it will be returned. The returned +Proc+ expects a single argument: the
|
|
48
|
-
# instance of +Endpoint+ to bind to the method during the call.
|
|
49
|
-
#
|
|
50
|
-
# @param [String, Symbol] method_name
|
|
51
|
-
# @return [Proc]
|
|
52
|
-
# @raise [NameError] an instance method with the same name already exists
|
|
53
|
-
def generate_api_method(method_name, &block)
|
|
54
|
-
raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name") if method_defined?(method_name)
|
|
55
|
-
|
|
56
|
-
define_method(method_name, &block)
|
|
57
|
-
method = instance_method(method_name)
|
|
58
|
-
remove_method(method_name)
|
|
59
|
-
|
|
60
|
-
proc do |endpoint_instance|
|
|
61
|
-
ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do
|
|
62
|
-
method.bind_call(endpoint_instance)
|
|
63
|
-
end
|
|
64
|
-
end
|
|
38
|
+
def block_to_unbound_method(block)
|
|
39
|
+
return unless block
|
|
40
|
+
|
|
41
|
+
define_method :temp_unbound_method, block
|
|
42
|
+
method = instance_method(:temp_unbound_method)
|
|
43
|
+
remove_method :temp_unbound_method
|
|
44
|
+
method
|
|
65
45
|
end
|
|
66
46
|
end
|
|
67
47
|
|
|
@@ -77,42 +57,30 @@ module Grape
|
|
|
77
57
|
# @note This happens at the time of API definition, so in this context the
|
|
78
58
|
# endpoint does not know if it will be mounted under a different endpoint.
|
|
79
59
|
# @yield a block defining what your API should do when this endpoint is hit
|
|
80
|
-
def initialize(new_settings, options
|
|
81
|
-
require_option(options, :path)
|
|
82
|
-
require_option(options, :method)
|
|
83
|
-
|
|
60
|
+
def initialize(new_settings, **options, &block)
|
|
84
61
|
self.inheritable_setting = new_settings.point_in_time_copy
|
|
85
62
|
|
|
86
63
|
# now +namespace_stackable(:declared_params)+ contains all params defined for
|
|
87
64
|
# this endpoint and its parents, but later it will be cleaned up,
|
|
88
65
|
# see +reset_validations!+ in lib/grape/dsl/validations.rb
|
|
89
|
-
|
|
90
|
-
|
|
66
|
+
inheritable_setting.route[:declared_params] = inheritable_setting.namespace_stackable[:declared_params].flatten
|
|
67
|
+
inheritable_setting.route[:saved_validations] = inheritable_setting.namespace_stackable[:validations]
|
|
91
68
|
|
|
92
|
-
namespace_stackable
|
|
93
|
-
namespace_inheritable
|
|
69
|
+
inheritable_setting.namespace_stackable[:representations] ||= []
|
|
70
|
+
inheritable_setting.namespace_inheritable[:default_error_status] ||= 500
|
|
94
71
|
|
|
95
72
|
@options = options
|
|
96
73
|
|
|
97
|
-
@options[:path] = Array(options[:path])
|
|
98
|
-
@options[:path] << '/' if options[:path].empty?
|
|
99
|
-
|
|
100
|
-
@options[:method] = Array(options[:method])
|
|
101
|
-
@options[:route_options] ||= {}
|
|
102
|
-
|
|
103
|
-
@lazy_initialize_lock = Mutex.new
|
|
104
|
-
@lazy_initialized = nil
|
|
105
|
-
@block = nil
|
|
74
|
+
@options[:path] = Array(@options[:path])
|
|
75
|
+
@options[:path] << '/' if @options[:path].empty?
|
|
76
|
+
@options[:method] = Array(@options[:method])
|
|
106
77
|
|
|
107
78
|
@status = nil
|
|
108
79
|
@stream = nil
|
|
109
80
|
@body = nil
|
|
110
|
-
@
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
@source = block
|
|
115
|
-
@block = self.class.generate_api_method(method_name, &block)
|
|
81
|
+
@source = self.class.block_to_unbound_method(block)
|
|
82
|
+
@before_filter_passed = false
|
|
83
|
+
@endpoints = @options[:app].endpoints if @options[:app].respond_to?(:endpoints)
|
|
116
84
|
end
|
|
117
85
|
|
|
118
86
|
# Update our settings from a given set of stackable parameters. Used when
|
|
@@ -126,20 +94,8 @@ module Grape
|
|
|
126
94
|
endpoints&.each { |e| e.inherit_settings(namespace_stackable) }
|
|
127
95
|
end
|
|
128
96
|
|
|
129
|
-
def require_option(options, key)
|
|
130
|
-
raise Grape::Exceptions::MissingOption.new(key) unless options.key?(key)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def method_name
|
|
134
|
-
[options[:method],
|
|
135
|
-
Namespace.joined_space(namespace_stackable(:namespace)),
|
|
136
|
-
(namespace_stackable(:mount_path) || []).join('/'),
|
|
137
|
-
options[:path].join('/')]
|
|
138
|
-
.join(' ')
|
|
139
|
-
end
|
|
140
|
-
|
|
141
97
|
def routes
|
|
142
|
-
@routes ||= endpoints&.
|
|
98
|
+
@routes ||= endpoints&.flat_map(&:routes) || to_routes
|
|
143
99
|
end
|
|
144
100
|
|
|
145
101
|
def reset_routes!
|
|
@@ -149,12 +105,16 @@ module Grape
|
|
|
149
105
|
end
|
|
150
106
|
|
|
151
107
|
def mount_in(router)
|
|
152
|
-
|
|
108
|
+
if endpoints
|
|
109
|
+
compile!
|
|
110
|
+
return endpoints.each { |e| e.mount_in(router) }
|
|
111
|
+
end
|
|
153
112
|
|
|
154
113
|
reset_routes!
|
|
114
|
+
compile!
|
|
155
115
|
routes.each do |route|
|
|
156
116
|
router.append(route.apply(self))
|
|
157
|
-
next
|
|
117
|
+
next if inheritable_setting.namespace_inheritable[:do_not_route_head] || route.request_method != Rack::GET
|
|
158
118
|
|
|
159
119
|
route.dup.then do |head_route|
|
|
160
120
|
head_route.convert_to_head_request!
|
|
@@ -163,77 +123,28 @@ module Grape
|
|
|
163
123
|
end
|
|
164
124
|
end
|
|
165
125
|
|
|
166
|
-
def to_routes
|
|
167
|
-
default_route_options = prepare_default_route_attributes
|
|
168
|
-
|
|
169
|
-
map_routes do |method, raw_path|
|
|
170
|
-
prepared_path = Path.new(raw_path, namespace, prepare_default_path_settings)
|
|
171
|
-
params = options[:route_options].present? ? options[:route_options].merge(default_route_options) : default_route_options
|
|
172
|
-
route = Grape::Router::Route.new(method, prepared_path.origin, prepared_path.suffix, params)
|
|
173
|
-
route.apply(self)
|
|
174
|
-
end.flatten
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def prepare_routes_requirements
|
|
178
|
-
{}.merge!(*namespace_stackable(:namespace).map(&:requirements)).tap do |requirements|
|
|
179
|
-
endpoint_requirements = options.dig(:route_options, :requirements)
|
|
180
|
-
requirements.merge!(endpoint_requirements) if endpoint_requirements
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def prepare_default_route_attributes
|
|
185
|
-
{
|
|
186
|
-
namespace: namespace,
|
|
187
|
-
version: prepare_version,
|
|
188
|
-
requirements: prepare_routes_requirements,
|
|
189
|
-
prefix: namespace_inheritable(:root_prefix),
|
|
190
|
-
anchor: options[:route_options].fetch(:anchor, true),
|
|
191
|
-
settings: inheritable_setting.route.except(:declared_params, :saved_validations),
|
|
192
|
-
forward_match: options[:forward_match]
|
|
193
|
-
}
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
def prepare_version
|
|
197
|
-
version = namespace_inheritable(:version)
|
|
198
|
-
return if version.blank?
|
|
199
|
-
|
|
200
|
-
version.length == 1 ? version.first : version
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def map_routes
|
|
204
|
-
options[:method].map { |method| options[:path].map { |path| yield method, path } }
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def prepare_default_path_settings
|
|
208
|
-
namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
|
|
209
|
-
namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash
|
|
210
|
-
namespace_stackable_hash.merge!(namespace_inheritable_hash)
|
|
211
|
-
end
|
|
212
|
-
|
|
213
126
|
def namespace
|
|
214
|
-
@namespace ||= Namespace.joined_space_path(namespace_stackable
|
|
127
|
+
@namespace ||= Namespace.joined_space_path(inheritable_setting.namespace_stackable[:namespace])
|
|
215
128
|
end
|
|
216
129
|
|
|
217
130
|
def call(env)
|
|
218
|
-
lazy_initialize!
|
|
219
131
|
dup.call!(env)
|
|
220
132
|
end
|
|
221
133
|
|
|
222
134
|
def call!(env)
|
|
223
135
|
env[Grape::Env::API_ENDPOINT] = self
|
|
224
136
|
@env = env
|
|
137
|
+
# this adds the helpers only to the instance
|
|
138
|
+
singleton_class.include(@helpers) if @helpers
|
|
225
139
|
@app.call(env)
|
|
226
140
|
end
|
|
227
141
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def equals?(endpoint)
|
|
235
|
-
(options == endpoint.options) && (inheritable_setting.to_hash == endpoint.inheritable_setting.to_hash)
|
|
142
|
+
def ==(other)
|
|
143
|
+
other.is_a?(self.class) &&
|
|
144
|
+
options == other.options &&
|
|
145
|
+
inheritable_setting.to_hash == other.inheritable_setting.to_hash
|
|
236
146
|
end
|
|
147
|
+
alias eql? ==
|
|
237
148
|
|
|
238
149
|
# The purpose of this override is solely for stripping internals when an error occurs while calling
|
|
239
150
|
# an endpoint through an api. See https://github.com/ruby-grape/grape/issues/2398
|
|
@@ -247,11 +158,12 @@ module Grape
|
|
|
247
158
|
protected
|
|
248
159
|
|
|
249
160
|
def run
|
|
250
|
-
ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env:
|
|
251
|
-
@request = Grape::Request.new(env, build_params_with: namespace_inheritable
|
|
161
|
+
ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env:) do
|
|
162
|
+
@request = Grape::Request.new(env, build_params_with: inheritable_setting.namespace_inheritable[:build_params_with])
|
|
252
163
|
begin
|
|
253
164
|
self.class.run_before_each(self)
|
|
254
165
|
run_filters befores, :before
|
|
166
|
+
@before_filter_passed = true
|
|
255
167
|
|
|
256
168
|
if env.key?(Grape::Env::GRAPE_ALLOWED_METHODS)
|
|
257
169
|
header['Allow'] = env[Grape::Env::GRAPE_ALLOWED_METHODS].join(', ')
|
|
@@ -262,7 +174,7 @@ module Grape
|
|
|
262
174
|
status 204
|
|
263
175
|
else
|
|
264
176
|
run_filters before_validations, :before_validation
|
|
265
|
-
run_validators
|
|
177
|
+
run_validators(request:)
|
|
266
178
|
run_filters after_validations, :after_validation
|
|
267
179
|
response_object = execute
|
|
268
180
|
end
|
|
@@ -284,63 +196,47 @@ module Grape
|
|
|
284
196
|
end
|
|
285
197
|
|
|
286
198
|
def execute
|
|
287
|
-
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
def helpers
|
|
291
|
-
lazy_initialize! && @helpers
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
def lazy_initialize!
|
|
295
|
-
return true if @lazy_initialized
|
|
296
|
-
|
|
297
|
-
@lazy_initialize_lock.synchronize do
|
|
298
|
-
return true if @lazy_initialized
|
|
299
|
-
|
|
300
|
-
@helpers = build_helpers&.tap { |mod| self.class.include mod }
|
|
301
|
-
@app = options[:app] || build_stack(@helpers)
|
|
199
|
+
return unless source
|
|
302
200
|
|
|
303
|
-
|
|
201
|
+
ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: self) do
|
|
202
|
+
source.bind_call(self)
|
|
304
203
|
end
|
|
305
204
|
end
|
|
306
205
|
|
|
307
|
-
def run_validators(
|
|
206
|
+
def run_validators(request:)
|
|
207
|
+
validators = inheritable_setting.route[:saved_validations]
|
|
208
|
+
return if validators.empty?
|
|
209
|
+
|
|
308
210
|
validation_errors = []
|
|
309
211
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
validator
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
212
|
+
Grape::Validations::ParamScopeTracker.track do
|
|
213
|
+
ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators:, request:) do
|
|
214
|
+
validators.each do |validator|
|
|
215
|
+
validator.validate(request)
|
|
216
|
+
rescue Grape::Exceptions::Validation => e
|
|
217
|
+
validation_errors << e
|
|
218
|
+
break if validator.fail_fast?
|
|
219
|
+
rescue Grape::Exceptions::ValidationArrayErrors => e
|
|
220
|
+
validation_errors.concat e.errors
|
|
221
|
+
break if validator.fail_fast?
|
|
222
|
+
end
|
|
319
223
|
end
|
|
320
224
|
end
|
|
321
225
|
|
|
322
|
-
|
|
226
|
+
raise(Grape::Exceptions::ValidationErrors.new(errors: validation_errors, headers: header)) if validation_errors.any?
|
|
323
227
|
end
|
|
324
228
|
|
|
325
229
|
def run_filters(filters, type = :other)
|
|
326
|
-
|
|
327
|
-
|
|
230
|
+
return unless filters
|
|
231
|
+
|
|
232
|
+
ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters:, type:) do
|
|
233
|
+
filters.each { |filter| instance_eval(&filter) }
|
|
328
234
|
end
|
|
329
|
-
post_extension = DSL::InsideRoute.post_filter_methods(type)
|
|
330
|
-
extend post_extension if post_extension
|
|
331
235
|
end
|
|
332
236
|
|
|
333
237
|
%i[befores before_validations after_validations afters finallies].each do |method|
|
|
334
238
|
define_method method do
|
|
335
|
-
namespace_stackable
|
|
336
|
-
end
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
def validations
|
|
340
|
-
return enum_for(:validations) unless block_given?
|
|
341
|
-
|
|
342
|
-
route_setting(:saved_validations)&.each do |saved_validation|
|
|
343
|
-
yield Grape::Validations::ValidatorFactory.create_validator(saved_validation)
|
|
239
|
+
inheritable_setting.namespace_stackable[method]
|
|
344
240
|
end
|
|
345
241
|
end
|
|
346
242
|
|
|
@@ -351,45 +247,104 @@ module Grape
|
|
|
351
247
|
|
|
352
248
|
private
|
|
353
249
|
|
|
354
|
-
|
|
250
|
+
attr_reader :before_filter_passed
|
|
251
|
+
|
|
252
|
+
def compile!
|
|
253
|
+
@app = options[:app] || build_stack
|
|
254
|
+
@helpers = build_helpers
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def to_routes
|
|
258
|
+
route_options = options[:route_options]
|
|
259
|
+
default_route_options = prepare_default_route_attributes(route_options)
|
|
260
|
+
complete_route_options = route_options.merge(default_route_options)
|
|
261
|
+
path_settings = prepare_default_path_settings
|
|
262
|
+
|
|
263
|
+
options[:method].flat_map do |method|
|
|
264
|
+
options[:path].map do |path|
|
|
265
|
+
prepared_path = Path.new(path, default_route_options[:namespace], path_settings)
|
|
266
|
+
pattern = Grape::Router::Pattern.new(
|
|
267
|
+
origin: prepared_path.origin,
|
|
268
|
+
suffix: prepared_path.suffix,
|
|
269
|
+
anchor: default_route_options[:anchor],
|
|
270
|
+
params: route_options[:params],
|
|
271
|
+
format: options[:format],
|
|
272
|
+
version: default_route_options[:version],
|
|
273
|
+
requirements: default_route_options[:requirements]
|
|
274
|
+
)
|
|
275
|
+
Grape::Router::Route.new(self, method, pattern, complete_route_options)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def prepare_default_route_attributes(route_options)
|
|
281
|
+
{
|
|
282
|
+
namespace:,
|
|
283
|
+
version: prepare_version(inheritable_setting.namespace_inheritable[:version]),
|
|
284
|
+
requirements: prepare_routes_requirements(route_options[:requirements]),
|
|
285
|
+
prefix: inheritable_setting.namespace_inheritable[:root_prefix],
|
|
286
|
+
anchor: route_options.fetch(:anchor, true),
|
|
287
|
+
settings: inheritable_setting.route.except(:declared_params, :saved_validations),
|
|
288
|
+
forward_match: options[:forward_match]
|
|
289
|
+
}
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def prepare_default_path_settings
|
|
293
|
+
namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
|
|
294
|
+
namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash
|
|
295
|
+
namespace_stackable_hash.merge!(namespace_inheritable_hash)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def prepare_routes_requirements(route_options_requirements)
|
|
299
|
+
namespace_requirements = inheritable_setting.namespace_stackable[:namespace].filter_map(&:requirements)
|
|
300
|
+
namespace_requirements << route_options_requirements if route_options_requirements.present?
|
|
301
|
+
namespace_requirements.reduce({}, :merge)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def prepare_version(namespace_inheritable_version)
|
|
305
|
+
return if namespace_inheritable_version.blank?
|
|
306
|
+
|
|
307
|
+
namespace_inheritable_version.length == 1 ? namespace_inheritable_version.first : namespace_inheritable_version
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def build_stack
|
|
355
311
|
stack = Grape::Middleware::Stack.new
|
|
356
312
|
|
|
357
|
-
content_types = namespace_stackable_with_hash(:content_types)
|
|
358
|
-
format = namespace_inheritable
|
|
313
|
+
content_types = inheritable_setting.namespace_stackable_with_hash(:content_types)
|
|
314
|
+
format = inheritable_setting.namespace_inheritable[:format]
|
|
359
315
|
|
|
360
316
|
stack.use Rack::Head
|
|
361
317
|
stack.use Rack::Lint if lint?
|
|
362
|
-
stack.use
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
mount_path: namespace_stackable(:mount_path).first
|
|
318
|
+
stack.use Grape::Middleware::Error,
|
|
319
|
+
format:,
|
|
320
|
+
content_types:,
|
|
321
|
+
default_status: inheritable_setting.namespace_inheritable[:default_error_status],
|
|
322
|
+
rescue_all: inheritable_setting.namespace_inheritable[:rescue_all],
|
|
323
|
+
rescue_grape_exceptions: inheritable_setting.namespace_inheritable[:rescue_grape_exceptions],
|
|
324
|
+
default_error_formatter: inheritable_setting.namespace_inheritable[:default_error_formatter],
|
|
325
|
+
error_formatters: inheritable_setting.namespace_stackable_with_hash(:error_formatters),
|
|
326
|
+
rescue_options: inheritable_setting.namespace_stackable_with_hash(:rescue_options),
|
|
327
|
+
rescue_handlers:,
|
|
328
|
+
base_only_rescue_handlers: inheritable_setting.namespace_stackable_with_hash(:base_only_rescue_handlers),
|
|
329
|
+
all_rescue_handler: inheritable_setting.namespace_inheritable[:all_rescue_handler],
|
|
330
|
+
grape_exceptions_rescue_handler: inheritable_setting.namespace_inheritable[:grape_exceptions_rescue_handler]
|
|
331
|
+
|
|
332
|
+
stack.concat inheritable_setting.namespace_stackable[:middleware]
|
|
333
|
+
|
|
334
|
+
if inheritable_setting.namespace_inheritable[:version].present?
|
|
335
|
+
stack.use Grape::Middleware::Versioner.using(inheritable_setting.namespace_inheritable[:version_options][:using]),
|
|
336
|
+
versions: inheritable_setting.namespace_inheritable[:version].flatten,
|
|
337
|
+
version_options: inheritable_setting.namespace_inheritable[:version_options],
|
|
338
|
+
prefix: inheritable_setting.namespace_inheritable[:root_prefix],
|
|
339
|
+
mount_path: inheritable_setting.namespace_stackable[:mount_path].first
|
|
385
340
|
end
|
|
386
341
|
|
|
387
342
|
stack.use Grape::Middleware::Formatter,
|
|
388
|
-
format
|
|
389
|
-
default_format: namespace_inheritable
|
|
390
|
-
content_types
|
|
391
|
-
formatters: namespace_stackable_with_hash(:formatters),
|
|
392
|
-
parsers: namespace_stackable_with_hash(:parsers)
|
|
343
|
+
format:,
|
|
344
|
+
default_format: inheritable_setting.namespace_inheritable[:default_format] || :txt,
|
|
345
|
+
content_types:,
|
|
346
|
+
formatters: inheritable_setting.namespace_stackable_with_hash(:formatters),
|
|
347
|
+
parsers: inheritable_setting.namespace_stackable_with_hash(:parsers)
|
|
393
348
|
|
|
394
349
|
builder = stack.build
|
|
395
350
|
builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
|
|
@@ -397,7 +352,7 @@ module Grape
|
|
|
397
352
|
end
|
|
398
353
|
|
|
399
354
|
def build_helpers
|
|
400
|
-
helpers = namespace_stackable
|
|
355
|
+
helpers = inheritable_setting.namespace_stackable[:helpers]
|
|
401
356
|
return if helpers.empty?
|
|
402
357
|
|
|
403
358
|
Module.new { helpers.each { |mod_to_include| include mod_to_include } }
|
|
@@ -405,13 +360,22 @@ module Grape
|
|
|
405
360
|
|
|
406
361
|
def build_response_cookies
|
|
407
362
|
response_cookies do |name, value|
|
|
408
|
-
cookie_value = value.is_a?(Hash) ? value : { value:
|
|
363
|
+
cookie_value = value.is_a?(Hash) ? value : { value: }
|
|
409
364
|
Rack::Utils.set_cookie_header! header, name, cookie_value
|
|
410
365
|
end
|
|
411
366
|
end
|
|
412
367
|
|
|
413
368
|
def lint?
|
|
414
|
-
namespace_inheritable
|
|
369
|
+
inheritable_setting.namespace_inheritable[:lint] || Grape.config.lint
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def rescue_handlers
|
|
373
|
+
rescue_handlers = inheritable_setting.namespace_reverse_stackable[:rescue_handlers]
|
|
374
|
+
return if rescue_handlers.blank?
|
|
375
|
+
|
|
376
|
+
rescue_handlers.each_with_object({}) do |rescue_handler, result|
|
|
377
|
+
result.merge!(rescue_handler) { |_k, s1, _s2| s1 }
|
|
378
|
+
end
|
|
415
379
|
end
|
|
416
380
|
end
|
|
417
381
|
end
|
|
@@ -40,7 +40,7 @@ module Grape
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
if presenter
|
|
43
|
-
embeds = { env:
|
|
43
|
+
embeds = { env: }
|
|
44
44
|
embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
|
|
45
45
|
presented_message = presenter.represent(presented_message, embeds).serializable_hash
|
|
46
46
|
end
|
|
@@ -51,13 +51,15 @@ module Grape
|
|
|
51
51
|
def wrap_message(message)
|
|
52
52
|
return message if message.is_a?(Hash)
|
|
53
53
|
|
|
54
|
-
{ message:
|
|
54
|
+
{ message: }
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def format_structured_message(_structured_message)
|
|
58
58
|
raise NotImplementedError
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
private
|
|
62
|
+
|
|
61
63
|
def inherited(klass)
|
|
62
64
|
super
|
|
63
65
|
ErrorFormatter.register(klass)
|