grape 0.1.5 → 0.2.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 (142) hide show
  1. data/.gitignore +13 -1
  2. data/.rspec +1 -1
  3. data/.travis.yml +1 -0
  4. data/Gemfile +10 -0
  5. data/Guardfile +15 -0
  6. data/README.markdown +472 -67
  7. data/grape.gemspec +4 -4
  8. data/lib/grape.rb +17 -5
  9. data/lib/grape/api.rb +227 -124
  10. data/lib/grape/cookies.rb +41 -0
  11. data/lib/grape/endpoint.rb +256 -27
  12. data/lib/grape/entity.rb +227 -0
  13. data/lib/grape/middleware/auth/oauth2.rb +1 -2
  14. data/lib/grape/middleware/base.rb +59 -6
  15. data/lib/grape/middleware/error.rb +15 -6
  16. data/lib/grape/middleware/filter.rb +17 -0
  17. data/lib/grape/middleware/formatter.rb +25 -31
  18. data/lib/grape/middleware/versioner.rb +20 -20
  19. data/lib/grape/middleware/versioner/header.rb +59 -0
  20. data/lib/grape/middleware/versioner/path.rb +42 -0
  21. data/lib/grape/route.rb +23 -0
  22. data/lib/grape/util/hash_stack.rb +100 -0
  23. data/lib/grape/version.rb +1 -1
  24. data/spec/grape/api_spec.rb +651 -162
  25. data/spec/grape/endpoint_spec.rb +216 -18
  26. data/spec/grape/entity_spec.rb +320 -0
  27. data/spec/grape/middleware/auth/basic_spec.rb +2 -2
  28. data/spec/grape/middleware/auth/digest_spec.rb +4 -6
  29. data/spec/grape/middleware/exception_spec.rb +1 -0
  30. data/spec/grape/middleware/formatter_spec.rb +81 -27
  31. data/spec/grape/middleware/versioner/header_spec.rb +148 -0
  32. data/spec/grape/middleware/versioner/path_spec.rb +40 -0
  33. data/spec/grape/middleware/versioner_spec.rb +6 -34
  34. data/spec/grape/util/hash_stack_spec.rb +133 -0
  35. data/spec/shared/versioning_examples.rb +77 -0
  36. data/spec/spec_helper.rb +11 -3
  37. data/spec/support/basic_auth_encode_helpers.rb +4 -0
  38. data/spec/support/rack_patch.rb +25 -0
  39. data/spec/support/versioned_helpers.rb +34 -0
  40. metadata +140 -241
  41. data/.yardoc/checksums +0 -13
  42. data/.yardoc/objects/Grape.dat +0 -0
  43. data/.yardoc/objects/Grape/API.dat +0 -0
  44. data/.yardoc/objects/Grape/API/auth_c.dat +0 -0
  45. data/.yardoc/objects/Grape/API/build_endpoint_c.dat +0 -0
  46. data/.yardoc/objects/Grape/API/call_c.dat +0 -0
  47. data/.yardoc/objects/Grape/API/compile_path_c.dat +0 -0
  48. data/.yardoc/objects/Grape/API/default_format_c.dat +0 -0
  49. data/.yardoc/objects/Grape/API/delete_c.dat +0 -0
  50. data/.yardoc/objects/Grape/API/get_c.dat +0 -0
  51. data/.yardoc/objects/Grape/API/group_c.dat +0 -0
  52. data/.yardoc/objects/Grape/API/head_c.dat +0 -0
  53. data/.yardoc/objects/Grape/API/helpers_c.dat +0 -0
  54. data/.yardoc/objects/Grape/API/http_basic_c.dat +0 -0
  55. data/.yardoc/objects/Grape/API/inherited_c.dat +0 -0
  56. data/.yardoc/objects/Grape/API/logger_c.dat +0 -0
  57. data/.yardoc/objects/Grape/API/namespace_c.dat +0 -0
  58. data/.yardoc/objects/Grape/API/nest_c.dat +0 -0
  59. data/.yardoc/objects/Grape/API/post_c.dat +0 -0
  60. data/.yardoc/objects/Grape/API/prefix_c.dat +0 -0
  61. data/.yardoc/objects/Grape/API/put_c.dat +0 -0
  62. data/.yardoc/objects/Grape/API/reset_21_c.dat +0 -0
  63. data/.yardoc/objects/Grape/API/resource_c.dat +0 -0
  64. data/.yardoc/objects/Grape/API/resources_c.dat +0 -0
  65. data/.yardoc/objects/Grape/API/route_c.dat +0 -0
  66. data/.yardoc/objects/Grape/API/route_set_c.dat +0 -0
  67. data/.yardoc/objects/Grape/API/scope_c.dat +0 -0
  68. data/.yardoc/objects/Grape/API/set_c.dat +0 -0
  69. data/.yardoc/objects/Grape/API/settings_c.dat +0 -0
  70. data/.yardoc/objects/Grape/API/settings_stack_c.dat +0 -0
  71. data/.yardoc/objects/Grape/API/version_c.dat +0 -0
  72. data/.yardoc/objects/Grape/Endpoint.dat +0 -0
  73. data/.yardoc/objects/Grape/Endpoint/block_3D_c.dat +0 -0
  74. data/.yardoc/objects/Grape/Endpoint/block_c.dat +0 -0
  75. data/.yardoc/objects/Grape/Endpoint/call_c.dat +0 -0
  76. data/.yardoc/objects/Grape/Endpoint/call_i.dat +0 -0
  77. data/.yardoc/objects/Grape/Endpoint/env_i.dat +0 -0
  78. data/.yardoc/objects/Grape/Endpoint/error_21_i.dat +0 -0
  79. data/.yardoc/objects/Grape/Endpoint/generate_c.dat +0 -0
  80. data/.yardoc/objects/Grape/Endpoint/header_i.dat +0 -0
  81. data/.yardoc/objects/Grape/Endpoint/params_i.dat +0 -0
  82. data/.yardoc/objects/Grape/Endpoint/request_i.dat +0 -0
  83. data/.yardoc/objects/Grape/Endpoint/status_i.dat +0 -0
  84. data/.yardoc/objects/Grape/Endpoint/version_i.dat +0 -0
  85. data/.yardoc/objects/Grape/Middleware.dat +0 -0
  86. data/.yardoc/objects/Grape/Middleware/Auth.dat +0 -0
  87. data/.yardoc/objects/Grape/Middleware/Auth/Basic.dat +0 -0
  88. data/.yardoc/objects/Grape/Middleware/Auth/Basic/authenticator_i.dat +0 -0
  89. data/.yardoc/objects/Grape/Middleware/Auth/Basic/basic_request_i.dat +0 -0
  90. data/.yardoc/objects/Grape/Middleware/Auth/Basic/before_i.dat +0 -0
  91. data/.yardoc/objects/Grape/Middleware/Auth/Basic/credentials_i.dat +0 -0
  92. data/.yardoc/objects/Grape/Middleware/Auth/Basic/initialize_i.dat +0 -0
  93. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2.dat +0 -0
  94. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/before_i.dat +0 -0
  95. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/default_options_i.dat +0 -0
  96. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/error_out_i.dat +0 -0
  97. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/parse_authorization_header_i.dat +0 -0
  98. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/token_class_i.dat +0 -0
  99. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/verify_token_i.dat +0 -0
  100. data/.yardoc/objects/Grape/Middleware/Base.dat +0 -0
  101. data/.yardoc/objects/Grape/Middleware/Base/after_i.dat +0 -0
  102. data/.yardoc/objects/Grape/Middleware/Base/app_i.dat +0 -0
  103. data/.yardoc/objects/Grape/Middleware/Base/before_i.dat +0 -0
  104. data/.yardoc/objects/Grape/Middleware/Base/call_21_i.dat +0 -0
  105. data/.yardoc/objects/Grape/Middleware/Base/call_i.dat +0 -0
  106. data/.yardoc/objects/Grape/Middleware/Base/default_options_i.dat +0 -0
  107. data/.yardoc/objects/Grape/Middleware/Base/env_i.dat +0 -0
  108. data/.yardoc/objects/Grape/Middleware/Base/initialize_i.dat +0 -0
  109. data/.yardoc/objects/Grape/Middleware/Base/options_i.dat +0 -0
  110. data/.yardoc/objects/Grape/Middleware/Base/request_i.dat +0 -0
  111. data/.yardoc/objects/Grape/Middleware/Base/response_i.dat +0 -0
  112. data/.yardoc/objects/Grape/Middleware/Error.dat +0 -0
  113. data/.yardoc/objects/Grape/Middleware/Error/call_21_i.dat +0 -0
  114. data/.yardoc/objects/Grape/Middleware/Error/error_response_i.dat +0 -0
  115. data/.yardoc/objects/Grape/Middleware/Formatter.dat +0 -0
  116. data/.yardoc/objects/Grape/Middleware/Formatter/CONTENT_TYPES.dat +0 -0
  117. data/.yardoc/objects/Grape/Middleware/Formatter/after_i.dat +0 -0
  118. data/.yardoc/objects/Grape/Middleware/Formatter/before_i.dat +0 -0
  119. data/.yardoc/objects/Grape/Middleware/Formatter/content_types_i.dat +0 -0
  120. data/.yardoc/objects/Grape/Middleware/Formatter/default_options_i.dat +0 -0
  121. data/.yardoc/objects/Grape/Middleware/Formatter/encode_json_i.dat +0 -0
  122. data/.yardoc/objects/Grape/Middleware/Formatter/encode_txt_i.dat +0 -0
  123. data/.yardoc/objects/Grape/Middleware/Formatter/format_from_extension_i.dat +0 -0
  124. data/.yardoc/objects/Grape/Middleware/Formatter/format_from_header_i.dat +0 -0
  125. data/.yardoc/objects/Grape/Middleware/Formatter/headers_i.dat +0 -0
  126. data/.yardoc/objects/Grape/Middleware/Formatter/mime_array_i.dat +0 -0
  127. data/.yardoc/objects/Grape/Middleware/Formatter/mime_types_i.dat +0 -0
  128. data/.yardoc/objects/Grape/Middleware/Prefixer.dat +0 -0
  129. data/.yardoc/objects/Grape/Middleware/Prefixer/before_i.dat +0 -0
  130. data/.yardoc/objects/Grape/Middleware/Prefixer/prefix_i.dat +0 -0
  131. data/.yardoc/objects/Grape/Middleware/Versioner.dat +0 -0
  132. data/.yardoc/objects/Grape/Middleware/Versioner/before_i.dat +0 -0
  133. data/.yardoc/objects/Grape/Middleware/Versioner/default_options_i.dat +0 -0
  134. data/.yardoc/objects/Grape/MiddlewareStack.dat +0 -0
  135. data/.yardoc/objects/Grape/MiddlewareStack/initialize_i.dat +0 -0
  136. data/.yardoc/objects/Grape/MiddlewareStack/stack_i.dat +0 -0
  137. data/.yardoc/objects/Grape/MiddlewareStack/to_app_i.dat +0 -0
  138. data/.yardoc/objects/Grape/MiddlewareStack/use_i.dat +0 -0
  139. data/.yardoc/objects/root.dat +0 -0
  140. data/.yardoc/proxy_types +0 -2
  141. data/Gemfile.lock +0 -52
  142. data/autotest/discover.rb +0 -1
