lib/contrast/framework/rails/support.rb in contrast-agent-4.3.2 vs lib/contrast/framework/rails/support.rb in contrast-agent-4.4.0

- old
+ new

@@ -45,36 +45,38 @@ def collect_routes find_all_routes(::Rails.application, []) end # Find the current route, based on the provided Request wrapper + # # @param request[Contrast::Agent::Request] # @return [Contrast::Api::Dtm::RouteCoverage] def current_route request return unless ::Rails.cs__respond_to?(:application) - # returns array of arrays [[match_data, path_parameters, route]], sorted by - # precedence - # match_data: ActionDispatch::Journey::Path::Pattern::MatchData - # path_parameters: hash of various things - # route: ActionDispatch::Journey::Route - full_routes = ::Rails.application.routes.router.send(:find_routes, request.rack_request) - return if full_routes.empty? + match, _params, route, path = get_full_route(request.rack_request) - full_route = full_routes[0] + original_url = request.rack_request.path_info - # the route is directly implemented within the application - if direct_route?(full_route) - route = full_route[2] # route w/ highest precedence - Contrast::Api::Dtm::RouteCoverage.from_action_dispatch_journey(route) - else - engine_route(full_route, request) + # Route is either the final rails route, or a router that points to a Sinatra controller. + if Contrast::Framework::Sinatra::Support.sinatra_controller?(route.app.app) + # Create a request copied from current request, but with the base path removed from path_info. + new_req = ::ActionDispatch::Request.new(request.env) + new_req.path_info = new_req.path_info.gsub((path << match).join, '') + + return Contrast::Framework::Sinatra::Support.current_route(new_req, route.app.app, original_url) end + + Contrast::Api::Dtm::RouteCoverage.from_action_dispatch_journey(route, original_url) rescue StandardError => _e nil end + # Copy a request for modification. + # + # @param [::ActionDispatch::Request] original env. + # @return [::ActionDispatch::Request] a copy of original env with rails env merged. def retrieve_request env rails_env = ::Rails.application.env_config.merge(env) ::ActionDispatch::Request.new(rails_env || env) end @@ -85,40 +87,37 @@ env[AC_INSTANCE].cs__class.included_modules.include?(::ActionController::Live) end private - # route is not mounted within an engine - def direct_route? full_route - full_route[2]&.app&.cs__class == ActionDispatch::Routing::RouteSet::Dispatcher || - (full_route[2].cs__class == ActionDispatch::Journey::Route && full_route[2]&.app&.cs__class == ActionDispatch::Routing::Mapper::Constraints) + # Determine if route is a Rails engine route. + # + # @param [Object] app or route that points to a ::Rails::Engine + # @return [bool] whether the router is an engine or not. + def engine_route? route + route.app.is_a?(::ActionDispatch::Routing::Mapper::Constraints) && route.app.app < ::Rails::Engine end - def engine_route full_route, request - engine_route = full_route[2] # supposed route - but actually an Engine mount point - return unless engine_route + # Recursively get final route traversing engines as required. + # + # @param request [::Rack::Request] the rack request as will be handed to rails controller. + # @param top_router [::ActionDispatch::Journer::Router] the current router relative to the previous. + # @param path [Array<String>] the chunks of path that have been seen. + # @return [Array<array>] the final set of rails route classes. + def get_full_route request, top_router = ::Rails.application.routes.router, path = [] + return if (route_matches = top_router.send(:find_routes, request)).empty? - engine_mount_name = engine_route.name - return unless engine_mount_name + match, params, route = route_matches.first - engine_path_segments = request.rack_request.path_info.split(engine_mount_name) - return if engine_path_segments.empty? - - path_within_engine = engine_path_segments[-1] - return unless path_within_engine - - engine_router = engine_route.app&.app&.routes&.router - return unless engine_router - - # Get all routes regardless of http method - matching_routes = engine_router.send(:filter_routes, path_within_engine) - return unless matching_routes - - # filter for current http method - reportable_routes = engine_router.send(:match_routes, matching_routes, request.rack_request) - return if reportable_routes.empty? - - Contrast::Api::Dtm::RouteCoverage.from_action_dispatch_journey(reportable_routes[0]) + # If the current routing node points to a sub-app (::Rais::Engine), dive deeper. + # Have sub-app route the remainder of the url. + if engine_route?(route) + new_req = retrieve_request request.env + new_req.path_info = new_req.path_info.gsub(match.to_s, '') + get_full_route(new_req, route.app.app.routes.router, path << match.to_s) + else + [match, params, route, path] + end end # Rails engine routes need to be detected by inspecting Engine class route set def find_all_routes app, route_list return route_list unless app.cs__respond_to?(:routes) && app.routes.cs__respond_to?(:routes)