grape 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +9 -4
  3. data/CHANGELOG.md +28 -0
  4. data/Gemfile +0 -1
  5. data/Gemfile.lock +166 -0
  6. data/README.md +305 -163
  7. data/Rakefile +30 -33
  8. data/UPGRADING.md +31 -0
  9. data/benchmark/simple.rb +27 -0
  10. data/gemfiles/rack_1.5.2.gemfile +13 -0
  11. data/gemfiles/rails_3.gemfile +2 -2
  12. data/gemfiles/rails_4.gemfile +1 -2
  13. data/grape.gemspec +5 -4
  14. data/lib/grape.rb +9 -5
  15. data/lib/grape/dsl/configuration.rb +5 -2
  16. data/lib/grape/dsl/helpers.rb +8 -3
  17. data/lib/grape/dsl/inside_route.rb +67 -44
  18. data/lib/grape/dsl/parameters.rb +21 -12
  19. data/lib/grape/dsl/request_response.rb +1 -1
  20. data/lib/grape/dsl/routing.rb +3 -4
  21. data/lib/grape/endpoint.rb +63 -28
  22. data/lib/grape/error_formatter/base.rb +6 -6
  23. data/lib/grape/exceptions/base.rb +5 -5
  24. data/lib/grape/exceptions/invalid_version_header.rb +10 -0
  25. data/lib/grape/formatter/serializable_hash.rb +3 -2
  26. data/lib/grape/locale/en.yml +4 -1
  27. data/lib/grape/middleware/auth/base.rb +2 -2
  28. data/lib/grape/middleware/auth/dsl.rb +1 -1
  29. data/lib/grape/middleware/auth/strategies.rb +1 -1
  30. data/lib/grape/middleware/base.rb +7 -4
  31. data/lib/grape/middleware/error.rb +3 -2
  32. data/lib/grape/middleware/filter.rb +1 -1
  33. data/lib/grape/middleware/formatter.rb +47 -44
  34. data/lib/grape/middleware/globals.rb +3 -3
  35. data/lib/grape/middleware/versioner/accept_version_header.rb +5 -7
  36. data/lib/grape/middleware/versioner/header.rb +113 -50
  37. data/lib/grape/middleware/versioner/param.rb +5 -8
  38. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +20 -0
  39. data/lib/grape/middleware/versioner/path.rb +3 -6
  40. data/lib/grape/path.rb +3 -3
  41. data/lib/grape/request.rb +40 -0
  42. data/lib/grape/util/content_types.rb +9 -9
  43. data/lib/grape/util/env.rb +22 -0
  44. data/lib/grape/util/strict_hash_configuration.rb +2 -1
  45. data/lib/grape/validations/attributes_iterator.rb +8 -3
  46. data/lib/grape/validations/params_scope.rb +83 -15
  47. data/lib/grape/validations/types.rb +144 -0
  48. data/lib/grape/validations/types/build_coercer.rb +53 -0
  49. data/lib/grape/validations/types/custom_type_coercer.rb +183 -0
  50. data/lib/grape/validations/types/file.rb +28 -0
  51. data/lib/grape/validations/types/json.rb +65 -0
  52. data/lib/grape/validations/types/multiple_type_coercer.rb +76 -0
  53. data/lib/grape/validations/types/variant_collection_coercer.rb +59 -0
  54. data/lib/grape/validations/types/virtus_collection_patch.rb +16 -0
  55. data/lib/grape/validations/validators/all_or_none.rb +1 -1
  56. data/lib/grape/validations/validators/allow_blank.rb +3 -3
  57. data/lib/grape/validations/validators/base.rb +7 -0
  58. data/lib/grape/validations/validators/coerce.rb +31 -42
  59. data/lib/grape/validations/validators/presence.rb +2 -3
  60. data/lib/grape/validations/validators/regexp.rb +2 -4
  61. data/lib/grape/validations/validators/values.rb +3 -3
  62. data/lib/grape/version.rb +1 -1
  63. data/pkg/grape-0.13.0.gem +0 -0
  64. data/spec/grape/api/custom_validations_spec.rb +5 -4
  65. data/spec/grape/api/deeply_included_options_spec.rb +7 -7
  66. data/spec/grape/api/nested_helpers_spec.rb +4 -2
  67. data/spec/grape/api/shared_helpers_spec.rb +8 -8
  68. data/spec/grape/api_spec.rb +88 -54
  69. data/spec/grape/dsl/configuration_spec.rb +13 -0
  70. data/spec/grape/dsl/helpers_spec.rb +16 -2
  71. data/spec/grape/dsl/inside_route_spec.rb +3 -2
  72. data/spec/grape/dsl/parameters_spec.rb +0 -6
  73. data/spec/grape/dsl/routing_spec.rb +1 -1
  74. data/spec/grape/endpoint_spec.rb +61 -20
  75. data/spec/grape/entity_spec.rb +10 -8
  76. data/spec/grape/exceptions/invalid_accept_header_spec.rb +1 -15
  77. data/spec/grape/integration/rack_spec.rb +3 -2
  78. data/spec/grape/middleware/base_spec.rb +7 -5
  79. data/spec/grape/middleware/error_spec.rb +16 -15
  80. data/spec/grape/middleware/exception_spec.rb +45 -43
  81. data/spec/grape/middleware/formatter_spec.rb +34 -0
  82. data/spec/grape/middleware/versioner/header_spec.rb +79 -47
  83. data/spec/grape/path_spec.rb +10 -10
  84. data/spec/grape/presenters/presenter_spec.rb +2 -2
  85. data/spec/grape/request_spec.rb +100 -0
  86. data/spec/grape/validations/params_scope_spec.rb +11 -9
  87. data/spec/grape/validations/types_spec.rb +95 -0
  88. data/spec/grape/validations/validators/coerce_spec.rb +335 -2
  89. data/spec/grape/validations/validators/values_spec.rb +15 -15
  90. data/spec/grape/validations_spec.rb +53 -24
  91. data/spec/shared/versioning_examples.rb +2 -2
  92. data/spec/spec_helper.rb +0 -1
  93. data/spec/support/versioned_helpers.rb +2 -2
  94. metadata +51 -13
  95. data/.gitignore +0 -46
  96. data/.rspec +0 -2
  97. data/.rubocop.yml +0 -7
  98. data/.rubocop_todo.yml +0 -84
  99. data/.travis.yml +0 -20
  100. data/.yardopts +0 -2
  101. data/lib/grape/http/request.rb +0 -35
  102. data/lib/grape/util/parameter_types.rb +0 -58
  103. data/spec/grape/util/parameter_types_spec.rb +0 -54