@@ -60,13 +60,12 @@ module Grape::Middleware::Auth
60
60
  end
61
61
 
62
62
  def error_out(status, error)
63
- throw :error, {
63
+ throw :error,
64
64
  :message => error,
65
65
  :status => status,
66
66
  :headers => {
67
67
  'WWW-Authenticate' => "OAuth realm='#{options[:realm]}', error='#{error}'"
68
68
  }
69
- }
70
69
  end
71
70
  end
72
71
  end
@@ -1,3 +1,6 @@
1
+ require 'multi_json'
2
+ require 'multi_xml'
3
+
1
4
  module Grape
2
5
  module Middleware
3
6
  class Base
@@ -9,20 +12,20 @@ module Grape
9
12
  @app = app
10
13
  @options = default_options.merge(options)
11
14
  end
12
-
15
+
13
16
  def default_options; {} end
14
-
17
+
15
18
  def call(env)
16
19
  dup.call!(env)
17
20
  end
18
-
21
+
19
22
  def call!(env)
20
23
  @env = env
21
24
  before
22
25
  @app_response = @app.call(@env)
23
26
  after || @app_response
24
27
  end
25
-
28
+
26
29
  # @abstract
27
30
  # Called before the application is called in the middleware lifecycle.
28
31
  def before; end
