Skip to content

Commit

Permalink
Merge branch 'add-version-to-response'
Browse files Browse the repository at this point in the history
  • Loading branch information
bwillis committed Sep 29, 2015
2 parents e44d918 + 9f2e53a commit c4485e1
Show file tree
Hide file tree
Showing 17 changed files with 266 additions and 9 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Bug Fixes:

Enhancements:

* None
* Respond with request version, support for either header or Content-Type (#39)

Deprecations:

Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,13 @@ If you do not wish to use the magic mapping of the version number to templates i
config.rails_view_versioning = false
```

#### Response Version

If a client requests a specific version (or does not) and a version applies to the resource you can configure it to be in the response. Use the following configuration:
```ruby
config.response_strategy = [:http_content_type, :http_header]
```

### Version your views

When a client makes a request to your controller the latest version of the view will be rendered. The latest version is determined by naming the template or partial with a version number that you configured to support.
Expand Down
9 changes: 9 additions & 0 deletions lib/generators/templates/versioncake.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,13 @@

# Enable Rails versioned filename mapping
# config.rails_view_versioning = true

# Response Strategies
# Define how (if at all) to include the version in the response. Similar to how to retrieve the
# version, you can set where it will be in the response. For example, the `http_header_strategy`
# will include it the response header under the key configured in `config.version_key`.
#
# Defaults to none
#
# config.response_strategy = [] # [:http_content_type, :http_header]
end
5 changes: 5 additions & 0 deletions lib/versioncake.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
require 'versioncake/strategies/request_parameter_strategy'
require 'versioncake/strategies/custom_strategy'

require 'versioncake/response_strategy/base'
require 'versioncake/response_strategy/http_header_strategy'
require 'versioncake/response_strategy/http_content_type_strategy'

require 'versioncake/exceptions'
require 'versioncake/configuration'
require 'versioncake/versioned_request'
require 'versioncake/version_checker'
require 'versioncake/version_context'
require 'versioncake/version_context_service'
require 'versioncake/versioned_response_service'
require 'versioncake/versioned_resource'
require 'versioncake/rack/middleware'
require 'versioncake/cli'
Expand Down
10 changes: 9 additions & 1 deletion lib/versioncake/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Configuration
SUPPORTED_VERSIONS_DEFAULT = (1..10)
VERSION_KEY_DEFAULT = 'api_version'

attr_reader :extraction_strategies, :supported_version_numbers, :versioned_resources
attr_reader :extraction_strategies, :response_strategies, :supported_version_numbers, :versioned_resources
attr_accessor :missing_version, :version_key, :rails_view_versioning

def initialize
Expand All @@ -22,6 +22,7 @@ def initialize
:path_parameter,
:query_parameter
]
self.response_strategy = []
end

def extraction_strategy=(val)
Expand All @@ -31,6 +32,13 @@ def extraction_strategy=(val)
end
end

def response_strategy=(val)
@response_strategies = []
Array.wrap(val).each do |configured_strategy|
@response_strategies << VersionCake::ResponseStrategy::Base.lookup(configured_strategy)
end
end

def supported_version_numbers=(val)
@supported_version_numbers = val.respond_to?(:to_a) ? val.to_a : Array.wrap(val)
@supported_version_numbers.sort!.reverse!
Expand Down
16 changes: 12 additions & 4 deletions lib/versioncake/rack/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@ module VersionCake
module Rack
class Middleware

def initialize(app)
def initialize(app, config=VersionCake.config)
@app = app
@version_service = VersionCake::VersionContextService.new(VersionCake.config)
@version_service = VersionCake::VersionContextService.new(config)
@response_service = VersionCake::VersionedResponseService.new(config)
@config = config
end

def call(env)
request = ::Rack::Request.new env
if context = @version_service.create_context_from_request(request)
env['versioncake.context'] = context
end

@app.call(env)
status, headers, response = @app.call(env)

@response_service.inject_version(context, status, headers, response)

[status, headers, response]
else
@app.call(env)
end
end
end
end
Expand Down
27 changes: 27 additions & 0 deletions lib/versioncake/response_strategy/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module VersionCake
module ResponseStrategy
class Base
def execute(_context, _status, _headers, _response)
raise Exception, "ResponseStrategy requires execute to be implemented"
end

def version_key
VersionCake.config.version_key
end

def self.lookup(strategy)
case strategy
when String, Symbol
strategy_name = "response_strategy/#{strategy}_strategy".camelize
begin
VersionCake.const_get(strategy_name).new
rescue
raise Exception, "Unknown VersionCake response strategy #{strategy_name}"
end
else
raise Exception, "Invalid response strategy"
end
end
end
end
end
12 changes: 12 additions & 0 deletions lib/versioncake/response_strategy/http_content_type_strategy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module VersionCake
module ResponseStrategy
class HttpContentTypeStrategy < Base
def execute(context, _status, headers, _response)
return if headers['Content-Type'].nil?

headers['Content-Type'] << ';' unless headers['Content-Type'].end_with? ';'
headers['Content-Type'] << " #{version_key}=#{context.version.to_s}"
end
end
end
end
13 changes: 13 additions & 0 deletions lib/versioncake/response_strategy/http_header_strategy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module VersionCake
module ResponseStrategy
class HttpHeaderStrategy < Base
def execute(context, _status, headers, _response)
headers[header_key] = context.version.to_s
end

def header_key
version_key.gsub('_', '-')
end
end
end
end
13 changes: 13 additions & 0 deletions lib/versioncake/versioned_response_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module VersionCake
class VersionedResponseService
def initialize(config)
@strategies = config.response_strategies
end

def inject_version(versioned_context, status, headers, response)
@strategies.each do |strategy|
strategy.execute(versioned_context, status, headers, response)
end
end
end
end
8 changes: 5 additions & 3 deletions spec/integration/rack/middleware_regression_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
describe VersionCake::Rack::Middleware do
let(:app) do
rack = Rack::Builder.new do
use VersionCake::Rack::Middleware
run lambda { |env| [ 200, {}, [ env['versioncake.context'].version ] ] }
config = VersionCake::Configuration.new
config.resources { |r| r.resource %r{.*}, [], [], (1..5) }
use VersionCake::Rack::Middleware, config
run lambda { |env| [ 200, {},[ env['versioncake.context'].version ] ] }
end
Rack::MockRequest.new(rack)
end
Expand All @@ -23,7 +25,7 @@

it "test yml test cases" do
begin
response = app.request(method, '/renders', headers.merge(params: params))
_response_status, _response_headers, response = app.request(method, '/renders', headers.merge(params: params))
expect(response.body).to(eq(test_response), custom_message(headers, params, method, response.body, test_response))
rescue => e
raise custom_message(headers, params, method, response.body, test_response) + ", but it failed with an exception '#{e.message}'"
Expand Down
2 changes: 2 additions & 0 deletions spec/test_app/config/initializers/versioncake.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@
# r.resource uri_regex, obsolete, deprecated, supported
r.resource %r{renders/*}, [2], [4], (1..5)
end

config.response_strategy = [:http_header]
end
60 changes: 60 additions & 0 deletions spec/unit/middleware_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require 'spec_helper'
require 'rack'

describe VersionCake::Rack::Middleware do
let(:response_strategy) { nil }
let(:config) do
VersionCake::Configuration.new.tap do |config|
config.extraction_strategy = [:http_header]
config.response_strategy = response_strategy
config.resources do |resource_config|
resource_config.resource %r{.*}, [], [], (1..5)
end
end
end
let(:upstream_headers) { {} }
let(:middleware) do
VersionCake::Rack::Middleware.new(
double(call: [nil, upstream_headers, nil] ),
config
)
end

context '#call' do
let(:env) do
{
'SCRIPT_NAME' => '',
'PATH_INFO' => '',
'HTTP_API_VERSION' => '1'
}
end

subject { middleware.call(env) }
let(:response_headers) { subject[1] }

context 'when response_strategy is http_header' do
let(:response_strategy) { [:http_header] }

it 'sets the version in the response header' do
expect(response_headers['api-version']).to eq '1'
end
end

context 'when response_strategy is http_content_type' do
let(:response_strategy) { [:http_content_type] }
let(:upstream_headers) { { 'Content-Type' => 'application/vnd.api+json; charset=utf-8;' } }

it 'sets the version in the content type' do
expect(response_headers['Content-Type']).to match 'application/vnd.api+json; charset=utf-8; api_version=1'
end

context 'with a simpler content type' do
let(:upstream_headers) { { 'Content-Type' => 'application/json' } }

it 'sets the version in the content type' do
expect(response_headers['Content-Type']).to match 'application/json; api_version=1'
end
end
end
end
end
16 changes: 16 additions & 0 deletions spec/unit/response_strategy/base_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require 'spec_helper'

describe VersionCake::ResponseStrategy::Base do
describe '.lookup' do
let(:strategy) { :http_content_type }
subject(:found_strategy) { VersionCake::ResponseStrategy::Base.lookup(strategy) }

it { expect(found_strategy.class).to eq VersionCake::ResponseStrategy::HttpContentTypeStrategy }
end

describe '#execute' do
subject(:execute) { VersionCake::ResponseStrategy::Base.new.execute(nil,nil,nil,nil) }

it { expect { execute }.to raise_error Exception }
end
end
27 changes: 27 additions & 0 deletions spec/unit/response_strategy/http_content_type_strategy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require 'spec_helper'

describe VersionCake::ResponseStrategy::HttpContentTypeStrategy do
describe '#execute' do
let(:headers) { { 'Content-Type' => 'application/json' } }
let(:context) { double('content', version: 4) }
before do
VersionCake::ResponseStrategy::HttpContentTypeStrategy.new.execute(
context, nil, headers, nil
)
end

it { expect(headers['Content-Type']).to eq 'application/json; api_version=4' }

context 'for a header that ends in a semi colon' do
let(:headers) { { 'Content-Type' => 'application/vnd.api+json; charset=utf-8;' } }

it { expect(headers['Content-Type']).to eq 'application/vnd.api+json; charset=utf-8; api_version=4' }
end

context 'when there is no content type' do
let(:headers) { {} }

it { expect(headers.empty?).to eq true }
end
end
end
18 changes: 18 additions & 0 deletions spec/unit/response_strategy/http_header_strategy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require 'spec_helper'

describe VersionCake::ResponseStrategy::HttpHeaderStrategy do
describe '#execute' do
let(:headers) { { } }
let(:context) { double('content', version: 8) }
before do
VersionCake::ResponseStrategy::HttpHeaderStrategy.new.execute(
context, nil, headers, nil
)
end

it do
expect(headers.keys).to include 'api-version'
expect(headers['api-version']).to eq '8'
end
end
end
30 changes: 30 additions & 0 deletions spec/unit/versioned_response_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require 'spec_helper'

describe VersionCake::VersionedResponseService do

let(:config) { double(response_strategies: [VersionCake::ResponseStrategy::HttpHeaderStrategy.new]) }
let(:service) { described_class.new(config)}

describe '#inject_version' do
let(:context) { double('content', version: 2) }
let(:headers) { { } }

before { service.inject_version(context, nil, headers, nil) }

it { expect(headers['api-version']).to eq '2' }

context 'when configured with multiple response strategies' do
let(:headers) { { 'Content-Type' => 'application/vnd.api+json; charset=utf-8;' } }

let(:config) do
double(response_strategies: [
VersionCake::ResponseStrategy::HttpHeaderStrategy.new,
VersionCake::ResponseStrategy::HttpContentTypeStrategy.new
])
end

it { expect(headers['Content-Type']).to eq 'application/vnd.api+json; charset=utf-8; api_version=2' }
it { expect(headers['api-version']).to eq '2' }
end
end
end

0 comments on commit c4485e1

Please sign in to comment.