diff --git a/lib/api_taster/parseable_route.rb b/lib/api_taster/parseable_route.rb new file mode 100644 index 0000000..c3d0ae3 --- /dev/null +++ b/lib/api_taster/parseable_route.rb @@ -0,0 +1,58 @@ +class ParseableRoute + + delegate :name, :requirements, :to => :route + + def initialize(route) + @route = route + @rack_app = ParseableRoute.discover_rack_app(@route.app) + end + + def normalisable? + return false if @route.app.is_a?(ActionDispatch::Routing::Mapper::Constraints) + return false if @route.app.is_a?(Sprockets::Environment) + return false if @route.app == ApiTaster::Engine + return false if @route.verb.is_a?(Regexp) && @route.verb == // + return false if @route.verb.is_a?(String) && @route.verb.empty? + true + end + + def rack_routes + @rack_app.routes.routes + rescue + [] + end + + def verbs + case @route.verb + when Regexp + @route.verb.source.split('|').map{|v| v.gsub(/[$^]/, '')} + when String + @route.verb.split('|') + end + end + + def sanitized_path + path_string = if @route.path.respond_to?(:spec) + @route.path.spec.to_s + else + @route.path.to_s + end + path_string.gsub('(.:format)', '') + end + + def self.discover_rack_app(app) + class_name = app.class.name + if class_name == "ActionDispatch::Routing::Mapper::Constraints" + discover_rack_app(app.app) + elsif class_name !~ /^ActionDispatch::Routing/ + app + end + end + + private + + def route + @route + end + +end \ No newline at end of file diff --git a/lib/api_taster/route.rb b/lib/api_taster/route.rb index 04eb871..dc8bf7b 100644 --- a/lib/api_taster/route.rb +++ b/lib/api_taster/route.rb @@ -1,3 +1,5 @@ +require File.expand_path('../parseable_route', __FILE__) + module ApiTaster class Route cattr_accessor :route_set @@ -34,20 +36,12 @@ def normalise_routes! raise ApiTaster::Exception.new('Route definitions are missing, have you defined ApiTaster.routes?') end - route_set.routes.each do |route| - next if route.app.is_a?(ActionDispatch::Routing::Mapper::Constraints) - next if route.app.is_a?(Sprockets::Environment) - next if route.app == ApiTaster::Engine - - if (rack_app = discover_rack_app(route.app)) && rack_app.respond_to?(:routes) - rack_app.routes.routes.each do |rack_route| - self.routes << normalise_route(rack_route, route.path.spec) - end if rack_app.routes.respond_to?(:routes) + route_set.routes.map { |r| ParseableRoute.new(r) }.each do |route| + route.rack_routes.map { |rr| ParseableRoute.new(rr) }.each do |rack_route| + self.routes << normalise_route(rack_route, route.sanitized_path) end - next if route.verb.source.empty? - - self.routes << normalise_route(route) + self.routes << normalise_route(route) if route.normalisable? end self.routes.flatten! @@ -103,22 +97,13 @@ def undefined_route?(route) r.is_a?(Hash) && r.has_key?(:undefined) end - def discover_rack_app(app) - class_name = app.class.name.to_s - if class_name == "ActionDispatch::Routing::Mapper::Constraints" - discover_rack_app(app.app) - elsif class_name !~ /^ActionDispatch::Routing/ - app - end - end - def normalise_route(route, path_prefix = nil) - route.verb.source.split('|').map do |verb| + route.verbs.map do |verb| { :id => @_route_counter+=1, :name => route.name, - :verb => verb.gsub(/[$^]/, ''), - :path => path_prefix.to_s + route.path.spec.to_s.sub('(.:format)', ''), + :verb => verb, + :path => path_prefix.to_s + route.sanitized_path, :reqs => route.requirements } end diff --git a/spec/parseable_route_spec.rb b/spec/parseable_route_spec.rb new file mode 100644 index 0000000..acda025 --- /dev/null +++ b/spec/parseable_route_spec.rb @@ -0,0 +1,120 @@ +require 'spec_helper' +require File.expand_path('../../lib/api_taster/parseable_route', __FILE__) + +describe ParseableRoute do + + class ActionDispatchRouteMock + attr_reader :name, :path, :verb, :app + + def initialize(options={}) + @name = options[:name] || 'mock_route' + @verb = options[:verb] || 'GET|POST' + @path = options[:path] || '/users(.:format)' + @requirements = options[:requirements] || {:action => 'index', :controller => 'users'} + @app = options[:app] || ActionDispatch::Routing::RouteSet.new + end + end + + let(:mock_route) { ActionDispatchRouteMock.new } + let(:real_routes) { ActionDispatch::Routing::RouteSet.new } + let(:home_route) { real_routes.routes.first } + let(:dual_route) { real_routes.routes.to_a[1] } + + before do + real_routes.draw do + get 'home' => 'application#home', :as => :home + match 'dual_action' => 'dummy/action', :via => [:get, :delete] + resources :users do + resources :comments + end + mount Rails.application => '/app' + mount proc {} => '/rack_app' + end + end + + context 'retrieving a sanitized path' do + it 'from an ActionDispatch::Routing::Route' do + ParseableRoute.new(mock_route).sanitized_path.should == '/users' + end + + it 'from a Journey::Route' do + ParseableRoute.new(home_route).sanitized_path.should == '/home' + end + end + + context 'retrieving verbs' do + it 'from an ActionDispatch::Routing::Route' do + ParseableRoute.new(mock_route).verbs.should == ['GET', 'POST'] + end + + it 'from a Journey::Route' do + ParseableRoute.new(home_route).verbs.should == ['GET'] + ParseableRoute.new(dual_route).verbs.should == ['GET', 'DELETE'] + end + end + + describe '#normalisable' do + let(:it_is_not_normalisable) { ParseableRoute.new(mock_route).normalisable?.should be_false } + + it 'is false for ActionDispatch::Routing::Mapper::Constraints' do + mock_route.app.stub(:is_a?).with(ActionDispatch::Routing::Mapper::Constraints).and_return(true) + it_is_not_normalisable + end + + it 'is false for Sprockets::Environment' do + mock_route.stub(:app).and_return(Sprockets::Environment.new) + it_is_not_normalisable + end + + it 'is false for ApiTaster::Engine' do + mock_route.stub(:app).and_return(ApiTaster::Engine) + it_is_not_normalisable + end + + it 'is false if the verb is empty' do + mock_route.stub(:verb).and_return(//) + it_is_not_normalisable + + mock_route.stub(:verb).and_return('') + it_is_not_normalisable + end + + # there is probably a better way to assert normalisability + it 'defaults to true in the absence of any of the above conditions' do + ParseableRoute.new(mock_route).normalisable?.should be_true + ParseableRoute.new(home_route).normalisable?.should be_true + end + end + + context "class methods" do + let(:rack_app) { Sprockets::Environment } + let(:inner_app) { ActionDispatch::Routing::Mapper::Constraints.new(rack_app, {}) } + let(:outer_app) { ActionDispatch::Routing::Mapper::Constraints.new(inner_app, {}) } + let(:journey_route) { Journey::Route.new(nil, ApiTaster::Engine, nil, {}) } + + describe '.discover_rack_app' do + context 'for ActionDispatch::Routing::Mapper::Constraints' do + it 'recursively finds rack apps' do + klass = Class.new + klass.stub(:class).and_return(ActionDispatch::Routing::Mapper::Constraints) + klass.stub(:app).and_return('klass') + + ParseableRoute.discover_rack_app(klass).should == 'klass' + end + + it 'goes multiple levels down recursively if necessary' do + ParseableRoute.discover_rack_app(outer_app).should == rack_app + end + end + + it "returns the app for anything not in the ActionDispatch::Routing namespace" do + ParseableRoute.discover_rack_app(journey_route.app).should == ApiTaster::Engine + end + + it "returns nil if the app is in the ActionDispatch::Routing namespace but is not Mapper::Constraints" do + ParseableRoute.discover_rack_app(ActionDispatch::Routing::RouteSet.new).should be_nil + end + end + end + +end \ No newline at end of file diff --git a/spec/route_spec.rb b/spec/route_spec.rb index 41747d2..8c42c19 100644 --- a/spec/route_spec.rb +++ b/spec/route_spec.rb @@ -127,19 +127,5 @@ module ApiTaster Route.missing_definitions.first[:path].should == '/awesome_route' Route.defined_definitions.should == Route.routes - Route.missing_definitions end - - context "private methods" do - it "#discover_rack_app" do - klass = Class.new - klass.stub_chain(:class, :name).and_return(ActionDispatch::Routing::Mapper::Constraints) - klass.stub(:app).and_return('klass') - - Route.send(:discover_rack_app, klass).should == 'klass' - end - - it "#discover_rack_app" do - Route.send(:discover_rack_app, ApiTaster::Engine).should == ApiTaster::Engine - end - end end end