grape 1.3.0 → 1.5.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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -0
  3. data/LICENSE +1 -1
  4. data/README.md +104 -21
  5. data/UPGRADING.md +243 -39
  6. data/lib/grape.rb +4 -5
  7. data/lib/grape/api.rb +4 -4
  8. data/lib/grape/api/instance.rb +32 -31
  9. data/lib/grape/content_types.rb +34 -0
  10. data/lib/grape/dsl/helpers.rb +2 -1
  11. data/lib/grape/dsl/inside_route.rb +76 -42
  12. data/lib/grape/dsl/parameters.rb +4 -4
  13. data/lib/grape/dsl/routing.rb +8 -8
  14. data/lib/grape/dsl/validations.rb +18 -1
  15. data/lib/grape/eager_load.rb +1 -1
  16. data/lib/grape/endpoint.rb +8 -6
  17. data/lib/grape/exceptions/base.rb +0 -4
  18. data/lib/grape/exceptions/validation_errors.rb +11 -12
  19. data/lib/grape/http/headers.rb +26 -0
  20. data/lib/grape/middleware/base.rb +3 -4
  21. data/lib/grape/middleware/error.rb +10 -12
  22. data/lib/grape/middleware/formatter.rb +3 -3
  23. data/lib/grape/middleware/stack.rb +19 -5
  24. data/lib/grape/middleware/versioner/header.rb +4 -4
  25. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  26. data/lib/grape/middleware/versioner/path.rb +1 -1
  27. data/lib/grape/namespace.rb +12 -2
  28. data/lib/grape/path.rb +13 -3
  29. data/lib/grape/request.rb +13 -8
  30. data/lib/grape/router.rb +26 -30
  31. data/lib/grape/router/attribute_translator.rb +25 -4
  32. data/lib/grape/router/pattern.rb +17 -16
  33. data/lib/grape/router/route.rb +5 -24
  34. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  35. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  36. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  37. data/lib/grape/util/base_inheritable.rb +15 -8
  38. data/lib/grape/util/cache.rb +20 -0
  39. data/lib/grape/util/lazy_object.rb +43 -0
  40. data/lib/grape/util/lazy_value.rb +1 -0
  41. data/lib/grape/util/reverse_stackable_values.rb +2 -0
  42. data/lib/grape/util/stackable_values.rb +7 -20
  43. data/lib/grape/validations/params_scope.rb +6 -5
  44. data/lib/grape/validations/types.rb +6 -5
  45. data/lib/grape/validations/types/array_coercer.rb +14 -5
  46. data/lib/grape/validations/types/build_coercer.rb +5 -8
  47. data/lib/grape/validations/types/custom_type_coercer.rb +14 -2
  48. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  49. data/lib/grape/validations/types/file.rb +15 -12
  50. data/lib/grape/validations/types/json.rb +40 -36
  51. data/lib/grape/validations/types/primitive_coercer.rb +15 -6
  52. data/lib/grape/validations/types/set_coercer.rb +6 -4
  53. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  54. data/lib/grape/validations/validators/as.rb +1 -1
  55. data/lib/grape/validations/validators/base.rb +2 -4
  56. data/lib/grape/validations/validators/coerce.rb +4 -11
  57. data/lib/grape/validations/validators/default.rb +3 -5
  58. data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
  59. data/lib/grape/validations/validators/except_values.rb +1 -1
  60. data/lib/grape/validations/validators/regexp.rb +1 -1
  61. data/lib/grape/validations/validators/values.rb +1 -1
  62. data/lib/grape/version.rb +1 -1
  63. data/spec/grape/api/instance_spec.rb +50 -0
  64. data/spec/grape/api_spec.rb +82 -6
  65. data/spec/grape/dsl/inside_route_spec.rb +182 -33
  66. data/spec/grape/endpoint/declared_spec.rb +590 -0
  67. data/spec/grape/endpoint_spec.rb +0 -521
  68. data/spec/grape/entity_spec.rb +6 -0
  69. data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
  70. data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
  71. data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
  72. data/spec/grape/middleware/error_spec.rb +1 -1
  73. data/spec/grape/middleware/formatter_spec.rb +3 -3
  74. data/spec/grape/middleware/stack_spec.rb +12 -1
  75. data/spec/grape/path_spec.rb +4 -4
  76. data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
  77. data/spec/grape/validations/params_scope_spec.rb +26 -0
  78. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  79. data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
  80. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  81. data/spec/grape/validations/types_spec.rb +1 -1
  82. data/spec/grape/validations/validators/coerce_spec.rb +329 -77
  83. data/spec/grape/validations/validators/default_spec.rb +170 -0
  84. data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
  85. data/spec/grape/validations/validators/except_values_spec.rb +1 -0
  86. data/spec/grape/validations/validators/values_spec.rb +1 -1
  87. data/spec/grape/validations_spec.rb +30 -30
  88. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  89. data/spec/spec_helper.rb +3 -10
  90. data/spec/support/chunks.rb +14 -0
  91. data/spec/support/eager_load.rb +19 -0
  92. data/spec/support/versioned_helpers.rb +3 -5
  93. metadata +121 -105
  94. data/lib/grape/util/content_types.rb +0 -28
