grape 1.3.0 → 1.3.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +3 -4
  4. data/lib/grape.rb +2 -3
  5. data/lib/grape/api.rb +2 -2
  6. data/lib/grape/api/instance.rb +4 -4
  7. data/lib/grape/content_types.rb +34 -0
  8. data/lib/grape/dsl/helpers.rb +1 -1
  9. data/lib/grape/dsl/inside_route.rb +10 -9
  10. data/lib/grape/dsl/parameters.rb +4 -4
  11. data/lib/grape/dsl/routing.rb +6 -4
  12. data/lib/grape/exceptions/base.rb +0 -4
  13. data/lib/grape/exceptions/validation_errors.rb +11 -12
  14. data/lib/grape/http/headers.rb +25 -0
  15. data/lib/grape/middleware/base.rb +1 -3
  16. data/lib/grape/middleware/stack.rb +2 -1
  17. data/lib/grape/middleware/versioner/header.rb +3 -3
  18. data/lib/grape/middleware/versioner/path.rb +1 -1
  19. data/lib/grape/namespace.rb +12 -2
  20. data/lib/grape/path.rb +11 -1
  21. data/lib/grape/request.rb +12 -7
  22. data/lib/grape/router.rb +16 -7
  23. data/lib/grape/router/pattern.rb +17 -16
  24. data/lib/grape/router/route.rb +2 -2
  25. data/lib/grape/util/base_inheritable.rb +4 -0
  26. data/lib/grape/util/cache.rb +20 -0
  27. data/lib/grape/util/lazy_object.rb +43 -0
  28. data/lib/grape/util/reverse_stackable_values.rb +1 -1
  29. data/lib/grape/util/stackable_values.rb +6 -21
  30. data/lib/grape/validations/params_scope.rb +1 -1
  31. data/lib/grape/validations/types/file.rb +1 -0
  32. data/lib/grape/validations/types/primitive_coercer.rb +7 -4
  33. data/lib/grape/validations/validators/coerce.rb +1 -1
  34. data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
  35. data/lib/grape/version.rb +1 -1
  36. data/spec/grape/api_spec.rb +7 -6
  37. data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
  38. data/spec/grape/middleware/formatter_spec.rb +2 -2
  39. data/spec/grape/middleware/stack_spec.rb +9 -0
  40. data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
  41. data/spec/grape/validations/types/primitive_coercer_spec.rb +75 -0
  42. data/spec/grape/validations/validators/coerce_spec.rb +15 -51
  43. data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
  44. data/spec/grape/validations_spec.rb +8 -12
  45. data/spec/spec_helper.rb +3 -0
  46. data/spec/support/eager_load.rb +19 -0
  47. metadata +12 -6
  48. data/lib/grape/util/content_types.rb +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 826e1365751c85c3f1f7d433e842a76c8a7f5dc6c4a1b13128679e4d56b2ccf5
4
- data.tar.gz: af8aef528c40764a53a3e397c306835745943a1d376ab3738bf7aa2547ef75d1
3
+ metadata.gz: 1711dd2fb0f0c86757c7e73d780907efa87e3da54e0a999a4b45c276b5eec92c
4
+ data.tar.gz: f5ba49001d1816d92130f70fb1b63ae061d7d9250142605f706d75c207880ad9
5
5
  SHA512:
