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 +4 -4
- data/.rubocop.yml +2 -33
- data/.rubocop_todo.yml +55 -0
- data/.travis.yml +12 -4
- data/CHANGELOG.md +14 -0
- data/Gemfile +8 -1
- data/README.md +10 -8
- data/{test → example}/api.rb +4 -0
- data/{test → example}/config.ru +0 -0
- data/{test → example}/splines.png +0 -0
- data/grape-swagger.gemspec +2 -2
- data/lib/grape-swagger.rb +211 -193
- data/lib/grape-swagger/version.rb +1 -1
- data/spec/api_global_models_spec.rb +10 -7
- data/spec/api_models_spec.rb +45 -17
- data/spec/api_paths_spec.rb +67 -0
- data/spec/api_root_spec.rb +31 -0
- data/spec/boolean_params_spec.rb +30 -0
- data/spec/form_params_spec.rb +7 -43
- data/spec/grape-swagger_helper_spec.rb +1 -23
- data/spec/group_params_spec.rb +31 -0
- data/spec/hash_params_spec.rb +30 -0
- data/spec/hide_api_spec.rb +3 -3
- data/spec/non_default_api_spec.rb +25 -22
- data/spec/response_model_spec.rb +42 -11
- data/spec/simple_mounted_api_spec.rb +3 -3
- data/spec/spec_helper.rb +1 -0
- metadata +20 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9e2d67cf94749b685aff4146aa6f831acd41539
|
4
|
+
data.tar.gz: 272fa686ecf0e978a0f07c41a42486a06ecfd56c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 612b8bff2f52780da77e09b63a929cb035d3cc6f8bcda1a75b426c7267c67e60f86d30eefa149e05f6ec27571317cef910daf53026b7d3394d149ccafb96ce07
|
7
|
+
data.tar.gz: 22b1bd381a910aedb712fa1efc53b36ded5965ae743dae44527f49208cc23243d8d97f3ff76694d984f06e2ede024d5d2b01feda3356c436a5890a44b3e06930
|
data/.rubocop.yml
CHANGED
@@ -1,36 +1,5 @@
|
|
1
1
|
AllCops:
|
2
|
-
|
2
|
+
Exclude:
|
3
3
|
- vendor/**/*
|
4
4
|
|
5
|
-
|
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
|
data/.rubocop_todo.yml
ADDED
@@ -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
|
data/.travis.yml
CHANGED
@@ -1,8 +1,16 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
sudo: false
|
4
|
+
|
1
5
|
rvm:
|
2
|
-
-
|
3
|
-
- 1.9.3
|
6
|
+
- 2.1.1
|
4
7
|
- 2.0.0
|
5
|
-
-
|
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
|
data/CHANGELOG.md
CHANGED
@@ -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
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](
|
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 [
|
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
|
318
|
+
desc "Reserve a burger in heaven", {
|
317
319
|
notes: <<-NOTE
|
318
|
-
|
320
|
+
Veggie Burgers in Heaven
|
319
321
|
-----------------
|
320
322
|
|
321
|
-
> A
|
323
|
+
> A veggie burger doesn't come for free
|
322
324
|
|
323
|
-
If you want to reserve a
|
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
|
data/{test → example}/api.rb
RENAMED
@@ -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] }
|
data/{test → example}/config.ru
RENAMED
File without changes
|
File without changes
|
data/grape-swagger.gemspec
CHANGED
@@ -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
|
+
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'
|
data/lib/grape-swagger.rb
CHANGED
@@ -17,25 +17,33 @@ module Grape
|
|
17
17
|
|
18
18
|
@combined_routes = {}
|
19
19
|
routes.each do |route|
|
20
|
-
|
21
|
-
|
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
|
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.
|
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
|
-
|
232
|
-
|
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.
|
141
|
+
content_types = (target_class.content_types || {}).values
|
296
142
|
|
297
143
|
if content_types.empty?
|
298
|
-
formats = [target_class.
|
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
|
-
|
415
|
-
|
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
|