@@ -30,11 +33,11 @@ module Grape
30
33
  # Called after the application is called in the middleware lifecycle.
31
34
  # @return [Response, nil] a Rack SPEC response or nil to call the application afterwards.
32
35
  def after; end
33
-
36
+
34
37
  def request
35
38
  Rack::Request.new(self.env)
36
39
  end
37
-
40
+
38
41
  def response
39
42
  Rack::Response.new(@app_response)
40
43
  end
@@ -52,12 +55,21 @@ module Grape
52
55
  FORMATTERS = {
53
56
  :json => :encode_json,
54
57
  :txt => :encode_txt,
58
+ :xml => :encode_xml
59
+ }
60
+ PARSERS = {
61
+ :json => :decode_json,
62
+ :xml => :decode_xml
55
63
  }
56
64
 
57
65
  def formatters
58
66
  FORMATTERS.merge(options[:formatters] || {})
59
67
  end
60
68
 
69
+ def parsers
70
+ PARSERS.merge(options[:parsers] || {})
71
+ end
72
+
61
73
  def content_types
62
74
  CONTENT_TYPES.merge(options[:content_types] || {})
63
75
  end
@@ -82,6 +94,47 @@ module Grape
82
94
  end
83
95
  end
84
96
 
97
+ def parser_for(api_format)
98
+ spec = parsers[api_format]
99
+ case spec
100
+ when nil
101
+ nil
102
+ when Symbol
103
+ method(spec)
104
+ else
105
+ spec
106
+ end
107
+ end
108
+
109
+ def decode_json(object)
110
+ MultiJson.decode(object)
111
+ end
112
+
113
+ def encode_json(object)
114
+ return object if object.is_a?(String)
115
+
116
+ if object.respond_to? :serializable_hash
117
+ MultiJson.encode(object.serializable_hash)
118
+ elsif object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false)
119
+ MultiJson.encode(object.map {|o| o.serializable_hash })
120
+ elsif object.respond_to? :to_json
121
+ object.to_json
122
+ else
123
+ MultiJson.encode(object)
124
+ end
125
+ end
126
+
127
+ def encode_txt(object)
128
+ object.respond_to?(:to_txt) ? object.to_txt : object.to_s
129
+ end
130
+
131
+ def decode_xml(object)
132
+ MultiXml.parse(object)
133
+ end
134
+
135
+ def encode_xml(object)
136
+ object.respond_to?(:to_xml) ? object.to_xml : object.to_s
137
+ end
85
138
  end