@@ -127,7 +127,7 @@ module Grape
127
127
 
128
128
  opts = attrs.extract_options!.clone
129
129
  opts[:presence] = { value: true, message: opts[:message] }
130
- opts = @group.merge(opts) if @group
130
+ opts = @group.merge(opts) if instance_variable_defined?(:@group) && @group
131
131
 
132
132
  if opts[:using]
133
133
  require_required_and_optional_fields(attrs.first, opts)
@@ -146,7 +146,7 @@ module Grape
146
146
 
147
147
  opts = attrs.extract_options!.clone
148
148
  type = opts[:type]
149
- opts = @group.merge(opts) if @group
149
+ opts = @group.merge(opts) if instance_variable_defined?(:@group) && @group
150
150
 
151
151
  # check type for optional parameter group
152
152
  if attrs && block_given?
@@ -243,8 +243,8 @@ module Grape
243
243
  # @return hash of parameters relevant for the current scope
244
244
  # @api private
245
245
  def params(params)
246
- params = @parent.params(params) if @parent
247
- params = map_params(params, @element) if @element
246
+ params = @parent.params(params) if instance_variable_defined?(:@parent) && @parent
247
+ params = map_params(params, @element) if instance_variable_defined?(:@element) && @element
248
248
  params
249
249
  end
250
250
 
@@ -51,7 +51,7 @@ module Grape
51
51
  end
52
52
  end
53
53
 
54
- @versions.last unless @versions.nil?
54
+ @versions.last if instance_variable_defined?(:@versions) && @versions
55
55
  end
56
56
 
57
57
  # Define a root URL prefix for your entire API.
@@ -79,7 +79,7 @@ module Grape
79
79
  namespace_inheritable(:do_not_route_options, true)
80
80
  end
81
81
 
82
- def mount(mounts, opts = {})
82
+ def mount(mounts, **opts)
83
83
  mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
84
84
  mounts.each_pair do |app, path|
85
85
  if app.respond_to?(:mount_instance)
@@ -142,11 +142,11 @@ module Grape
142
142
  reset_validations!
143
143
  end
144
144
 
145
- %w[get post put head delete options patch].each do |meth|
146
- define_method meth do |*args, &block|
145
+ Grape::Http::Headers::SUPPORTED_METHODS.each do |supported_method|
146
+ define_method supported_method.downcase do |*args, &block|
147
147
  options = args.extract_options!
148
148
  paths = args.first || ['/']
149
- route(meth.upcase, paths, options, &block)
149
+ route(supported_method, paths, options, &block)
150
150
  end
151
151
  end
152
152
 
@@ -163,14 +163,14 @@ module Grape
163
163
  # end
164
164
  # end
165
165
  def namespace(space = nil, options = {}, &block)
