Skip to content

Commit b525159

Browse files
committed
Allow doc API to be mounted from separate class
Fixes the `target_class` option to fully work. The code structure here is highly questionable (no separation of concerns, for one thing). But the whole class needs some serious refactoring, so this will work until that happens, at least.
1 parent 834ee01 commit b525159

File tree

3 files changed

+87
-20
lines changed

3 files changed

+87
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#### Fixes
99

10+
* [#252](https://github.com/tim-vandecasteele/grape-swagger/pull/252): Allow docs to mounted in separate class than target - [@iangreenleaf](https://github.com/iangreenleaf).
1011
* [#251](https://github.com/tim-vandecasteele/grape-swagger/pull/251): Fixed model id equal to model name when root existing in entities - [@aitortomas](https://github.com/aitortomas).
1112
* [#232](https://github.com/tim-vandecasteele/grape-swagger/pull/232): Fixed missing raw array params - [@u2](https://github.com/u2).
1213
* [#234](https://github.com/tim-vandecasteele/grape-swagger/pull/234): Fixed range :values with float - [@azhi](https://github.com/azhi).

lib/grape-swagger.rb

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@
88
module Grape
99
class API
1010
class << self
11-
attr_reader :combined_routes, :combined_namespaces, :combined_namespace_routes, :combined_namespace_identifiers
11+
attr_accessor :combined_routes, :combined_namespaces, :combined_namespace_routes, :combined_namespace_identifiers
1212

1313
def add_swagger_documentation(options = {})
1414
documentation_class = create_documentation_class
1515

16-
documentation_class.setup({ target_class: self }.merge(options))
16+
options = { target_class: self }.merge(options)
17+
@target_class = options[:target_class]
18+
19+
documentation_class.setup(options)
1720
mount(documentation_class)
1821

19-
@combined_routes = {}
20-
routes.each do |route|
22+
@target_class.combined_routes = {}
23+
@target_class.routes.each do |route|
2124
route_path = route.route_path
2225
route_match = route_path.split(/^.*?#{route.route_prefix.to_s}/).last
2326
next unless route_match
@@ -26,20 +29,20 @@ def add_swagger_documentation(options = {})
2629
resource = route_match.captures.first
2730
next if resource.empty?
2831
resource.downcase!
29-
@combined_routes[resource] ||= []
32+
@target_class.combined_routes[resource] ||= []
3033
next if documentation_class.hide_documentation_path && route.route_path.include?(documentation_class.mount_path)
31-
@combined_routes[resource] << route
34+
@target_class.combined_routes[resource] << route
3235
end
3336

34-
@combined_namespaces = {}
35-
combine_namespaces(self)
37+
@target_class.combined_namespaces = {}
38+
combine_namespaces(@target_class)
3639

37-
@combined_namespace_routes = {}
38-
@combined_namespace_identifiers = {}
39-
combine_namespace_routes(@combined_namespaces)
40+
@target_class.combined_namespace_routes = {}
41+
@target_class.combined_namespace_identifiers = {}
42+
combine_namespace_routes(@target_class.combined_namespaces)
4043

41-
exclusive_route_keys = @combined_routes.keys - @combined_namespaces.keys
42-
exclusive_route_keys.each { |key| @combined_namespace_routes[key] = @combined_routes[key] }
44+
exclusive_route_keys = @target_class.combined_routes.keys - @target_class.combined_namespaces.keys
45+
exclusive_route_keys.each { |key| @target_class.combined_namespace_routes[key] = @target_class.combined_routes[key] }
4346
documentation_class
4447
end
4548

@@ -54,7 +57,7 @@ def combine_namespaces(app)
5457
end
5558
# use the full namespace here (not the latest level only)
5659
# and strip leading slash
57-
@combined_namespaces[endpoint.namespace.sub(/^\//, '')] = ns if ns
60+
@target_class.combined_namespaces[endpoint.namespace.sub(/^\//, '')] = ns if ns
5861

5962
combine_namespaces(endpoint.options[:app]) if endpoint.options[:app]
6063
end
@@ -65,7 +68,7 @@ def combine_namespace_routes(namespaces)
6568
namespaces.each do |name, namespace|
6669
# get the parent route for the namespace
6770
parent_route_name = name.match(%r{^/?([^/]*).*$})[1]
68-
parent_route = @combined_routes[parent_route_name]
71+
parent_route = @target_class.combined_routes[parent_route_name]
6972
# fetch all routes that are within the current namespace
7073
namespace_routes = parent_route.collect do |route|
7174
route if (route.route_path.start_with?(route.route_prefix ? "/#{route.route_prefix}/#{name}" : "/#{name}") || route.route_path.start_with?((route.route_prefix ? "/#{route.route_prefix}/:version/#{name}" : "/:version/#{name}"))) &&
@@ -79,8 +82,8 @@ def combine_namespace_routes(namespaces)
7982
else
8083
identifier = name.gsub(/_/, '-').gsub(/\//, '_')
8184
end
82-
@combined_namespace_identifiers[identifier] = name
83-
@combined_namespace_routes[identifier] = namespace_routes
85+
@target_class.combined_namespace_identifiers[identifier] = name
86+
@target_class.combined_namespace_routes[identifier] = namespace_routes
8487

8588
# get all nested namespaces below the current namespace
8689
sub_namespaces = standalone_sub_namespaces(name, namespaces)
@@ -92,16 +95,16 @@ def combine_namespace_routes(namespaces)
9295
route if sub_ns_paths.include?(route.instance_variable_get(:@options)[:namespace]) || sub_ns_paths_versioned.include?(route.instance_variable_get(:@options)[:namespace])
9396
end.compact
9497
# add all determined routes of the sub namespaces to standalone resource
95-
@combined_namespace_routes[identifier].push(*sub_routes)
98+
@target_class.combined_namespace_routes[identifier].push(*sub_routes)
9699
else
97100
# default case when not explicitly specified or nested == true
98101
standalone_namespaces = namespaces.reject { |_, ns| !ns.options.key?(:swagger) || !ns.options[:swagger].key?(:nested) || ns.options[:swagger][:nested] != false }
99102
parent_standalone_namespaces = standalone_namespaces.reject { |ns_name, _| !name.start_with?(ns_name) }
100103
# add only to the main route if the namespace is not within any other namespace appearing as standalone resource
101104
if parent_standalone_namespaces.empty?
102105
# default option, append namespace methods to parent route
103-
@combined_namespace_routes[parent_route_name] = [] unless @combined_namespace_routes.key?(parent_route_name)
104-
@combined_namespace_routes[parent_route_name].push(*namespace_routes)
106+
@target_class.combined_namespace_routes[parent_route_name] = [] unless @target_class.combined_namespace_routes.key?(parent_route_name)
107+
@target_class.combined_namespace_routes[parent_route_name].push(*namespace_routes)
105108
end
106109
end
107110
end

spec/mounted_target_class_spec.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
require 'spec_helper'
2+
3+
describe 'docs mounted separately from api' do
4+
before :all do
5+
class ActualApi < Grape::API
6+
desc 'Document root'
7+
8+
desc 'This gets something.',
9+
notes: '_test_'
10+
get '/simple' do
11+
{ bla: 'something' }
12+
end
13+
end
14+
15+
class MountedDocs < Grape::API
16+
add_swagger_documentation(target_class: ActualApi)
17+
end
18+
19+
class WholeApp < Grape::API
20+
mount ActualApi
21+
mount MountedDocs
22+
end
23+
end
24+
25+
def app
26+
WholeApp
27+
end
28+
29+
it 'retrieves docs for actual api class' do
30+
get '/swagger_doc.json'
31+
expect(JSON.parse(last_response.body)).to eq(
32+
'apiVersion' => '0.1',
33+
'swaggerVersion' => '1.2',
34+
'info' => {},
35+
'produces' => Grape::ContentTypes::CONTENT_TYPES.values.uniq,
36+
'apis' => [
37+
{ 'path' => '/simple.{format}', 'description' => 'Operations about simples' },
38+
]
39+
)
40+
end
41+
42+
it 'retrieves docs for endpoint in actual api class' do
43+
get '/swagger_doc/simple.json'
44+
expect(JSON.parse(last_response.body)).to eq(
45+
'apiVersion' => '0.1',
46+
'swaggerVersion' => '1.2',
47+
'basePath' => 'http://example.org',
48+
'resourcePath' => '/simple',
49+
'produces' => Grape::ContentTypes::CONTENT_TYPES.values.uniq,
50+
'apis' => [{
51+
'path' => '/simple.{format}',
52+
'operations' => [{
53+
'notes' => '_test_',
54+
'summary' => 'This gets something.',
55+
'nickname' => 'GET-simple---format-',
56+
'method' => 'GET',
57+
'parameters' => [],
58+
'type' => 'void'
59+
}]
60+
}]
61+
)
62+
end
63+
end

0 commit comments

Comments
 (0)