grape 0.3.0 → 0.7.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.

Potentially problematic release.


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

Files changed (101) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +70 -0
  4. data/.travis.yml +7 -6
  5. data/CHANGELOG.md +134 -4
  6. data/CONTRIBUTING.md +118 -0
  7. data/Gemfile +5 -2
  8. data/README.md +551 -116
  9. data/RELEASING.md +105 -0
  10. data/Rakefile +29 -8
  11. data/UPGRADING.md +124 -0
  12. data/grape.gemspec +3 -3
  13. data/lib/grape/api.rb +207 -88
  14. data/lib/grape/cookies.rb +4 -8
  15. data/lib/grape/endpoint.rb +198 -144
  16. data/lib/grape/error_formatter/base.rb +5 -7
  17. data/lib/grape/error_formatter/json.rb +3 -5
  18. data/lib/grape/error_formatter/txt.rb +1 -3
  19. data/lib/grape/error_formatter/xml.rb +4 -6
  20. data/lib/grape/exceptions/base.rb +9 -9
  21. data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
  22. data/lib/grape/exceptions/invalid_formatter.rb +1 -4
  23. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -5
  24. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -6
  25. data/lib/grape/exceptions/missing_mime_type.rb +1 -5
  26. data/lib/grape/exceptions/missing_option.rb +1 -4
  27. data/lib/grape/exceptions/missing_vendor_option.rb +1 -4
  28. data/lib/grape/exceptions/unknown_options.rb +1 -5
  29. data/lib/grape/exceptions/unknown_validator.rb +1 -3
  30. data/lib/grape/exceptions/validation.rb +13 -3
  31. data/lib/grape/exceptions/validation_errors.rb +43 -0
  32. data/lib/grape/formatter/base.rb +5 -7
  33. data/lib/grape/formatter/json.rb +0 -3
  34. data/lib/grape/formatter/serializable_hash.rb +15 -15
  35. data/lib/grape/formatter/txt.rb +0 -2
  36. data/lib/grape/formatter/xml.rb +0 -2
  37. data/lib/grape/http/request.rb +26 -0
  38. data/lib/grape/locale/en.yml +8 -5
  39. data/lib/grape/middleware/auth/base.rb +30 -0
  40. data/lib/grape/middleware/auth/basic.rb +3 -20
  41. data/lib/grape/middleware/auth/digest.rb +2 -19
  42. data/lib/grape/middleware/auth/oauth2.rb +31 -24
  43. data/lib/grape/middleware/base.rb +7 -7
  44. data/lib/grape/middleware/error.rb +36 -22
  45. data/lib/grape/middleware/filter.rb +3 -3
  46. data/lib/grape/middleware/formatter.rb +99 -61
  47. data/lib/grape/middleware/globals.rb +13 -0
  48. data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
  49. data/lib/grape/middleware/versioner/header.rb +22 -16
  50. data/lib/grape/middleware/versioner/param.rb +9 -11
  51. data/lib/grape/middleware/versioner/path.rb +10 -13
  52. data/lib/grape/middleware/versioner.rb +3 -1
  53. data/lib/grape/namespace.rb +23 -0
  54. data/lib/grape/parser/base.rb +3 -5
  55. data/lib/grape/parser/json.rb +0 -2
  56. data/lib/grape/parser/xml.rb +0 -2
  57. data/lib/grape/path.rb +70 -0
  58. data/lib/grape/route.rb +10 -6
  59. data/lib/grape/util/content_types.rb +2 -1
  60. data/lib/grape/util/deep_merge.rb +5 -5
  61. data/lib/grape/util/hash_stack.rb +13 -2
  62. data/lib/grape/validations/coerce.rb +11 -10
  63. data/lib/grape/validations/default.rb +25 -0
  64. data/lib/grape/validations/presence.rb +7 -3
  65. data/lib/grape/validations/regexp.rb +2 -5
  66. data/lib/grape/validations/values.rb +17 -0
  67. data/lib/grape/validations.rb +161 -54
  68. data/lib/grape/version.rb +1 -1
  69. data/lib/grape.rb +19 -4
  70. data/spec/grape/api_spec.rb +897 -268
  71. data/spec/grape/endpoint_spec.rb +283 -66
  72. data/spec/grape/entity_spec.rb +132 -29
  73. data/spec/grape/exceptions/missing_mime_type_spec.rb +3 -9
  74. data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
  75. data/spec/grape/middleware/auth/basic_spec.rb +8 -8
  76. data/spec/grape/middleware/auth/digest_spec.rb +5 -5
  77. data/spec/grape/middleware/auth/oauth2_spec.rb +81 -36
  78. data/spec/grape/middleware/base_spec.rb +8 -13
  79. data/spec/grape/middleware/error_spec.rb +13 -17
  80. data/spec/grape/middleware/exception_spec.rb +47 -27
  81. data/spec/grape/middleware/formatter_spec.rb +103 -41
  82. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
  83. data/spec/grape/middleware/versioner/header_spec.rb +76 -51
  84. data/spec/grape/middleware/versioner/param_spec.rb +18 -18
  85. data/spec/grape/middleware/versioner/path_spec.rb +6 -6
  86. data/spec/grape/middleware/versioner_spec.rb +5 -2
  87. data/spec/grape/path_spec.rb +229 -0
  88. data/spec/grape/util/hash_stack_spec.rb +31 -32
  89. data/spec/grape/validations/coerce_spec.rb +116 -51
  90. data/spec/grape/validations/default_spec.rb +123 -0
  91. data/spec/grape/validations/presence_spec.rb +42 -44
  92. data/spec/grape/validations/regexp_spec.rb +9 -9
  93. data/spec/grape/validations/values_spec.rb +138 -0
  94. data/spec/grape/validations/zh-CN.yml +4 -3
  95. data/spec/grape/validations_spec.rb +681 -48
  96. data/spec/shared/versioning_examples.rb +22 -6
  97. data/spec/spec_helper.rb +3 -2
  98. data/spec/support/basic_auth_encode_helpers.rb +0 -1
  99. data/spec/support/content_type_helpers.rb +11 -0
  100. data/spec/support/versioned_helpers.rb +13 -5
  101. metadata +34 -84
