Skip to content

Commit 87642b5

Browse files
author
Jerry Cheung
committed
refactors Grape::Middleware::Versioner to dispatch
to versioning strategy
1 parent c6ce837 commit 87642b5

File tree

5 files changed

+113
-54
lines changed

5 files changed

+113
-54
lines changed

lib/grape.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ module Auth
2121
autoload :Basic, 'grape/middleware/auth/basic'
2222
autoload :Digest, 'grape/middleware/auth/digest'
2323
end
24+
25+
module Versioner
26+
autoload :Path, 'grape/middleware/versioner/path'
27+
autoload :Header, 'grape/middleware/versioner/header'
28+
end
2429
end
2530

2631
module Util

lib/grape/middleware/versioner.rb

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
38
module Grape
49
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}")
2323
end
2424
end
2525
end
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
require 'spec_helper'
2+
3+
describe Grape::Middleware::Versioner::Path do
4+
let(:app) { lambda{|env| [200, env, env['api.version']]} }
5+
subject { Grape::Middleware::Versioner::Path.new(app, @options || {}) }
6+
7+
it 'should set the API version based on the first path' do
8+
subject.call('PATH_INFO' => '/v1/awesome').last.should == 'v1'
9+
end
10+
11+
it 'should cut the version out of the path' do
12+
subject.call('PATH_INFO' => '/v1/awesome')[1]['PATH_INFO'].should == '/awesome'
13+
end
14+
15+
it 'should provide a nil version if no path is given' do
16+
subject.call('PATH_INFO' => '/').last.should be_nil
17+
end
18+
19+
context 'with a pattern' do
20+
before{ @options = {:pattern => /v./i} }
21+
it 'should set the version if it matches' do
22+
subject.call('PATH_INFO' => '/v1/awesome').last.should == 'v1'
23+
end
24+
25+
it 'should ignore the version if it fails to match' do
26+
subject.call('PATH_INFO' => '/awesome/radical').last.should be_nil
27+
end
28+
end
29+
30+
context 'with specified versions' do
31+
before{ @options = {:versions => ['v1', 'v2']}}
32+
it 'should throw an error if a non-allowed version is specified' do
33+
catch(:error){subject.call('PATH_INFO' => '/v3/awesome')}[:status].should == 404
34+
end
35+
36+
it 'should allow versions that have been specified' do
37+
subject.call('PATH_INFO' => '/v1/asoasd').last.should == 'v1'
38+
end
39+
end
40+
end
Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,12 @@
11
require 'spec_helper'
22

33
describe Grape::Middleware::Versioner do
4-
let(:app) { lambda{|env| [200, env, env['api.version']]} }
5-
subject { Grape::Middleware::Versioner.new(app, @options || {}) }
6-
7-
it 'should set the API version based on the first path' do
8-
subject.call('PATH_INFO' => '/v1/awesome').last.should == 'v1'
4+
let(:klass) { Grape::Middleware::Versioner }
5+
it 'should recognize :path' do
6+
klass.using(:path).should == Grape::Middleware::Versioner::Path
97
end
10-
11-
it 'should cut the version out of the path' do
12-
subject.call('PATH_INFO' => '/v1/awesome')[1]['PATH_INFO'].should == '/awesome'
13-
end
14-
15-
it 'should provide a nil version if no path is given' do
16-
subject.call('PATH_INFO' => '/').last.should be_nil
17-
end
18-
19-
context 'with a pattern' do
20-
before{ @options = {:pattern => /v./i} }
21-
it 'should set the version if it matches' do
22-
subject.call('PATH_INFO' => '/v1/awesome').last.should == 'v1'
23-
end
24-
25-
it 'should ignore the version if it fails to match' do
26-
subject.call('PATH_INFO' => '/awesome/radical').last.should be_nil
27-
end
28-
end
29-
30-
context 'with specified versions' do
31-
before{ @options = {:versions => ['v1', 'v2']}}
32-
it 'should throw an error if a non-allowed version is specified' do
33-
catch(:error){subject.call('PATH_INFO' => '/v3/awesome')}[:status].should == 404
34-
end
35-
36-
it 'should allow versions that have been specified' do
37-
subject.call('PATH_INFO' => '/v1/asoasd').last.should == 'v1'
38-
end
8+
9+
it 'should recognize :header' do
10+
klass.using(:header).should == Grape::Middleware::Versioner::Header
3911
end
4012
end

0 commit comments

Comments
 (0)