86
139
 
87
140
  end
@@ -12,9 +12,9 @@ module Grape
12
12
  :default_message => "",
13
13
  :format => :txt,
14
14
  :formatters => {},
15
-
16
15
  :rescue_all => false, # true to rescue all exceptions
17
- :rescue_options => {:backtrace => false}, # true to display backtrace
16
+ :rescue_options => { :backtrace => false }, # true to display backtrace
17
+ :rescue_handlers => {}, # rescue handler blocks
18
18
  :rescued_errors => []
19
19
  }
20
20
  end
@@ -45,23 +45,32 @@ module Grape
45
45
  })
46
46
  rescue Exception => e
47
47
  raise unless options[:rescue_all] || (options[:rescued_errors] || []).include?(e.class)
48
- error_response({ :message => e.message, :backtrace => e.backtrace })
48
+ handler = options[:rescue_handlers][e.class] || options[:rescue_handlers][:all]
49
+ handler.nil? ? handle_error(e) : self.instance_exec(e, &handler)
49
50
  end
50
51
 
51
52
  end
52
53
 
54
+ def handle_error(e)
55
+ error_response({ :message => e.message, :backtrace => e.backtrace })
56
+ end
57
+
53
58
  def error_response(error = {})
54
59
  status = error[:status] || options[:default_status]