@@ -3,18 +3,18 @@ require 'grape/middleware/base'
3
3
  module Grape
4
4
  module Middleware
5
5
  class Error < Base
6
-
7
6
  def default_options
8
7
  {
9
- :default_status => 403, # default status returned on error
10
- :default_message => "",
11
- :format => :txt,
12
- :formatters => {},
13
- :error_formatters => {},
14
- :rescue_all => false, # true to rescue all exceptions
15
- :rescue_options => { :backtrace => false }, # true to display backtrace
16
- :rescue_handlers => {}, # rescue handler blocks
17
- :rescued_errors => []
8
+ default_status: 500, # default status returned on error
9
+ default_message: "",
10
+ format: :txt,
11
+ formatters: {},
12
+ error_formatters: {},
13
+ rescue_all: false, # true to rescue all exceptions
14
+ rescue_subclasses: true, # rescue subclasses of exceptions listed
15
+ rescue_options: { backtrace: false }, # true to display backtrace
16
+ rescue_handlers: {}, # rescue handler blocks
17
+ base_only_rescue_handlers: {} # rescue handler blocks rescuing only the base class
18
18
  }
19
19
  end
20
20
 
@@ -22,49 +22,63 @@ module Grape
22
22
  @env = env
23
23
 
24
24
  begin
25
- error_response(catch(:error){
25
+ error_response(catch(:error) do
26
26
  return @app.call(@env)
27
- })
28
- rescue Exception => e
27
+ end)
28
+ rescue StandardError => e
29
29
  is_rescuable = rescuable?(e.class)