6
- metadata.gz: 0af9a855214f1fbacd92403111aa1f4761b7257adf6ca9dbd6ab4e1cb889469b76a489971d058a58e98d3bf6906dacb67eb8a5eae11aad16782f318b3453988b
7
- data.tar.gz: 25004acd2431ab7cd174466e82ca5240b888ca045036227909ed9c8c886cd8706f1d3b696f4b1ede8eaa69c68c156fe064557afea8cdf53c9d39b136db7ae4b1
6
+ metadata.gz: 8b2dcf4d4903e3923dd10086a3371258404756cf294cd42c6fb64f3d2520fe7243c0accfe1b68f2e02fbaa85f29396c161d830cde4c5fdedd660b31e8d223a7f
7
+ data.tar.gz: 88a4d5e9740495430cd28ed1e213d6aafd92895c1b7966bd6358dd6aeb5b2e1f16eac2e14834d63e7791a6d66e16475ef30cdef5e37d10bca0f38f1d1a22c1e4
@@ -1,3 +1,25 @@
1
+ ### 1.3.1 (2020/03/11)
2
+
3
+ #### Features
4
+
5
+ * [#2005](https://github.com/ruby-grape/grape/pull/2005): Content types registrable - [@ericproulx](https://github.com/ericproulx).
6
+ * [#2003](https://github.com/ruby-grape/grape/pull/2003): Upgraded Rubocop to 0.80.1 - [@ericproulx](https://github.com/ericproulx).
7
+ * [#2002](https://github.com/ruby-grape/grape/pull/2002): Objects allocation optimization (lazy_lookup) - [@ericproulx](https://github.com/ericproulx).
8
+
9
+ #### Fixes
10
+
11
+ * [#2006](https://github.com/ruby-grape/grape/pull/2006): Fix explicit rescue StandardError - [@ericproulx](https://github.com/ericproulx).
12
+ * [#2004](https://github.com/ruby-grape/grape/pull/2004): Rubocop fixes - [@ericproulx](https://github.com/ericproulx).
13
+ * [#1995](https://github.com/ruby-grape/grape/pull/1995): Fix: "undefined instance variables" and "method redefined" warnings - [@nbeyer](https://github.com/nbeyer).
14
+ * [#1994](https://github.com/ruby-grape/grape/pull/1993): Fix typos in README - [@bellmyer](https://github.com/bellmyer).
15
+ * [#1993](https://github.com/ruby-grape/grape/pull/1993): Lazy join allow header - [@ericproulx](https://github.com/ericproulx).
16
+ * [#1987](https://github.com/ruby-grape/grape/pull/1987): Re-add exactly_one_of mutually exclusive error message - [@ZeroInputCtrl](https://github.com/ZeroInputCtrl).
17
+ * [#1977](https://github.com/ruby-grape/grape/pull/1977): Skip validation for a file if it is optional and nil - [@dnesteryuk](https://github.com/dnesteryuk).
18
+ * [#1976](https://github.com/ruby-grape/grape/pull/1976): Ensure classes/modules listed for autoload really exist - [@dnesteryuk](https://github.com/dnesteryuk).
19
+ * [#1971](https://github.com/ruby-grape/grape/pull/1971): Fix BigDecimal coercion - [@FlickStuart](https://github.com/FlickStuart).
20
+ * [#1968](https://github.com/ruby-grape/grape/pull/1968): Fix args forwarding in Grape::Middleware::Stack#merge_with for ruby 2.7.0 - [@dm1try](https://github.com/dm1try).
21
+ * [#1988](https://github.com/ruby-grape/grape/pull/1988): Refactored the full_messages method and stop overriding full_message - [@hosseintoussi](https://github.com/hosseintoussi).
22
+
1
23
  ### 1.3.0 (2020/01/11)
2
24
 
3
25
  #### Features
data/README.md CHANGED
@@ -154,8 +154,7 @@ content negotiation, versioning and much more.
154
154
 
155
155
  ## Stable Release
156
156
 
157
- You're reading the documentation for the stable release of Grape, **1.3.0**.
158
- Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
157
+ You're reading the documentation for the stable release of Grape, **1.3.1**.
159
158
 
160
159
  ## Project Resources
161
160
 
@@ -1722,7 +1721,7 @@ params do
1722
1721
  end
1723
1722
  ```
1724
1723
 
1725
- Every validation will have it's own instance of the validator, which means that the validator can have a state.
1724
+ Every validation will have its own instance of the validator, which means that the validator can have a state.
1726
1725
 
1727
1726
  ### Validation Errors
1728
1727
 
@@ -3301,7 +3300,7 @@ end
3301
3300
 
3302
3301
  Blocks can be executed before or after every API call, using `before`, `after`,
3303
3302
  `before_validation` and `after_validation`.
3304
- If the API fails the `after` call will not be trigered, if you need code to execute for sure
3303
+ If the API fails the `after` call will not be triggered, if you need code to execute for sure
3305
3304
  use the `finally`.
3306
3305
 
3307
3306
  Before and after callbacks execute in the following order:
@@ -20,7 +20,6 @@ require 'active_support/core_ext/hash/conversions'
20
20
  require 'active_support/dependencies/autoload'
21
21
  require 'active_support/notifications'
22
22
  require 'i18n'
23
- require 'thread'
24
23
 
25
24
  I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__)
26
25
 
@@ -84,7 +83,6 @@ module Grape
84
83
  eager_autoload do
85
84
  autoload :DeepMergeableHash
86
85
  autoload :DeepSymbolizeHash
87
- autoload :DeepHashWithIndifferentAccess
88
86
  autoload :Hash
89
87
  end
90
88
  module ActiveSupport
@@ -219,7 +217,8 @@ module Grape
219
217
  end
220
218
 
221
219
  require 'grape/config'
222
- require 'grape/util/content_types'
220
+ require 'grape/content_types'
221
+
223
222
  require 'grape/util/lazy_value'
224
223
  require 'grape/util/lazy_block'
225
224
  require 'grape/util/endpoint_configuration'
@@ -8,7 +8,7 @@ module Grape
8
8
  # should subclass this class in order to build an API.
9
9
  class API
10
10
  # Class methods that we want to call on the API rather than on the API object
11
- NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile!]).freeze
11
+ NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile! inherited]).freeze
12
12
 
13
13
  class << self
14
14
  attr_accessor :base_instance, :instances
@@ -175,7 +175,7 @@ module Grape
175
175
  if argument.respond_to?(:lazy?) && argument.lazy?
176
176
  argument.evaluate_from(configuration)
177
177
  elsif argument.is_a?(Hash)
178
- argument.map { |key, value| [key, evaluate_arguments(configuration, value).first] }.to_h
178
+ argument.transform_values { |value| evaluate_arguments(configuration, value).first }
179
179
  elsif argument.is_a?(Array)
180
180
  evaluate_arguments(configuration, *argument)
181
181
  else
@@ -74,7 +74,7 @@ module Grape
74
74
  # (see #cascade?)
75
75
  def cascade(value = nil)
76
76
  if value.nil?
77
- inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
77
+ inheritable_setting.namespace_inheritable.key?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
78
78
  else
79
79
  namespace_inheritable(:cascade, value)
80
80
  end
@@ -178,7 +178,7 @@ module Grape
178
178
  # errors from reaching upstream. This is effectivelly done by unsetting
179
179
  # X-Cascade. Default :cascade is true.
180
180
  def cascade?
181
- return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
181
+ return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.key?(:cascade)
182
182
  return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
183
183
  true
184
184
  end
@@ -209,7 +209,7 @@ module Grape
209
209
  route_settings[:endpoint] = route.app
210
210
 
211
211
  # using the :any shorthand produces [nil] for route methods, substitute all manually
212
- route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*')
212
+ route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS if route_settings[:methods].include?('*')
213
213
  end
214
214
  end
215
215
 
@@ -227,7 +227,7 @@ module Grape
227
227
  allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
228
228
  end
229
229
 
230
- allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
230
+ allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods)
231
231
 
232
232
  unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
233
233
  config[:endpoint].options[:options_route_enabled] = true
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'grape/util/registrable'
4
+
5
+ module Grape
6
+ module ContentTypes
7
+ extend Util::Registrable
8
+
9
+ # Content types are listed in order of preference.
10
+ CONTENT_TYPES = {
11
+ xml: 'application/xml',
12
+ serializable_hash: 'application/json',
13
+ json: 'application/json',
14
+ binary: 'application/octet-stream',
15
+ txt: 'text/plain'
16
+ }.freeze
17
+
18
+ class << self
19
+ def content_types_for_settings(settings)
20
+ return if settings.blank?
21
+
22
+ settings.each_with_object({}) { |value, result| result.merge!(value) }
23
+ end
24
+
25
+ def content_types_for(from_settings)
26
+ if from_settings.present?
27
+ from_settings
28
+ else
29
+ Grape::ContentTypes::CONTENT_TYPES.merge(default_elements)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -94,7 +94,7 @@ module Grape
94
94
  protected
95
95
 
96
96
  def process_named_params
97
- return unless @named_params && @named_params.any?
97
+ return unless instance_variable_defined?(:@named_params) && @named_params && @named_params.any?
98
98
  api.namespace_stackable(:named_params, @named_params)
99
99
  end
100
100
  end
@@ -177,17 +177,17 @@ module Grape
177
177
  def status(status = nil)
178
178
  case status
179
179
  when Symbol
180
- raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.keys.include?(status)
180
+ raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(status)
181
181
  @status = Rack::Utils.status_code(status)
182
182
  when Integer
183
183
  @status = status
184
184
  when nil
185
- return @status if @status
185
+ return @status if instance_variable_defined?(:@status) && @status
186
186
  case request.request_method.to_s.upcase
187
187
  when Grape::Http::Headers::POST
188
188
  201
189
189
  when Grape::Http::Headers::DELETE
190
- if @body.present?
190
+ if instance_variable_defined?(:@body) && @body.present?
191
191
  200
192
192
  else
193
193
  204
@@ -238,7 +238,7 @@ module Grape
238
238
  @body = ''
239
239
  status 204
240
240
  else
241
- @body
241
+ instance_variable_defined?(:@body) ? @body : nil
242
242
  end
243
243
  end
244
244
 
@@ -272,7 +272,7 @@ module Grape
272
272
  warn '[DEPRECATION] Argument as FileStreamer-like object is deprecated. Use path to file instead.'
273
273
  @file = Grape::ServeFile::FileResponse.new(value)
274
274
  else
275
- @file
275
+ instance_variable_defined?(:@file) ? @file : nil
276
276
  end
277
277
  end
278
278
 
@@ -331,11 +331,12 @@ module Grape
331
331
  end
332
332
 
333
333
  representation = { root => representation } if root
334
+
334
335
  if key
335
- representation = (@body || {}).merge(key => representation)
336
- elsif entity_class.present? && @body
336
+ representation = (body || {}).merge(key => representation)
337
+ elsif entity_class.present? && body
337
338
  raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
338
- representation = @body.merge(representation)
339
+ representation = body.merge(representation)
339
340
  end
340
341
 
341
342
  body representation
@@ -387,7 +388,7 @@ module Grape
387
388
  def entity_representation_for(entity_class, object, options)
388
389
  embeds = { env: env }
389
390
  embeds[:version] = env[Grape::Env::API_VERSION] if env[Grape::Env::API_VERSION]
390
- entity_class.represent(object, **embeds.merge(options))
391
+ entity_class.represent(object, embeds.merge(options))
391
392
  end
392
393
  end
393
394
  end
@@ -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.
@@ -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,6 +163,8 @@ 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
@@ -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
@@ -27,6 +29,29 @@ module Grape
27
29
 
28
30
  FORMAT = 'format'
29
31
 
32
+ HTTP_HEADERS = Grape::Util::LazyObject.new do
33
+ common_http_headers = %w[
34
+ Version
35
+ Host
36
+ Connection
37
+ Cache-Control
38
+ Dnt
39
+ Upgrade-Insecure-Requests
40
+ User-Agent
41
+ Sec-Fetch-Dest
42
+ Accept
43
+ Sec-Fetch-Site
44
+ Sec-Fetch-Mode
45
+ Sec-Fetch-User
46
+ Accept-Encoding
47
+ Accept-Language
48
+ Cookie
49
+ ].freeze
50
+ common_http_headers.each_with_object({}) do |header, response|
51
+ response["HTTP_#{header.upcase.tr('-', '_')}"] = header
52
+ end.freeze
53
+ end
54
+
30
55
  def self.find_supported_method(route_method)
31
56
  Grape::Http::Headers::SUPPORTED_METHODS.detect { |supported_method| supported_method.casecmp(route_method).zero? }
32
57
  end
@@ -74,11 +74,9 @@ module Grape
74
74
  end
75
75
 
76
76
  def mime_types
77
- types_without_params = {}
78
- content_types.each_pair do |k, v|
77
+ @mime_type ||= content_types.each_pair.with_object({}) do |(k, v), types_without_params|
79
78
  types_without_params[v.split(';').first] = k
80
79
  end
81
- types_without_params
82
80
  end
83
81
 
84
82
  private
@@ -78,7 +78,8 @@ module Grape
78
78
  def merge_with(middleware_specs)
79
79
  middleware_specs.each do |operation, *args|
80
80
  if args.last.is_a?(Proc)
81
- public_send(operation, *args, &args.pop)
81
+ last_proc = args.pop
82
+ public_send(operation, *args, &last_proc)
82
83
  else
83
84
  public_send(operation, *args)
84
85
  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?
@@ -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