Skip to content

Commit c6ce837

Browse files
author
Jerry Cheung
committed
accept header based versioning middleware
1 parent 37056aa commit c6ce837

File tree

2 files changed

+112
-0
lines changed

2 files changed

+112
-0
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 404 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+
accept.strip.scan(/^(.+?)\/(.+?)$/) do |type, subtype|
28+
env['api.type'] = type
29+
env['api.subtype'] = subtype
30+
31+
subtype.scan(/vnd\.(.+)?-(.+)?\+(.*)?/) do |vendor, version, format|
32+
if options[:versions] && !options[:versions].include?(version)
33+
throw :error, :status => 404, :headers => {'X-Cascade' => 'pass'}, :message => "404 API Version Not Found"
34+
end
35+
36+
env['api.version'] = version
37+
env['api.vendor'] = vendor
38+
env['api.format'] = format # weird that Grape::Middleware::Formatter also does this
39+
end
40+
end
41+
end
42+
end
43+
end
44+
end
45+
end
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
require 'spec_helper'
2+
3+
describe Grape::Middleware::Versioner::Header do
4+
let(:app) { lambda{|env| [200, env, env]} }
5+
let(:accept) { 'application/vnd.vendor-v1+json' }
6+
subject { Grape::Middleware::Versioner::Header.new(app, @options || {}) }
7+
8+
context 'api.type and api.subtype' do
9+
it 'should set any type and any subtype' do
10+
env = subject.call('HTTP_ACCEPT' => '*/*').last
11+
env['api.type'].should eql '*'
12+
env['api.subtype'].should eql '*'
13+
end
14+
15+
it 'should set preferred type and subtype' do
16+
env = subject.call('HTTP_ACCEPT' => 'text/html').last
17+
env['api.type'].should eql 'text'
18+
env['api.subtype'].should eql 'html'
19+
end
20+
end
21+
22+
context 'api.format' do
23+
it 'should be set' do
24+
env = subject.call('HTTP_ACCEPT' => accept).last
25+
env['api.format'].should eql 'json'
26+
end
27+
28+
it 'should be nil if not provided' do
29+
env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1').last
30+
env['api.format'].should eql nil
31+
end
32+
end
33+
34+
context 'matched version' do
35+
before do
36+
@options = {
37+
:versions => ['v1'],
38+
:version_options => {:using => :header}
39+
}
40+
end
41+
42+
it 'should set api.vendor' do
43+
env = subject.call('HTTP_ACCEPT' => accept).last
44+
env['api.vendor'].should eql 'vendor'
45+
end
46+
47+
it 'should set api.version' do
48+
env = subject.call('HTTP_ACCEPT' => accept).last
49+
env['api.version'].should eql 'v1'
50+
end
51+
end
52+
53+
context 'no matched version' do
54+
before do
55+
@options = {
56+
:versions => ['unknown_version'],
57+
:version_options => {:using => :header}
58+
}
59+
end
60+
61+
it 'should throw 404 error with X-Cascade header set to pass' do
62+
expect {
63+
env = subject.call('HTTP_ACCEPT' => accept).last
64+
}.to throw_symbol(:error, :status => 404, :headers => {'X-Cascade' => 'pass'}, :message => "404 API Version Not Found")
65+
end
66+
end
67+
end

0 commit comments

Comments
 (0)