30
30
  if e.is_a?(Grape::Exceptions::Base) && !is_rescuable
31
- handler = lambda {|e| error_response(e) }
31
+ handler = lambda { |arg| error_response(arg) }
32
32
  else
33
33
  raise unless is_rescuable
34
- handler = options[:rescue_handlers][e.class] || options[:rescue_handlers][:all]
34
+ handler = find_handler(e.class)
35
35
  end
36
- handler.nil? ? handle_error(e) : self.instance_exec(e, &handler)
36
+
37
+ handler.nil? ? handle_error(e) : exec_handler(e, &handler)
37
38
  end
38
39
  end
39
40
 
41
+ def find_handler(klass)
42
+ handler = options[:rescue_handlers].find(-> { [] }) { |error, _| klass <= error }[1]
43
+ handler = options[:base_only_rescue_handlers][klass] || options[:base_only_rescue_handlers][:all] unless handler
44
+ handler
45
+ end
46
+
40
47
  def rescuable?(klass)
41
- options[:rescue_all] || (options[:rescued_errors] || []).include?(klass)
48
+ options[:rescue_all] || (options[:rescue_handlers] || []).any? { |error, handler| klass <= error } || (options[:base_only_rescue_handlers] || []).include?(klass)
49
+ end
50
+
51
+ def exec_handler(e, &handler)
52
+ if handler.lambda? && handler.arity == 0
53
+ instance_exec(&handler)
54
+ else
55
+ instance_exec(e, &handler)
56
+ end
42
57
  end
43
58
 
44
59
  def handle_error(e)
45
- error_response({ :message => e.message, :backtrace => e.backtrace })
60
+ error_response(message: e.message, backtrace: e.backtrace)
46
61
  end
47
62
 
48
63
  def error_response(error = {})
49
64
  status = error[:status] || options[:default_status]
50
65
  message = error[:message] || options[:default_message]
51
- headers = {'Content-Type' => content_type}
66
+ headers = { 'Content-Type' => content_type }
52
67
  headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
53
68
  backtrace = error[:backtrace] || []
54
69
  rack_response(format_message(message, backtrace), status, headers)
55
70
  end
56
71
 
57
72
  def rack_response(message, status = options[:default_status], headers = { 'Content-Type' => content_type })
58
- Rack::Response.new([ message ], status, headers).finish
73
+ Rack::Response.new([message], status, headers).finish
59
74
  end
60
75
 
61
76
  def format_message(message, backtrace)
62
77
  format = env['api.format'] || options[:format]
63
78
  formatter = Grape::ErrorFormatter::Base.formatter_for(format, options)
64
- throw :error, :status => 406, :message => "The requested format '#{format}' is not supported." unless formatter
79
+ throw :error, status: 406, message: "The requested format '#{format}' is not supported." unless formatter
65
80
  formatter.call(message, backtrace, options, env)
66
81
  end
67
-
68
82
  end
69
83
  end
70
84
  end
@@ -3,14 +3,14 @@ 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: lambda { do_something }, after: lambda { do_something }
7
7
  class Filter < Base
8
8
  def before
9
- app.instance_eval &options[:before] if options[:before]
9
+ app.instance_eval(&options[:before]) if options[:before]
10
10
  end
11
11
 
12
12
  def after
13
- app.instance_eval &options[:after] if options[:after]
13
+ app.instance_eval(&options[:after]) if options[:after]
14
14
  end
15
15
  end
16
16
  end
@@ -3,17 +3,19 @@ require 'grape/middleware/base'
3
3
  module Grape
4
4
  module Middleware
5
5
  class Formatter < Base
6
-
7
6
  def default_options
8
7
  {
9
- :default_format => :txt,
10
- :formatters => {},
11
- :parsers => {}
8
+ default_format: :txt,
9
+ formatters: {},
10
+ parsers: {}
12
11
  }
13
12
  end
