grape-swagger 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a1bf3c5bf4c84bdda47522ddc8148c78c538995
4
- data.tar.gz: 04f9c3468140334518a3051593c5ff94da824a0a
3
+ metadata.gz: e9e2d67cf94749b685aff4146aa6f831acd41539
4
+ data.tar.gz: 272fa686ecf0e978a0f07c41a42486a06ecfd56c
5
5
  SHA512:
6
- metadata.gz: 302ac60608a12fcbb40ea2988ad73a4459e469605058992aa379f6c6d5d043855560db1d57de531a280e05e054cab6d962cbc6847f405166fec25aadd5d2deb7
7
- data.tar.gz: 9b1a01f0aac40776531b393465e24baa14b873824ada7171df0e546711bb92d143804d44339b8ae27fd1313afcd74e46aa50fe92ce059807c55d86b87dcff219
6
+ metadata.gz: 612b8bff2f52780da77e09b63a929cb035d3cc6f8bcda1a75b426c7267c67e60f86d30eefa149e05f6ec27571317cef910daf53026b7d3394d149ccafb96ce07
7
+ data.tar.gz: 22b1bd381a910aedb712fa1efc53b36ded5965ae743dae44527f49208cc23243d8d97f3ff76694d984f06e2ede024d5d2b01feda3356c436a5890a44b3e06930
@@ -1,36 +1,5 @@
1
1
  AllCops:
2
- Excludes:
2
+ Exclude:
3
3
  - vendor/**/*
4
4
 
5
- LineLength:
6
- Enabled: false
7
-
8
- MethodLength:
9
- Enabled: false
10
-
11
- ClassLength:
12
- Enabled: false
13
-
14
- Documentation:
15
- # don't require classes to be documented
16
- Enabled: false
17
-
18
- Encoding:
19
- # no need to always specify encoding
20
- Enabled: false
21
-
22
- FileName:
23
- # allow grape-swagger.rb
24
- Enabled: false
25
-
26
- PredicateName:
27
- Enabled: false
28
-
29
- DoubleNegation:
30
- Enabled: false
31
-
32
- CyclomaticComplexity:
33
- Enabled: false
34
-
35
- ClassVars:
36
- Enabled: false
5
+ inherit_from: .rubocop_todo.yml
@@ -0,0 +1,55 @@
1
+ # This configuration was generated by `rubocop --auto-gen-config`
2
+ # on 2014-11-29 13:48:01 -0500 using RuboCop version 0.27.0.
3
+ # The point is for the user to remove these configuration records
4
+ # one by one as the offenses are removed from the code base.
5
+ # Note that changes in the inspected code, or installation of new
6
+ # versions of RuboCop, may require this file to be generated again.
7
+
8
+ # Offense count: 8
9
+ Metrics/AbcSize:
10
+ Max: 327
11
+
12
+ # Offense count: 1
13
+ # Configuration parameters: CountComments.
14
+ Metrics/ClassLength:
15
+ Max: 397
16
+
17
+ # Offense count: 5
18
+ Metrics/CyclomaticComplexity:
19
+ Max: 93
20
+
21
+ # Offense count: 195
22
+ # Configuration parameters: AllowURI, URISchemes.
23
+ Metrics/LineLength:
24
+ Max: 254
25
+
26
+ # Offense count: 13
27
+ # Configuration parameters: CountComments.
28
+ Metrics/MethodLength:
29
+ Max: 359
30
+
31
+ # Offense count: 4
32
+ Metrics/PerceivedComplexity:
33
+ Max: 96
34
+
35
+ # Offense count: 7
36
+ Style/ClassVars:
37
+ Enabled: false
38
+
39
+ # Offense count: 69
40
+ Style/Documentation:
41
+ Enabled: false
42
+
43
+ # Offense count: 2
44
+ Style/DoubleNegation:
45
+ Enabled: false
46
+
47
+ # Offense count: 3
48
+ # Configuration parameters: Exclude.
49
+ Style/FileName:
50
+ Enabled: false
51
+
52
+ # Offense count: 1
53
+ # Configuration parameters: NamePrefix, NamePrefixBlacklist.
54
+ Style/PredicateName:
55
+ Enabled: false
@@ -1,8 +1,16 @@
1
+ language: ruby
2
+
3
+ sudo: false
4
+
1
5
  rvm:
