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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +33 -0
  3. data/.rubocop.yml +36 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +3 -0
  7. data/CHANGELOG.md +90 -0
  8. data/CONTRIBUTING.md +126 -0
  9. data/Gemfile +1 -21
  10. data/LICENSE.txt +1 -1
  11. data/README.md +397 -0
  12. data/RELEASING.md +80 -0
  13. data/Rakefile +6 -23
  14. data/UPGRADING.md +47 -0
  15. data/grape-swagger.gemspec +26 -84
  16. data/lib/grape-swagger.rb +237 -111
  17. data/lib/grape-swagger/errors.rb +9 -0
  18. data/lib/grape-swagger/markdown.rb +23 -0
  19. data/lib/grape-swagger/markdown/kramdown_adapter.rb +37 -0
  20. data/lib/grape-swagger/markdown/redcarpet_adapter.rb +89 -0
  21. data/lib/grape-swagger/version.rb +3 -0
  22. data/spec/api_description_spec.rb +41 -0
  23. data/spec/api_global_models_spec.rb +76 -0
  24. data/spec/api_models_spec.rb +190 -93
  25. data/spec/default_api_spec.rb +31 -36
  26. data/spec/form_params_spec.rb +56 -53
  27. data/spec/grape-swagger_helper_spec.rb +88 -49
  28. data/spec/grape-swagger_spec.rb +7 -5
  29. data/spec/hide_api_spec.rb +58 -55
  30. data/spec/markdown/kramdown_adapter_spec.rb +38 -0
  31. data/spec/markdown/markdown_spec.rb +27 -0
  32. data/spec/markdown/redcarpet_adapter_spec.rb +81 -0
  33. data/spec/namespaced_api_spec.rb +47 -0
  34. data/spec/non_default_api_spec.rb +372 -222
  35. data/spec/response_model_spec.rb +80 -0
  36. data/spec/simple_mounted_api_spec.rb +113 -118
  37. data/spec/spec_helper.rb +0 -1
  38. data/spec/version_spec.rb +8 -0
  39. data/test/api.rb +62 -0
  40. data/test/config.ru +10 -2
  41. data/test/splines.png +0 -0
  42. metadata +145 -91
  43. data/.rvmrc +0 -48
  44. data/CHANGELOG.markdown +0 -55
  45. data/Gemfile.lock +0 -94
  46. data/README.markdown +0 -168
  47. data/VERSION +0 -1
  48. 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
- task :default => :spec
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.
@@ -1,90 +1,32 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
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 = "grape-swagger"
8
- s.version = "0.7.2"
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.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Tim Vandecasteele"]
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
- if s.respond_to? :specification_version then
52
- s.specification_version = 3
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
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
55
- s.add_runtime_dependency(%q<grape>, [">= 0.2.0"])
56
- s.add_runtime_dependency(%q<grape-entity>, [">= 0.3.0"])
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 'kramdown'
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({:target_class => self}.merge(options))
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
- unless @@hide_documentation_path and route.route_path.include?(@@mount_path)
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 create_documentation_class
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
- :target_class => nil,
46
- :mount_path => '/swagger_doc',
47
- :base_path => nil,
48
- :api_version => '0.1',
49
- :markdown => false,
50
- :hide_documentation_path => false,
51
- :hide_format => false,
52
- :format => nil,
53
- :models => [],
54
- :info => {},
55
- :authorizations => nil,
56
- :root_base_path => true,
57
- :include_base_url => true
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 'Swagger compatible API description'
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::combined_routes
101
+ routes = target_class.combined_routes
102
+ namespaces = target_class.combined_namespaces
88
103
 
89
104
  if @@hide_documentation_path
90
- routes.reject!{ |route, value| "/#{route}/".index(parse_path(@@mount_path, nil) << '/') == 0 }
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
- :path => "#{url_base}/#{local_route}#{url_format}",
100
- #:description => "..."
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: "1.2",
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
- basePath = parse_base_path(base_path, request)
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 'Swagger compatible API description for specific API', :params => {
121
- "name" => {
122
- :desc => "Resource name of mounted API",
123
- :type => "string",
124
- :required => true
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::combined_routes[params[:name]]
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, routes|
141
- operations = routes.map do |route|
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
- models << route.route_entity if route.route_entity
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
- :produces => content_types_for(target_class),
149
- :notes => notes.to_s,
150
- :summary => route.route_description || '',
151
- :nickname => route.route_nickname || (route.route_method + route.route_path.gsub(/[\/:\(\)\.]/,'-')),
152
- :httpMethod => route.route_method,
153
- :parameters => parse_header_params(route.route_headers) +
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.merge!(:type => parse_entity_name(route.route_entity)) if route.route_entity
157
- operation.merge!(:responseMessages => http_codes) unless http_codes.empty?
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: "1.2",
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
- basePath = parse_base_path(base_path, request)
174
- api_description[:basePath] = basePath if basePath && basePath.size > 0
175
- api_description[:models] = parse_entity_models(models) unless models.empty?
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 ? Kramdown::Document.new(strip_heredoc(description), :input => 'GFM', :enable_coderay => false).to_html : description
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] = 'file' if value.is_a?(Hash) && value[:type] == 'Rack::Multipart::UploadedFile'
192
-
193
- dataType = value.is_a?(Hash) ? (value[:type] || 'String').to_s : 'String'
194
- description = value.is_a?(Hash) ? value[:desc] || value[:description] : ''
195
- required = value.is_a?(Hash) ? !!value[:required] : false
196
- defaultValue = value.is_a?(Hash) ? value[:defaultValue] : nil
197
- paramType = if path.include?(":#{param}")
198
- 'path'
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
- %w[ POST PUT PATCH ].include?(method) ? 'form' : 'query'
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 = (value.is_a?(Hash) && value[:full_name]) || param
275
+ name = (value.is_a?(Hash) && value[:full_name]) || param
203
276
 
204
277
  parsed_params = {
205
- paramType: paramType,
206
- name: name,
207
- description: as_markdown(description),
208
- type: dataType,
209
- dataType: dataType,
210
- required: required
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!({defaultValue: defaultValue}) if defaultValue
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, mime_type| formats.include? content_type}.values
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
- dataType = 'String'
247
- description = value.is_a?(Hash) ? value[:description] : ''
248
- required = value.is_a?(Hash) ? !!value[:required] : false
249
- defaultValue = value.is_a?(Hash) ? value[:defaultValue] : nil
250
- paramType = "header"
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: paramType,
328
+ paramType: param_type,
254
329
  name: param,
255
330
  description: as_markdown(description),
256
- type: dataType,
257
- dataType: dataType,
331
+ type: data_type,
258
332
  required: required
259
333
  }
260
334
 
261
- parsed_params.merge!({defaultValue: defaultValue}) if defaultValue
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(name)
281
- entity_parts = name.to_s.split('::')
282
- entity_parts.reject! {|p| p == "Entity" || p == "Entities"}
283
- entity_parts.join("::")
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 = parse_entity_name(model)
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
- properties[property_name] = property_info
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
- if property_description = property_info.delete(:desc)
298
- property_info[:description] = property_description
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 parse_http_codes codes
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