grape 2.4.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/CONTRIBUTING.md +1 -9
  4. data/README.md +72 -31
  5. data/UPGRADING.md +34 -0
  6. data/grape.gemspec +4 -4
  7. data/lib/grape/api/instance.rb +49 -72
  8. data/lib/grape/api.rb +24 -34
  9. data/lib/grape/dry_types.rb +48 -4
  10. data/lib/grape/dsl/callbacks.rb +8 -58
  11. data/lib/grape/dsl/desc.rb +8 -67
  12. data/lib/grape/dsl/helpers.rb +59 -64
  13. data/lib/grape/dsl/inside_route.rb +20 -43
  14. data/lib/grape/dsl/logger.rb +3 -6
  15. data/lib/grape/dsl/middleware.rb +22 -40
  16. data/lib/grape/dsl/parameters.rb +7 -16
  17. data/lib/grape/dsl/request_response.rb +136 -139
  18. data/lib/grape/dsl/routing.rb +229 -201
  19. data/lib/grape/dsl/settings.rb +22 -134
  20. data/lib/grape/dsl/validations.rb +37 -45
  21. data/lib/grape/endpoint.rb +64 -96
  22. data/lib/grape/error_formatter/base.rb +2 -0
  23. data/lib/grape/exceptions/base.rb +1 -1
  24. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  25. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  26. data/lib/grape/middleware/auth/dsl.rb +5 -6
  27. data/lib/grape/middleware/error.rb +1 -11
  28. data/lib/grape/middleware/formatter.rb +4 -2
  29. data/lib/grape/middleware/stack.rb +2 -2
  30. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
  31. data/lib/grape/middleware/versioner/base.rb +24 -42
  32. data/lib/grape/middleware/versioner/header.rb +1 -1
  33. data/lib/grape/middleware/versioner/param.rb +2 -2
  34. data/lib/grape/middleware/versioner/path.rb +1 -1
  35. data/lib/grape/namespace.rb +11 -0
  36. data/lib/grape/params_builder/base.rb +2 -0
  37. data/lib/grape/router.rb +4 -3
  38. data/lib/grape/util/api_description.rb +56 -0
  39. data/lib/grape/util/base_inheritable.rb +5 -2
  40. data/lib/grape/util/inheritable_setting.rb +7 -0
  41. data/lib/grape/util/media_type.rb +1 -1
  42. data/lib/grape/util/registry.rb +1 -1
  43. data/lib/grape/validations/contract_scope.rb +2 -2
  44. data/lib/grape/validations/params_documentation.rb +50 -0
  45. data/lib/grape/validations/params_scope.rb +38 -53
  46. data/lib/grape/validations/types/array_coercer.rb +2 -3
  47. data/lib/grape/validations/types/dry_type_coercer.rb +4 -11
  48. data/lib/grape/validations/types/primitive_coercer.rb +1 -28
  49. data/lib/grape/validations/types.rb +10 -25
  50. data/lib/grape/validations/validators/base.rb +0 -7
  51. data/lib/grape/version.rb +1 -1
  52. data/lib/grape.rb +7 -10
  53. metadata +24 -14
  54. data/lib/grape/api/helpers.rb +0 -9
  55. data/lib/grape/dsl/api.rb +0 -17
  56. data/lib/grape/dsl/configuration.rb +0 -15
  57. data/lib/grape/types/invalid_value.rb +0 -8
  58. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  59. data/lib/grape/validations/attributes_doc.rb +0 -60