14
13
 
15
14
  def headers
16
- env.dup.inject({}){|h,(k,v)| h[k.to_s.downcase[5..-1]] = v if k.to_s.downcase.start_with?('http_'); h}
15
+ env.dup.inject({}) do |h, (k, v)|
16
+ h[k.to_s.downcase[5..-1]] = v if k.to_s.downcase.start_with?('http_')
17
+ h
18
+ end
17
19
  end
18
20
 
19
21
  def before
@@ -23,13 +25,15 @@ module Grape
23
25
 
24
26
  def after
25
27
  status, headers, bodies = *@app_response
26
- formatter = Grape::Formatter::Base.formatter_for env['api.format'], options
28
+ # allow content-type to be explicitly overwritten
29
+ api_format = mime_types[headers["Content-Type"]] || env['api.format']
30
+ formatter = Grape::Formatter::Base.formatter_for api_format, options
27
31
  begin
28
32
  bodymap = bodies.collect do |body|
29
33
  formatter.call body, env
30
34
  end
31
- rescue Exception => e
32
- throw :error, :status => 500, :message => e.message
35
+ rescue Grape::Exceptions::InvalidFormatter => e
36
+ throw :error, status: 500, message: e.message
33
37
  end
34
38
  headers['Content-Type'] = content_type_for(env['api.format']) unless headers['Content-Type']
35
39
  Rack::Response.new(bodymap, status, headers).to_a
@@ -37,76 +41,110 @@ module Grape
37
41
 
38
42
  private
39
43
 
40
- def read_body_input
41
- if (request.post? || request.put? || request.patch?) && (! request.form_data?) && (! request.parseable_data?) && (request.content_length.to_i > 0)
42
- if env['rack.input'] && (body = env['rack.input'].read).length > 0
43
- begin
44
- fmt = mime_types[request.media_type] if request.media_type
45
- if content_type_for(fmt)
46
- parser = Grape::Parser::Base.parser_for fmt, options
47
- unless parser.nil?
48
- begin
49
- body = parser.call body, env
50
- env['rack.request.form_hash'] = env['rack.request.form_hash'] ? env['rack.request.form_hash'].merge(body) : body
51
- env['rack.request.form_input'] = env['rack.input']
52
- rescue Exception => e
53
- throw :error, :status => 400, :message => e.message
54
- end
55
- end
56
- else
57
- throw :error, :status => 406, :message => "The requested content-type '#{request.media_type}' is not supported."
58
- end
59
- ensure
60
- env['rack.input'].rewind
61
- end
44
+ def request
45
+ @request ||= Rack::Request.new(env)
46
+ end
47
+
48
+ # store read input in env['api.request.input']
49
+ def read_body_input
50
+ if (request.post? || request.put? || request.patch? || request.delete?) &&
51
+ (!request.form_data? || !request.media_type) &&
52
+ (!request.parseable_data?) &&
53
+ (request.content_length.to_i > 0 || request.env['HTTP_TRANSFER_ENCODING'] == 'chunked')
54
+
55
+ if (input = env['rack.input'])
56
+ input.rewind
57
+ body = env['api.request.input'] = input.read
58
+ begin
59
+ read_rack_input(body) if body && body.length > 0
60
+ ensure
61
+ input.rewind
62
62
  end
63
63
  end
64
64
  end
65
+ end
65
66
 
66
- def negotiate_content_type
67
- fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
68
- if content_type_for(fmt)
69
- env['api.format'] = fmt
67
+ # store parsed input in env['api.request.body']
68
+ def read_rack_input(body)
69
+ fmt = mime_types[request.media_type] if request.media_type
70
+ fmt ||= options[:default_format]
71
+ if content_type_for(fmt)
72
+ parser = Grape::Parser::Base.parser_for fmt, options
73
+ if parser
74
+ begin
75
+ body = (env['api.request.body'] = parser.call(body, env))
76
+ if body.is_a?(Hash)
77
+ if env['rack.request.form_hash']
78
+ env['rack.request.form_hash'] = env['rack.request.form_hash'].merge(body)
79
+ else
80
+ env['rack.request.form_hash'] = body
81
+ end
82
+ env['rack.request.form_input'] = env['rack.input']
83
+ end
84
+ rescue StandardError => e
85
+ throw :error, status: 400, message: e.message
86
+ end
70
87
  else
