grape-swagger 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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