@@ -3,54 +3,46 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module Validations
6
- extend ActiveSupport::Concern
7
-
8
- include Grape::DSL::Configuration
9
-
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
6
+ # Clears all defined parameters and validations. The main purpose of it is to clean up
7
+ # settings, so next endpoint won't interfere with previous one.
8
+ #
9
+ # params do
10
+ # # params for the endpoint below this block
11
+ # end
12
+ # post '/current' do
13
+ # # whatever
14
+ # end
15
+ #
16
+ # # somewhere between them the reset_validations! method gets called
17
+ #
18
+ # params do
19
+ # # params for the endpoint below this block
20
+ # end
21
+ # post '/next' do
22
+ # # whatever
23
+ # end
24
+ def reset_validations!
25
+ inheritable_setting.namespace_stackable.delete(:declared_params, :params, :validations)
26
+ end
34
27
 
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
28
+ # Opens a root-level ParamsScope, defining parameter coercions and
29
+ # validations for the endpoint.
30
+ # @yield instance context of the new scope
31
+ def params(&block)
32
+ Grape::Validations::ParamsScope.new(api: self, type: Hash, &block)
33
+ end
41
34
 
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)
35
+ # Declare the contract to be used for the endpoint's parameters.
36
+ # @param contract [Class<Dry::Validation::Contract> | Dry::Schema::Processor]
37
+ # The contract or schema to be used for validation. Optional.
38
+ # @yield a block yielding a new instance of Dry::Schema::Params
39
+ # subclass, allowing to define the schema inline. When the
40
+ # +contract+ parameter is a schema, it will be used as a parent. Optional.
41
+ def contract(contract = nil, &block)
42
+ raise ArgumentError, 'Either contract or block must be provided' unless contract || block
43
+ raise ArgumentError, 'Cannot inherit from contract, only schema' if block && contract.respond_to?(:schema)
51
44
 
52
- Grape::Validations::ContractScope.new(self, contract, &block)
53
- end
45
+ Grape::Validations::ContractScope.new(self, contract, &block)
54
46
  end
55
47
  end
56
48
  end
@@ -8,19 +8,15 @@ 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
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
@@ -36,33 +32,6 @@ module Grape
36
32
  superclass.run_before_each(endpoint) unless self == Endpoint
37
33
  before_each.each { |blk| blk.try(:call, endpoint) }
38
34
  end
39
-
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
65
- end
66
35
  end
67
36
 
68
37
  # Create a new endpoint.
@@ -86,11 +55,11 @@ module Grape
86
55
  # now +namespace_stackable(:declared_params)+ contains all params defined for
87
56
  # this endpoint and its parents, but later it will be cleaned up,
88
57
  # 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))
58
+ inheritable_setting.route[:declared_params] = inheritable_setting.namespace_stackable[:declared_params].flatten
59
+ inheritable_setting.route[:saved_validations] = inheritable_setting.namespace_stackable[:validations]
91
60
 
92
- namespace_stackable(:representations, []) unless namespace_stackable(:representations)
93
- namespace_inheritable(:default_error_status, 500) unless namespace_inheritable(:default_error_status)
61
+ inheritable_setting.namespace_stackable[:representations] = [] unless inheritable_setting.namespace_stackable[:representations]
62
+ inheritable_setting.namespace_inheritable[:default_error_status] = 500 unless inheritable_setting.namespace_inheritable[:default_error_status]
94
63
 
95
64
  @options = options
96
65
 
@@ -102,17 +71,11 @@ module Grape
102
71
 
103
72
  @lazy_initialize_lock = Mutex.new
104
73
  @lazy_initialized = nil
105
- @block = nil
106
-
107
74
  @status = nil
108
75
  @stream = nil
109
76
  @body = nil
110
- @proc = nil
111
-
112
- return unless block
113
-
114
77
  @source = block
115
- @block = self.class.generate_api_method(method_name, &block)
78
+ @helpers = build_helpers
116
79
  end
117
80
 
118
81
  # Update our settings from a given set of stackable parameters. Used when
@@ -130,14 +93,6 @@ module Grape
130
93
  raise Grape::Exceptions::MissingOption.new(key) unless options.key?(key)
131
94
  end
132
95
 
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
96
  def routes
142
97
  @routes ||= endpoints&.collect(&:routes)&.flatten || to_routes
143
98
  end
@@ -154,7 +109,7 @@ module Grape
154
109
  reset_routes!
155
110
  routes.each do |route|
156
111
  router.append(route.apply(self))
157
- next unless !namespace_inheritable(:do_not_route_head) && route.request_method == Rack::GET
112
+ next unless !inheritable_setting.namespace_inheritable[:do_not_route_head] && route.request_method == Rack::GET
158
113
 
159
114
  route.dup.then do |head_route|
160
115
  head_route.convert_to_head_request!
@@ -175,7 +130,7 @@ module Grape
175
130
  end
176
131
 
177
132
  def prepare_routes_requirements