166
+ @namespace_description = nil unless instance_variable_defined?(:@namespace_description) && @namespace_description
167
+
166
168
  if space || block_given?
167
169
  within_namespace do
168
170
  previous_namespace_description = @namespace_description
169
171
  @namespace_description = (@namespace_description || {}).deep_merge(namespace_setting(:description) || {})
170
172
  nest(block) do
171
- if space
172
- namespace_stackable(:namespace, Namespace.new(space, **options))
173
- end
173
+ namespace_stackable(:namespace, Namespace.new(space, **options)) if space
174
174
  end
175
175
  @namespace_description = previous_namespace_description
176
176
  end
@@ -10,7 +10,24 @@ module Grape
10
10
  include Grape::DSL::Configuration
11
11
 
12
12
  module ClassMethods
13
- # Clears all defined parameters and validations.
13
+ # Clears all defined parameters and validations. The main purpose of it is to clean up
14
+ # settings, so next endpoint won't interfere with previous one.
15
+ #
16
+ # params do
17
+ # # params for the endpoint below this block
18
+ # end
19
+ # post '/current' do
20
+ # # whatever
21
+ # end
22
+ #
23
+ # # somewhere between them the reset_validations! method gets called
24
+ #
25
+ # params do
26
+ # # params for the endpoint below this block
27
+ # end
28
+ # post '/next' do
29
+ # # whatever
30
+ # end
14
31
  def reset_validations!
15
32
  unset_namespace_stackable :declared_params
16
33
  unset_namespace_stackable :validations
@@ -16,5 +16,5 @@ Grape::Parser.eager_load!
16
16
  Grape::DSL.eager_load!
17
17
  Grape::API.eager_load!
18
18
  Grape::Presenters.eager_load!
19
- Grape::ServeFile.eager_load!
19
+ Grape::ServeStream.eager_load!
20
20
  Rack::Head # AutoLoads the Rack::Head
@@ -80,7 +80,10 @@ module Grape
80
80
 
81
81
  self.inheritable_setting = new_settings.point_in_time_copy
82
82
 
83
- route_setting(:saved_declared_params, namespace_stackable(:declared_params))
83
+ # now +namespace_stackable(:declared_params)+ contains all params defined for
84
+ # this endpoint and its parents, but later it will be cleaned up,
85
+ # see +reset_validations!+ in lib/grape/dsl/validations.rb
86
+ route_setting(:declared_params, namespace_stackable(:declared_params).flatten)
84
87
  route_setting(:saved_validations, namespace_stackable(:validations))
85
88
 
86
89
  namespace_stackable(:representations, []) unless namespace_stackable(:representations)
@@ -99,7 +102,7 @@ module Grape
99
102
  @block = nil
100
103
 
101
104
  @status = nil
102
- @file = nil
105
+ @stream = nil
103
106
  @body = nil
104
107
  @proc = nil
105
108
 
@@ -116,7 +119,6 @@ module Grape
116
119
  parent_declared_params = namespace_stackable[:declared_params]
117
120
 
118
121
  if parent_declared_params
119
- inheritable_setting.route[:declared_params] ||= []
120
122
  inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten)
121
123
  end
122
124
 
@@ -190,7 +192,7 @@ module Grape
190
192
  requirements: prepare_routes_requirements,
191
193
  prefix: namespace_inheritable(:root_prefix),
192
194
  anchor: options[:route_options].fetch(:anchor, true),
193
- settings: inheritable_setting.route.except(:saved_declared_params, :saved_validations),
195
+ settings: inheritable_setting.route.except(:declared_params, :saved_validations),
194
196
  forward_match: options[:forward_match]
195
197
  }
196
198
  end
@@ -271,8 +273,8 @@ module Grape
271
273
  # status verifies body presence when DELETE
272
274
  @body ||= response_object
273
275
 
274
- # The body commonly is an Array of Strings, the application instance itself, or a File-like object
275
- response_object = file || [body]
276
+ # The body commonly is an Array of Strings, the application instance itself, or a Stream-like object
277
+ response_object = stream || [body]
276
278
 
