Permalink
Browse files

Yield the endpoint from the api_version block as well.

  • Loading branch information...
1 parent 84a44ec commit 223724aa14a45435d2a3848265bc34d4abeb7682 @myronmarston myronmarston committed Apr 4, 2012
View
@@ -119,9 +119,9 @@ Interpol.default_configuration do |config|
# Determines which versioned endpoint definition Interpol uses
# for a request. You can also use a block form, which yields
- # the rack env hash as an argument. This is useful when you need
- # to extract the version from a request header (e.g. Accept) or
- # from the request URI.
+ # the rack env hash and the endpoint object as arguments.
+ # This is useful when you need to extract the version from a
+ # request header (e.g. Accept) or from the request URI.
#
# Needed by Interpol::StubApp and Interpol::ResponseSchemaValidator.
config.api_version '1.0'
@@ -7,21 +7,19 @@ module DefinitionFinder
include HashFetcher
NoDefinitionFound = Class.new
- def find_definition(options)
- method, path, version = extract_search_options_from(options)
- endpoint = find { |e| e.method == method && e.route_matches?(path) }
- return NoDefinitionFound if endpoint.nil?
- endpoint.definitions.find { |d| d.version == version } || NoDefinitionFound
+ def find_definition(method, path)
+ with_endpoint_matching(method, path) do |endpoint|
+ version = yield endpoint
+ endpoint.definitions.find { |d| d.version == version }
+ end
end
private
- def extract_search_options_from(options)
- method = fetch_from(options, :method).downcase.to_sym
- path = fetch_from(options, :path)
- version = fetch_from(options, :version)
-
- return method, path, version
+ def with_endpoint_matching(method, path)
+ method = method.downcase.to_sym
+ endpoint = find { |e| e.method == method && e.route_matches?(path) }
+ (yield endpoint if endpoint) || NoDefinitionFound
end
end
@@ -67,11 +65,11 @@ def api_version(version=nil, &block)
"or a dynamic block, but not both")
end
- @api_version_block = block || lambda { |env| version }
+ @api_version_block = block || lambda { |*a| version }
end
- def api_version_for(rack_env_hash)
- @api_version_block.call(rack_env_hash).to_s
+ def api_version_for(rack_env, endpoint=nil)
+ @api_version_block.call(rack_env, endpoint).to_s
end
def validate_if(&block)
@@ -33,9 +33,12 @@ def initialize(status, headers, body, env, config)
end
def validate!
- return validator.validate_data!(data) if validator
+ unless validator == Interpol::DefinitionFinder::NoDefinitionFound
+ return validator.validate_data!(data)
+ end
+
raise NoEndpointDefinitionFoundError,
- "No endpoint definition could be found for: #{request_method} '#{path}' (#{version})"
+ "No endpoint definition could be found for: #{request_method} '#{path}'"
end
# The only interface we can count on from the body is that it
@@ -63,13 +66,10 @@ def path
env.fetch('PATH_INFO')
end
- def version
- @version ||= @config.api_version_for(@env)
- end
-
def validator
- @validator ||= @config.endpoints.find_definition \
- method: request_method, path: path, version: version
+ @validator ||= @config.endpoints.find_definition(request_method, path) do |endpoint|
+ @config.api_version_for(env, endpoint)
+ end
end
end
View
@@ -48,7 +48,7 @@ def build
def endpoint_definition(endpoint)
lambda do
- version = interpol_config.api_version_for(request.env)
+ version = interpol_config.api_version_for(request.env, endpoint)
example = example_for(endpoint, version)
example.validate!
JSON.dump(example.data)
@@ -20,21 +20,30 @@ def endpoint(method, route, *versions)
let(:endpoint_2) { endpoint 'POST', '/foo/bar', '2.3', '2.7' }
let(:all_endpoints) { [endpoint_1, endpoint_2].extend(DefinitionFinder) }
- def find(*args)
- all_endpoints.find_definition(*args)
+ def find(options)
+ all_endpoints.find_definition(options[:method], options[:path]) { |e| options[:version] }
end
- it 'finds a matching endpoint' do
+ it 'finds a matching endpoint definition' do
found = find(method: 'POST', path: '/foo/bar', version: '2.3')
found.endpoint.should be(endpoint_2)
found.version.should eq('2.3')
end
- it 'finds the correct version of the endpoint' do
+ it 'finds the correct versioned definition of the endpoint' do
found = find(method: 'POST', path: '/foo/bar', version: '2.7')
found.version.should eq('2.7')
end
+ it 'calls the version block with the endpoint' do
+ endpoint = nil
+ all_endpoints.find_definition('POST', '/foo/bar') do |e|
+ endpoint = e
+ end
+
+ endpoint.should be(endpoint_2)
+ end
+
it 'returns NoDefinitionFound if it cannot find a matching route' do
result = find(method: 'POST', path: '/goo/bar', version: '2.7')
result.should be(DefinitionFinder::NoDefinitionFound)
@@ -49,14 +58,6 @@ def find(*args)
found = find(method: 'GET', path: '/users/17/overview', version: '1.3')
found.endpoint.should be(endpoint_1)
end
-
- [:method, :path, :version].each do |key|
- it "raises a helpful error if you do not include #{key.inspect} in the options" do
- options = { method: 'POST', path: '/foo/bar', version: '2.3' }
- options.delete(key)
- expect { find(options) }.to raise_error(/key not found.*#{key}/)
- end
- end
end
end
@@ -149,30 +150,30 @@ def find(*args)
context 'when configured with a static version' do
it 'returns the configured static api version number' do
config.api_version '1.2'
- config.api_version_for({}).should eq('1.2')
+ config.api_version_for({}, stub.as_null_object).should eq('1.2')
end
it 'always returns a string, even when configured as an integer' do
config.api_version 3
- config.api_version_for({}).should eq('3')
+ config.api_version_for({}, stub.as_null_object).should eq('3')
end
end
context 'when configured with a block' do
it "returns the blocks's return value" do
config.api_version { |e| e[:path][%r|/api/v(\d+)/|, 1] }
- config.api_version_for(path: "/api/v2/foo").should eq('2')
+ config.api_version_for({ path: "/api/v2/foo" }, stub.as_null_object).should eq('2')
end
it 'always returns a string, even when configured as a string' do
config.api_version { |e| 3 }
- config.api_version_for({}).should eq('3')
+ config.api_version_for({}, stub.as_null_object).should eq('3')
end
end
it 'raises a helpful error when api_version has not been configured' do
expect {
- config.api_version_for({})
+ config.api_version_for({}, stub.as_null_object)
}.to raise_error(ConfigurationError)
end
end
@@ -16,7 +16,8 @@ def configuration
end
end
- attr_accessor :validation_mode, :validate_if_block
+ attr_accessor :validation_mode, :validate_if_block, :definition_finder
+
def set_validation_mode(mode)
self.validation_mode = mode
end
@@ -32,6 +33,7 @@ def validate_if(&block)
end
let(:app) do
+ self.definition_finder ||= default_definition_finder
config = configuration
_closable_body = closable_body
Rack::Builder.new do
@@ -61,30 +63,51 @@ def validate_if(&block)
end
let(:validator) { fire_double("Interpol::EndpointDefinition", validate_data!: nil) }
- let(:definition_finder) { fire_double("Interpol::DefinitionFinder") }
+ let(:endpoint) { Interpol::Endpoint.new(stub.as_null_object) }
+ let(:default_definition_finder) { fire_double("Interpol::DefinitionFinder") }
def stub_lookup(v = validator)
- definition_finder.stub(find_definition: v)
+ default_definition_finder.stub(find_definition: v)
end
it 'validates the data against the correct versioned endpoint definition' do
validator.should_receive(:validate_data!).with("a" => "b")
- definition_finder.should_receive(:find_definition).
- with(method: "GET", path: "/search/200/overview", version: "1.0").
+ default_definition_finder.should_receive(:find_definition).
+ with("GET", "/search/200/overview").
and_return(validator)
get '/search/200/overview'
end
it 'falls back to the default configuration' do
- Interpol.default_configuration { |c| c.api_version '2.4' }
-
- definition_finder.should_receive(:find_definition).
- with(method: "GET", path: "/search/200/overview", version: "2.4").
- and_return(validator)
+ default_config_called = false
+ Interpol.default_configuration do |c|
+ c.validate_if do
+ default_config_called = true
+ false
+ end
+ end
get '/search/200/overview'
+ default_config_called.should be_true
+ end
+
+ it 'calls the api_version callback with the rack env and the endpoint' do
+ endpoint.stub(method: :get, route_matches?: true)
+ self.definition_finder = [endpoint].extend(Interpol::DefinitionFinder)
+
+ yielded_args = nil
+ Interpol.default_configuration do |c|
+ c.api_version do |*args|
+ yielded_args = args
+ '1.0'
+ end
+ end
+
+ expect { get '/search/200/overview' }.to raise_error(NoEndpointDefinitionFoundError)
+
+ yielded_args.map(&:class).should eq([Hash, Interpol::Endpoint])
end
it 'yields the env, status, headers and body from the validate_if callback' do
@@ -101,21 +124,20 @@ def stub_lookup(v = validator)
it 'does not validate if the validate_if config returns false' do
validate_if { |*args| false }
-
validator.should_not_receive(:validate_data!)
- definition_finder.should_not_receive(:find_definition)
+ default_definition_finder.should_not_receive(:find_definition)
get '/search/200/overview'
end
it 'does not validate if the response is not 2xx when no validate_if callback has been set' do
validator.should_not_receive(:validate_data!)
- definition_finder.should_not_receive(:find_definition)
+ default_definition_finder.should_not_receive(:find_definition)
get '/not_found'
end
it 'does not validate a 204 no content response when no validate_if callback has been set' do
validator.should_not_receive(:validate_data!)
- definition_finder.should_not_receive(:find_definition)
+ default_definition_finder.should_not_receive(:find_definition)
get '/search/204/overview'
end
@@ -137,7 +159,7 @@ def stub_lookup(v = validator)
it 'raises an error when no endpoint definition can be found' do
validator.stub(:validate_data!)
- stub_lookup(nil)
+ stub_lookup(DefinitionFinder::NoDefinitionFound)
expect { get '/search/200/overview' }.to raise_error(NoEndpointDefinitionFoundError)
end
@@ -164,7 +186,7 @@ def stub_lookup(v = validator)
it 'prints a warning when no endpoint definition can be found' do
validator.stub(:validate_data!)
- stub_lookup(nil)
+ stub_lookup(DefinitionFinder::NoDefinitionFound)
warner.should_receive(:warn).with(/No endpoint definition could be found/)
get '/search/200/overview'
@@ -47,6 +47,20 @@ def parsed_body
parsed_body.should include('name' => 'some project')
end
+ it 'calls the api_version callback with the rack env and the endpoint' do
+ yielded_args = nil
+ Interpol.default_configuration do |c|
+ c.api_version do |*args|
+ yielded_args = args
+ '1.0'
+ end
+ end
+
+ get '/users/3/projects'
+
+ yielded_args.map(&:class).should eq([Hash, Interpol::Endpoint])
+ end
+
it 'renders the example data' do
header 'API-Version', '1.0'
get '/users/3/projects'

0 comments on commit 223724a

Please sign in to comment.