@@ -47,6 +47,7 @@ module Grape
47
47
  end
48
48
 
49
49
  def rescuable?(klass)
50
+ return false if klass == Grape::Exceptions::InvalidVersionHeader
50
51
  options[:rescue_all] || (options[:rescue_handlers] || []).any? { |error, _handler| klass <= error } || (options[:base_only_rescue_handlers] || []).include?(klass)
51
52
  end
52
53
 
@@ -59,7 +60,7 @@ module Grape
59
60
  end
60
61
 
61
62
  def error!(message, status = options[:default_status], headers = {}, backtrace = [])
62
- headers = { Grape::Http::Headers::CONTENT_TYPE => content_type }.merge(headers)
63
+ headers = headers.reverse_merge(Grape::Http::Headers::CONTENT_TYPE => content_type)
63
64
  rack_response(format_message(message, backtrace), status, headers)
64
65
  end
65
66
 
@@ -82,7 +83,7 @@ module Grape
82
83
  end
83
84
 
84
85
  def format_message(message, backtrace)
85
- format = env['api.format'] || options[:format]
86
+ format = env[Grape::Env::API_FORMAT] || options[:format]
86
87
  formatter = Grape::ErrorFormatter::Base.formatter_for(format, options)
87
88
  throw :error, status: 406, message: "The requested format '#{format}' is not supported." unless formatter
88
89
  formatter.call(message, backtrace, options, env)
@@ -3,7 +3,7 @@ module Grape
3
3
  # This is a simple middleware for adding before and after filters
4
4
  # to Grape APIs. It is used like so:
5
5
  #
6
- # use Grape::Middleware::Filter, before: lambda { do_something }, after: lambda { do_something }
6
+ # use Grape::Middleware::Filter, before: -> { do_something }, after: -> { do_something }
7
7
  class Filter < Base
8
8
  def before
9
9
  app.instance_eval(&options[:before]) if options[:before]
@@ -3,6 +3,8 @@ require 'grape/middleware/base'
3
3
  module Grape
4
4
  module Middleware
5
5
  class Formatter < Base
6
+ CHUNKED = 'chunked'.freeze
7
+
6
8
  def default_options