178
- {}.merge!(*namespace_stackable(:namespace).map(&:requirements)).tap do |requirements|
133
+ {}.merge!(*inheritable_setting.namespace_stackable[:namespace].map(&:requirements)).tap do |requirements|
179
134
  endpoint_requirements = options.dig(:route_options, :requirements)
180
135
  requirements.merge!(endpoint_requirements) if endpoint_requirements
181
136
  end
@@ -186,7 +141,7 @@ module Grape
186
141
  namespace: namespace,
187
142
  version: prepare_version,
188
143
  requirements: prepare_routes_requirements,
189
- prefix: namespace_inheritable(:root_prefix),
144
+ prefix: inheritable_setting.namespace_inheritable[:root_prefix],
190
145
  anchor: options[:route_options].fetch(:anchor, true),
191
146
  settings: inheritable_setting.route.except(:declared_params, :saved_validations),
192
147
  forward_match: options[:forward_match]
@@ -194,7 +149,7 @@ module Grape
194
149
  end
195
150
 
196
151
  def prepare_version
197
- version = namespace_inheritable(:version)
152
+ version = inheritable_setting.namespace_inheritable[:version]
198
153
  return if version.blank?
199
154
 
200
155
  version.length == 1 ? version.first : version
@@ -211,7 +166,7 @@ module Grape
211
166
  end
212
167
 
213
168
  def namespace
214
- @namespace ||= Namespace.joined_space_path(namespace_stackable(:namespace))
169
+ @namespace ||= Namespace.joined_space_path(inheritable_setting.namespace_stackable[:namespace])
215
170
  end
216
171
 
217
172
  def call(env)
@@ -222,6 +177,8 @@ module Grape
222
177
  def call!(env)
223
178
  env[Grape::Env::API_ENDPOINT] = self
224
179
  @env = env
180
+ # this adds the helpers only to the instance
181
+ singleton_class.include(@helpers) if @helpers
225
182
  @app.call(env)
226
183
  end
227
184
 
@@ -248,7 +205,7 @@ module Grape
248
205
 
249
206
  def run
250
207
  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))
208
+ @request = Grape::Request.new(env, build_params_with: inheritable_setting.namespace_inheritable[:build_params_with])
252
209
  begin
253
210
  self.class.run_before_each(self)
254
211
  run_filters befores, :before
@@ -284,11 +241,14 @@ module Grape
284
241
  end
285
242
 
286
243
  def execute
287
- @block&.call(self)
288
- end
244
+ return unless @source
289
245
 
290
- def helpers
291
- lazy_initialize! && @helpers
246
+ ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: self) do
247
+ instance_exec(&@source)
248
+ rescue LocalJumpError => e
249
+ Grape.deprecator.warn 'Using `return` in an endpoint has been deprecated. Use `next` instead.'
250
+ return e.exit_value
251
+ end
292
252
  end
293
253
 
294
254
  def lazy_initialize!
@@ -297,9 +257,7 @@ module Grape
297
257
  @lazy_initialize_lock.synchronize do
298
258
  return true if @lazy_initialized
299
259
 
300
- @helpers = build_helpers&.tap { |mod| self.class.include mod }
301
- @app = options[:app] || build_stack(@helpers)
302
-
260
+ @app = options[:app] || build_stack
303
261
  @lazy_initialized = true
304
262
  end
305
263
  end
@@ -332,14 +290,16 @@ module Grape
332
290
 
333
291
  %i[befores before_validations after_validations afters finallies].each do |method|
334
292
  define_method method do
335
- namespace_stackable(method)
293
+ inheritable_setting.namespace_stackable[method]
336
294
  end
337
295
  end
338
296
 
339
297
  def validations
298
+ saved_validations = inheritable_setting.route[:saved_validations]
299
+ return if saved_validations.nil?
340
300
  return enum_for(:validations) unless block_given?
341
301
 
342
- route_setting(:saved_validations)&.each do |saved_validation|
302
+ saved_validations.each do |saved_validation|
343
303
  yield Grape::Validations::ValidatorFactory.create_validator(saved_validation)
344
304
  end
345
305
  end
@@ -351,45 +311,44 @@ module Grape
351
311
 
352
312
  private
353
313
 
354
- def build_stack(helpers)
314
+ def build_stack
355
315
  stack = Grape::Middleware::Stack.new
