grape-swagger 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +33 -0
- data/.rubocop.yml +36 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +90 -0
- data/CONTRIBUTING.md +126 -0
- data/Gemfile +1 -21
- data/LICENSE.txt +1 -1
- data/README.md +397 -0
- data/RELEASING.md +80 -0
- data/Rakefile +6 -23
- data/UPGRADING.md +47 -0
- data/grape-swagger.gemspec +26 -84
- data/lib/grape-swagger.rb +237 -111
- data/lib/grape-swagger/errors.rb +9 -0
- data/lib/grape-swagger/markdown.rb +23 -0
- data/lib/grape-swagger/markdown/kramdown_adapter.rb +37 -0
- data/lib/grape-swagger/markdown/redcarpet_adapter.rb +89 -0
- data/lib/grape-swagger/version.rb +3 -0
- data/spec/api_description_spec.rb +41 -0
- data/spec/api_global_models_spec.rb +76 -0
- data/spec/api_models_spec.rb +190 -93
- data/spec/default_api_spec.rb +31 -36
- data/spec/form_params_spec.rb +56 -53
- data/spec/grape-swagger_helper_spec.rb +88 -49
- data/spec/grape-swagger_spec.rb +7 -5
- data/spec/hide_api_spec.rb +58 -55
- data/spec/markdown/kramdown_adapter_spec.rb +38 -0
- data/spec/markdown/markdown_spec.rb +27 -0
- data/spec/markdown/redcarpet_adapter_spec.rb +81 -0
- data/spec/namespaced_api_spec.rb +47 -0
- data/spec/non_default_api_spec.rb +372 -222
- data/spec/response_model_spec.rb +80 -0
- data/spec/simple_mounted_api_spec.rb +113 -118
- data/spec/spec_helper.rb +0 -1
- data/spec/version_spec.rb +8 -0
- data/test/api.rb +62 -0
- data/test/config.ru +10 -2
- data/test/splines.png +0 -0
- metadata +145 -91
- data/.rvmrc +0 -48
- data/CHANGELOG.markdown +0 -55
- data/Gemfile.lock +0 -94
- data/README.markdown +0 -168
- data/VERSION +0 -1
- data/test/nested_api.rb +0 -30
data/RELEASING.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# Releasing Grape-Swagger
|
2
|
+
|
3
|
+
There're no particular rules about when to release grape-swagger. Release bug fixes frequenty, features not so frequently and breaking API changes rarely.
|
4
|
+
|
5
|
+
### Release
|
6
|
+
|
7
|
+
Run tests, check that all tests succeed locally.
|
8
|
+
|
9
|
+
```
|
10
|
+
bundle install
|
11
|
+
rake
|
12
|
+
```
|
13
|
+
|
14
|
+
Check that the last build succeeded in [Travis CI](https://travis-ci.org/tim-vandecasteele/grape-swagger) for all supported platforms.
|
15
|
+
|
16
|
+
Increment the version, modify [lib/grape-swagger/version.rb](lib/grape-swagger/version.rb).
|
17
|
+
|
18
|
+
* Increment the third number if the release has bug fixes and/or very minor features, only (eg. change `0.7.1` to `0.7.2`).
|
19
|
+
* Increment the second number if the release contains major features or breaking API changes (eg. change `0.7.1` to `0.8.0`).
|
20
|
+
|
21
|
+
Change "Next Release" in [CHANGELOG.md](CHANGELOG.md) to the new version.
|
22
|
+
|
23
|
+
```
|
24
|
+
### 0.7.2 (February 6, 2014)
|
25
|
+
```
|
26
|
+
|
27
|
+
Remove the line with "Your contribution here.", since there will be no more contributions to this release.
|
28
|
+
|
29
|
+
Commit your changes.
|
30
|
+
|
31
|
+
```
|
32
|
+
git add CHANGELOG.md lib/grape-swagger/version.rb
|
33
|
+
git commit -m "Preparing for release, 0.7.2."
|
34
|
+
git push origin master
|
35
|
+
```
|
36
|
+
|
37
|
+
Release.
|
38
|
+
|
39
|
+
```
|
40
|
+
$ rake release
|
41
|
+
|
42
|
+
grape-swagger 0.7.2 built to pkg/grape-swagger-0.7.2.gem.
|
43
|
+
Tagged v0.7.2.
|
44
|
+
Pushed git commits and tags.
|
45
|
+
Pushed grape-swagger 0.7.2 to rubygems.org.
|
46
|
+
```
|
47
|
+
|
48
|
+
### Prepare for the Next Version
|
49
|
+
|
50
|
+
Add the next release to [CHANGELOG.md](CHANGELOG.md).
|
51
|
+
|
52
|
+
```
|
53
|
+
Next Release
|
54
|
+
============
|
55
|
+
|
56
|
+
* Your contribution here.
|
57
|
+
```
|
58
|
+
|
59
|
+
Comit your changes.
|
60
|
+
|
61
|
+
```
|
62
|
+
git add CHANGELOG.md
|
63
|
+
git commit -m "Preparing for next release."
|
64
|
+
git push origin master
|
65
|
+
```
|
66
|
+
|
67
|
+
### Make an Announcement
|
68
|
+
|
69
|
+
Make an announcement on the [ruby-grape@googlegroups.com](mailto:ruby-grape@googlegroups.com) mailing list. The general format is as follows.
|
70
|
+
|
71
|
+
```
|
72
|
+
Grape-Swagger 0.7.2 has been released.
|
73
|
+
|
74
|
+
There were 8 contributors to this release, not counting documentation.
|
75
|
+
|
76
|
+
Please note the breaking API change in ...
|
77
|
+
|
78
|
+
[copy/paste CHANGELOG here]
|
79
|
+
|
80
|
+
```
|
data/Rakefile
CHANGED
@@ -2,30 +2,10 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'bundler'
|
5
|
-
begin
|
6
|
-
Bundler.setup(:default, :development)
|
7
|
-
rescue Bundler::BundlerError => e
|
8
|
-
$stderr.puts e.message
|
9
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
-
exit e.status_code
|
11
|
-
end
|
12
|
-
require 'rake'
|
13
|
-
|
14
|
-
require 'jeweler'
|
15
|
-
Jeweler::Tasks.new do |gem|
|
16
|
-
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
-
gem.name = "grape-swagger"
|
18
|
-
gem.homepage = "http://github.com/tim-vandecasteele/grape-swagger"
|
19
|
-
gem.license = "MIT"
|
20
|
-
gem.summary = %Q{Add swagger compliant documentation to your grape API}
|
21
|
-
gem.description = %Q{A simple way to add proper auto generated documentation - that can be displayed with swagger - to your inline described grape API}
|
22
|
-
gem.email = "tim.vandecasteele@gmail.com"
|
23
|
-
gem.authors = ["Tim Vandecasteele"]
|
24
|
-
# dependencies defined in Gemfile
|
25
|
-
end
|
26
|
-
Jeweler::RubygemsDotOrgTasks.new
|
27
5
|
|
6
|
+
Bundler.setup(:default, :development)
|
28
7
|
|
8
|
+
require 'rake'
|
29
9
|
|
30
10
|
Bundler::GemHelper.install_tasks
|
31
11
|
|
@@ -34,4 +14,7 @@ require 'rspec/core/rake_task'
|
|
34
14
|
|
35
15
|
RSpec::Core::RakeTask.new(:spec)
|
36
16
|
|
37
|
-
|
17
|
+
require 'rubocop/rake_task'
|
18
|
+
RuboCop::RakeTask.new(:rubocop)
|
19
|
+
|
20
|
+
task default: [:rubocop, :spec]
|
data/UPGRADING.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
Upgrading Grape-swagger
|
2
|
+
=======================
|
3
|
+
|
4
|
+
### Upgrading to >= 0.8.0
|
5
|
+
|
6
|
+
#### Changes in Configuration
|
7
|
+
|
8
|
+
The following options have been added, removed or have been changed in the grape-swagger interface:
|
9
|
+
|
10
|
+
* `markdown: true/false` => `markdown: GrapeSwagger::Markdown::KramdownAdapter`
|
11
|
+
|
12
|
+
#### Markdown
|
13
|
+
|
14
|
+
You can now configure a markdown adapter. This was originally changed because of performance issues with Kramdown and the `markdown` option no longer takes a boolean argument. Built-in adapters include Kramdown and Redcarpet.
|
15
|
+
|
16
|
+
##### Kramdown
|
17
|
+
|
18
|
+
To configure the markdown with Kramdown, add the kramdown gem to your Gemfile:
|
19
|
+
|
20
|
+
`gem 'kramdown'`
|
21
|
+
|
22
|
+
Configure grape-swagger as follows:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
add_swagger_documentation (
|
26
|
+
markdown: GrapeSwagger::Markdown::KramdownAdapter
|
27
|
+
)
|
28
|
+
```
|
29
|
+
|
30
|
+
#### Redcarpet
|
31
|
+
|
32
|
+
To configure markdown with Redcarpet, add the redcarpet and the rouge gem to your Gemfile. Note that Redcarpet does not work with JRuby.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
gem 'redcarpet'
|
36
|
+
gem 'rouge'
|
37
|
+
```
|
38
|
+
|
39
|
+
Configure grape-swagger as follows:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
add_swagger_documentation (
|
43
|
+
markdown: GrapeSwagger::Markdown::RedcarpetAdapter
|
44
|
+
)
|
45
|
+
```
|
46
|
+
|
47
|
+
See [#142](https://github.com/tim-vandecasteele/grape-swagger/pull/142) and documentation section [Markdown in Notes](https://github.com/tim-vandecasteele/grape-swagger#markdown-in-notes) for more information.
|
data/grape-swagger.gemspec
CHANGED
@@ -1,90 +1,32 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
-
# -*- encoding: utf-8 -*-
|
1
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
2
|
+
require 'grape-swagger/version'
|
5
3
|
|
6
4
|
Gem::Specification.new do |s|
|
7
|
-
s.name
|
8
|
-
s.version
|
5
|
+
s.name = 'grape-swagger'
|
6
|
+
s.version = GrapeSwagger::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ['Tim Vandecasteele']
|
9
|
+
s.email = ['tim.vandecasteele@gmail.com']
|
10
|
+
s.homepage = 'https://github.com/tim-vandecasteele/grape-swagger'
|
11
|
+
s.summary = 'A simple way to add auto generated documentation to your Grape API that can be displayed with Swagger.'
|
12
|
+
s.license = 'MIT'
|
9
13
|
|
10
|
-
s.
|
11
|
-
s.
|
12
|
-
s.date = "2014-02-06"
|
13
|
-
s.description = "A simple way to add proper auto generated documentation - that can be displayed with swagger - to your inline described grape API"
|
14
|
-
s.email = "tim.vandecasteele@gmail.com"
|
15
|
-
s.extra_rdoc_files = [
|
16
|
-
"LICENSE.txt",
|
17
|
-
"README.markdown"
|
18
|
-
]
|
19
|
-
s.files = [
|
20
|
-
".document",
|
21
|
-
".rspec",
|
22
|
-
".rvmrc",
|
23
|
-
".travis.yml",
|
24
|
-
"CHANGELOG.markdown",
|
25
|
-
"Gemfile",
|
26
|
-
"Gemfile.lock",
|
27
|
-
"LICENSE.txt",
|
28
|
-
"README.markdown",
|
29
|
-
"Rakefile",
|
30
|
-
"VERSION",
|
31
|
-
"grape-swagger.gemspec",
|
32
|
-
"lib/grape-swagger.rb",
|
33
|
-
"spec/api_models_spec.rb",
|
34
|
-
"spec/default_api_spec.rb",
|
35
|
-
"spec/form_params_spec.rb",
|
36
|
-
"spec/grape-swagger_helper_spec.rb",
|
37
|
-
"spec/grape-swagger_spec.rb",
|
38
|
-
"spec/hide_api_spec.rb",
|
39
|
-
"spec/non_default_api_spec.rb",
|
40
|
-
"spec/simple_mounted_api_spec.rb",
|
41
|
-
"spec/spec_helper.rb",
|
42
|
-
"test/config.ru",
|
43
|
-
"test/nested_api.rb"
|
44
|
-
]
|
45
|
-
s.homepage = "http://github.com/tim-vandecasteele/grape-swagger"
|
46
|
-
s.licenses = ["MIT"]
|
47
|
-
s.require_paths = ["lib"]
|
48
|
-
s.rubygems_version = "1.8.23"
|
49
|
-
s.summary = "Add swagger compliant documentation to your grape API"
|
14
|
+
s.add_runtime_dependency 'grape'
|
15
|
+
s.add_runtime_dependency 'grape-entity'
|
50
16
|
|
51
|
-
|
52
|
-
|
17
|
+
s.add_development_dependency 'rake'
|
18
|
+
s.add_development_dependency 'shoulda'
|
19
|
+
s.add_development_dependency 'rdoc'
|
20
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
21
|
+
s.add_development_dependency 'bundler'
|
22
|
+
s.add_development_dependency 'rack-test'
|
23
|
+
s.add_development_dependency 'rack-cors'
|
24
|
+
s.add_development_dependency 'rubocop', '0.24.1'
|
25
|
+
s.add_development_dependency 'kramdown', '~> 1.4.1'
|
26
|
+
s.add_development_dependency 'redcarpet', '~> 3.1.2' unless RUBY_PLATFORM.eql? 'java'
|
27
|
+
s.add_development_dependency 'rouge', '~> 1.6.1'
|
53
28
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
s.add_runtime_dependency(%q<kramdown>, [">= 1.3.1"])
|
58
|
-
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
59
|
-
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
60
|
-
s.add_development_dependency(%q<bundler>, ["> 1.0.0"])
|
61
|
-
s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
|
62
|
-
s.add_development_dependency(%q<pry>, [">= 0"])
|
63
|
-
s.add_development_dependency(%q<rack-test>, [">= 0"])
|
64
|
-
s.add_development_dependency(%q<rspec>, [">= 0"])
|
65
|
-
else
|
66
|
-
s.add_dependency(%q<grape>, [">= 0.2.0"])
|
67
|
-
s.add_dependency(%q<grape-entity>, [">= 0.3.0"])
|
68
|
-
s.add_dependency(%q<kramdown>, [">= 1.3.1"])
|
69
|
-
s.add_dependency(%q<shoulda>, [">= 0"])
|
70
|
-
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
71
|
-
s.add_dependency(%q<bundler>, ["> 1.0.0"])
|
72
|
-
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
73
|
-
s.add_dependency(%q<pry>, [">= 0"])
|
74
|
-
s.add_dependency(%q<rack-test>, [">= 0"])
|
75
|
-
s.add_dependency(%q<rspec>, [">= 0"])
|
76
|
-
end
|
77
|
-
else
|
78
|
-
s.add_dependency(%q<grape>, [">= 0.2.0"])
|
79
|
-
s.add_dependency(%q<grape-entity>, [">= 0.3.0"])
|
80
|
-
s.add_dependency(%q<kramdown>, [">= 1.3.1"])
|
81
|
-
s.add_dependency(%q<shoulda>, [">= 0"])
|
82
|
-
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
83
|
-
s.add_dependency(%q<bundler>, ["> 1.0.0"])
|
84
|
-
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
85
|
-
s.add_dependency(%q<pry>, [">= 0"])
|
86
|
-
s.add_dependency(%q<rack-test>, [">= 0"])
|
87
|
-
s.add_dependency(%q<rspec>, [">= 0"])
|
88
|
-
end
|
29
|
+
s.files = `git ls-files`.split("\n")
|
30
|
+
s.test_files = `git ls-files -- {test,spec}/*`.split("\n")
|
31
|
+
s.require_paths = ['lib']
|
89
32
|
end
|
90
|
-
|
data/lib/grape-swagger.rb
CHANGED
@@ -1,38 +1,48 @@
|
|
1
|
-
require '
|
1
|
+
require 'grape-swagger/version'
|
2
|
+
require 'grape-swagger/errors'
|
3
|
+
require 'grape-swagger/markdown'
|
4
|
+
require 'grape-swagger/markdown/kramdown_adapter'
|
5
|
+
require 'grape-swagger/markdown/redcarpet_adapter'
|
2
6
|
|
3
7
|
module Grape
|
4
8
|
class API
|
5
9
|
class << self
|
6
|
-
attr_reader :combined_routes
|
10
|
+
attr_reader :combined_routes, :combined_namespaces
|
7
11
|
|
8
|
-
def add_swagger_documentation(options={})
|
12
|
+
def add_swagger_documentation(options = {})
|
9
13
|
documentation_class = create_documentation_class
|
10
14
|
|
11
|
-
documentation_class.setup({:
|
15
|
+
documentation_class.setup({ target_class: self }.merge(options))
|
12
16
|
mount(documentation_class)
|
13
17
|
|
14
18
|
@combined_routes = {}
|
15
|
-
|
16
19
|
routes.each do |route|
|
17
20
|
route_match = route.route_path.split(route.route_prefix).last.match('\/([\w|-]*?)[\.\/\(]')
|
18
21
|
next if route_match.nil?
|
19
22
|
resource = route_match.captures.first
|
20
23
|
next if resource.empty?
|
21
24
|
resource.downcase!
|
22
|
-
|
23
25
|
@combined_routes[resource] ||= []
|
24
|
-
|
25
|
-
|
26
|
-
@combined_routes[resource] << route
|
27
|
-
end
|
26
|
+
next if @@hide_documentation_path && route.route_path.include?(@@mount_path)
|
27
|
+
@combined_routes[resource] << route
|
28
28
|
end
|
29
29
|
|
30
|
+
@combined_namespaces = {}
|
31
|
+
combine_namespaces(self)
|
30
32
|
end
|
31
33
|
|
32
34
|
private
|
33
35
|
|
34
|
-
def
|
36
|
+
def combine_namespaces(app)
|
37
|
+
app.endpoints.each do |endpoint|
|
38
|
+
ns = endpoint.settings.stack.last[:namespace]
|
39
|
+
@combined_namespaces[ns.space] = ns if ns
|
35
40
|
|
41
|
+
combine_namespaces(endpoint.options[:app]) if endpoint.options[:app]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_documentation_class
|
36
46
|
Class.new(Grape::API) do
|
37
47
|
class << self
|
38
48
|
def name
|
@@ -42,19 +52,20 @@ module Grape
|
|
42
52
|
|
43
53
|
def self.setup(options)
|
44
54
|
defaults = {
|
45
|
-
:
|
46
|
-
:
|
47
|
-
:
|
48
|
-
:
|
49
|
-
:
|
50
|
-
:
|
51
|
-
:
|
52
|
-
:
|
53
|
-
:
|
54
|
-
:
|
55
|
-
:
|
56
|
-
:
|
57
|
-
:
|
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' }
|
58
69
|
}
|
59
70
|
|
60
71
|
options = defaults.merge(options)
|
@@ -62,14 +73,16 @@ module Grape
|
|
62
73
|
target_class = options[:target_class]
|
63
74
|
@@mount_path = options[:mount_path]
|
64
75
|
@@class_name = options[:class_name] || options[:mount_path].gsub('/', '')
|
65
|
-
@@markdown = options[:markdown]
|
76
|
+
@@markdown = options[:markdown] ? GrapeSwagger::Markdown.new(options[:markdown]) : nil
|
66
77
|
@@hide_format = options[:hide_format]
|
67
78
|
api_version = options[:api_version]
|
68
79
|
base_path = options[:base_path]
|
69
80
|
authorizations = options[:authorizations]
|
70
|
-
include_base_url = options[:include_base_url]
|
71
81
|
root_base_path = options[:root_base_path]
|
72
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] || []
|
73
86
|
|
74
87
|
@@hide_documentation_path = options[:hide_documentation_path]
|
75
88
|
|
@@ -79,82 +92,109 @@ module Grape
|
|
79
92
|
end
|
80
93
|
end
|
81
94
|
|
82
|
-
desc
|
95
|
+
desc api_doc.delete(:desc), params: api_doc.delete(:params)
|
96
|
+
@last_description.merge!(api_doc)
|
83
97
|
get @@mount_path do
|
84
98
|
header['Access-Control-Allow-Origin'] = '*'
|
85
99
|
header['Access-Control-Request-Method'] = '*'
|
86
100
|
|
87
|
-
routes = target_class
|
101
|
+
routes = target_class.combined_routes
|
102
|
+
namespaces = target_class.combined_namespaces
|
88
103
|
|
89
104
|
if @@hide_documentation_path
|
90
|
-
routes.reject!{ |route,
|
105
|
+
routes.reject! { |route, _value| "/#{route}/".index(parse_path(@@mount_path, nil) << '/') == 0 }
|
91
106
|
end
|
92
107
|
|
93
108
|
routes_array = routes.keys.map do |local_route|
|
94
109
|
next if routes[local_route].all?(&:route_hidden)
|
95
110
|
|
96
|
-
url_base = parse_path(route.route_path.gsub('(.:format)', ''), route.route_version) if include_base_url
|
97
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
|
+
|
98
116
|
{
|
99
|
-
:
|
100
|
-
|
117
|
+
path: "/#{local_route}#{url_format}",
|
118
|
+
description: description
|
101
119
|
}
|
102
120
|
end.compact
|
103
121
|
|
104
122
|
output = {
|
105
123
|
apiVersion: api_version,
|
106
|
-
swaggerVersion:
|
124
|
+
swaggerVersion: '1.2',
|
107
125
|
produces: content_types_for(target_class),
|
108
|
-
operations: [],
|
109
126
|
apis: routes_array,
|
110
127
|
info: parse_info(extra_info)
|
111
128
|
}
|
112
129
|
|
113
|
-
|
114
|
-
output[:basePath] = basePath if basePath && basePath.size > 0 && root_base_path != false
|
115
|
-
output[:authorizations] = authorizations if authorizations
|
130
|
+
output[:authorizations] = authorizations unless authorizations.nil? || authorizations.empty?
|
116
131
|
|
117
132
|
output
|
118
133
|
end
|
119
134
|
|
120
|
-
desc
|
121
|
-
|
122
|
-
:
|
123
|
-
:
|
124
|
-
:
|
135
|
+
desc specific_api_doc.delete(:desc), params: {
|
136
|
+
'name' => {
|
137
|
+
desc: 'Resource name of mounted API',
|
138
|
+
type: 'string',
|
139
|
+
required: true
|
125
140
|
}
|
126
|
-
}
|
141
|
+
}.merge(specific_api_doc.delete(:params) || {})
|
142
|
+
@last_description.merge!(specific_api_doc)
|
127
143
|
get "#{@@mount_path}/:name" do
|
128
144
|
header['Access-Control-Allow-Origin'] = '*'
|
129
145
|
header['Access-Control-Request-Method'] = '*'
|
130
146
|
|
131
147
|
models = []
|
132
|
-
routes = target_class
|
148
|
+
routes = target_class.combined_routes[params[:name]]
|
149
|
+
error!('Not Found', 404) unless routes
|
133
150
|
|
134
151
|
ops = routes.reject(&:route_hidden).group_by do |route|
|
135
152
|
parse_path(route.route_path, api_version)
|
136
153
|
end
|
137
154
|
|
155
|
+
error!('Not Found', 404) unless ops.any?
|
156
|
+
|
138
157
|
apis = []
|
139
158
|
|
140
|
-
ops.each do |path,
|
141
|
-
operations =
|
159
|
+
ops.each do |path, op_routes|
|
160
|
+
operations = op_routes.map do |route|
|
142
161
|
notes = as_markdown(route.route_notes)
|
143
|
-
http_codes = parse_http_codes(route.route_http_codes)
|
144
162
|
|
145
|
-
|
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)
|
146
170
|
|
147
171
|
operation = {
|
148
|
-
:
|
149
|
-
:
|
150
|
-
:
|
151
|
-
:
|
152
|
-
:
|
153
|
-
:
|
154
|
-
parse_params(route.route_params, route.route_path, route.route_method)
|
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'
|
155
178
|
}
|
156
|
-
operation
|
157
|
-
operation.
|
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
|
158
198
|
operation
|
159
199
|
end.compact
|
160
200
|
apis << {
|
@@ -165,14 +205,16 @@ module Grape
|
|
165
205
|
|
166
206
|
api_description = {
|
167
207
|
apiVersion: api_version,
|
168
|
-
swaggerVersion:
|
169
|
-
resourcePath: "",
|
208
|
+
swaggerVersion: '1.2',
|
209
|
+
resourcePath: "/#{params[:name]}",
|
210
|
+
produces: content_types_for(target_class),
|
170
211
|
apis: apis
|
171
212
|
}
|
172
213
|
|
173
|
-
|
174
|
-
api_description[:basePath]
|
175
|
-
api_description[:models]
|
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
|
176
218
|
|
177
219
|
api_description
|
178
220
|
end
|
@@ -181,37 +223,70 @@ module Grape
|
|
181
223
|
helpers do
|
182
224
|
|
183
225
|
def as_markdown(description)
|
184
|
-
description && @@markdown ?
|
226
|
+
description && @@markdown ? @@markdown.as_markdown(strip_heredoc(description)) : description
|
185
227
|
end
|
186
228
|
|
187
229
|
def parse_params(params, path, method)
|
188
230
|
params ||= []
|
189
|
-
|
190
231
|
params.map do |param, value|
|
191
|
-
value[:type] = '
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
232
|
+
value[:type] = 'File' if value.is_a?(Hash) && ['Rack::Multipart::UploadedFile', 'Hash'].include?(value[:type])
|
233
|
+
items = {}
|
234
|
+
|
235
|
+
raw_data_type = value.is_a?(Hash) ? (value[:type] || 'string').to_s : 'string'
|
236
|
+
data_type = case raw_data_type
|
237
|
+
when 'Boolean', 'Date', 'Integer', 'String'
|
238
|
+
raw_data_type.downcase
|
239
|
+
when 'BigDecimal'
|
240
|
+
'long'
|
241
|
+
when 'DateTime'
|
242
|
+
'dateTime'
|
243
|
+
when 'Numeric'
|
244
|
+
'double'
|
245
|
+
else
|
246
|
+
parse_entity_name(raw_data_type)
|
247
|
+
end
|
248
|
+
description = value.is_a?(Hash) ? value[:desc] || value[:description] : ''
|
249
|
+
required = value.is_a?(Hash) ? !!value[:required] : false
|
250
|
+
default_value = value.is_a?(Hash) ? value[:default] : nil
|
251
|
+
is_array = value.is_a?(Hash) ? (value[:is_array] || false) : false
|
252
|
+
enum_values = value.is_a?(Hash) ? value[:values] : nil
|
253
|
+
enum_values = enum_values.call if enum_values && enum_values.is_a?(Proc)
|
254
|
+
|
255
|
+
if value.is_a?(Hash) && value.key?(:param_type)
|
256
|
+
param_type = value[:param_type]
|
257
|
+
if is_array
|
258
|
+
items = { '$ref' => data_type }
|
259
|
+
data_type = 'array'
|
260
|
+
end
|
199
261
|
else
|
200
|
-
|
262
|
+
param_type = case
|
263
|
+
when path.include?(":#{param}")
|
264
|
+
'path'
|
265
|
+
when %w(POST PUT PATCH).include?(method)
|
266
|
+
if is_primitive?(data_type)
|
267
|
+
'form'
|
268
|
+
else
|
269
|
+
'body'
|
270
|
+
end
|
271
|
+
else
|
272
|
+
'query'
|
273
|
+
end
|
201
274
|
end
|
202
|
-
name
|
275
|
+
name = (value.is_a?(Hash) && value[:full_name]) || param
|
203
276
|
|
204
277
|
parsed_params = {
|
205
|
-
paramType:
|
206
|
-
name:
|
207
|
-
description:
|
208
|
-
type:
|
209
|
-
|
210
|
-
|
278
|
+
paramType: param_type,
|
279
|
+
name: name,
|
280
|
+
description: as_markdown(description),
|
281
|
+
type: data_type,
|
282
|
+
required: required,
|
283
|
+
allowMultiple: is_array
|
211
284
|
}
|
212
|
-
|
213
|
-
parsed_params.merge!(
|
214
|
-
|
285
|
+
parsed_params.merge!(format: 'int32') if data_type == 'integer'
|
286
|
+
parsed_params.merge!(format: 'int64') if data_type == 'long'
|
287
|
+
parsed_params.merge!(items: items) if items.present?
|
288
|
+
parsed_params.merge!(defaultValue: default_value) if default_value
|
289
|
+
parsed_params.merge!(enum: enum_values) if enum_values
|
215
290
|
parsed_params
|
216
291
|
end
|
217
292
|
end
|
@@ -222,7 +297,7 @@ module Grape
|
|
222
297
|
if content_types.empty?
|
223
298
|
formats = [target_class.settings[:format], target_class.settings[:default_format]].compact.uniq
|
224
299
|
formats = Grape::Formatter::Base.formatters({}).keys if formats.empty?
|
225
|
-
content_types = Grape::ContentTypes::CONTENT_TYPES.select{|content_type,
|
300
|
+
content_types = Grape::ContentTypes::CONTENT_TYPES.select { |content_type, _mime_type| formats.include? content_type }.values
|
226
301
|
end
|
227
302
|
|
228
303
|
content_types.uniq
|
@@ -231,34 +306,33 @@ module Grape
|
|
231
306
|
def parse_info(info)
|
232
307
|
{
|
233
308
|
contact: info[:contact],
|
234
|
-
description: info[:description],
|
309
|
+
description: as_markdown(info[:description]),
|
235
310
|
license: info[:license],
|
236
311
|
licenseUrl: info[:license_url],
|
237
312
|
termsOfServiceUrl: info[:terms_of_service_url],
|
238
313
|
title: info[:title]
|
239
|
-
}.delete_if{|_, value| value.blank?}
|
314
|
+
}.delete_if { |_, value| value.blank? }
|
240
315
|
end
|
241
316
|
|
242
317
|
def parse_header_params(params)
|
243
318
|
params ||= []
|
244
319
|
|
245
320
|
params.map do |param, value|
|
246
|
-
|
247
|
-
description
|
248
|
-
required
|
249
|
-
|
250
|
-
|
321
|
+
data_type = 'String'
|
322
|
+
description = value.is_a?(Hash) ? value[:description] : ''
|
323
|
+
required = value.is_a?(Hash) ? !!value[:required] : false
|
324
|
+
default_value = value.is_a?(Hash) ? value[:default] : nil
|
325
|
+
param_type = 'header'
|
251
326
|
|
252
327
|
parsed_params = {
|
253
|
-
paramType:
|
328
|
+
paramType: param_type,
|
254
329
|
name: param,
|
255
330
|
description: as_markdown(description),
|
256
|
-
type:
|
257
|
-
dataType: dataType,
|
331
|
+
type: data_type,
|
258
332
|
required: required
|
259
333
|
}
|
260
334
|
|
261
|
-
parsed_params.merge!(
|
335
|
+
parsed_params.merge!(defaultValue: default_value) if default_value
|
262
336
|
|
263
337
|
parsed_params
|
264
338
|
end
|
@@ -277,46 +351,98 @@ module Grape
|
|
277
351
|
version ? parsed_path.gsub('{version}', version) : parsed_path
|
278
352
|
end
|
279
353
|
|
280
|
-
def parse_entity_name(
|
281
|
-
|
282
|
-
|
283
|
-
|
354
|
+
def parse_entity_name(model)
|
355
|
+
if model.respond_to?(:entity_name)
|
356
|
+
model.entity_name
|
357
|
+
else
|
358
|
+
name = model.to_s
|
359
|
+
entity_parts = name.split('::')
|
360
|
+
entity_parts.reject! { |p| p == 'Entity' || p == 'Entities' }
|
361
|
+
entity_parts.join('::')
|
362
|
+
end
|
284
363
|
end
|
285
364
|
|
286
365
|
def parse_entity_models(models)
|
287
366
|
result = {}
|
288
|
-
|
289
367
|
models.each do |model|
|
290
|
-
name
|
291
|
-
properties
|
368
|
+
name = parse_entity_name(model)
|
369
|
+
properties = {}
|
370
|
+
required = []
|
292
371
|
|
293
372
|
model.documentation.each do |property_name, property_info|
|
294
|
-
|
373
|
+
p = property_info.dup
|
374
|
+
|
375
|
+
required << property_name.to_s if p.delete(:required)
|
376
|
+
|
377
|
+
if p.delete(:is_array)
|
378
|
+
p[:items] = generate_typeref(p[:type])
|
379
|
+
p[:type] = 'array'
|
380
|
+
else
|
381
|
+
p.merge! generate_typeref(p.delete(:type))
|
382
|
+
end
|
295
383
|
|
296
384
|
# rename Grape Entity's "desc" to "description"
|
297
|
-
|
298
|
-
|
385
|
+
property_description = p.delete(:desc)
|
386
|
+
p[:description] = property_description if property_description
|
387
|
+
|
388
|
+
# rename Grape's 'values' to 'enum'
|
389
|
+
select_values = p.delete(:values)
|
390
|
+
if select_values
|
391
|
+
select_values = select_values.call if select_values.is_a?(Proc)
|
392
|
+
p[:enum] = select_values
|
299
393
|
end
|
394
|
+
|
395
|
+
properties[property_name] = p
|
396
|
+
|
300
397
|
end
|
301
398
|
|
302
399
|
result[name] = {
|
303
400
|
id: model.instance_variable_get(:@root) || name,
|
304
|
-
name: model.instance_variable_get(:@root) || name,
|
305
401
|
properties: properties
|
306
402
|
}
|
403
|
+
result[name].merge!(required: required) unless required.empty?
|
307
404
|
end
|
308
405
|
|
309
406
|
result
|
310
407
|
end
|
311
408
|
|
312
|
-
def
|
409
|
+
def models_with_included_presenters(models)
|
410
|
+
all_models = models
|
411
|
+
|
412
|
+
models.each do |model|
|
413
|
+
# get model references from exposures with a documentation
|
414
|
+
additional_models = model.exposures.map do |_, config|
|
415
|
+
config[:using] if config.key?(:documentation)
|
416
|
+
end.compact
|
417
|
+
|
418
|
+
all_models += additional_models
|
419
|
+
end
|
420
|
+
|
421
|
+
all_models
|
422
|
+
end
|
423
|
+
|
424
|
+
def is_primitive?(type)
|
425
|
+
%w(integer long float double string byte boolean date dateTime).include? type
|
426
|
+
end
|
427
|
+
|
428
|
+
def generate_typeref(type)
|
429
|
+
if is_primitive? type
|
430
|
+
{ 'type' => type }
|
431
|
+
else
|
432
|
+
{ '$ref' => type }
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
def parse_http_codes(codes, models)
|
313
437
|
codes ||= {}
|
314
|
-
codes.map do |k, v|
|
315
|
-
|
438
|
+
codes.map do |k, v, m|
|
439
|
+
models << m if m
|
440
|
+
http_code_hash = {
|
316
441
|
code: k,
|
317
|
-
message: v
|
318
|
-
#responseModel: ...
|
442
|
+
message: v
|
319
443
|
}
|
444
|
+
http_code_hash[:responseModel] = parse_entity_name(m) if m
|
445
|
+
http_code_hash
|
320
446
|
end
|
321
447
|
end
|
322
448
|
|