7
9
  {
8
10
  default_format: :txt,
@@ -19,35 +21,36 @@ module Grape
19
21
  def after
20
22
  status, headers, bodies = *@app_response
21
23
 
22
- if bodies.is_a?(Grape::Util::FileResponse)
23
- headers = ensure_content_type(headers)
24
-
25
- response =
26
- Rack::Response.new([], status, headers) do |resp|
27
- resp.body = bodies.file
28
- end
24
+ if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
25
+ @app_response
29
26
  else
30
- # Allow content-type to be explicitly overwritten
31
- api_format = mime_types[headers[Grape::Http::Headers::CONTENT_TYPE]] || env['api.format']
32
- formatter = Grape::Formatter::Base.formatter_for(api_format, options)
27
+ build_formatted_response(status, headers, bodies)
28
+ end
29
+ end
33
30
 
34
- begin
35
- bodymap = bodies.collect do |body|
36
- formatter.call(body, env)
37
- end
31
+ private
38
32
 
39
- headers = ensure_content_type(headers)
33
+ def build_formatted_response(status, headers, bodies)
34
+ headers = ensure_content_type(headers)
40
35
 
41
- response = Rack::Response.new(bodymap, status, headers)
42
- rescue Grape::Exceptions::InvalidFormatter => e
43
- throw :error, status: 500, message: e.message
36
+ if bodies.is_a?(Grape::Util::FileResponse)
37
+ Rack::Response.new([], status, headers) do |resp|
38
+ resp.body = bodies.file
44
39
  end
40
+ else
41
+ # Allow content-type to be explicitly overwritten
42
+ formatter = fetch_formatter(headers, options)
43
+ bodymap = bodies.collect { |body| formatter.call(body, env) }
44
+ Rack::Response.new(bodymap, status, headers)
45
45
  end
46
-
47
- response
46
+ rescue Grape::Exceptions::InvalidFormatter => e
47
+ throw :error, status: 500, message: e.message
48
48
  end
49
49
 
50
- private
50
+ def fetch_formatter(headers, options)
51
+ api_format = mime_types[headers[Grape::Http::Headers::CONTENT_TYPE]] || env[Grape::Env::API_FORMAT]
52
+ Grape::Formatter::Base.formatter_for(api_format, options)
53
+ end
51
54
 
52
55
  # Set the content type header for the API format if it is not already present.
53
56
  #
@@ -57,7 +60,7 @@ module Grape
57
60
  if headers[Grape::Http::Headers::CONTENT_TYPE]
58
61
  headers
59
62
  else
60
- headers.merge(Grape::Http::Headers::CONTENT_TYPE => content_type_for(env['api.format']))
63
+ headers.merge(Grape::Http::Headers::CONTENT_TYPE => content_type_for(env[Grape::Env::API_FORMAT]))
61
64
  end
62
65
  end
63
66
 
@@ -67,20 +70,20 @@ module Grape
67
70
 
68
71
  # store read input in env['api.request.input']
69
72
  def read_body_input
70
- if (request.post? || request.put? || request.patch? || request.delete?) &&
71
- (!request.form_data? || !request.media_type) &&
72
- (!request.parseable_data?) &&
73
- (request.content_length.to_i > 0 || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == 'chunked')
74
-
75
- if (input = env['rack.input'])
76
- input.rewind
77
- body = env['api.request.input'] = input.read
78
- begin
79
- read_rack_input(body) if body && body.length > 0
80
- ensure
81
- input.rewind
82
- end
83
- end
73
+ return unless
74
+ (request.post? || request.put? || request.patch? || request.delete?) &&
75
+ (!request.form_data? || !request.media_type) &&
76
+ (!request.parseable_data?) &&
77
+ (request.content_length.to_i > 0 || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED)
78
+
79
+ return unless (input = env[Grape::Env::RACK_INPUT])
80
+
81
+ input.rewind
82
+ body = env[Grape::Env::API_REQUEST_INPUT] = input.read
83
+ begin
84
+ read_rack_input(body) if body && body.length > 0
85
+ ensure
86
+ input.rewind
84
87
  end
85
88
  end
86
89
 
@@ -92,14 +95,14 @@ module Grape
92
95
  parser = Grape::Parser::Base.parser_for fmt, options
93
96
  if parser
94
97
  begin
95
- body = (env['api.request.body'] = parser.call(body, env))
98
+ body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))
96
99
  if body.is_a?(Hash)
97
- if env['rack.request.form_hash']
98
- env['rack.request.form_hash'] = env['rack.request.form_hash'].merge(body)
100
+ if env[Grape::Env::RACK_REQUEST_FORM_HASH]
101
+ env[Grape::Env::RACK_REQUEST_FORM_HASH] = env[Grape::Env::RACK_REQUEST_FORM_HASH].merge(body)
99
102
  else