55
60
  message = error[:message] || options[:default_message]
56
61
  headers = {'Content-Type' => content_type}
57
62
  headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
58
63
  backtrace = error[:backtrace] || []
59
- Rack::Response.new([format_message(message, backtrace, status)], status, headers).finish
64
+ rack_response(format_message(message, backtrace, status), status, headers)
60
65
  end
61
-
66
+
67
+ def rack_response(message, status = options[:default_status], headers = { 'Content-Type' => content_type })
68
+ Rack::Response.new([ message ], status, headers).finish
69
+ end
70
+
62
71
  def format_message(message, backtrace, status)
63
72
  formatter = formatter_for(options[:format])
64
- throw :error, :status => 406, :message => "The requested format #{options[:format]} is not supported." unless formatter
73
+ throw :error, :status => 406, :message => "The requested format #{options[:format]} is not supported." unless formatter
65
74
  formatter.call(message, backtrace)
66
75
  end
67
76
 
@@ -0,0 +1,17 @@
1
+ module Grape
2
+ module Middleware
3
+ # This is a simple middleware for adding before and after filters
4
+ # to Grape APIs. It is used like so:
5
+ #
6
+ # use Grape::Middleware::Filter, :before => lambda{ do_something }, after: => lambda{ do_something }
7
+ class Filter < Base
8
+ def before
9
+ app.instance_eval &options[:before] if options[:before]
10
+ end
11
+
12
+ def after
13
+ app.instance_eval &options[:after] if options[:after]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,5 +1,4 @@
1
1
  require 'grape/middleware/base'
2
- require 'multi_json'
3
2
 
4
3
  module Grape
5
4
  module Middleware
@@ -7,22 +6,35 @@ module Grape
7
6
  include Formats
8
7
 
9
8
  def default_options
