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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +109 -0
  3. data/CONTRIBUTING.md +2 -10
  4. data/README.md +137 -181
  5. data/UPGRADING.md +154 -0
  6. data/grape.gemspec +4 -4
  7. data/lib/grape/api/instance.rb +51 -104
  8. data/lib/grape/api.rb +29 -42
  9. data/lib/grape/content_types.rb +1 -4
  10. data/lib/grape/declared_params_handler.rb +118 -0
  11. data/lib/grape/dry_types.rb +48 -4
  12. data/lib/grape/dsl/callbacks.rb +8 -58
  13. data/lib/grape/dsl/declared.rb +35 -0
  14. data/lib/grape/dsl/desc.rb +8 -67
  15. data/lib/grape/dsl/helpers.rb +59 -64
  16. data/lib/grape/dsl/inside_route.rb +28 -189
  17. data/lib/grape/dsl/logger.rb +3 -6
  18. data/lib/grape/dsl/middleware.rb +22 -40
  19. data/lib/grape/dsl/parameters.rb +24 -51
  20. data/lib/grape/dsl/request_response.rb +136 -139
  21. data/lib/grape/dsl/routing.rb +240 -200
  22. data/lib/grape/dsl/settings.rb +23 -135
  23. data/lib/grape/dsl/validations.rb +38 -44
  24. data/lib/grape/endpoint.rb +169 -205
  25. data/lib/grape/error_formatter/base.rb +4 -2
  26. data/lib/grape/exceptions/base.rb +19 -45
  27. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  28. data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
  29. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  30. data/lib/grape/exceptions/invalid_message_body.rb +1 -1
  31. data/lib/grape/exceptions/invalid_version_header.rb +1 -1
  32. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  33. data/lib/grape/exceptions/method_not_allowed.rb +1 -1
  34. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  35. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  36. data/lib/grape/exceptions/request_error.rb +11 -0
  37. data/lib/grape/exceptions/unknown_auth_strategy.rb +1 -1
  38. data/lib/grape/exceptions/unknown_parameter.rb +1 -1
  39. data/lib/grape/exceptions/unknown_params_builder.rb +1 -1
  40. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  41. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  42. data/lib/grape/exceptions/validation.rb +7 -4
  43. data/lib/grape/exceptions/validation_errors.rb +13 -7
  44. data/lib/grape/locale/en.yml +0 -5
  45. data/lib/grape/middleware/auth/base.rb +2 -0
  46. data/lib/grape/middleware/auth/dsl.rb +9 -10
  47. data/lib/grape/middleware/base.rb +6 -4
  48. data/lib/grape/middleware/error.rb +4 -14
  49. data/lib/grape/middleware/formatter.rb +11 -7
  50. data/lib/grape/middleware/stack.rb +3 -2
  51. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
  52. data/lib/grape/middleware/versioner/base.rb +42 -40
  53. data/lib/grape/middleware/versioner/header.rb +2 -18
  54. data/lib/grape/middleware/versioner/param.rb +2 -2
  55. data/lib/grape/middleware/versioner/path.rb +2 -2
  56. data/lib/grape/namespace.rb +15 -8
  57. data/lib/grape/params_builder/base.rb +2 -0
  58. data/lib/grape/params_builder.rb +2 -19
  59. data/lib/grape/request.rb +2 -10
  60. data/lib/grape/router/base_route.rb +14 -5
  61. data/lib/grape/router/greedy_route.rb +11 -5
  62. data/lib/grape/router/pattern.rb +6 -20
  63. data/lib/grape/router/route.rb +7 -11
  64. data/lib/grape/router.rb +42 -65
  65. data/lib/grape/util/api_description.rb +58 -0
  66. data/lib/grape/util/base_inheritable.rb +5 -2
  67. data/lib/grape/util/cache.rb +1 -0
  68. data/lib/grape/util/deep_freeze.rb +35 -0
  69. data/lib/grape/util/inheritable_setting.rb +8 -1
  70. data/lib/grape/util/media_type.rb +2 -2
  71. data/lib/grape/util/registry.rb +1 -1
  72. data/lib/grape/util/translation.rb +42 -0
  73. data/lib/grape/validations/attributes_iterator.rb +35 -20
  74. data/lib/grape/validations/contract_scope.rb +2 -8
  75. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  76. data/lib/grape/validations/param_scope_tracker.rb +57 -0
  77. data/lib/grape/validations/params_documentation.rb +50 -0
  78. data/lib/grape/validations/params_scope.rb +147 -156
  79. data/lib/grape/validations/single_attribute_iterator.rb +2 -2
  80. data/lib/grape/validations/types/array_coercer.rb +2 -3
  81. data/lib/grape/validations/types/dry_type_coercer.rb +4 -11
  82. data/lib/grape/validations/types/primitive_coercer.rb +1 -28
  83. data/lib/grape/validations/types.rb +10 -25
  84. data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
  85. data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
  86. data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
  87. data/lib/grape/validations/validators/base.rb +95 -25
  88. data/lib/grape/validations/validators/coerce_validator.rb +15 -35
  89. data/lib/grape/validations/validators/contract_scope_validator.rb +9 -5
  90. data/lib/grape/validations/validators/default_validator.rb +12 -18
  91. data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
  92. data/lib/grape/validations/validators/except_values_validator.rb +13 -4
  93. data/lib/grape/validations/validators/length_validator.rb +21 -22
  94. data/lib/grape/validations/validators/multiple_params_base.rb +5 -5
  95. data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
  96. data/lib/grape/validations/validators/presence_validator.rb +4 -2
  97. data/lib/grape/validations/validators/regexp_validator.rb +11 -3
  98. data/lib/grape/validations/validators/same_as_validator.rb +6 -15
  99. data/lib/grape/validations/validators/values_validator.rb +29 -21
  100. data/lib/grape/version.rb +1 -1
  101. data/lib/grape.rb +29 -23
  102. metadata +31 -26
  103. data/lib/grape/api/helpers.rb +0 -9
  104. data/lib/grape/dsl/api.rb +0 -17
  105. data/lib/grape/dsl/configuration.rb +0 -15
  106. data/lib/grape/exceptions/conflicting_types.rb +0 -11
  107. data/lib/grape/exceptions/empty_message_body.rb +0 -11
  108. data/lib/grape/exceptions/invalid_parameters.rb +0 -11
  109. data/lib/grape/exceptions/missing_option.rb +0 -11
  110. data/lib/grape/exceptions/too_deep_parameters.rb +0 -11
  111. data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
  112. data/lib/grape/exceptions/unknown_options.rb +0 -11
  113. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -24
  114. data/lib/grape/extensions/hash.rb +0 -27
  115. data/lib/grape/extensions/hashie/mash.rb +0 -24
  116. data/lib/grape/types/invalid_value.rb +0 -8
  117. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  118. data/lib/grape/validations/attributes_doc.rb +0 -60
  119. 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