356
316
 
357
- content_types = namespace_stackable_with_hash(:content_types)
358
- format = namespace_inheritable(:format)
317
+ content_types = inheritable_setting.namespace_stackable_with_hash(:content_types)
318
+ format = inheritable_setting.namespace_inheritable[:format]
359
319
 
360
320
  stack.use Rack::Head
361
321
  stack.use Rack::Lint if lint?
362
- stack.use Class.new(Grape::Middleware::Error),
363
- helpers: helpers,
322
+ stack.use Grape::Middleware::Error,
364
323
  format: format,
365
324
  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
325
+ default_status: inheritable_setting.namespace_inheritable[:default_error_status],
326
+ rescue_all: inheritable_setting.namespace_inheritable[:rescue_all],
327
+ rescue_grape_exceptions: inheritable_setting.namespace_inheritable[:rescue_grape_exceptions],
328
+ default_error_formatter: inheritable_setting.namespace_inheritable[:default_error_formatter],
329
+ error_formatters: inheritable_setting.namespace_stackable_with_hash(:error_formatters),
330
+ rescue_options: inheritable_setting.namespace_stackable_with_hash(:rescue_options),
331
+ rescue_handlers: rescue_handlers,
332
+ base_only_rescue_handlers: inheritable_setting.namespace_stackable_with_hash(:base_only_rescue_handlers),
333
+ all_rescue_handler: inheritable_setting.namespace_inheritable[:all_rescue_handler],
334
+ grape_exceptions_rescue_handler: inheritable_setting.namespace_inheritable[:grape_exceptions_rescue_handler]
335
+
336
+ stack.concat inheritable_setting.namespace_stackable[:middleware]
337
+
338
+ if inheritable_setting.namespace_inheritable[:version].present?
339
+ stack.use Grape::Middleware::Versioner.using(inheritable_setting.namespace_inheritable[:version_options][:using]),
340
+ versions: inheritable_setting.namespace_inheritable[:version].flatten,
341
+ version_options: inheritable_setting.namespace_inheritable[:version_options],
342
+ prefix: inheritable_setting.namespace_inheritable[:root_prefix],
343
+ mount_path: inheritable_setting.namespace_stackable[:mount_path].first
385
344
  end
386
345
 
387
346
  stack.use Grape::Middleware::Formatter,
388
347
  format: format,
389
- default_format: namespace_inheritable(:default_format) || :txt,
348
+ default_format: inheritable_setting.namespace_inheritable[:default_format] || :txt,
390
349
  content_types: content_types,
391
- formatters: namespace_stackable_with_hash(:formatters),
392
- parsers: namespace_stackable_with_hash(:parsers)
350
+ formatters: inheritable_setting.namespace_stackable_with_hash(:formatters),
351
+ parsers: inheritable_setting.namespace_stackable_with_hash(:parsers)
393
352
 
394
353
  builder = stack.build
395
354
  builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
@@ -397,7 +356,7 @@ module Grape
397
356
  end
398
357
 
399
358
  def build_helpers
400
- helpers = namespace_stackable(:helpers)
359
+ helpers = inheritable_setting.namespace_stackable[:helpers]
401
360
  return if helpers.empty?
402
361
 
403
362
  Module.new { helpers.each { |mod_to_include| include mod_to_include } }
@@ -411,7 +370,16 @@ module Grape
411
370
  end
412
371
 
413
372
  def lint?
414
- namespace_inheritable(:lint) || Grape.config.lint
373
+ inheritable_setting.namespace_inheritable[:lint] || Grape.config.lint
374
+ end
375
+
376
+ def rescue_handlers
377
+ rescue_handlers = inheritable_setting.namespace_reverse_stackable[:rescue_handlers]
378
+ return if rescue_handlers.blank?
379
+
380
+ rescue_handlers.each_with_object({}) do |rescue_handler, result|
381
+ result.merge!(rescue_handler) { |_k, s1, _s2| s1 }
382
+ end
415
383
  end
416
384
  end
417
385
  end
@@ -58,6 +58,8 @@ module Grape
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)
@@ -17,7 +17,7 @@ module Grape
17
17
  end
18
18
 
19
19
  def [](index)