100
- env['rack.request.form_hash'] = body
103
+ env[Grape::Env::RACK_REQUEST_FORM_HASH] = body
101
104
  end
102
- env['rack.request.form_input'] = env['rack.input']
105
+ env[Grape::Env::RACK_REQUEST_FORM_INPUT] = env[Grape::Env::RACK_INPUT]
103
106
  end
104
107
  rescue Grape::Exceptions::Base => e
105
108
  raise e
@@ -107,7 +110,7 @@ module Grape
107
110
  throw :error, status: 400, message: e.message
108
111
  end
109
112
  else
110
- env['api.request.body'] = body
113
+ env[Grape::Env::API_REQUEST_BODY] = body
111
114
  end
112
115
  else
113
116
  throw :error, status: 406, message: "The requested content-type '#{request.media_type}' is not supported."
@@ -117,7 +120,7 @@ module Grape
117
120
  def negotiate_content_type
118
121
  fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
119
122
  if content_type_for(fmt)
120
- env['api.format'] = fmt
123
+ env[Grape::Env::API_FORMAT] = fmt
121
124
  else
122
125
  throw :error, status: 406, message: "The requested format '#{fmt}' is not supported."
123
126
  end
@@ -165,7 +168,7 @@ module Grape
165
168
 
166
169
  accept.scan(accept_into_mime_and_quality)
167
170
  .sort_by { |_, quality_preference| -quality_preference.to_f }
168
- .map { |mime, _| mime.sub(vendor_prefix_pattern, '') }
171
+ .flat_map { |mime, _| [mime, mime.sub(vendor_prefix_pattern, '')] }
169
172
  end
170
173
  end
171
174
  end
@@ -5,9 +5,9 @@ module Grape
5
5
  class Globals < Base
6
6
  def before
7
7
  request = Grape::Request.new(@env)
8
- @env['grape.request'] = request
9
- @env['grape.request.headers'] = request.headers
10
- @env['grape.request.params'] = request.params if @env['rack.input']
8
+ @env[Grape::Env::GRAPE_REQUEST] = request
9
+ @env[Grape::Env::GRAPE_REQUEST_HEADERS] = request.headers
10
+ @env[Grape::Env::GRAPE_REQUEST_PARAMS] = request.params if @env[Grape::Env::RACK_INPUT]
11
11
  end
12
12
  end
13
13
  end
@@ -27,14 +27,12 @@ module Grape
27
27
  end
28
28
  end
29
29
 
30
- unless potential_version.empty?
31
- # If the requested version is not supported:
32
- unless versions.any? { |v| v.to_s == potential_version }
33
- throw :error, status: 406, headers: error_headers, message: 'The requested version is not supported.'
34
- end
30
+ return if potential_version.empty?
35
31
 
36
- env['api.version'] = potential_version
37
- end
32
+ # If the requested version is not supported:
33
+ throw :error, status: 406, headers: error_headers, message: 'The requested version is not supported.' unless versions.any? { |v| v.to_s == potential_version }
34
+
35
+ env[Grape::Env::API_VERSION] = potential_version
38
36
  end
39
37
 
40
38
  private
@@ -1,4 +1,5 @@
1
1
  require 'grape/middleware/base'
2
+ require 'grape/middleware/versioner/parse_media_type_patch'
2
3
 
3
4
  module Grape
4
5
  module Middleware
@@ -8,13 +9,13 @@ module Grape
8
9
  # application/vnd.:vendor-:version+:format
9
10
  #
10
11
  # Example: For request header
11
- # Accept: application/vnd.mycompany-v1+json
12
+ # Accept: application/vnd.mycompany.a-cool-resource-v1+json
12
13
  #
13
14
  # The following rack env variables are set:
14
15
  #
15
16
  # env['api.type'] => 'application'
16
- # env['api.subtype'] => 'vnd.mycompany-v1+json'
17
- # env['api.vendor] => 'mycompany'
17
+ # env['api.subtype'] => 'vnd.mycompany.a-cool-resource-v1+json'
18
+ # env['api.vendor] => 'mycompany.a-cool-resource'
18
19
  # env['api.version] => 'v1'
19
20
  # env['api.format] => 'json'
20
21
  #
@@ -22,54 +23,88 @@ module Grape
22
23
  # X-Cascade header to alert Rack::Mount to attempt the next matched
23
24
  # route.
