grape-swagger 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|
-

|
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
|