grape-swagger 0.10.1 → 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +10 -10
- data/.travis.yml +4 -1
- data/CHANGELOG.md +89 -65
- data/CONTRIBUTING.md +8 -8
- data/Gemfile +1 -1
- data/README.md +90 -16
- data/RELEASING.md +1 -1
- data/UPGRADING.md +20 -3
- data/grape-swagger.gemspec +1 -1
- data/lib/grape-swagger.rb +49 -476
- data/lib/grape-swagger/doc_methods.rb +495 -0
- data/lib/grape-swagger/version.rb +1 -1
- data/spec/api_description_spec.rb +2 -2
- data/spec/api_global_models_spec.rb +21 -21
- data/spec/api_models_spec.rb +62 -42
- data/spec/api_paths_spec.rb +64 -0
- data/spec/api_with_standalone_namespace_spec.rb +16 -16
- data/spec/array_entity_spec.rb +34 -0
- data/spec/array_params_spec.rb +35 -6
- data/spec/default_api_spec.rb +42 -0
- data/spec/float_api_spec.rb +1 -1
- data/spec/grape-swagger_helper_spec.rb +14 -1
- data/spec/mounted_target_class_spec.rb +63 -0
- data/spec/non_default_api_spec.rb +47 -16
- data/spec/param_type_spec.rb +52 -0
- data/spec/param_values_spec.rb +130 -0
- data/spec/reference_entity.rb +80 -0
- data/spec/simple_mounted_api_spec.rb +2 -2
- data/spec/support/grape_version.rb +11 -0
- metadata +50 -40
- data/.ruby-version +0 -1
- data/spec/range_values_spec.rb +0 -49
data/RELEASING.md
CHANGED
@@ -11,7 +11,7 @@ bundle install
|
|
11
11
|
rake
|
12
12
|
```
|
13
13
|
|
14
|
-
Check that the last build succeeded in [Travis CI](https://travis-ci.org/
|
14
|
+
Check that the last build succeeded in [Travis CI](https://travis-ci.org/ruby-grape/grape-swagger) for all supported platforms.
|
15
15
|
|
16
16
|
Increment the version, modify [lib/grape-swagger/version.rb](lib/grape-swagger/version.rb).
|
17
17
|
|
data/UPGRADING.md
CHANGED
@@ -1,11 +1,28 @@
|
|
1
1
|
Upgrading Grape-swagger
|
2
2
|
=======================
|
3
3
|
|
4
|
+
### Upgrading to >= 0.10.2
|
5
|
+
|
6
|
+
With grape >= 0.12.0, support for `notes` is replaced by passing a block `detail` option specified. For future compatibility, update your code:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
desc 'Get all kittens!', notes: 'this will expose all the kittens'
|
10
|
+
```
|
11
|
+
|
12
|
+
to
|
13
|
+
|
14
|
+
``` ruby
|
15
|
+
desc 'Get all kittens!' do
|
16
|
+
detail 'this will expose all the kittens'
|
17
|
+
end
|
18
|
+
```
|
19
|
+
Be aware of https://github.com/ruby-grape/grape/issues/920, currently grape accepts either an option hash OR a block for `desc`.
|
20
|
+
|
4
21
|
### Upgrading to >= 0.9.0
|
5
22
|
|
6
23
|
#### Grape-Swagger-Rails
|
7
24
|
|
8
|
-
If you're using [grape-swagger-rails](https://github.com/
|
25
|
+
If you're using [grape-swagger-rails](https://github.com/ruby-grape/grape-swagger-rails), remove the `.json` extension from `GrapeSwaggerRails.options.url`.
|
9
26
|
|
10
27
|
For example, change
|
11
28
|
|
@@ -19,7 +36,7 @@ to
|
|
19
36
|
GrapeSwaggerRails.options.url = '/api/v1/swagger_doc'
|
20
37
|
```
|
21
38
|
|
22
|
-
See [#187](https://github.com/
|
39
|
+
See [#187](https://github.com/ruby-grape/grape-swagger/issues/187) for more information.
|
23
40
|
|
24
41
|
#### Grape 0.10.0
|
25
42
|
|
@@ -68,4 +85,4 @@ add_swagger_documentation (
|
|
68
85
|
)
|
69
86
|
```
|
70
87
|
|
71
|
-
See [#142](https://github.com/
|
88
|
+
See [#142](https://github.com/ruby-grape/grape-swagger/pull/142) and documentation section [Markdown in Notes](https://github.com/ruby-grape/grape-swagger#markdown-in-notes) for more information.
|
data/grape-swagger.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.platform = Gem::Platform::RUBY
|
8
8
|
s.authors = ['Tim Vandecasteele']
|
9
9
|
s.email = ['tim.vandecasteele@gmail.com']
|
10
|
-
s.homepage = 'https://github.com/
|
10
|
+
s.homepage = 'https://github.com/ruby-grape/grape-swagger'
|
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
|
|
data/lib/grape-swagger.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'grape'
|
2
2
|
require 'grape-swagger/version'
|
3
3
|
require 'grape-swagger/errors'
|
4
|
+
require 'grape-swagger/doc_methods'
|
4
5
|
require 'grape-swagger/markdown'
|
5
6
|
require 'grape-swagger/markdown/kramdown_adapter'
|
6
7
|
require 'grape-swagger/markdown/redcarpet_adapter'
|
@@ -8,16 +9,19 @@ require 'grape-swagger/markdown/redcarpet_adapter'
|
|
8
9
|
module Grape
|
9
10
|
class API
|
10
11
|
class << self
|
11
|
-
|
12
|
+
attr_accessor :combined_routes, :combined_namespaces, :combined_namespace_routes, :combined_namespace_identifiers
|
12
13
|
|
13
14
|
def add_swagger_documentation(options = {})
|
14
15
|
documentation_class = create_documentation_class
|
15
16
|
|
16
|
-
|
17
|
+
options = { target_class: self }.merge(options)
|
18
|
+
@target_class = options[:target_class]
|
19
|
+
|
20
|
+
documentation_class.setup(options)
|
17
21
|
mount(documentation_class)
|
18
22
|
|
19
|
-
@combined_routes = {}
|
20
|
-
routes.each do |route|
|
23
|
+
@target_class.combined_routes = {}
|
24
|
+
@target_class.routes.each do |route|
|
21
25
|
route_path = route.route_path
|
22
26
|
route_match = route_path.split(/^.*?#{route.route_prefix.to_s}/).last
|
23
27
|
next unless route_match
|
@@ -26,20 +30,20 @@ module Grape
|
|
26
30
|
resource = route_match.captures.first
|
27
31
|
next if resource.empty?
|
28
32
|
resource.downcase!
|
29
|
-
@combined_routes[resource] ||= []
|
30
|
-
next if documentation_class.hide_documentation_path && route.route_path.
|
31
|
-
@combined_routes[resource] << route
|
33
|
+
@target_class.combined_routes[resource] ||= []
|
34
|
+
next if documentation_class.hide_documentation_path && route.route_path.match(/#{documentation_class.mount_path}($|\/|\(\.)/)
|
35
|
+
@target_class.combined_routes[resource] << route
|
32
36
|
end
|
33
37
|
|
34
|
-
@combined_namespaces = {}
|
35
|
-
combine_namespaces(
|
38
|
+
@target_class.combined_namespaces = {}
|
39
|
+
combine_namespaces(@target_class)
|
36
40
|
|
37
|
-
@combined_namespace_routes = {}
|
38
|
-
@combined_namespace_identifiers = {}
|
39
|
-
combine_namespace_routes(@combined_namespaces)
|
41
|
+
@target_class.combined_namespace_routes = {}
|
42
|
+
@target_class.combined_namespace_identifiers = {}
|
43
|
+
combine_namespace_routes(@target_class.combined_namespaces)
|
40
44
|
|
41
|
-
exclusive_route_keys = @combined_routes.keys - @combined_namespaces.keys
|
42
|
-
exclusive_route_keys.each { |key| @combined_namespace_routes[key] = @combined_routes[key] }
|
45
|
+
exclusive_route_keys = @target_class.combined_routes.keys - @target_class.combined_namespaces.keys
|
46
|
+
exclusive_route_keys.each { |key| @target_class.combined_namespace_routes[key] = @target_class.combined_routes[key] }
|
43
47
|
documentation_class
|
44
48
|
end
|
45
49
|
|
@@ -54,7 +58,7 @@ module Grape
|
|
54
58
|
end
|
55
59
|
# use the full namespace here (not the latest level only)
|
56
60
|
# and strip leading slash
|
57
|
-
@combined_namespaces[endpoint.namespace.sub(/^\//, '')] = ns if ns
|
61
|
+
@target_class.combined_namespaces[endpoint.namespace.sub(/^\//, '')] = ns if ns
|
58
62
|
|
59
63
|
combine_namespaces(endpoint.options[:app]) if endpoint.options[:app]
|
60
64
|
end
|
@@ -65,7 +69,7 @@ module Grape
|
|
65
69
|
namespaces.each do |name, namespace|
|
66
70
|
# get the parent route for the namespace
|
67
71
|
parent_route_name = name.match(%r{^/?([^/]*).*$})[1]
|
68
|
-
parent_route = @combined_routes[parent_route_name]
|
72
|
+
parent_route = @target_class.combined_routes[parent_route_name]
|
69
73
|
# fetch all routes that are within the current namespace
|
70
74
|
namespace_routes = parent_route.collect do |route|
|
71
75
|
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 +83,8 @@ module Grape
|
|
79
83
|
else
|
80
84
|
identifier = name.gsub(/_/, '-').gsub(/\//, '_')
|
81
85
|
end
|
82
|
-
@combined_namespace_identifiers[identifier] = name
|
83
|
-
@combined_namespace_routes[identifier] = namespace_routes
|
86
|
+
@target_class.combined_namespace_identifiers[identifier] = name
|
87
|
+
@target_class.combined_namespace_routes[identifier] = namespace_routes
|
84
88
|
|
85
89
|
# get all nested namespaces below the current namespace
|
86
90
|
sub_namespaces = standalone_sub_namespaces(name, namespaces)
|
@@ -92,7 +96,7 @@ module Grape
|
|
92
96
|
route if sub_ns_paths.include?(route.instance_variable_get(:@options)[:namespace]) || sub_ns_paths_versioned.include?(route.instance_variable_get(:@options)[:namespace])
|
93
97
|
end.compact
|
94
98
|
# add all determined routes of the sub namespaces to standalone resource
|
95
|
-
@combined_namespace_routes[identifier].push(*sub_routes)
|
99
|
+
@target_class.combined_namespace_routes[identifier].push(*sub_routes)
|
96
100
|
else
|
97
101
|
# default case when not explicitly specified or nested == true
|
98
102
|
standalone_namespaces = namespaces.reject { |_, ns| !ns.options.key?(:swagger) || !ns.options[:swagger].key?(:nested) || ns.options[:swagger][:nested] != false }
|
@@ -100,8 +104,8 @@ module Grape
|
|
100
104
|
# add only to the main route if the namespace is not within any other namespace appearing as standalone resource
|
101
105
|
if parent_standalone_namespaces.empty?
|
102
106
|
# 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)
|
107
|
+
@target_class.combined_namespace_routes[parent_route_name] = [] unless @target_class.combined_namespace_routes.key?(parent_route_name)
|
108
|
+
@target_class.combined_namespace_routes[parent_route_name].push(*namespace_routes)
|
105
109
|
end
|
106
110
|
end
|
107
111
|
end
|
@@ -125,7 +129,7 @@ module Grape
|
|
125
129
|
|
126
130
|
def get_non_nested_params(params)
|
127
131
|
# Duplicate the params as we are going to modify them
|
128
|
-
dup_params = params.each_with_object(
|
132
|
+
dup_params = params.each_with_object({}) do |(param, value), dparams|
|
129
133
|
dparams[param] = value.dup
|
130
134
|
end
|
131
135
|
|
@@ -145,11 +149,13 @@ module Grape
|
|
145
149
|
params.each_key do |k|
|
146
150
|
if params[k].is_a?(Hash) && params[k][:type] == 'Array'
|
147
151
|
array_param = k
|
152
|
+
modified_params[k] = params[k]
|
148
153
|
else
|
149
154
|
new_key = k
|
150
155
|
unless array_param.nil?
|
151
156
|
if k.to_s.start_with?(array_param.to_s + '[')
|
152
157
|
new_key = array_param.to_s + '[]' + k.to_s.split(array_param)[1]
|
158
|
+
modified_params.delete array_param
|
153
159
|
end
|
154
160
|
end
|
155
161
|
modified_params[new_key] = params[k]
|
@@ -158,462 +164,29 @@ module Grape
|
|
158
164
|
modified_params
|
159
165
|
end
|
160
166
|
|
161
|
-
def
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
non_nested_parent_params = get_non_nested_params(parsed_array_params)
|
178
|
-
|
179
|
-
non_nested_parent_params.map do |param, value|
|
180
|
-
items = {}
|
181
|
-
|
182
|
-
raw_data_type = value.is_a?(Hash) ? (value[:type] || 'string').to_s : 'string'
|
183
|
-
data_type = case raw_data_type
|
184
|
-
when 'Hash'
|
185
|
-
'object'
|
186
|
-
when 'Rack::Multipart::UploadedFile'
|
187
|
-
'File'
|
188
|
-
when 'Virtus::Attribute::Boolean'
|
189
|
-
'boolean'
|
190
|
-
when 'Boolean', 'Date', 'Integer', 'String', 'Float'
|
191
|
-
raw_data_type.downcase
|
192
|
-
when 'BigDecimal'
|
193
|
-
'long'
|
194
|
-
when 'DateTime'
|
195
|
-
'dateTime'
|
196
|
-
when 'Numeric'
|
197
|
-
'double'
|
198
|
-
when 'Symbol'
|
199
|
-
'string'
|
200
|
-
else
|
201
|
-
@@documentation_class.parse_entity_name(raw_data_type)
|
202
|
-
end
|
203
|
-
description = value.is_a?(Hash) ? value[:desc] || value[:description] : ''
|
204
|
-
required = value.is_a?(Hash) ? !!value[:required] : false
|
205
|
-
default_value = value.is_a?(Hash) ? value[:default] : nil
|
206
|
-
is_array = value.is_a?(Hash) ? (value[:is_array] || false) : false
|
207
|
-
enum_values = value.is_a?(Hash) ? value[:values] : nil
|
208
|
-
enum_values = enum_values.to_a if enum_values && enum_values.is_a?(Range)
|
209
|
-
enum_values = enum_values.call if enum_values && enum_values.is_a?(Proc)
|
210
|
-
|
211
|
-
if value.is_a?(Hash) && value.key?(:param_type)
|
212
|
-
param_type = value[:param_type]
|
213
|
-
if is_array
|
214
|
-
items = { '$ref' => data_type }
|
215
|
-
data_type = 'array'
|
216
|
-
end
|
217
|
-
else
|
218
|
-
param_type = case
|
219
|
-
when path.include?(":#{param}")
|
220
|
-
'path'
|
221
|
-
when %w(POST PUT PATCH).include?(method)
|
222
|
-
if is_primitive?(data_type)
|
223
|
-
'form'
|
224
|
-
else
|
225
|
-
'body'
|
226
|
-
end
|
227
|
-
else
|
228
|
-
'query'
|
229
|
-
end
|
230
|
-
end
|
231
|
-
name = (value.is_a?(Hash) && value[:full_name]) || param
|
232
|
-
|
233
|
-
parsed_params = {
|
234
|
-
paramType: param_type,
|
235
|
-
name: name,
|
236
|
-
description: as_markdown(description),
|
237
|
-
type: data_type,
|
238
|
-
required: required,
|
239
|
-
allowMultiple: is_array
|
240
|
-
}
|
241
|
-
parsed_params.merge!(format: 'int32') if data_type == 'integer'
|
242
|
-
parsed_params.merge!(format: 'int64') if data_type == 'long'
|
243
|
-
parsed_params.merge!(items: items) if items.present?
|
244
|
-
parsed_params.merge!(defaultValue: default_value) if default_value
|
245
|
-
parsed_params.merge!(enum: enum_values) if enum_values
|
246
|
-
parsed_params
|
247
|
-
end
|
248
|
-
end
|
249
|
-
|
250
|
-
def content_types_for(target_class)
|
251
|
-
content_types = (target_class.content_types || {}).values
|
252
|
-
|
253
|
-
if content_types.empty?
|
254
|
-
formats = [target_class.format, target_class.default_format].compact.uniq
|
255
|
-
formats = Grape::Formatter::Base.formatters({}).keys if formats.empty?
|
256
|
-
content_types = Grape::ContentTypes::CONTENT_TYPES.select { |content_type, _mime_type| formats.include? content_type }.values
|
257
|
-
end
|
258
|
-
|
259
|
-
content_types.uniq
|
260
|
-
end
|
261
|
-
|
262
|
-
def parse_info(info)
|
263
|
-
{
|
264
|
-
contact: info[:contact],
|
265
|
-
description: as_markdown(info[:description]),
|
266
|
-
license: info[:license],
|
267
|
-
licenseUrl: info[:license_url],
|
268
|
-
termsOfServiceUrl: info[:terms_of_service_url],
|
269
|
-
title: info[:title]
|
270
|
-
}.delete_if { |_, value| value.blank? }
|
271
|
-
end
|
272
|
-
|
273
|
-
def parse_header_params(params)
|
274
|
-
params ||= []
|
275
|
-
|
276
|
-
params.map do |param, value|
|
277
|
-
data_type = 'String'
|
278
|
-
description = value.is_a?(Hash) ? value[:description] : ''
|
279
|
-
required = value.is_a?(Hash) ? !!value[:required] : false
|
280
|
-
default_value = value.is_a?(Hash) ? value[:default] : nil
|
281
|
-
param_type = 'header'
|
282
|
-
|
283
|
-
parsed_params = {
|
284
|
-
paramType: param_type,
|
285
|
-
name: param,
|
286
|
-
description: as_markdown(description),
|
287
|
-
type: data_type,
|
288
|
-
required: required
|
289
|
-
}
|
290
|
-
|
291
|
-
parsed_params.merge!(defaultValue: default_value) if default_value
|
292
|
-
|
293
|
-
parsed_params
|
294
|
-
end
|
295
|
-
end
|
296
|
-
|
297
|
-
def parse_path(path, version)
|
298
|
-
# adapt format to swagger format
|
299
|
-
parsed_path = path.gsub('(.:format)', @@hide_format ? '' : '.{format}')
|
300
|
-
# This is attempting to emulate the behavior of
|
301
|
-
# Rack::Mount::Strexp. We cannot use Strexp directly because
|
302
|
-
# all it does is generate regular expressions for parsing URLs.
|
303
|
-
# TODO: Implement a Racc tokenizer to properly generate the
|
304
|
-
# parsed path.
|
305
|
-
parsed_path = parsed_path.gsub(/:([a-zA-Z_]\w*)/, '{\1}')
|
306
|
-
# add the version
|
307
|
-
version ? parsed_path.gsub('{version}', version) : parsed_path
|
308
|
-
end
|
309
|
-
|
310
|
-
def parse_entity_name(model)
|
311
|
-
if model.respond_to?(:entity_name)
|
312
|
-
model.entity_name
|
313
|
-
else
|
314
|
-
name = model.to_s
|
315
|
-
entity_parts = name.split('::')
|
316
|
-
entity_parts.reject! { |p| p == 'Entity' || p == 'Entities' }
|
317
|
-
entity_parts.join('::')
|
318
|
-
end
|
319
|
-
end
|
320
|
-
|
321
|
-
def parse_entity_models(models)
|
322
|
-
result = {}
|
323
|
-
models.each do |model|
|
324
|
-
name = parse_entity_name(model)
|
325
|
-
properties = {}
|
326
|
-
required = []
|
327
|
-
|
328
|
-
model.documentation.each do |property_name, property_info|
|
329
|
-
p = property_info.dup
|
330
|
-
|
331
|
-
required << property_name.to_s if p.delete(:required)
|
332
|
-
|
333
|
-
type = if p[:type]
|
334
|
-
p.delete(:type)
|
335
|
-
else
|
336
|
-
exposure = model.exposures[property_name]
|
337
|
-
parse_entity_name(exposure[:using]) if exposure
|
338
|
-
end
|
339
|
-
|
340
|
-
if p.delete(:is_array)
|
341
|
-
p[:items] = generate_typeref(type)
|
342
|
-
p[:type] = 'array'
|
343
|
-
else
|
344
|
-
p.merge! generate_typeref(type)
|
345
|
-
end
|
346
|
-
|
347
|
-
# rename Grape Entity's "desc" to "description"
|
348
|
-
property_description = p.delete(:desc)
|
349
|
-
p[:description] = property_description if property_description
|
350
|
-
|
351
|
-
# rename Grape's 'values' to 'enum'
|
352
|
-
select_values = p.delete(:values)
|
353
|
-
if select_values
|
354
|
-
select_values = select_values.call if select_values.is_a?(Proc)
|
355
|
-
p[:enum] = select_values
|
356
|
-
end
|
357
|
-
|
358
|
-
properties[property_name] = p
|
359
|
-
end
|
360
|
-
|
361
|
-
result[name] = {
|
362
|
-
id: model.instance_variable_get(:@root) || name,
|
363
|
-
properties: properties
|
364
|
-
}
|
365
|
-
result[name].merge!(required: required) unless required.empty?
|
366
|
-
end
|
367
|
-
|
368
|
-
result
|
369
|
-
end
|
370
|
-
|
371
|
-
def models_with_included_presenters(models)
|
372
|
-
all_models = models
|
373
|
-
|
374
|
-
models.each do |model|
|
375
|
-
# get model references from exposures with a documentation
|
376
|
-
nested_models = model.exposures.map do |_, config|
|
377
|
-
if config.key?(:documentation)
|
378
|
-
model = config[:using]
|
379
|
-
model.respond_to?(:constantize) ? model.constantize : model
|
380
|
-
end
|
381
|
-
end.compact
|
382
|
-
|
383
|
-
# get all nested models recursively
|
384
|
-
additional_models = nested_models.map do |nested_model|
|
385
|
-
models_with_included_presenters([nested_model])
|
386
|
-
end.flatten
|
387
|
-
|
388
|
-
all_models += additional_models
|
389
|
-
end
|
390
|
-
|
391
|
-
all_models
|
392
|
-
end
|
393
|
-
|
394
|
-
def is_primitive?(type)
|
395
|
-
%w(object integer long float double string byte boolean date dateTime).include? type
|
396
|
-
end
|
397
|
-
|
398
|
-
def generate_typeref(type)
|
399
|
-
type = type.to_s.sub(/^[A-Z]/) { |f| f.downcase } if type.is_a?(Class)
|
400
|
-
if is_primitive? type
|
401
|
-
{ 'type' => type }
|
402
|
-
else
|
403
|
-
{ '$ref' => type }
|
404
|
-
end
|
405
|
-
end
|
406
|
-
|
407
|
-
def parse_http_codes(codes, models)
|
408
|
-
codes ||= {}
|
409
|
-
codes.map do |k, v, m|
|
410
|
-
models << m if m
|
411
|
-
http_code_hash = {
|
412
|
-
code: k,
|
413
|
-
message: v
|
414
|
-
}
|
415
|
-
http_code_hash[:responseModel] = parse_entity_name(m) if m
|
416
|
-
http_code_hash
|
417
|
-
end
|
418
|
-
end
|
419
|
-
|
420
|
-
def strip_heredoc(string)
|
421
|
-
indent = string.scan(/^[ \t]*(?=\S)/).min.try(:size) || 0
|
422
|
-
string.gsub(/^[ \t]{#{indent}}/, '')
|
423
|
-
end
|
424
|
-
|
425
|
-
def parse_base_path(base_path, request)
|
426
|
-
if base_path.is_a?(Proc)
|
427
|
-
base_path.call(request)
|
428
|
-
elsif base_path.is_a?(String)
|
429
|
-
URI(base_path).relative? ? URI.join(request.base_url, base_path).to_s : base_path
|
430
|
-
else
|
431
|
-
request.base_url
|
432
|
-
end
|
433
|
-
end
|
434
|
-
|
435
|
-
def hide_documentation_path
|
436
|
-
@@hide_documentation_path
|
437
|
-
end
|
438
|
-
|
439
|
-
def mount_path
|
440
|
-
@@mount_path
|
441
|
-
end
|
442
|
-
|
443
|
-
def setup(options)
|
444
|
-
defaults = {
|
445
|
-
target_class: nil,
|
446
|
-
mount_path: '/swagger_doc',
|
447
|
-
base_path: nil,
|
448
|
-
api_version: '0.1',
|
449
|
-
markdown: nil,
|
450
|
-
hide_documentation_path: false,
|
451
|
-
hide_format: false,
|
452
|
-
format: nil,
|
453
|
-
models: [],
|
454
|
-
info: {},
|
455
|
-
authorizations: nil,
|
456
|
-
root_base_path: true,
|
457
|
-
api_documentation: { desc: 'Swagger compatible API description' },
|
458
|
-
specific_api_documentation: { desc: 'Swagger compatible API description for specific API' }
|
459
|
-
}
|
460
|
-
|
461
|
-
options = defaults.merge(options)
|
462
|
-
|
463
|
-
target_class = options[:target_class]
|
464
|
-
@@mount_path = options[:mount_path]
|
465
|
-
@@class_name = options[:class_name] || options[:mount_path].gsub('/', '')
|
466
|
-
@@markdown = options[:markdown] ? GrapeSwagger::Markdown.new(options[:markdown]) : nil
|
467
|
-
@@hide_format = options[:hide_format]
|
468
|
-
api_version = options[:api_version]
|
469
|
-
authorizations = options[:authorizations]
|
470
|
-
root_base_path = options[:root_base_path]
|
471
|
-
extra_info = options[:info]
|
472
|
-
api_doc = options[:api_documentation].dup
|
473
|
-
specific_api_doc = options[:specific_api_documentation].dup
|
474
|
-
@@models = options[:models] || []
|
475
|
-
|
476
|
-
@@hide_documentation_path = options[:hide_documentation_path]
|
477
|
-
|
478
|
-
if options[:format]
|
479
|
-
[:format, :default_format, :default_error_formatter].each do |method|
|
480
|
-
send(method, options[:format])
|
481
|
-
end
|
482
|
-
end
|
483
|
-
|
484
|
-
@@documentation_class = self
|
485
|
-
|
486
|
-
desc api_doc.delete(:desc), api_doc
|
487
|
-
get @@mount_path do
|
488
|
-
header['Access-Control-Allow-Origin'] = '*'
|
489
|
-
header['Access-Control-Request-Method'] = '*'
|
490
|
-
|
491
|
-
namespaces = target_class.combined_namespaces
|
492
|
-
namespace_routes = target_class.combined_namespace_routes
|
493
|
-
|
494
|
-
if @@hide_documentation_path
|
495
|
-
namespace_routes.reject! { |route, _value| "/#{route}/".index(@@documentation_class.parse_path(@@mount_path, nil) << '/') == 0 }
|
496
|
-
end
|
497
|
-
|
498
|
-
namespace_routes_array = namespace_routes.keys.map do |local_route|
|
499
|
-
next if namespace_routes[local_route].map(&:route_hidden).all? { |value| value.respond_to?(:call) ? value.call : value }
|
500
|
-
|
501
|
-
url_format = '.{format}' unless @@hide_format
|
502
|
-
|
503
|
-
original_namespace_name = target_class.combined_namespace_identifiers.key?(local_route) ? target_class.combined_namespace_identifiers[local_route] : local_route
|
504
|
-
description = namespaces[original_namespace_name] && namespaces[original_namespace_name].options[:desc]
|
505
|
-
description ||= "Operations about #{original_namespace_name.pluralize}"
|
506
|
-
|
507
|
-
{
|
508
|
-
path: "/#{local_route}#{url_format}",
|
509
|
-
description: description
|
510
|
-
}
|
511
|
-
end.compact
|
512
|
-
|
513
|
-
output = {
|
514
|
-
apiVersion: api_version,
|
515
|
-
swaggerVersion: '1.2',
|
516
|
-
produces: @@documentation_class.content_types_for(target_class),
|
517
|
-
apis: namespace_routes_array,
|
518
|
-
info: @@documentation_class.parse_info(extra_info)
|
519
|
-
}
|
520
|
-
|
521
|
-
output[:authorizations] = authorizations unless authorizations.nil? || authorizations.empty?
|
522
|
-
|
523
|
-
output
|
524
|
-
end
|
525
|
-
|
526
|
-
desc specific_api_doc.delete(:desc), { params: {
|
527
|
-
'name' => {
|
528
|
-
desc: 'Resource name of mounted API',
|
529
|
-
type: 'string',
|
530
|
-
required: true
|
531
|
-
}
|
532
|
-
}.merge(specific_api_doc.delete(:params) || {}) }.merge(specific_api_doc)
|
533
|
-
|
534
|
-
get "#{@@mount_path}/:name" do
|
535
|
-
header['Access-Control-Allow-Origin'] = '*'
|
536
|
-
header['Access-Control-Request-Method'] = '*'
|
537
|
-
|
538
|
-
models = []
|
539
|
-
routes = target_class.combined_namespace_routes[params[:name]]
|
540
|
-
error!('Not Found', 404) unless routes
|
541
|
-
|
542
|
-
visible_ops = routes.reject do |route|
|
543
|
-
route.route_hidden.respond_to?(:call) ? route.route_hidden.call : route.route_hidden
|
544
|
-
end
|
545
|
-
|
546
|
-
ops = visible_ops.group_by do |route|
|
547
|
-
@@documentation_class.parse_path(route.route_path, api_version)
|
548
|
-
end
|
549
|
-
|
550
|
-
error!('Not Found', 404) unless ops.any?
|
551
|
-
|
552
|
-
apis = []
|
553
|
-
|
554
|
-
ops.each do |path, op_routes|
|
555
|
-
operations = op_routes.map do |route|
|
556
|
-
notes = @@documentation_class.as_markdown(route.route_notes)
|
557
|
-
|
558
|
-
http_codes = @@documentation_class.parse_http_codes(route.route_http_codes, models)
|
559
|
-
|
560
|
-
models |= @@models if @@models.present?
|
561
|
-
|
562
|
-
models |= Array(route.route_entity) if route.route_entity.present?
|
563
|
-
|
564
|
-
models = @@documentation_class.models_with_included_presenters(models.flatten.compact)
|
565
|
-
|
566
|
-
operation = {
|
567
|
-
notes: notes.to_s,
|
568
|
-
summary: route.route_description || '',
|
569
|
-
nickname: route.route_nickname || (route.route_method + route.route_path.gsub(/[\/:\(\)\.]/, '-')),
|
570
|
-
method: route.route_method,
|
571
|
-
parameters: @@documentation_class.parse_header_params(route.route_headers) + @@documentation_class.parse_params(route.route_params, route.route_path, route.route_method),
|
572
|
-
type: 'void'
|
573
|
-
}
|
574
|
-
operation[:authorizations] = route.route_authorizations unless route.route_authorizations.nil? || route.route_authorizations.empty?
|
575
|
-
if operation[:parameters].any? { | param | param[:type] == 'File' }
|
576
|
-
operation.merge!(consumes: ['multipart/form-data'])
|
577
|
-
end
|
578
|
-
operation.merge!(responseMessages: http_codes) unless http_codes.empty?
|
579
|
-
|
580
|
-
if route.route_entity
|
581
|
-
type = @@documentation_class.parse_entity_name(Array(route.route_entity).first)
|
582
|
-
operation.merge!('type' => type)
|
583
|
-
end
|
584
|
-
|
585
|
-
operation[:nickname] = route.route_nickname if route.route_nickname
|
586
|
-
operation
|
587
|
-
end.compact
|
588
|
-
apis << {
|
589
|
-
path: path,
|
590
|
-
operations: operations
|
591
|
-
}
|
592
|
-
end
|
593
|
-
|
594
|
-
# use custom resource naming if available
|
595
|
-
if target_class.combined_namespace_identifiers.key? params[:name]
|
596
|
-
resource_path = target_class.combined_namespace_identifiers[params[:name]]
|
597
|
-
else
|
598
|
-
resource_path = params[:name]
|
599
|
-
end
|
600
|
-
api_description = {
|
601
|
-
apiVersion: api_version,
|
602
|
-
swaggerVersion: '1.2',
|
603
|
-
resourcePath: "/#{resource_path}",
|
604
|
-
produces: @@documentation_class.content_types_for(target_class),
|
605
|
-
apis: apis
|
606
|
-
}
|
167
|
+
def parse_enum_or_range_values(values)
|
168
|
+
case values
|
169
|
+
when Range
|
170
|
+
parse_range_values(values) if values.first.is_a?(Integer)
|
171
|
+
when Proc
|
172
|
+
values_result = values.call
|
173
|
+
if values_result.is_a?(Range) && values_result.first.is_a?(Integer)
|
174
|
+
parse_range_values(values_result)
|
175
|
+
else
|
176
|
+
{ enum: values_result }
|
177
|
+
end
|
178
|
+
else
|
179
|
+
{ enum: values } if values
|
180
|
+
end
|
181
|
+
end
|
607
182
|
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
api_description[:authorizations] = authorizations if authorizations
|
183
|
+
def parse_range_values(values)
|
184
|
+
{ minimum: values.first, maximum: values.last }
|
185
|
+
end
|
612
186
|
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
end
|
187
|
+
def create_documentation_class
|
188
|
+
Class.new(Grape::API) do
|
189
|
+
extend GrapeSwagger::DocMethods
|
617
190
|
end
|
618
191
|
end
|
619
192
|
end
|