Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor route and namespace combination logic #926

Merged
merged 1 commit into from
Apr 30, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 34 additions & 29 deletions lib/grape-swagger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module SwaggerRouting
private

def combine_routes(app, doc_klass)
app.routes.each do |route|
app.routes.each_with_object({}) do |route, combined_routes|
route_path = route.path
route_match = route_path.split(/^.*?#{route.prefix}/).last
next unless route_match
Expand All @@ -37,31 +37,32 @@ def combine_routes(app, doc_klass)

resource = route_match.captures.first
resource = '/' if resource.empty?
@target_class.combined_routes[resource] ||= []
combined_routes[resource] ||= []
next if doc_klass.hide_documentation_path && route.path.match(/#{doc_klass.mount_path}($|\/|\(\.)/)

@target_class.combined_routes[resource] << route
combined_routes[resource] << route
end
end

def determine_namespaced_routes(name, parent_route)
def determine_namespaced_routes(name, parent_route, routes)
if parent_route.nil?
@target_class.combined_routes.values.flatten
routes.values.flatten
else
parent_route.reject do |route|
!route_path_start_with?(route, name) || !route_instance_variable_equals?(route, name)
end
end
end

def combine_namespace_routes(namespaces)
def combine_namespace_routes(namespaces, routes)
combined_namespace_routes = {}
# iterate over each single namespace
namespaces.each_key do |name, _|
# get the parent route for the namespace
parent_route_name = extract_parent_route(name)
parent_route = @target_class.combined_routes[parent_route_name]
parent_route = routes[parent_route_name]
# fetch all routes that are within the current namespace
namespace_routes = determine_namespaced_routes(name, parent_route)
namespace_routes = determine_namespaced_routes(name, parent_route, routes)

# default case when not explicitly specified or nested == true
standalone_namespaces = namespaces.reject do |_, ns|
Expand All @@ -76,12 +77,13 @@ def combine_namespace_routes(namespaces)
# rubocop:disable Style/Next
if parent_standalone_namespaces.empty?
# default option, append namespace methods to parent route
parent_route = @target_class.combined_namespace_routes.key?(parent_route_name)
@target_class.combined_namespace_routes[parent_route_name] = [] unless parent_route
@target_class.combined_namespace_routes[parent_route_name].push(*namespace_routes)
combined_namespace_routes[parent_route_name] ||= []
combined_namespace_routes[parent_route_name].push(*namespace_routes)
end
# rubocop:enable Style/Next
end

combined_namespace_routes
end

def extract_parent_route(name)
Expand Down Expand Up @@ -110,7 +112,7 @@ def route_path_start_with?(route, name)
end

module SwaggerDocumentationAdder
attr_accessor :combined_namespaces, :combined_namespace_identifiers, :combined_routes, :combined_namespace_routes
attr_accessor :combined_namespaces, :combined_routes, :combined_namespace_routes

include SwaggerRouting

Expand All @@ -127,20 +129,16 @@ def add_swagger_documentation(options = {})
documentation_class.setup(options)
mount(documentation_class)

@target_class.combined_routes = {}
combine_routes(@target_class, documentation_class)

@target_class.combined_namespaces = {}
combine_namespaces(@target_class)
combined_routes = combine_routes(@target_class, documentation_class)
combined_namespaces = combine_namespaces(@target_class)
combined_namespace_routes = combine_namespace_routes(combined_namespaces, combined_routes)
exclusive_route_keys = combined_routes.keys - combined_namespaces.keys
@target_class.combined_namespace_routes = combined_namespace_routes.merge(
combined_routes.slice(*exclusive_route_keys)
)
@target_class.combined_routes = combined_routes
@target_class.combined_namespaces = combined_namespaces

@target_class.combined_namespace_routes = {}
@target_class.combined_namespace_identifiers = {}
combine_namespace_routes(@target_class.combined_namespaces)

exclusive_route_keys = @target_class.combined_routes.keys - @target_class.combined_namespaces.keys
exclusive_route_keys.each do |key|
@target_class.combined_namespace_routes[key] = @target_class.combined_routes[key]
end
documentation_class
end

Expand All @@ -151,17 +149,24 @@ def version_for(options)
end

def combine_namespaces(app)
app.endpoints.each do |endpoint|
combined_namespaces = {}
endpoints = app.endpoints.clone

while endpoints.any?
endpoint = endpoints.shift

endpoints.push(*endpoint.options[:app].endpoints) if endpoint.options[:app]
ns = endpoint.namespace_stackable(:namespace).last
next unless ns

# use the full namespace here (not the latest level only)
# and strip leading slash
mount_path = (endpoint.namespace_stackable(:mount_path) || []).join('/')
full_namespace = (mount_path + endpoint.namespace).sub(/\/{2,}/, '/').sub(/^\//, '')
@target_class.combined_namespaces[full_namespace] = ns if ns

combine_namespaces(endpoint.options[:app]) if endpoint.options[:app]
combined_namespaces[full_namespace] = ns
end

combined_namespaces
end

def create_documentation_class
Expand Down