2
- - jruby-19mode
3
- - 1.9.3
6
+ - 2.1.1
4
7
  - 2.0.0
5
- - 2.1.2
8
+ - 1.9.3
6
9
  - rbx-2.2.10
10
+ - jruby-19mode
7
11
 
8
-
12
+ env:
13
+ - GRAPE_VERSION=0.8.0
14
+ - GRAPE_VERSION=0.9.0
15
+ - GRAPE_VERSION=0.10.0
16
+ - GRAPE_VERSION=HEAD
@@ -1,3 +1,17 @@
1
+ ### 0.9.0 (December 19, 2014)
2
+
3
+ * [#91](https://github.com/tim-vandecasteele/grape-swagger/issues/91): Fixed empty field for group parameters' name with type hash or Array - [@dukedave](https://github.com/dukedave).
4
+ * [#154](https://github.com/tim-vandecasteele/grape-swagger/pull/154): Allow classes for type declarations inside documentation - [@mrmargolis](https://github.com/mrmargolis).
5
+ * [#162](https://github.com/tim-vandecasteele/grape-swagger/pull/162): Fix performance issue related to having a large number of models - [@elado](https://github.com/elado).
6
+ * [#169](https://github.com/tim-vandecasteele/grape-swagger/pull/169): Test against multiple versions of Grape - [@dblock](https://github.com/dblock).
7
+ * [#166](https://github.com/tim-vandecasteele/grape-swagger/pull/166): Ensure compatibility with Grape 0.8.0 or newer - [@dblock](https://github.com/dblock).
8
+ * [#174](https://github.com/tim-vandecasteele/grape-swagger/pull/172): Fix problem with using prefix name somewhere in api paths - [@grzesiek](https://github.com/grzesiek).
9
+ * [#176](https://github.com/tim-vandecasteele/grape-swagger/pull/176): Added ability to load nested models recursively - [@sergey-verevkin](https://github.com/sergey-verevkin).
10
+ * [#179](https://github.com/tim-vandecasteele/grape-swagger/pull/179): Document `Virtus::Attribute::Boolean` as boolean - [@eashman](https://github.com/eashman), [@dblock](https://github.com/dblock).
11
+ * [#178](https://github.com/tim-vandecasteele/grape-swagger/issues/178): Fixed `Hash` parameters, now exposed as Swagger `object` types - [@dblock](https://github.com/dblock).
12
+ * [#167](https://github.com/tim-vandecasteele/grape-swagger/pull/167): Support mutli-tenanted APIs, don't cache `base_path` - [@bradrobertson](https://github.com/bradrobertson), (https://github.com/dblock).
13
+ * [#185](https://github.com/tim-vandecasteele/grape-swagger/pull/185): Support strings in `Grape::Entity.expose`'s `:using` option - [@jhollinger](https://github.com/jhollinger).
14
+
1
15
  ### 0.8.0 (August 30, 2014)
2
16
 
3
17
  #### Features
data/Gemfile CHANGED
@@ -1,3 +1,10 @@
1
- source "http://rubygems.org"
1
+ source 'http://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ case version = ENV['GRAPE_VERSION'] || '~> 0.9.0'
6
+ when 'HEAD'
7
+ gem 'grape', github: 'intridea/grape'
8
+ else
9
+ gem 'grape', version
10
+ end
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  The grape-swagger gem provides an autogenerated documentation for your [Grape](https://github.com/intridea/grape) API. The generated documentation is Swagger-compliant, meaning it can easily be discovered in [Swagger UI](https://github.com/wordnik/swagger-ui). You should be able to point [the petstore demo](http://petstore.swagger.wordnik.com) to your API.
8
8
 
9
- ![Demo Screenshot](test/splines.png)
9
+ ![Demo Screenshot](example/splines.png)
10
10
 
11
11
  ## Related Projects
12
12
 
@@ -25,7 +25,7 @@ Please see [UPGRADING](UPGRADING.md) when upgrading from a previous version.
25
25
 
26
26
  ## Usage
27
27
 
28
- Mount all your different APIs (with ```Grape::API``` superclass) on a root node. In the root class definition, include ```add_swagger_documentation```, this sets up the system and registers the documentation on '/swagger_doc.json'. See [test/api.rb](test/api.rb) for a simple demo.
28
+ Mount all your different APIs (with ```Grape::API``` superclass) on a root node. In the root class definition, include ```add_swagger_documentation```, this sets up the system and registers the documentation on '/swagger_doc.json'. See [example/api.rb](example/api.rb) for a simple demo.
29
29
 
30
30
 
31
31
  ``` ruby
@@ -227,6 +227,8 @@ end
227
227
 
228
228
  ### Relationships
229
229
 
230
+ Put the full name of the relationship's class in `type`, leaving out any modules named `Entities` or `Entity`. So for the entity class `API::Entities::Address`, you would put `type: 'API::Address'`.
231
+
230
232
  #### 1xN
231
233
 
232
234
  ```ruby
@@ -235,7 +237,7 @@ module API
235
237
  class Client < Grape::Entity
236
238
  expose :name, documentation: { type: 'string', desc: 'Name' }
237
239
  expose :addresses, using: Entities::Address,
238
- documentation: { type: 'Address', desc: 'Addresses.', param_type: 'body', is_array: true }
240
+ documentation: { type: 'API::Address', desc: 'Addresses.', param_type: 'body', is_array: true }
239
241
  end
240
242
 
241
243
  class Address < Grape::Entity
@@ -266,7 +268,7 @@ module API
266
268
  class Client < Grape::Entity
267
269
  expose :name, documentation: { type: 'string', desc: 'Name' }
268
270
  expose :address, using: Entities::Address,
269
- documentation: { type: 'Address', desc: 'Addresses.', param_type: 'body', is_array: false }
271
+ documentation: { type: 'API::Address', desc: 'Addresses.', param_type: 'body', is_array: false }
270
272
  end
271
273
 
272
274
  class Address < Grape::Entity
@@ -313,14 +315,14 @@ add_swagger_documentation(
313
315
  Finally you can write endpoint descriptions the with markdown enabled.
314
316
 
315
317
  ``` ruby
316
- desc "Reserve a virgin in heaven", {
318
+ desc "Reserve a burger in heaven", {
317
319
  notes: <<-NOTE
318
- Virgins in Heaven
320
+ Veggie Burgers in Heaven
319
321
  -----------------
320
322
 
321
- > A virgin doesn't come for free
323
+ > A veggie burger doesn't come for free
322
324
 
323
- If you want to reserve a virgin in heaven, you have to do
325
+ If you want to reserve a veggie burger in heaven, you have to do
324
326
  some crazy stuff on earth.
325
327
 
326
328
  def do_good
@@ -34,6 +34,10 @@ class Api < Grape::API
34
34
  desc 'Create a spline.'
35
35
  params do
36
36
  optional :reticulated, type: Boolean, default: true, desc: 'True if the spline is reticulated.'
37
+ requires :required_group, type: Hash do
38
+ requires :required_param_1
39
+ requires :required_param_2
40
+ end
37
41
  end
38
42
  post do
39
43
  spline = { id: @@splines.size + 1, reticulated: params[:reticulated] }
File without changes
File without changes
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.summary = 'A simple way to add auto generated documentation to your Grape API that can be displayed with Swagger.'
12
12
  s.license = 'MIT'
13
13
 
14
- s.add_runtime_dependency 'grape'
14
+ s.add_runtime_dependency 'grape', '>= 0.8.0'
15
15
  s.add_runtime_dependency 'grape-entity'
16
16
 
17
17
  s.add_development_dependency 'rake'
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.add_development_dependency 'bundler'
22
22
  s.add_development_dependency 'rack-test'
23
23
  s.add_development_dependency 'rack-cors'
24
- s.add_development_dependency 'rubocop', '0.24.1'
24
+ s.add_development_dependency 'rubocop', '0.27.0'
25
25
  s.add_development_dependency 'kramdown', '~> 1.4.1'
26
26
  s.add_development_dependency 'redcarpet', '~> 3.1.2' unless RUBY_PLATFORM.eql? 'java'
27
27
  s.add_development_dependency 'rouge', '~> 1.6.1'
@@ -17,25 +17,33 @@ module Grape
17
17
 
18
18
  @combined_routes = {}
19
19
  routes.each do |route|
20
- route_match = route.route_path.split(route.route_prefix).last.match('\/([\w|-]*?)[\.\/\(]')
21
- next if route_match.nil?
20
+ route_path = route.route_path
21
+ route_match = route_path.split(/^.*?#{route.route_prefix.to_s}/).last
22
+ next unless route_match
23
+ route_match = route_match.match('\/([\w|-]*?)[\.\/\(]') || route_match.match('\/([\w|-]*)')
24
+ next unless route_match
22
25
  resource = route_match.captures.first
23
26
  next if resource.empty?
24
27
  resource.downcase!
25
28
  @combined_routes[resource] ||= []
26
- next if @@hide_documentation_path && route.route_path.include?(@@mount_path)
29
+ next if documentation_class.hide_documentation_path && route.route_path.include?(documentation_class.mount_path)
27
30
  @combined_routes[resource] << route
28
31
  end
29
32
 
30
33
  @combined_namespaces = {}
31
34
  combine_namespaces(self)
35
+ documentation_class
32
36
  end
33
37
 
34
38
  private
35
39
 
36
40
  def combine_namespaces(app)
37
41
  app.endpoints.each do |endpoint|
38
- ns = endpoint.settings.stack.last[:namespace]
42
+ ns = if endpoint.respond_to?(:namespace_stackable)
43
+ endpoint.namespace_stackable(:namespace).last
44
+ else
45
+ endpoint.settings.stack.last[:namespace]
46
+ end
39
47
  @combined_namespaces[ns.space] = ns if ns
40
48
 
41
49
  combine_namespaces(endpoint.options[:app]) if endpoint.options[:app]
@@ -48,179 +56,6 @@ module Grape
48
56
  def name
49
57
  @@class_name
50
58
  end
51
- end
52
-
53
- def self.setup(options)
54
- defaults = {
55
- target_class: nil,
56
- mount_path: '/swagger_doc',
57
- base_path: nil,
58
- api_version: '0.1',
59
- markdown: nil,
60
- hide_documentation_path: false,
61
- hide_format: false,
62
- format: nil,
63
- models: [],
64
- info: {},
65
- authorizations: nil,
66
- root_base_path: true,
67
- api_documentation: { desc: 'Swagger compatible API description' },
68
- specific_api_documentation: { desc: 'Swagger compatible API description for specific API' }
69
- }
70
-
71
- options = defaults.merge(options)
72
-
73
- target_class = options[:target_class]
74
- @@mount_path = options[:mount_path]
75
- @@class_name = options[:class_name] || options[:mount_path].gsub('/', '')
76
- @@markdown = options[:markdown] ? GrapeSwagger::Markdown.new(options[:markdown]) : nil
77
- @@hide_format = options[:hide_format]
78
- api_version = options[:api_version]
79
- base_path = options[:base_path]
80
- authorizations = options[:authorizations]
81
- root_base_path = options[:root_base_path]
82
- extra_info = options[:info]
83
- api_doc = options[:api_documentation].dup
84
- specific_api_doc = options[:specific_api_documentation].dup
85
- @@models = options[:models] || []
86
-
87
- @@hide_documentation_path = options[:hide_documentation_path]
88
-
89
- if options[:format]
90
- [:format, :default_format, :default_error_formatter].each do |method|
91
- send(method, options[:format])
92
- end
93
- end
94
-
95
- desc api_doc.delete(:desc), params: api_doc.delete(:params)
96
- @last_description.merge!(api_doc)
97
- get @@mount_path do
98
- header['Access-Control-Allow-Origin'] = '*'
99
- header['Access-Control-Request-Method'] = '*'
100
-
101
- routes = target_class.combined_routes
102
- namespaces = target_class.combined_namespaces
103
-
104
- if @@hide_documentation_path
105
- routes.reject! { |route, _value| "/#{route}/".index(parse_path(@@mount_path, nil) << '/') == 0 }
106
- end
107
-
108
- routes_array = routes.keys.map do |local_route|
109
- next if routes[local_route].all?(&:route_hidden)
110
-
111
- url_format = '.{format}' unless @@hide_format
112
-
113
- description = namespaces[local_route] && namespaces[local_route].options[:desc]
114
- description ||= "Operations about #{local_route.pluralize}"
115
-
116
- {
117
- path: "/#{local_route}#{url_format}",
118
- description: description
119
- }
120
- end.compact
121
-
122
- output = {
123
- apiVersion: api_version,
124
- swaggerVersion: '1.2',
125
- produces: content_types_for(target_class),
126
- apis: routes_array,
127
- info: parse_info(extra_info)
128
- }
129
-
130
- output[:authorizations] = authorizations unless authorizations.nil? || authorizations.empty?
131
-
132
- output
133
- end
134
-
135
- desc specific_api_doc.delete(:desc), params: {
136
- 'name' => {
137
- desc: 'Resource name of mounted API',
138
- type: 'string',
139
- required: true
140
- }
141
- }.merge(specific_api_doc.delete(:params) || {})
142
- @last_description.merge!(specific_api_doc)
143
- get "#{@@mount_path}/:name" do
144
- header['Access-Control-Allow-Origin'] = '*'
145
- header['Access-Control-Request-Method'] = '*'
146
-
147
- models = []
148
- routes = target_class.combined_routes[params[:name]]
149
- error!('Not Found', 404) unless routes
150
-
151
- ops = routes.reject(&:route_hidden).group_by do |route|
152
- parse_path(route.route_path, api_version)
153
- end
154
-
155
- error!('Not Found', 404) unless ops.any?
156
-
157
- apis = []
158
-
159
- ops.each do |path, op_routes|
160
- operations = op_routes.map do |route|
161
- notes = as_markdown(route.route_notes)
162
-
163
- http_codes = parse_http_codes(route.route_http_codes, models)
164
-
165
- models << @@models if @@models.present?
166
-
167
- models << route.route_entity if route.route_entity.present?
168
-
169
- models = models_with_included_presenters(models.flatten.compact)
170
-
171
- operation = {
172
- notes: notes.to_s,
173
- summary: route.route_description || '',
174
- nickname: route.route_nickname || (route.route_method + route.route_path.gsub(/[\/:\(\)\.]/, '-')),
175
- method: route.route_method,
176
- parameters: parse_header_params(route.route_headers) + parse_params(route.route_params, route.route_path, route.route_method),
177
- type: 'void'
178
- }
179
- operation[:authorizations] = route.route_authorizations unless route.route_authorizations.nil? || route.route_authorizations.empty?
180
- if operation[:parameters].any? { | param | param[:type] == 'File' }
181
- operation.merge!(consumes: ['multipart/form-data'])
182
- end
183
- operation.merge!(responseMessages: http_codes) unless http_codes.empty?
184
-
185
- if route.route_entity
186
- type = parse_entity_name(route.route_entity)
187
- if route.instance_variable_get(:@options)[:is_array]
188
- operation.merge!(
189
- 'type' => 'array',
190
- 'items' => generate_typeref(type)
191
- )
192
- else
193
- operation.merge!('type' => type)
194
- end
195
- end
196
-
197
- operation[:nickname] = route.route_nickname if route.route_nickname
198
- operation
199
- end.compact
200
- apis << {
201
- path: path,
202
- operations: operations
203
- }
204
- end
205
-
206
- api_description = {
207
- apiVersion: api_version,
208
- swaggerVersion: '1.2',
209
- resourcePath: "/#{params[:name]}",
210
- produces: content_types_for(target_class),
211
- apis: apis
212
- }
213
-
214
- base_path = parse_base_path(base_path, request)
215
- api_description[:basePath] = base_path if base_path && base_path.size > 0 && root_base_path != false
216
- api_description[:models] = parse_entity_models(models) unless models.empty?
217
- api_description[:authorizations] = authorizations if authorizations
218
-
219
- api_description
220
- end
221
- end
222
-
223
- helpers do
224
59
 
225
60
  def as_markdown(description)
226
61
  description && @@markdown ? @@markdown.as_markdown(strip_heredoc(description)) : description
@@ -228,12 +63,23 @@ module Grape
228
63
 
229
64
  def parse_params(params, path, method)
230
65
  params ||= []
231
- params.map do |param, value|
232
- value[:type] = 'File' if value.is_a?(Hash) && ['Rack::Multipart::UploadedFile', 'Hash'].include?(value[:type])
66
+
67
+ non_nested_parent_params = params.reject do |param, _|
68
+ is_nested_param = /^#{ Regexp.quote param }\[.+\]$/
69
+ params.keys.any? { |p| p.match is_nested_param }
70
+ end
71
+
72
+ non_nested_parent_params.map do |param, value|
233
73
  items = {}
234
74
 
235
75
  raw_data_type = value.is_a?(Hash) ? (value[:type] || 'string').to_s : 'string'
236
76
  data_type = case raw_data_type
77
+ when 'Hash'
78
+ 'object'
79
+ when 'Rack::Multipart::UploadedFile'
80
+ 'File'
81
+ when 'Virtus::Attribute::Boolean'
82
+ 'boolean'
237
83
  when 'Boolean', 'Date', 'Integer', 'String'
238
84
  raw_data_type.downcase
239
85
  when 'BigDecimal'
@@ -243,7 +89,7 @@ module Grape
243
89
  when 'Numeric'
244
90
  'double'
245
91
  else
246
- parse_entity_name(raw_data_type)
92
+ @@documentation_class.parse_entity_name(raw_data_type)
247
93
  end
248
94
  description = value.is_a?(Hash) ? value[:desc] || value[:description] : ''
249
95
  required = value.is_a?(Hash) ? !!value[:required] : false
@@ -292,10 +138,10 @@ module Grape
292
138
  end
293
139
 
294
140
  def content_types_for(target_class)
295
- content_types = (target_class.settings[:content_types] || {}).values
141
+ content_types = (target_class.content_types || {}).values
296
142
 
297
143
  if content_types.empty?
298
- formats = [target_class.settings[:format], target_class.settings[:default_format]].compact.uniq
144
+ formats = [target_class.format, target_class.default_format].compact.uniq
299
145
  formats = Grape::Formatter::Base.formatters({}).keys if formats.empty?
300
146
  content_types = Grape::ContentTypes::CONTENT_TYPES.select { |content_type, _mime_type| formats.include? content_type }.values
301
147
  end
@@ -411,10 +257,18 @@ module Grape
411
257
 
412
258
  models.each do |model|
413
259
  # get model references from exposures with a documentation
414
- additional_models = model.exposures.map do |_, config|
415
- config[:using] if config.key?(:documentation)
260
+ nested_models = model.exposures.map do |_, config|
261
+ if config.key?(:documentation)
262
+ model = config[:using]
263
+ model.respond_to?(:constantize) ? model.constantize : model
264
+ end
416
265
  end.compact
417
266
 
267
+ # get all nested models recursively
268
+ additional_models = nested_models.map do |nested_model|
269
+ models_with_included_presenters([nested_model])
270
+ end.flatten
271
+
418
272
  all_models += additional_models
419
273
  end
420
274
 
@@ -422,10 +276,11 @@ module Grape
422
276
  end
423
277
 
424
278
  def is_primitive?(type)
425
- %w(integer long float double string byte boolean date dateTime).include? type
279
+ %w(object integer long float double string byte boolean date dateTime).include? type
426
280
  end
427
281
 
428
282
  def generate_typeref(type)
283
+ type = type.to_s.sub(/^[A-Z]/) { |f| f.downcase } if type.is_a?(Class)
429
284
  if is_primitive? type
430
285
  { 'type' => type }
431
286
  else
@@ -446,14 +301,6 @@ module Grape
446
301
  end
447
302
  end
448
303
 
449
- def try(*args, &block)
450
- if args.empty? && block_given?
451
- yield self
452
- elsif respond_to?(args.first)
453
- public_send(*args, &block)
454
- end
455
- end
456
-
457
304
  def strip_heredoc(string)
458
305
  indent = string.scan(/^[ \t]*(?=\S)/).min.try(:size) || 0
459
306
  string.gsub(/^[ \t]{#{indent}}/, '')
@@ -468,6 +315,177 @@ module Grape
468
315
  request.base_url
469
316
  end
470
317
  end
318
+
319
+ def hide_documentation_path
320
+ @@hide_documentation_path
321
+ end
322
+
323
+ def mount_path
324
+ @@mount_path
325
+ end
326
+
327
+ def setup(options)
328
+ defaults = {
329
+ target_class: nil,
330
+ mount_path: '/swagger_doc',
331
+ base_path: nil,
332
+ api_version: '0.1',
333
+ markdown: nil,
334
+ hide_documentation_path: false,
335
+ hide_format: false,
336
+ format: nil,
337
+ models: [],
338
+ info: {},
339
+ authorizations: nil,
340
+ root_base_path: true,
341
+ api_documentation: { desc: 'Swagger compatible API description' },
342
+ specific_api_documentation: { desc: 'Swagger compatible API description for specific API' }
343
+ }
344
+
345
+ options = defaults.merge(options)
346
+
347
+ target_class = options[:target_class]
348
+ @@mount_path = options[:mount_path]
349
+ @@class_name = options[:class_name] || options[:mount_path].gsub('/', '')
350
+ @@markdown = options[:markdown] ? GrapeSwagger::Markdown.new(options[:markdown]) : nil
351
+ @@hide_format = options[:hide_format]
352
+ api_version = options[:api_version]
353
+ authorizations = options[:authorizations]
354
+ root_base_path = options[:root_base_path]
355
+ extra_info = options[:info]
356
+ api_doc = options[:api_documentation].dup
357
+ specific_api_doc = options[:specific_api_documentation].dup
358
+ @@models = options[:models] || []
359
+
360
+ @@hide_documentation_path = options[:hide_documentation_path]
361
+
362
+ if options[:format]
363
+ [:format, :default_format, :default_error_formatter].each do |method|
364
+ send(method, options[:format])
365
+ end
366
+ end
367
+
368
+ @@documentation_class = self
369
+
370
+ desc api_doc.delete(:desc), api_doc
371
+ get @@mount_path do
372
+ header['Access-Control-Allow-Origin'] = '*'
373
+ header['Access-Control-Request-Method'] = '*'
374
+
375
+ routes = target_class.combined_routes
376
+ namespaces = target_class.combined_namespaces
377
+
378
+ if @@hide_documentation_path
379
+ routes.reject! { |route, _value| "/#{route}/".index(@@documentation_class.parse_path(@@mount_path, nil) << '/') == 0 }
380
+ end
381
+
382
+ routes_array = routes.keys.map do |local_route|
383
+ next if routes[local_route].all?(&:route_hidden)
384
+
385
+ url_format = '.{format}' unless @@hide_format
386
+
387
+ description = namespaces[local_route] && namespaces[local_route].options[:desc]
388
+ description ||= "Operations about #{local_route.pluralize}"
389
+
390
+ {
391
+ path: "/#{local_route}#{url_format}",
392
+ description: description
393
+ }
394
+ end.compact
395
+
396
+ output = {
397
+ apiVersion: api_version,
398
+ swaggerVersion: '1.2',
399
+ produces: @@documentation_class.content_types_for(target_class),
400
+ apis: routes_array,
401
+ info: @@documentation_class.parse_info(extra_info)
402
+ }
403
+
404
+ output[:authorizations] = authorizations unless authorizations.nil? || authorizations.empty?
405
+
406
+ output
407
+ end
408
+
409
+ desc specific_api_doc.delete(:desc), { params: {
410
+ 'name' => {
411
+ desc: 'Resource name of mounted API',
412
+ type: 'string',
413
+ required: true
414
+ }
415
+ }.merge(specific_api_doc.delete(:params) || {}) }.merge(specific_api_doc)
416
+
417
+ get "#{@@mount_path}/:name" do
418
+ header['Access-Control-Allow-Origin'] = '*'
419
+ header['Access-Control-Request-Method'] = '*'
420
+
421
+ models = []
422
+ routes = target_class.combined_routes[params[:name]]
423
+ error!('Not Found', 404) unless routes
424
+
425
+ ops = routes.reject(&:route_hidden).group_by do |route|
426
+ @@documentation_class.parse_path(route.route_path, api_version)
427
+ end
428
+
429
+ error!('Not Found', 404) unless ops.any?
430
+
431
+ apis = []
432
+
433
+ ops.each do |path, op_routes|
434
+ operations = op_routes.map do |route|
435
+ notes = @@documentation_class.as_markdown(route.route_notes)
436
+
437
+ http_codes = @@documentation_class.parse_http_codes(route.route_http_codes, models)
438
+
439
+ models |= @@models if @@models.present?
440
+
441
+ models |= [route.route_entity] if route.route_entity.present?
442
+
443
+ models = @@documentation_class.models_with_included_presenters(models.flatten.compact)
444
+
445
+ operation = {
446
+ notes: notes.to_s,
447
+ summary: route.route_description || '',
448
+ nickname: route.route_nickname || (route.route_method + route.route_path.gsub(/[\/:\(\)\.]/, '-')),
449
+ method: route.route_method,
450
+ parameters: @@documentation_class.parse_header_params(route.route_headers) + @@documentation_class.parse_params(route.route_params, route.route_path, route.route_method),
451
+ type: 'void'
452
+ }
453
+ operation[:authorizations] = route.route_authorizations unless route.route_authorizations.nil? || route.route_authorizations.empty?
454
+ if operation[:parameters].any? { | param | param[:type] == 'File' }
455
+ operation.merge!(consumes: ['multipart/form-data'])
456
+ end
457
+ operation.merge!(responseMessages: http_codes) unless http_codes.empty?
458
+
459
+ if route.route_entity
460
+ type = @@documentation_class.parse_entity_name(route.route_entity)
461
+ operation.merge!('type' => type)
462
+ end
463
+
464
+ operation[:nickname] = route.route_nickname if route.route_nickname
465
+ operation
466
+ end.compact
467
+ apis << {
468
+ path: path,
469
+ operations: operations
470
+ }
471
+ end
472
+
473
+ api_description = {
474
+ apiVersion: api_version,
475
+ swaggerVersion: '1.2',
476
+ resourcePath: "/#{params[:name]}",
477
+ produces: @@documentation_class.content_types_for(target_class),
478
+ apis: apis
479
+ }
480
+
481
+ base_path = @@documentation_class.parse_base_path(options[:base_path], request)
482
+ api_description[:basePath] = base_path if base_path && base_path.size > 0 && root_base_path != false
483
+ api_description[:models] = @@documentation_class.parse_entity_models(models) unless models.empty?
484
+ api_description[:authorizations] = authorizations if authorizations
485
+
486
+ api_description
487
+ end
488
+ end
471
489
  end
472
490
  end
473
491
  end