grape-swagger 0.7.2 → 0.8.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.
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