10
- {
9
+ {
11
10
  :default_format => :txt,
12
11
  :formatters => {},
13
- :content_types => {}
12
+ :content_types => {},
13
+ :parsers => {}
14
14
  }
15
15
  end
16
16
 
17
17
  def headers
18
- env.dup.inject({}){|h,(k,v)| h[k.downcase] = v; h}
18
+ env.dup.inject({}){|h,(k,v)| h[k.downcase[5..-1]] = v if k.downcase.start_with?('http_'); h}
19
19
  end
20
20
 
21
21
  def before
22
- fmt = format_from_extension || format_from_header || options[:default_format]
23
-
22
+ fmt = format_from_extension || options[:format] || format_from_header || options[:default_format]
24
23
  if content_types.key?(fmt)
25
- env['api.format'] = fmt
24
+ if !env['rack.input'].nil? and (body = env['rack.input'].read).strip.length != 0
25
+ parser = parser_for fmt
26
+ unless parser.nil?
27
+ begin
28
+ body = parser.call(body)
29
+ env['rack.request.form_hash'] = !env['rack.request.form_hash'].nil? ? env['rack.request.form_hash'].merge(body) : body
30
+ env['rack.request.form_input'] = env['rack.input']
31
+ rescue
32
+ # It's possible that it's just regular POST content -- just back off
33
+ end
34
+ end
35
+ env['rack.input'].rewind
36
+ end
37
+ env['api.format'] = fmt
26
38
  else
27
39
  throw :error, :status => 406, :message => 'The requested format is not supported.'
28
40
  end
@@ -40,7 +52,7 @@ module Grape
40
52
  end
41
53
 
42
54
  def format_from_header
43
- mime_array.each do |t|
55
+ mime_array.each do |t|
44
56
  if mime_types.key?(t)
45
57
  return mime_types[t]
46
58
  end
@@ -49,15 +61,11 @@ module Grape
49
61
  end
50
62
 
51
63
  def mime_array
52
- accept = headers['accept']
53
- if accept
54
- accept.gsub(/\b/,'').
55
- scan(/(\w+\/[\w+]+)(?:;[^,]*q=([0-9.]+)[^,]*)?/i).
56
- sort_by{|a| -a[1].to_f}.
57
- map{|a| a[0]}
58
- else
59
- []
60
- end
64
+ accept = headers['accept'] or return []
65
+
66
+ accept.gsub(/\b/,'').scan(%r((\w+/[\w+.-]+)(?:(?:;[^,]*?)?;\s*q=([\d.]+))?)).sort_by { |_, q| -q.to_f }.map {|mime, _|
67
+ mime.sub(%r(vnd\.[^+]+\+), '')
68
+ }
61
69
  end
62
70
 
63
71
  def after
@@ -69,20 +77,6 @@ module Grape
69
77
  headers['Content-Type'] = content_types[env['api.format']]
70
78
  Rack::Response.new(bodymap, status, headers).to_a
71
79
  end
72
-
73
- def encode_json(object)
74
- if object.respond_to? :serializable_hash
75
- MultiJson.encode(object.serializable_hash)
76
- elsif object.respond_to? :to_json
77
- object.to_json
78
- else
79
- MultiJson.encode(object)
80
- end
81
- end
82
-
83
- def encode_txt(object)
84
- object.respond_to?(:to_txt) ? object.to_txt : object.to_s
85
- end
86
80
  end
87
81
  end
88
82
  end
@@ -1,25 +1,25 @@
1
- require 'grape/middleware/base'
2
-
1
+ # Versioners set env['api.version'] when a version is defined on an API and
2
+ # on the requests. The current methods for determining version are:
3
+ #
4
+ # :header - version from HTTP Accept header.
5
+ # :path - version from uri. e.g. /v1/resource
6
+ #
7
+ # See individual classes for details.
3
8
  module Grape
4
9
  module Middleware
5
- class Versioner < Base
6
- def default_options
7
- {
8
- :pattern => /.*/i
9
- }
10
- end
11
-
12
- def before
13
- pieces = env['PATH_INFO'].split('/')
14
- potential_version = pieces[1]
15
- if potential_version =~ options[:pattern]
16
- if options[:versions] && !options[:versions].include?(potential_version)
17
- throw :error, :status => 404, :message => "404 API Version Not Found"
18
- end
19
-
20
- truncated_path = "/#{pieces[2..-1].join('/')}"
21
- env['api.version'] = potential_version
22
- env['PATH_INFO'] = truncated_path
10
+ module Versioner
11
+ extend self
12
+
13
+ # @param strategy [Symbol] :path or :header
14
+ # @return a middleware class based on strategy
15
+ def using(strategy)
16
+ case strategy
17
+ when :path
18
+ Path
19
+ when :header
20
+ Header
21
+ else
22
+ raise ArgumentError.new("Unknown :using for versioner: #{strategy}")
23
23
  end
24
24
  end
25
25
  end
@@ -0,0 +1,59 @@
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 header with the pattern:
8
+ # application/vnd.:vendor-:version+:format
9
+ #
10
+ # Example: For request header
11
+ # Accept: application/vnd.mycompany-v1+json
12
+ #
13
+ # The following rack env variables are set:
14
+ #
15
+ # env['api.type'] => 'application'
16
+ # env['api.subtype'] => 'vnd.mycompany-v1+json'
17
+ # env['api.vendor] => 'mycompany'
18
+ # env['api.version] => 'v1'
19
+ # env['api.format] => 'format'
20
+ #
21
+ # If version does not match this route, then a 406 is throw with
22
+ # X-Cascade header to alert Rack::Mount to attempt the next matched
23
+ # route.
24
+ class Header < Base
25
+ def before
26
+ accept = env['HTTP_ACCEPT'] || ""
27
+
28
+ if options[:version_options] && options[:version_options].keys.include?(:strict) && options[:version_options][:strict]
29
+ if (is_accept_header_valid?(accept)) && options[:version_options][:using] == :header
30
+ throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => "406 API Version Not Found"
31
+ end
32
+ end
33
+ accept.strip.scan(/^(.+?)\/(.+?)$/) do |type, subtype|
34
+ env['api.type'] = type
35
+ env['api.subtype'] = subtype
36
+
37
+ subtype.scan(/vnd\.(.+)?-(.+)?\+(.*)?/) do |vendor, version, format|
38
+ is_vendored = options[:version_options] && options[:version_options][:vendor]
39
+ is_vendored_match = is_vendored ? options[:version_options][:vendor] == vendor : true
40
+
41
+ if (options[:versions] && !options[:versions].include?(version)) || !is_vendored_match
42
+ throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => "406 API Version Not Found"
43
+ end
44
+
45
+ env['api.version'] = version
46
+ env['api.vendor'] = vendor
47
+ env['api.format'] = format # weird that Grape::Middleware::Formatter also does this
48
+ end
49
+ end
50
+ end
51
+
52
+ protected
53
+ def is_accept_header_valid?(header)
54
+ (header.strip =~ /application\/vnd\.(.+?)-(.+?)\+(.+?)/).nil?
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,42 @@
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 uri path and removes the version substring from the uri
8
+ # path. If the version substring does not match any potential initialized
9
+ # versions, a 404 error is thrown.
10
+ #
11
+ # Example: For a uri path
12
+ # /v1/resource
13
+ #
14
+ # The following rack env variables are set and path is rewritten to
15
+ # '/resource':
16
+ #
17
+ # env['api.version'] => 'v1'
18
+ #
19
+ class Path < Base
20
+ def default_options
21
+ {
22
+ :pattern => /.*/i
23
+ }
24
+ end
25
+
26
+ def before
27
+ pieces = env['PATH_INFO'].split('/')
28
+ potential_version = pieces[1]
29
+ if potential_version =~ options[:pattern]
30
+ if options[:versions] && !options[:versions].include?(potential_version)
31
+ throw :error, :status => 404, :message => "404 API Version Not Found"
32
+ end
33
+
34
+ truncated_path = "/#{pieces[2..-1].join('/')}"
35
+ env['api.version'] = potential_version
36
+ env['PATH_INFO'] = truncated_path
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ module Grape
2
+
3
+ # A compiled route for inspection.
4
+ class Route
5
+
6
+ def initialize(options = {})
7
+ @options = options || {}
8
+ end
9
+
10
+ def method_missing(method_id, *arguments)
11
+ if match = /route_([_a-zA-Z]\w*)/.match(method_id.to_s)
12
+ @options[match.captures.last.to_sym]
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def to_s
19
+ "version=#{route_version}, method=#{route_method}, path=#{route_path}"
20
+ end
21
+
22
+ end
23
+ end