Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Grape::API versioning based on http accept header

  • Loading branch information...
commit a603d187c8b677c17a1469a56577b1fcb177515f 1 parent 74e3f3d
@jch jch authored
Showing with 81 additions and 106 deletions.
  1. +1 −0  Guardfile
  2. +33 −21 lib/grape/api.rb
  3. +47 −85 spec/grape/api_spec.rb
View
1  Guardfile
@@ -4,6 +4,7 @@
guard 'rspec', :version => 2 do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
+ watch(%r{^spec/support/shared_versioning_examples.rb$}) { |m| "spec/" }
watch('spec/spec_helper.rb') { "spec/" }
end
View
54 lib/grape/api.rb
@@ -72,12 +72,16 @@ def prefix(prefix = nil)
# end
# end
#
- def version(*new_versions, &block)
- if new_versions.any?
- @versions = versions | new_versions
- nest(block) { set(:version, new_versions) }
- else
- settings[:version]
+ def version(*args, &block)
+ if args.any?
+ options = args.pop if args.last.is_a? Hash
+ options ||= {}
+ options = {:using => :header}.merge!(options)
+ @versions = versions | args
+ nest(block) do
+ set(:version, args)
+ set(:version_options, options)
+ end
end
end
@@ -226,7 +230,8 @@ def route(methods, paths = ['/'], route_options = {}, &block)
endpoint = build_endpoint(&block)
endpoint_options = {}
- endpoint_options[:version] = /#{version.join('|')}/ if version
+ # TODO: preferably versioning would be handled in a central point
+ endpoint_options[:version] = /#{settings[:version].join('|')}/ if settings[:version] && settings[:version_options][:using] == :path
route_options ||= {}
@@ -241,15 +246,15 @@ def route(methods, paths = ['/'], route_options = {}, &block)
request_method = (method.to_s.upcase unless method == :any)
routes << Route.new(route_options.merge({
- :prefix => prefix,
- :version => version ? version.join('|') : nil,
- :namespace => namespace,
- :method => request_method,
+ :prefix => prefix,
+ :version => settings[:version] ? settings[:version].join('|') : nil,
+ :namespace => namespace,
+ :method => request_method,
:path => compiled_path,
:params => path_params}))
- route_set.add_route(endpoint,
- :path_info => path,
+ route_set.add_route(endpoint,
+ :path_info => path,
:request_method => request_method
)
end
@@ -350,10 +355,18 @@ def build_endpoint(&block)
:format => settings[:error_format] || :txt,
:rescue_options => settings[:rescue_options],
:rescue_handlers => settings[:rescue_handlers] || {}
- b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic
+
+ b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic
b.use Rack::Auth::Digest::MD5, settings[:auth][:realm], settings[:auth][:opaque], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_digest
- b.use Grape::Middleware::Prefixer, :prefix => prefix if prefix
- b.use Grape::Middleware::Versioner, :versions => (version if version.is_a?(Array)) if version
+ b.use Grape::Middleware::Prefixer, :prefix => prefix if prefix
+
+ if settings[:version]
+ b.use Grape::Middleware::Versioner.using(settings[:version_options][:using]), {
+ :versions => settings[:version],
+ :version_options => settings[:version_options]
+ }
+ end
+
b.use Grape::Middleware::Formatter, :default_format => default_format || :json
middleware.each{|m| b.use *m }
@@ -368,7 +381,6 @@ def build_endpoint(&block)
}, &block)
endpoint.send :include, helpers
b.run endpoint
-
b.to_app
end
@@ -389,14 +401,14 @@ def route_set
def compile_path(path)
parts = []
parts << prefix if prefix
- parts << ':version' if version
+ parts << ':version' if settings[:version] && settings[:version_options][:using] == :path
parts << namespace.to_s if namespace
parts << path.to_s if path && '/' != path
parts.last << '(.:format)'
Rack::Mount::Utils.normalize_path(parts.join('/'))
end
- end
-
- reset!
+ end
+
+ reset!
end
end
View
132 spec/grape/api_spec.rb
@@ -1,4 +1,5 @@
require 'spec_helper'
+require 'shared_versioning_examples'
describe Grape::API do
subject { Class.new(Grape::API) }
@@ -21,84 +22,44 @@ def app; subject end
end
end
- describe '.version' do
- it 'should set the API version' do
- subject.version 'v1'
- subject.get :hello do
- "Version: #{request.env['api.version']}"
+ describe '.version using path' do
+ it_should_behave_like 'versioning' do
+ let(:macro_options) do
+ {
+ :using => :path
+ }
end
-
- get '/v1/hello'
- last_response.body.should eql "Version: v1"
end
-
- it 'should add the prefix before the API version' do
- subject.prefix 'api'
- subject.version 'v1'
- subject.get :hello do
- "Version: #{request.env['api.version']}"
- end
-
- get '/api/v1/hello'
- last_response.body.should eql "Version: v1"
- end
-
- it 'should be able to specify version as a nesting' do
- subject.version 'v2'
- subject.get '/awesome' do
- "Radical"
- end
-
- subject.version 'v1' do
- get '/legacy' do
- "Totally"
- end
+ end
+
+ describe '.version using header' do
+ it_should_behave_like 'versioning' do
+ let(:macro_options) do
+ {
+ :using => :header,
+ :vendor => 'mycompany',
+ :format => 'json'
+ }
end
-
- get '/v1/awesome'
- last_response.status.should eql 404
- get '/v2/awesome'
- last_response.status.should eql 200
- get '/v1/legacy'
- last_response.status.should eql 200
- get '/v2/legacy'
- last_response.status.should eql 404
end
-
- it 'should be able to specify multiple versions' do
- subject.version 'v1', 'v2'
- subject.get 'awesome' do
- "I exist"
- end
-
- get '/v1/awesome'
- last_response.status.should eql 200
- get '/v2/awesome'
- last_response.status.should eql 200
- get '/v3/awesome'
- last_response.status.should eql 404
+
+ # Behavior as defined by rfc2616 when no header is defined
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+ describe 'no specified accept header' do
+ # subject.version 'v1', :using => :header
+ # subject.get '/hello' do
+ # 'hello'
+ # end
+
+ # it 'should route' do
+ # get '/hello'
+ # last_response.status.should eql 200
+ # end
end
-
- it 'should allow the same endpoint to be implemented for different versions' do
- subject.version 'v2'
- subject.get 'version' do
- request.env['api.version']
- end
-
- subject.version 'v1' do
- get 'version' do
- "version " + request.env['api.version']
- end
- end
+
+ it 'should route if any media type is allowed' do
- get '/v2/version'
- last_response.status.should == 200
- last_response.body.should == 'v2'
- get '/v1/version'
- last_response.status.should == 200
- last_response.body.should == 'version v1'
end
-
end
describe '.represent' do
@@ -122,7 +83,7 @@ def app; subject end
it 'should come after the prefix and version' do
subject.prefix :rad
- subject.version :v1
+ subject.version :v1, :using => :path
subject.namespace :awesome do
compile_path('hello').should == '/rad/:version/awesome/hello(.:format)'
@@ -162,7 +123,7 @@ def app; subject end
it 'should be callable with nil just to push onto the stack' do
subject.namespace do
- version 'v2'
+ version 'v2', :using => :path
compile_path('hello').should == '/:version/hello(.:format)'
end
subject.send(:compile_path, 'hello').should == '/hello(.:format)'
@@ -536,28 +497,29 @@ def two
end
describe '.scope' do
+ # TODO: refactor this to not be tied to versioning. How about a generic
+ # .setting macro?
it 'should scope the various settings' do
- subject.version 'v2'
-
+ subject.prefix 'new'
+
subject.scope :legacy do
- version 'v1'
-
+ prefix 'legacy'
get '/abc' do
- version
+ 'abc'
end
end
subject.get '/def' do
- version
+ 'def'
end
- get '/v2/abc'
+ get '/new/abc'
last_response.status.should eql 404
- get '/v1/abc'
+ get '/legacy/abc'
last_response.status.should eql 200
- get '/v1/def'
+ get '/legacy/def'
last_response.status.should eql 404
- get '/v2/def'
+ get '/new/def'
last_response.status.should eql 200
end
end
@@ -693,12 +655,12 @@ def two
describe "api structure with two versions and a namespace" do
class TwitterAPI < Grape::API
# version v1
- version 'v1'
+ version 'v1', :using => :path
get "version" do
api.version
end
# version v2
- version 'v2'
+ version 'v2', :using => :path
prefix 'p'
namespace "n1" do
namespace "n2" do
Please sign in to comment.
Something went wrong with that request. Please try again.