71
- throw :error, :status => 406, :message => "The requested format '#{fmt}' is not supported."
88
+ env['api.request.body'] = body
72
89
  end
90
+ else
91
+ throw :error, status: 406, message: "The requested content-type '#{request.media_type}' is not supported."
73
92
  end
93
+ end
74
94
 
75
- def format_from_extension
76
- parts = request.path.split('.')
77
-
78
- if parts.size > 1
79
- extension = parts.last
80
- # avoid symbol memory leak on an unknown format
81
- return extension.to_sym if content_type_for(extension)
82
- end
83
- nil
95
+ def negotiate_content_type
96
+ fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
97
+ if content_type_for(fmt)
98
+ env['api.format'] = fmt
99
+ else
100
+ throw :error, status: 406, message: "The requested format '#{fmt}' is not supported."
84
101
  end
102
+ end
103
+
104
+ def format_from_extension
105
+ parts = request.path.split('.')
85
106
 
86
- def format_from_params
87
- fmt = Rack::Utils.parse_nested_query(env['QUERY_STRING'])["format"]
107
+ if parts.size > 1
108
+ extension = parts.last
88
109
  # avoid symbol memory leak on an unknown format
89
- return fmt.to_sym if content_type_for(fmt)
90
- fmt
110
+ return extension.to_sym if content_type_for(extension)
91
111
  end
112
+ nil
113
+ end
92
114
 
93
- def format_from_header
94
- mime_array.each do |t|
95
- if mime_types.key?(t)
96
- return mime_types[t]
97
- end
98
- end
99
- nil
115
+ def format_from_params
116
+ fmt = Rack::Utils.parse_nested_query(env['QUERY_STRING'])["format"]
117
+ # avoid symbol memory leak on an unknown format
118
+ return fmt.to_sym if content_type_for(fmt)
119
+ fmt
120
+ end
121
+
122
+ def format_from_header
123
+ mime_array.each do |t|
124
+ return mime_types[t] if mime_types.key?(t)
100
125
  end
126
+ nil
127
+ end
101
128
 
102
- def mime_array
103
- accept = headers['accept'] or return []
129
+ def mime_array
130
+ accept = headers['accept']
131
+ return [] unless accept
104
132
 
105
- accept.gsub(/\b/,'').scan(%r((\w+/[\w+.-]+)(?:(?:;[^,]*?)?;\s*q=([\d.]+))?)).sort_by { |_, q| -q.to_f }.map {|mime, _|
106
- mime.sub(%r(vnd\.[^+]+\+), '')
107
- }
108
- end
133
+ accept_into_mime_and_quality = %r(
134
+ (
135
+ \w+/[\w+.-]+) # eg application/vnd.example.myformat+xml
136
+ (?:
137
+ (?:;[^,]*?)? # optionally multiple formats in a row
138
+ ;\s*q=([\d.]+) # optional "quality" preference (eg q=0.5)
139
+ )?
140
+ )x
141
+
142
+ vendor_prefix_pattern = /vnd\.[^+]+\+/
109
143
 
144
+ accept.scan(accept_into_mime_and_quality)
145
+ .sort_by { |_, quality_preference| -quality_preference.to_f }
146
+ .map { |mime, _| mime.sub(vendor_prefix_pattern, '') }
147
+ end
110
148
  end
111
149
  end
112
150
  end