277
279
  [status, header, response_object]
278
280
  ensure
@@ -57,10 +57,6 @@ module Grape
57
57
  end.join(', ')
58
58
  end
59
59
 
60
- def translate_attribute(key, **options)
61
- translate("#{BASE_ATTRIBUTES_KEY}.#{key}", default: key, **options)
62
- end
63
-
64
60
  def translate_message(key, **options)
65
61
  case key
66
62
  when Symbol
@@ -5,6 +5,9 @@ require 'grape/exceptions/base'
5
5
  module Grape
6
6
  module Exceptions
7
7
  class ValidationErrors < Grape::Exceptions::Base
8
+ ERRORS_FORMAT_KEY = 'grape.errors.format'
9
+ DEFAULT_ERRORS_FORMAT = '%{attributes} %{message}'
10
+
8
11
  include Enumerable
9
12
 
10
13
  attr_reader :errors
@@ -41,21 +44,17 @@ module Grape
41
44
  end
42
45
 
43
46
  def full_messages
44
- messages = map { |attributes, error| full_message(attributes, error) }
47
+ messages = map do |attributes, error|
48
+ I18n.t(
49
+ ERRORS_FORMAT_KEY,
50
+ default: DEFAULT_ERRORS_FORMAT,
51
+ attributes: translate_attributes(attributes),
52
+ message: error.message
53
+ )
54
+ end
45
55
  messages.uniq!
46
56
  messages
47
57
  end
48
-
49
- private
50
-
51
- def full_message(attributes, error)
52
- I18n.t(
53
- 'grape.errors.format',
54
- default: '%{attributes} %{message}',
55
- attributes: attributes.count == 1 ? translate_attribute(attributes.first) : translate_attributes(attributes),
56
- message: error.message
57
- )
58
- end
59
58
  end
60
59
  end
61
60
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'grape/util/lazy_object'
4
+
3
5
  module Grape
4
6
  module Http
5
7
  module Headers
@@ -19,6 +21,7 @@ module Grape
19
21
  OPTIONS = 'OPTIONS'
20
22
 
21
23
  SUPPORTED_METHODS = [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS].freeze
24
+ SUPPORTED_METHODS_WITHOUT_OPTIONS = Grape::Util::LazyObject.new { [GET, POST, PUT, PATCH, DELETE, HEAD].freeze }
22
25
 
23
26
  HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION'
24
27
  X_CASCADE = 'X-Cascade'
@@ -27,6 +30,29 @@ module Grape
27
30
 
28
31
  FORMAT = 'format'
29
32
 
33
+ HTTP_HEADERS = Grape::Util::LazyObject.new do
34
+ common_http_headers = %w[
35
+ Version
36
+ Host
37
+ Connection
38
+ Cache-Control
39
+ Dnt
40
+ Upgrade-Insecure-Requests
41
+ User-Agent
42
+ Sec-Fetch-Dest
43
+ Accept
44
+ Sec-Fetch-Site
45
+ Sec-Fetch-Mode
46
+ Sec-Fetch-User
47
+ Accept-Encoding
48
+ Accept-Language
49
+ Cookie
50
+ ].freeze
51
+ common_http_headers.each_with_object({}) do |header, response|
52
+ response["HTTP_#{header.upcase.tr('-', '_')}"] = header
53
+ end.freeze
54
+ end
55
+
30
56
  def self.find_supported_method(route_method)
31
57
  Grape::Http::Headers::SUPPORTED_METHODS.detect { |supported_method| supported_method.casecmp(route_method).zero? }
32
58
  end
@@ -8,6 +8,7 @@ module Grape
8
8
  include Helpers
9
9
 
10
10
  attr_reader :app, :env, :options
11
+
11
12
  TEXT_HTML = 'text/html'
12
13
 
13
14
  include Grape::DSL::Headers
@@ -16,7 +17,7 @@ module Grape
16
17
  # @param [Hash] options A hash of options, simply stored for use by subclasses.
17
18
  def initialize(app, **options)