- extend ActiveSupport::Concern
7
-
8
- include Grape::DSL::Configuration
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
- module ClassMethods
11
- # Clears all defined parameters and validations. The main purpose of it is to clean up
12
- # settings, so next endpoint won't interfere with previous one.
13
- #
14
- # params do
15
- # # params for the endpoint below this block
16
- # end
17
- # post '/current' do
18
- # # whatever
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
- # Opens a root-level ParamsScope, defining parameter coercions and
36
- # validations for the endpoint.
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
- # Declare the contract to be used for the endpoint's parameters.
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
- Grape::Validations::ContractScope.new(self, contract, &block)
53
- end
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
@@ -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
- attr_accessor :block, :source, :options
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
- else
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.try(:call, endpoint) }
35
+ before_each.each { |blk| blk.call(endpoint) }
38
36
  end
39
37
 
40
- # @api private
41
- #
42
- # Create an UnboundMethod that is appropriate for executing an endpoint
43
- # route.
44
- #
45
- # The unbound method allows explicit calls to +return+ without raising a
46
- # +LocalJumpError+. The method will be removed, but a +Proc+ reference to
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 = {}, &block)
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
- route_setting(:declared_params, namespace_stackable(:declared_params).flatten)
90
- route_setting(:saved_validations, namespace_stackable(:validations))
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(:representations, []) unless namespace_stackable(:representations)
93
- namespace_inheritable(:default_error_status, 500) unless namespace_inheritable(:default_error_status)
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
- @proc = nil
111
-
112
- return unless block
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&.collect(&:routes)&.flatten || to_routes
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
- return endpoints.each { |e| e.mount_in(router) } if endpoints
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 unless !namespace_inheritable(:do_not_route_head) && route.request_method == Rack::GET
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(:namespace))
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
- # Return the collection of endpoints within this endpoint.
229
- # This is the case when an Grape::API mounts another Grape::API.
230
- def endpoints
231
- @endpoints ||= options[:app].try(:endpoints)
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: env) do
251
- @request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with))
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 validations, request
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
- @block&.call(self)
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
- @lazy_initialized = true
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(validators, request)
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
- ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do
311
- validators.each do |validator|
312
- validator.validate(request)
313
- rescue Grape::Exceptions::Validation => e
314
- validation_errors << e
315
- break if validator.fail_fast?
316
- rescue Grape::Exceptions::ValidationArrayErrors => e
317
- validation_errors.concat e.errors
318
- break if validator.fail_fast?
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
- validation_errors.any? && raise(Grape::Exceptions::ValidationErrors.new(errors: validation_errors, headers: header))
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
- ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do
327
- filters&.each { |filter| instance_eval(&filter) }
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(method)
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
- def build_stack(helpers)
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(:format)
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 Class.new(Grape::Middleware::Error),
363
- helpers: helpers,
364
- format: format,
365
- content_types: content_types,
366
- default_status: namespace_inheritable(:default_error_status),
367
- rescue_all: namespace_inheritable(:rescue_all),
368
- rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
369
- default_error_formatter: namespace_inheritable(:default_error_formatter),
370
- error_formatters: namespace_stackable_with_hash(:error_formatters),
371
- rescue_options: namespace_stackable_with_hash(:rescue_options),
372
- rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers),
373
- base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers),
374
- all_rescue_handler: namespace_inheritable(:all_rescue_handler),
375
- grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler)
376
-
377
- stack.concat namespace_stackable(:middleware)
378
-
379
- if namespace_inheritable(:version).present?
380
- stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
381
- versions: namespace_inheritable(:version).flatten,
382
- version_options: namespace_inheritable(:version_options),
383
- prefix: namespace_inheritable(:root_prefix),
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: format,
389
- default_format: namespace_inheritable(:default_format) || :txt,
390
- content_types: 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(:helpers)
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: 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(:lint) || Grape.config.lint
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: 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: 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)