20
- send index
20
+ __send__ index
21
21
  end
22
22
 
23
23
  protected
@@ -9,5 +9,3 @@ module Grape
9
9
  end
10
10
  end
11
11
  end
12
-
13
- Grape::Exceptions::MissingGroupTypeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Grape::Exceptions::MissingGroupTypeError', 'Grape::Exceptions::MissingGroupType', Grape.deprecator)
@@ -9,5 +9,3 @@ module Grape
9
9
  end
10
10
  end
11
11
  end
12
-
13
- Grape::Exceptions::UnsupportedGroupTypeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Grape::Exceptions::UnsupportedGroupTypeError', 'Grape::Exceptions::UnsupportedGroupType', Grape.deprecator)
@@ -5,12 +5,11 @@ module Grape
5
5
  module Auth
6
6
  module DSL
7
7
  def auth(type = nil, options = {}, &block)
8
- if type
9
- namespace_inheritable(:auth, options.reverse_merge(type: type.to_sym, proc: block))
10
- use Grape::Middleware::Auth::Base, namespace_inheritable(:auth)
11
- else
12
- namespace_inheritable(:auth)
13
- end
8
+ namespace_inheritable = inheritable_setting.namespace_inheritable
9
+ return namespace_inheritable[:auth] unless type
10
+
11
+ namespace_inheritable[:auth] = options.reverse_merge(type: type.to_sym, proc: block)
12
+ use Grape::Middleware::Auth::Base, namespace_inheritable[:auth]
14
13
  end
15
14
 
16
15
  # Add HTTP Basic authorization to the API.
@@ -16,11 +16,6 @@ module Grape
16
16
  }.freeze
17
17
  }.freeze
18
18
 
19
- def initialize(app, **options)
20
- super
21
- self.class.include(options[:helpers]) if options[:helpers]
22
- end
23
-
24
19
  def call!(env)
25
20
  @env = env
26
21
  error_response(catch(:error) { return @app.call(@env) })
@@ -103,12 +98,7 @@ module Grape
103
98
  end
104
99
 
105
100
  def run_rescue_handler(handler, error, endpoint)
106
- if handler.instance_of?(Symbol)
107
- raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)
108
-
109
- handler = public_method(handler)
110
- end
111
-
101
+ handler = endpoint.public_method(handler) if handler.instance_of?(Symbol)
112
102
  response = catch(:error) do
113
103
  handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
114
104
  end
@@ -7,6 +7,8 @@ module Grape
7
7
  default_format: :txt
8
8
  }.freeze
9
9
 
10
+ ALL_MEDIA_TYPES = '*/*'
11
+
10
12
  def before
11
13
  negotiate_content_type
12
14
  read_body_input
@@ -132,13 +134,13 @@ module Grape
132
134
  dot_pos = request_path.rindex('.')
133
135
  return unless dot_pos
134
136
 
135
- extension = request_path[dot_pos + 1..]
137
+ extension = request_path[(dot_pos + 1)..]
136
138
  extension if content_type_for(extension)
137
139
  end
138
140
 
139
141
  def format_from_header
140
142
  accept_header = env['HTTP_ACCEPT'].try(:scrub)
141
- return if accept_header.blank?
143
+ return if accept_header.blank? || accept_header == ALL_MEDIA_TYPES
142
144
 
143
145
  media_type = Rack::Utils.best_q_match(accept_header, mime_types.keys)
144
146
  mime_types[media_type] if media_type
@@ -59,9 +59,9 @@ module Grape
59
59
 
60
60
  alias insert_before insert
61
61
 
62
- def insert_after(index, *args, &block)
62
+ def insert_after(index, ...)
63
63
  index = assert_index(index, :after)
64
- insert(index + 1, *args, &block)
64
+ insert(index + 1, ...)
65
65
  end
66
66
 
67
67
  def use(klass, *args, &block)
@@ -19,7 +19,7 @@ module Grape
19
19
  class AcceptVersionHeader < Base
20
20
  def before
21
21
  potential_version = env['HTTP_ACCEPT_VERSION'].try(:scrub)
22
- not_acceptable!('Accept-Version header must be set.') if strict? && potential_version.blank?
22
+ not_acceptable!('Accept-Version header must be set.') if strict && potential_version.blank?
23
23
 
