grape-swagger 0.10.1 → 0.10.2
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 +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
|