@@ -0,0 +1,13 @@
1
+ require 'grape/middleware/base'
2
+
3
+ module Grape
4
+ module Middleware
5
+ class Globals < Base
6
+ def before
7
+ @env['grape.request'] = Grape::Request.new(@env)
8
+ @env['grape.request.headers'] = request.headers
9
+ @env['grape.request.params'] = request.params if @env['rack.input']
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,67 @@
1
+ require 'grape/middleware/base'
2
+
3
+ module Grape
4
+ module Middleware
5
+ module Versioner
6
+ # This middleware sets various version related rack environment variables
7
+ # based on the HTTP Accept-Version header
8
+ #
9
+ # Example: For request header
10
+ # Accept-Version: v1
11
+ #
12
+ # The following rack env variables are set:
13
+ #
14
+ # env['api.version'] => 'v1'
15
+ #
16
+ # If version does not match this route, then a 406 is raised with
17
+ # X-Cascade header to alert Rack::Mount to attempt the next matched
18
+ # route.
19
+ class AcceptVersionHeader < Base
20
+ def before
21
+ potential_version = (env['HTTP_ACCEPT_VERSION'] || '').strip
22
+
23
+ if strict?
24
+ # If no Accept-Version header:
25
+ if potential_version.empty?
26
+ throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.'
27
+ end
28
+ end
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
35
+
36
+ env['api.version'] = potential_version
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def versions
43
+ options[:versions] || []
44
+ end
45
+
46
+ def strict?
47
+ options[:version_options] && options[:version_options][:strict]
48
+ end
49
+
50
+ # By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
51
+ # of routes (see [Rack::Mount](https://github.com/josh/rack-mount) for more information). To prevent
52
+ # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
53
+ def cascade?
54
+ if options[:version_options] && options[:version_options].has_key?(:cascade)
55
+ !!options[:version_options][:cascade]
56
+ else
57
+ true
58
+ end
59
+ end
60
+
61
+ def error_headers
62
+ cascade? ? { 'X-Cascade' => 'pass' } : {}
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -22,22 +22,25 @@ module Grape
22
22
  # X-Cascade header to alert Rack::Mount to attempt the next matched
23
23
  # route.
24
24
  class Header < Base
25
-
26
25
  def before
27
- header = Rack::Accept::MediaType.new env['HTTP_ACCEPT']
26
+ begin
27
+ header = Rack::Accept::MediaType.new env['HTTP_ACCEPT']
28
+ rescue RuntimeError => e
29
+ throw :error, status: 406, headers: error_headers, message: e.message
30
+ end
28
31
 
29
32
  if strict?
30
33
  # If no Accept header:
31
34
  if header.qvalues.empty?
32
- throw :error, :status => 406, :headers => error_headers, :message => 'Accept header must be set.'
35
+ throw :error, status: 406, headers: error_headers, message: 'Accept header must be set.'
33
36
  end
34
37
  # Remove any acceptable content types with ranges.
35
- header.qvalues.reject! do |media_type,_|
36
- Rack::Accept::Header.parse_media_type(media_type).find{|s| s == '*'}
38
+ header.qvalues.reject! do |media_type, _|
39
+ Rack::Accept::Header.parse_media_type(media_type).find { |s| s == '*' }
37
40
  end
38
41
  # If all Accept headers included a range:
39
42
  if header.qvalues.empty?
40
- throw :error, :status => 406, :headers => error_headers, :message => 'Accept header must not contain ranges ("*").'
43
+ throw :error, status: 406, headers: error_headers, message: 'Accept header must not contain ranges ("*").'
41
44
  end
42
45
  end
43
46
 
@@ -55,19 +58,19 @@ module Grape
55
58
  end
56
59
  # If none of the available content types are acceptable:
57
60
  elsif strict?
58
- throw :error, :status => 406, :headers => error_headers, :message => '406 Not Acceptable'
61
+ throw :error, status: 406, headers: error_headers, message: '406 Not Acceptable'
59
62
  # If all acceptable content types specify a vendor or version that doesn't exist:
60
- elsif header.values.all?{ |media_type| has_vendor?(media_type) || has_version?(media_type)}
61
- throw :error, :status => 406, :headers => error_headers, :message => 'API vendor or version not found.'
63
+ elsif header.values.all? { |header_value| has_vendor?(header_value) || has_version?(header_value) }
64
+ throw :error, status: 406, headers: error_headers, message: 'API vendor or version not found.'
62
65
  end
63
66
  end
64
67
 
65
- private
68
+ private
66
69
 
67
70
  def available_media_types
68
71
  available_media_types = []
69
72
 
70
- content_types.each do |extension,media_type|
73
+ content_types.each do |extension, media_type|
71
74
  versions.reverse.each do |version|
72
75
  available_media_types += ["application/vnd.#{vendor}-#{version}+#{extension}", "application/vnd.#{vendor}-#{version}"]
73
76
  end
@@ -76,7 +79,7 @@ module Grape
76
79
 
77
80
  available_media_types << "application/vnd.#{vendor}"
78
81
 
79
- content_types.each do |_,media_type|
82
+ content_types.each do |_, media_type|
80
83
  available_media_types << media_type
81
84
  end
82
85
 
@@ -99,7 +102,11 @@ module Grape
99
102
  # of routes (see [Rack::Mount](https://github.com/josh/rack-mount) for more information). To prevent
100
103
  # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
101
104
  def cascade?
102
- options[:version_options] && (options[:version_options].has_key?(:cascade) ? options[:version_options][:cascade] : true)
105
+ if options[:version_options] && options[:version_options].has_key?(:cascade)
106
+ !!options[:version_options][:cascade]
107
+ else
108
+ true
109
+ end
103
110
  end
104
111
 
105
112
  def error_headers
@@ -109,17 +116,16 @@ module Grape
109
116
  # @param [String] media_type a content type
110
117
  # @return [Boolean] whether the content type sets a vendor
111
118
  def has_vendor?(media_type)
112
- type, subtype = Rack::Accept::Header.parse_media_type media_type
119
+ _, subtype = Rack::Accept::Header.parse_media_type media_type
113
120
  subtype[/\Avnd\.[a-z0-9*.]+/]
114
121
  end
115
122
 
116
123
  # @param [String] media_type a content type
117
124
  # @return [Boolean] whether the content type sets an API version
118
125
  def has_version?(media_type)
119
- type, subtype = Rack::Accept::Header.parse_media_type media_type
126
+ _, subtype = Rack::Accept::Header.parse_media_type media_type
120
127
  subtype[/\Avnd\.[a-z0-9*.]+-[a-z0-9*\-.]+/]
121
128
  end
122
-
123
129
  end
124
130
  end
125
131
  end
@@ -4,40 +4,38 @@ module Grape
4
4
  module Middleware
5
5
  module Versioner
6
6
  # This middleware sets various version related rack environment variables
7
- # based on the request parameters and removes that parameter from the
7
+ # based on the request parameters and removes that parameter from the
8
8
  # request parameters for subsequent middleware and API.
9
9
  # If the version substring does not match any potential initialized
10
10
  # versions, a 404 error is thrown.
11
11
  # If the version substring is not passed the version (highest mounted)
12
12
  # version will be used.
13
- #
13
+ #
14
14
  # Example: For a uri path
15
15
  # /resource?apiver=v1
16
- #
16
+ #
17
17
  # The following rack env variables are set and path is rewritten to
18
18
  # '/resource':
19
- #
19
+ #
20
20
  # env['api.version'] => 'v1'
21
21
  class Param < Base
22
22
  def default_options
23
23
  {
24
- :parameter => "apiver"
24
+ parameter: "apiver"
25
25
  }
26
26
  end
27
27
 
28
28
  def before
29
29
  paramkey = options[:parameter]