24
25
  class Header < Base
25
- def before
26
- header = rack_accept_header
26
+ VENDOR_VERSION_HEADER_REGEX =
27
+ /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z/
27
28
 
28
- if strict?
29
- # If no Accept header:
30
- if header.qvalues.empty?
31
- fail Grape::Exceptions::InvalidAcceptHeader.new('Accept header must be set.', error_headers)
32
- end
33
- # Remove any acceptable content types with ranges.
34
- header.qvalues.reject! do |media_type, _|
35
- Rack::Accept::Header.parse_media_type(media_type).find { |s| s == '*' }
36
- end
37
- # If all Accept headers included a range:
38
- if header.qvalues.empty?
39
- fail Grape::Exceptions::InvalidAcceptHeader.new('Accept header must not contain ranges ("*").',
40
- error_headers)
41
- end
42
- end
29
+ HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#\$&\^]+/
30
+ HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))+/
43
31
 
44
- media_type = header.best_of available_media_types
32
+ def before
33
+ strict_header_checks if strict?
45
34
 
46
35
  if media_type
47
- type, subtype = Rack::Accept::Header.parse_media_type media_type
48
- env['api.type'] = type
49
- env['api.subtype'] = subtype
50
-
51
- if /\Avnd\.([a-z0-9*.]+)(?:-([a-z0-9*\-.]+))?(?:\+([a-z0-9*\-.+]+))?\z/ =~ subtype
52
- env['api.vendor'] = Regexp.last_match[1]
53
- env['api.version'] = Regexp.last_match[2]
54
- env['api.format'] = Regexp.last_match[3] # weird that Grape::Middleware::Formatter also does this
55
- end
56
- # If none of the available content types are acceptable:
57
- elsif strict?
58
- fail Grape::Exceptions::InvalidAcceptHeader.new('406 Not Acceptable', error_headers)
59
- # If all acceptable content types specify a vendor or version that doesn't exist:
60
- elsif header.values.all? { |header_value| has_vendor?(header_value) || version?(header_value) }
61
- fail Grape::Exceptions::InvalidAcceptHeader.new('API vendor or version not found.', error_headers)
36
+ media_type_header_handler
37
+ elsif headers_contain_wrong_vendor?
38
+ fail_with_invalid_accept_header!('API vendor not found.')
39
+ elsif headers_contain_wrong_version?
40
+ fail_with_invalid_version_header!('API version not found.')
62
41
  end
63
42
  end
64
43
 
65
44
  private
66
45
 
46
+ def strict_header_checks
47
+ strict_accept_header_presence_check
48
+ strict_version_vendor_accept_header_presence_check
49
+ end
50
+
51
+ def strict_accept_header_presence_check
52
+ return unless header.qvalues.empty?
53
+ fail_with_invalid_accept_header!('Accept header must be set.')
54
+ end
55
+
56
+ def strict_version_vendor_accept_header_presence_check
57
+ return unless versions.present?
58
+ return if an_accept_header_with_version_and_vendor_is_present?
59
+ fail_with_invalid_accept_header!('API vendor or version not found.')
60
+ end
61
+
62
+ def an_accept_header_with_version_and_vendor_is_present?
63
+ header.qvalues.keys.any? do |h|
64
+ VENDOR_VERSION_HEADER_REGEX =~ h.sub('application/', '')
65
+ end
66
+ end
67
+
68
+ def header
69
+ @header ||= rack_accept_header
70
+ end
71
+
72
+ def media_type
73
+ @media_type ||= header.best_of(available_media_types)
74
+ end
75
+
76
+ def media_type_header_handler
77
+ type, subtype = Rack::Accept::Header.parse_media_type(media_type)
78
+ env[Grape::Env::API_TYPE] = type
79
+ env[Grape::Env::API_SUBTYPE] = subtype
80
+
81
+ return unless VENDOR_VERSION_HEADER_REGEX =~ subtype
82
+
83
+ env[Grape::Env::API_VENDOR] = Regexp.last_match[1]
84
+ env[Grape::Env::API_VERSION] = Regexp.last_match[2]
85
+ # weird that Grape::Middleware::Formatter also does this
86
+ env[Grape::Env::API_FORMAT] = Regexp.last_match[3]
87
+ end
88
+
89
+ def fail_with_invalid_accept_header!(message)
90
+ fail Grape::Exceptions::InvalidAcceptHeader
91
+ .new(message, error_headers)
92
+ end
93
+
94
+ def fail_with_invalid_version_header!(message)
95
+ fail Grape::Exceptions::InvalidVersionHeader
96
+ .new(message, error_headers)
97
+ end
98
+
67
99
  def available_media_types