24
24
  return if potential_version.blank?
25
25
 
@@ -5,68 +5,50 @@ module Grape
5
5
  module Versioner
6
6
  class Base < Grape::Middleware::Base
7
7
  DEFAULT_OPTIONS = {
8
- pattern: /.*/i.freeze,
8
+ pattern: /.*/i,
9
+ prefix: nil,
10
+ mount_path: nil,
9
11
  version_options: {
10
12
  strict: false,
11
13
  cascade: true,
12
- parameter: 'apiver'
14
+ parameter: 'apiver',
15
+ vendor: nil
13
16
  }.freeze
14
17
  }.freeze
15
18
 
16
- def self.inherited(klass)
17
- super
18
- Versioner.register(klass)
19
- end
20
-
21
- def versions
22
- options[:versions]
23
- end
24
-
25
- def prefix
26
- options[:prefix]
27
- end
28
-
29
- def mount_path
30
- options[:mount_path]
31
- end
19
+ CASCADE_PASS_HEADER = { 'X-Cascade' => 'pass' }.freeze
32
20
 
33
- def pattern
34
- options[:pattern]
21
+ DEFAULT_OPTIONS.each_key do |key|
22
+ define_method key do
23
+ options[key]
24
+ end
35
25
  end
36
26
 
37
- def version_options
38
- options[:version_options]
27
+ DEFAULT_OPTIONS[:version_options].each_key do |key|
28
+ define_method key do
29
+ options[:version_options][key]
30
+ end
39
31
  end
40
32
 
41
- def strict?
42
- version_options[:strict]
43
- end
44
-
45
- # By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
46
- # of routes (see Grape::Router) for more information). To prevent
47
- # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
48
- def cascade?
49
- version_options[:cascade]
50
- end
51
-
52
- def parameter_key
53
- version_options[:parameter]
33
+ def self.inherited(klass)
34
+ super
35
+ Versioner.register(klass)
54
36
  end
55
37
 
56
- def vendor
57
- version_options[:vendor]
58
- end
38
+ attr_reader :error_headers, :versions
59
39
 
60
- def error_headers
61
- cascade? ? { 'X-Cascade' => 'pass' } : {}
40
+ def initialize(app, **options)
41
+ super
42
+ @error_headers = cascade ? CASCADE_PASS_HEADER : {}
43
+ @versions = options[:versions]&.map(&:to_s) # making sure versions are strings to ease potential match
62
44
  end
63
45
 
64
46
  def potential_version_match?(potential_version)
65
- versions.blank? || versions.any? { |v| v.to_s == potential_version }
47
+ versions.blank? || versions.include?(potential_version)
66
48
  end
67
49
 
68
50
  def version_not_found!
69
- throw :error, status: 404, message: '404 API Version Not Found', headers: { 'X-Cascade' => 'pass' }
51
+ throw :error, status: 404, message: '404 API Version Not Found', headers: CASCADE_PASS_HEADER
70
52
  end
71
53
  end
72
54
  end
@@ -53,7 +53,7 @@ module Grape
53
53
  end
54
54
 
55
55
  def strict_header_checks!
56
- return unless strict?
56
+ return unless strict
57
57
 
58
58
  accept_header_check!
59
59
  version_and_vendor_check!
@@ -20,11 +20,11 @@ module Grape
20
20
  # env['api.version'] => 'v1'
21
21
  class Param < Base
22
22
  def before
23
- potential_version = query_params[parameter_key]
23
+ potential_version = query_params[parameter]
24
24
  return if potential_version.blank?
25
25
 
26
26
  version_not_found! unless potential_version_match?(potential_version)
27
- env[Grape::Env::API_VERSION] = env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key)
27
+ env[Grape::Env::API_VERSION] = env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter)
28
28
  end
29
29
  end
30
30
  end
@@ -28,7 +28,7 @@ module Grape
28
28
  slash_position = path_info.index('/', 1) # omit the first one
29
29
  return unless slash_position
30
30
 
31
- potential_version = path_info[1..slash_position - 1]
31
+ potential_version = path_info[1..(slash_position - 1)]
32
32
  return unless potential_version.match?(pattern)
33
33
 
34
34
  version_not_found! unless potential_version_match?(potential_version)