30
- potential_version = request.params[paramkey]
31
-
30
+ potential_version = Rack::Utils.parse_nested_query(env['QUERY_STRING'])[paramkey]
32
31
  unless potential_version.nil?
33
- if options[:versions] && ! options[:versions].find { |v| v.to_s == potential_version }
34
- throw :error, :status => 404, :message => "404 API Version Not Found", :headers => {'X-Cascade' => 'pass'}
32
+ if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
33
+ throw :error, status: 404, message: "404 API Version Not Found", headers: { 'X-Cascade' => 'pass' }
35
34
  end
36
35
  env['api.version'] = potential_version
37
- env['rack.request.query_hash'].delete(paramkey)
36
+ env['rack.request.query_hash'].delete(paramkey) if env.key? 'rack.request.query_hash'
38
37
  end
39
38
  end
40
-
41
39
  end
42
40
  end
43
41
  end
@@ -7,19 +7,19 @@ module Grape
7
7
  # based on the uri path and removes the version substring from the uri
8
8
  # path. If the version substring does not match any potential initialized
9
9
  # versions, a 404 error is thrown.
10
- #
10
+ #
11
11
  # Example: For a uri path
12
12
  # /v1/resource
13
- #
13
+ #
14
14
  # The following rack env variables are set and path is rewritten to
15
15
  # '/resource':
16
- #
16
+ #
17
17
  # env['api.version'] => 'v1'
18
- #
18
+ #
19
19
  class Path < Base
20
20
  def default_options
21
21
  {
22
- :pattern => /.*/i
22
+ pattern: /.*/i
23
23
  }
24
24
  end
25
25
 
@@ -34,21 +34,18 @@ module Grape
34
34
  pieces = path.split('/')
35
35
  potential_version = pieces[1]
36
36
  if potential_version =~ options[:pattern]
37
- if options[:versions] && ! options[:versions].find { |v| v.to_s == potential_version }
38
- throw :error, :status => 404, :message => "404 API Version Not Found"
37
+ if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
38
+ throw :error, status: 404, message: "404 API Version Not Found"
39
39
  end
40
-
41
- truncated_path = "/#{pieces[2..-1].join('/')}"
42
40
  env['api.version'] = potential_version
43
41
  end
44
42
  end
45
43
 
46
44
  private
47
45
 
48
- def prefix
49
- Rack::Mount::Utils.normalize_path(options[:prefix].to_s) if options[:prefix]
50
- end
51
-
46
+ def prefix
47
+ Rack::Mount::Utils.normalize_path(options[:prefix].to_s) if options[:prefix]
48
+ end
52
49
  end
53
50
  end
54
51
  end
@@ -9,7 +9,7 @@
9
9
  module Grape
10
10
  module Middleware
11
11
  module Versioner
12
- extend self
12
+ module_function
13
13
 
14
14
  # @param strategy [Symbol] :path, :header or :param
15
15
  # @return a middleware class based on strategy
@@ -21,6 +21,8 @@ module Grape
21
21
  Header
22
22
  when :param
23
23
  Param
24
+ when :accept_version_header
25
+ AcceptVersionHeader
24
26
  else
25
27
  raise Grape::Exceptions::InvalidVersionerOption.new(strategy)
26
28
  end
@@ -0,0 +1,23 @@
1
+ module Grape
2
+ class Namespace
3
+ attr_reader :space, :options
4
+
5
+ # options:
6
+ # requirements: a hash
7
+ def initialize(space, options = {})
8
+ @space, @options = space.to_s, options
9
+ end
10
+
11
+ def requirements
12
+ options[:requirements] || {}
13
+ end
14
+
15
+ def self.joined_space(settings)
16
+ settings.gather(:namespace).map(&:space).join("/")
17
+ end
18
+
19
+ def self.joined_space_path(settings)
20
+ Rack::Mount::Utils.normalize_path(joined_space(settings))
21
+ end
22
+ end
23
+ end