18
19
  @app = app
19
- @options = default_options.merge(**options)
20
+ @options = default_options.merge(options)
20
21
  @app_response = nil
21
22
  end
22
23
 
@@ -74,11 +75,9 @@ module Grape
74
75
  end
75
76
 
76
77
  def mime_types
77
- types_without_params = {}
78
- content_types.each_pair do |k, v|
78
+ @mime_type ||= content_types.each_pair.with_object({}) do |(k, v), types_without_params|
79
79
  types_without_params[v.split(';').first] = k
80
80
  end
81
- types_without_params
82
81
  end
83
82
 
84
83
  private
@@ -19,11 +19,11 @@ module Grape
19
19
  rescue_subclasses: true, # rescue subclasses of exceptions listed
20
20
  rescue_options: {
21
21
  backtrace: false, # true to display backtrace, true to let Grape handle Grape::Exceptions
22
- original_exception: false, # true to display exception
22
+ original_exception: false # true to display exception
23
23
  },
24
24
  rescue_handlers: {}, # rescue handler blocks
25
25
  base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
26
- all_rescue_handler: nil, # rescue handler block to rescue from all exceptions
26
+ all_rescue_handler: nil # rescue handler block to rescue from all exceptions
27
27
  }
28
28
  end
29
29
 
@@ -38,15 +38,15 @@ module Grape
38
38
  error_response(catch(:error) do
39
39
  return @app.call(@env)
40
40
  end)
41
- rescue Exception => error # rubocop:disable Lint/RescueException
41
+ rescue Exception => e # rubocop:disable Lint/RescueException
42
42
  handler =
43
- rescue_handler_for_base_only_class(error.class) ||
44
- rescue_handler_for_class_or_its_ancestor(error.class) ||
45
- rescue_handler_for_grape_exception(error.class) ||
46
- rescue_handler_for_any_class(error.class) ||
43
+ rescue_handler_for_base_only_class(e.class) ||
44
+ rescue_handler_for_class_or_its_ancestor(e.class) ||
45
+ rescue_handler_for_grape_exception(e.class) ||
46
+ rescue_handler_for_any_class(e.class) ||
47
47
  raise
48
48
 
49
- run_rescue_handler(handler, error)
49
+ run_rescue_handler(handler, e)
50
50
  end
51
51
  end
52
52
 
@@ -65,15 +65,13 @@ module Grape
65
65
  message = error[:message] || options[:default_message]
66
66
  headers = { Grape::Http::Headers::CONTENT_TYPE => content_type }
67
67
  headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
68
- backtrace = error[:backtrace] || error[:original_exception] && error[:original_exception].backtrace || []
68
+ backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
69
69
  original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
70
70
  rack_response(format_message(message, backtrace, original_exception), status, headers)
71
71
  end
72
72
 
73
73
  def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type })
74
- if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
75
- message = ERB::Util.html_escape(message)
76
- end
74
+ message = ERB::Util.html_escape(message) if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
77
75
  Rack::Response.new([message], status, headers)
78
76
  end
79
77
 
@@ -36,9 +36,9 @@ module Grape
36
36
  def build_formatted_response(status, headers, bodies)
37
37
  headers = ensure_content_type(headers)
38
38
 
39
- if bodies.is_a?(Grape::ServeFile::FileResponse)
40
- Grape::ServeFile::SendfileResponse.new([], status, headers) do |resp|
41
- resp.body = bodies.file
39
+ if bodies.is_a?(Grape::ServeStream::StreamResponse)
40
+ Grape::ServeStream::SendfileResponse.new([], status, headers) do |resp|
41
+ resp.body = bodies.stream
42
42
  end
43
43
  else
44
44
  # Allow content-type to be explicitly overwritten
@@ -6,11 +6,12 @@ module Grape
6
6
  # It allows to insert and insert after
7
7
  class Stack
8
8
  class Middleware
9
- attr_reader :args, :block, :klass
9
+ attr_reader :args, :opts, :block, :klass
10
10
 