68
100
  available_media_types = []
69
101
 
70
102
  content_types.each do |extension, _media_type|
71
103
  versions.reverse_each do |version|
72
- available_media_types += ["application/vnd.#{vendor}-#{version}+#{extension}", "application/vnd.#{vendor}-#{version}"]
104
+ available_media_types += [
105
+ "application/vnd.#{vendor}-#{version}+#{extension}",
106
+ "application/vnd.#{vendor}-#{version}"
107
+ ]
73
108
  end
74
109
  available_media_types << "application/vnd.#{vendor}+#{extension}"
75
110
  end
@@ -83,10 +118,22 @@ module Grape
83
118
  available_media_types.flatten
84
119
  end
85
120
 
121
+ def headers_contain_wrong_vendor?
122
+ header.values.all? do |header_value|
123
+ vendor?(header_value) && request_vendor(header_value) != vendor
124
+ end
125
+ end
126
+
127
+ def headers_contain_wrong_version?
128
+ header.values.all? do |header_value|
129
+ version?(header_value) && !versions.include?(request_version(header_value))
130
+ end
131
+ end
132
+
86
133
  def rack_accept_header
87
134
  Rack::Accept::MediaType.new env[Grape::Http::Headers::HTTP_ACCEPT]
88
135
  rescue RuntimeError => e
89
- raise Grape::Exceptions::InvalidAcceptHeader.new(e.message, error_headers)
136
+ fail_with_invalid_accept_header!(e.message)
90
137
  end
91
138
 
92
139
  def versions
@@ -94,19 +141,25 @@ module Grape
94
141
  end
95
142
 
96
143
  def vendor
97
- options[:version_options] && options[:version_options][:vendor]
144
+ version_options && version_options[:vendor]
98
145
  end
99
146
 
100
147
  def strict?
101
- options[:version_options] && options[:version_options][:strict]
148
+ version_options && version_options[:strict]
149
+ end
150
+
151
+ def version_options
152
+ options[:version_options]
102
153
  end
103
154
 
104
- # By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
105
- # of routes (see [Rack::Mount](https://github.com/josh/rack-mount) for more information). To prevent
106
- # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
155
+ # By default those errors contain an `X-Cascade` header set to `pass`,
156
+ # which allows nesting and stacking of routes
157
+ # (see [Rack::Mount](https://github.com/josh/rack-mount) for more
158
+ # information). To prevent # this behavior, and not add the `X-Cascade`
159
+ # header, one can set the `:cascade` option to `false`.
107
160
  def cascade?
108
- if options[:version_options] && options[:version_options].key?(:cascade)
109
- !!options[:version_options][:cascade]
161
+ if version_options && version_options.key?(:cascade)
162
+ !!version_options[:cascade]
110
163
  else
111
164
  true
112
165
  end
@@ -118,16 +171,26 @@ module Grape
118
171
 
119
172
  # @param [String] media_type a content type
120
173
  # @return [Boolean] whether the content type sets a vendor
121
- def has_vendor?(media_type)
122
- _, subtype = Rack::Accept::Header.parse_media_type media_type
123
- subtype[/\Avnd\.[a-z0-9*.]+/]
174
+ def vendor?(media_type)
175
+ _, subtype = Rack::Accept::Header.parse_media_type(media_type)
176
+ subtype[HAS_VENDOR_REGEX]
177
+ end
178
+
179
+ def request_vendor(media_type)
180
+ _, subtype = Rack::Accept::Header.parse_media_type(media_type)
181
+ subtype.match(VENDOR_VERSION_HEADER_REGEX)[1]
182
+ end
183
+
184
+ def request_version(media_type)
185
+ _, subtype = Rack::Accept::Header.parse_media_type(media_type)
186
+ subtype.match(VENDOR_VERSION_HEADER_REGEX)[2]
124
187
  end
125
188
 
126
189
  # @param [String] media_type a content type
127
190
  # @return [Boolean] whether the content type sets an API version
128
191
  def version?(media_type)
129
- _, subtype = Rack::Accept::Header.parse_media_type media_type
130
- subtype[/\Avnd\.[a-z0-9*.]+-[a-z0-9*\-.]+/]
192
+ _, subtype = Rack::Accept::Header.parse_media_type(media_type)
193
+ subtype[HAS_VERSION_REGEX]
131
194
  end
132
195
  end
133
196
  end