11
- def initialize(klass, *args, &block)
11
+ def initialize(klass, *args, **opts, &block)
12
12
  @klass = klass
13
- @args = args
13
+ @args = args
14
+ @opts = opts
14
15
  @block = block
15
16
  end
16
17
 
@@ -30,6 +31,18 @@ module Grape
30
31
  def inspect
31
32
  klass.to_s
32
33
  end
34
+
35
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7')
36
+ def use_in(builder)
37
+ block ? builder.use(klass, *args, **opts, &block) : builder.use(klass, *args, **opts)
38
+ end
39
+ else
40
+ def use_in(builder)
41
+ args = self.args
42
+ args += [opts] unless opts.empty?
43
+ block ? builder.use(klass, *args, &block) : builder.use(klass, *args)
44
+ end
45
+ end
33
46
  end
34
47
 
35
48
  include Enumerable
@@ -78,7 +91,8 @@ module Grape
78
91
  def merge_with(middleware_specs)
79
92
  middleware_specs.each do |operation, *args|
80
93
  if args.last.is_a?(Proc)
81
- public_send(operation, *args, &args.pop)
94
+ last_proc = args.pop
95
+ public_send(operation, *args, &last_proc)
82
96
  else
83
97
  public_send(operation, *args)
84
98
  end
@@ -89,7 +103,7 @@ module Grape
89
103
  def build(builder = Rack::Builder.new)
90
104
  others.shift(others.size).each(&method(:merge_with))
91
105
  middlewares.each do |m|
92
- m.block ? builder.use(m.klass, *m.args, &m.block) : builder.use(m.klass, *m.args)
106
+ m.use_in(builder)
93
107
  end
94
108
  builder
95
109
  end
@@ -26,10 +26,10 @@ module Grape
26
26
  # route.
27
27
  class Header < Base
28
28
  VENDOR_VERSION_HEADER_REGEX =
29
- /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z/
29
+ /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z/.freeze
30
30
 
31
- HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#\$&\^]+/
32
- HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))+/
31
+ HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#\$&\^]+/.freeze
32
+ HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))+/.freeze
33
33
 
34
34
  def before
35
35
  strict_header_checks if strict?
@@ -63,7 +63,7 @@ module Grape
63
63
 
64
64
  def an_accept_header_with_version_and_vendor_is_present?
65
65
  header.qvalues.keys.any? do |h|
66
- VENDOR_VERSION_HEADER_REGEX =~ h.sub('application/', '')
66
+ VENDOR_VERSION_HEADER_REGEX.match?(h.sub('application/', ''))
67
67
  end
68
68
  end
69
69
 
@@ -3,11 +3,12 @@
3
3
  module Rack
4
4
  module Accept
5
5
  module Header
6
+ ALLOWED_CHARACTERS = %r{^([a-z*]+)\/([a-z0-9*\&\^\-_#\$!.+]+)(?:;([a-z0-9=;]+))?$}.freeze
6
7
  class << self
7
8
  # Corrected version of https://github.com/mjackson/rack-accept/blob/master/lib/rack/accept/header.rb#L40-L44
8
9
  def parse_media_type(media_type)
9
10
  # see http://tools.ietf.org/html/rfc6838#section-4.2 for allowed characters in media type names
10
- m = media_type.to_s.match(%r{^([a-z*]+)\/([a-z0-9*\&\^\-_#\$!.+]+)(?:;([a-z0-9=;]+))?$})
11
+ m = media_type&.match(ALLOWED_CHARACTERS)
11
12
  m ? [m[1], m[2], m[3] || ''] : []
12
13
  end
13
14
  end
@@ -36,7 +36,7 @@ module Grape
36
36
 
37
37
  pieces = path.split('/')
38
38
  potential_version = pieces[1]
39
- return unless potential_version =~ options[:pattern]
39
+ return unless potential_version&.match?(options[:pattern])
40
40
  throw :error, status: 404, message: '404 API Version Not Found' if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
41
41
  env[Grape::Env::API_VERSION] = potential_version
42
42
  end