grape-swagger 1.0.0 → 1.3.1

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +94 -1
  3. data/.rubocop_todo.yml +1 -1
  4. data/.travis.yml +14 -8
  5. data/CHANGELOG.md +56 -6
  6. data/Gemfile +9 -4
  7. data/README.md +191 -12
  8. data/UPGRADING.md +34 -0
  9. data/grape-swagger.gemspec +1 -1
  10. data/lib/grape-swagger.rb +7 -4
  11. data/lib/grape-swagger/doc_methods.rb +65 -62
  12. data/lib/grape-swagger/doc_methods/build_model_definition.rb +53 -2
  13. data/lib/grape-swagger/doc_methods/data_type.rb +5 -5
  14. data/lib/grape-swagger/doc_methods/format_data.rb +2 -2
  15. data/lib/grape-swagger/doc_methods/operation_id.rb +2 -2
  16. data/lib/grape-swagger/doc_methods/parse_params.rb +19 -4
  17. data/lib/grape-swagger/endpoint.rb +73 -31
  18. data/lib/grape-swagger/rake/oapi_tasks.rb +12 -2
  19. data/lib/grape-swagger/version.rb +1 -1
  20. data/spec/issues/427_entity_as_string_spec.rb +1 -1
  21. data/spec/issues/430_entity_definitions_spec.rb +7 -5
  22. data/spec/issues/537_enum_values_spec.rb +1 -0
  23. data/spec/issues/776_multiple_presents_spec.rb +56 -0
  24. data/spec/issues/784_extensions_on_params_spec.rb +38 -0
  25. data/spec/issues/809_utf8_routes_spec.rb +55 -0
  26. data/spec/lib/data_type_spec.rb +12 -0
  27. data/spec/lib/move_params_spec.rb +2 -2
  28. data/spec/lib/oapi_tasks_spec.rb +15 -5
  29. data/spec/support/empty_model_parser.rb +1 -2
  30. data/spec/support/mock_parser.rb +1 -2
  31. data/spec/support/model_parsers/entity_parser.rb +8 -8
  32. data/spec/support/model_parsers/mock_parser.rb +8 -8
  33. data/spec/support/model_parsers/representable_parser.rb +8 -8
  34. data/spec/support/namespace_tags.rb +1 -0
  35. data/spec/swagger_v2/api_swagger_v2_hide_param_spec.rb +1 -1
  36. data/spec/swagger_v2/api_swagger_v2_mounted_spec.rb +1 -0
  37. data/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb +2 -2
  38. data/spec/swagger_v2/api_swagger_v2_response_with_models_spec.rb +53 -0
  39. data/spec/swagger_v2/api_swagger_v2_spec.rb +1 -0
  40. data/spec/swagger_v2/boolean_params_spec.rb +1 -0
  41. data/spec/swagger_v2/float_api_spec.rb +1 -0
  42. data/spec/swagger_v2/inheritance_and_discriminator_spec.rb +56 -0
  43. data/spec/swagger_v2/namespace_tags_prefix_spec.rb +1 -0
  44. data/spec/swagger_v2/param_multi_type_spec.rb +2 -0
  45. data/spec/swagger_v2/param_type_spec.rb +3 -0
  46. data/spec/swagger_v2/param_values_spec.rb +6 -0
  47. data/spec/swagger_v2/reference_entity_spec.rb +74 -29
  48. data/spec/swagger_v2/security_requirement_spec.rb +2 -2
  49. data/spec/swagger_v2/simple_mounted_api_spec.rb +1 -0
  50. metadata +19 -9
@@ -1,5 +1,39 @@
1
1
  ## Upgrading Grape-swagger
2
2
 
3
+ ### Upgrading to >= 1.3.0
4
+
5
+ - The model (entity) description no longer comes from the route description. It will have a default value: `<<EntityName>> model`.
6
+
7
+ ### Upgrading to >= 1.2.0
8
+
9
+ - The entity_name class method is now called on parent classes for inherited entities. Now you can do this
10
+
11
+ ```ruby
12
+ module Some::Long::Module
13
+ class Base < Grape::Entity
14
+ # ... other shared logic
15
+ def self.entity_name
16
+ "V2::#{self.to_s.demodulize}"
17
+ end
18
+ end
19
+
20
+ def MyEntity < Base
21
+ # ....
22
+ end
23
+
24
+ def OtherEntity < Base
25
+ # revert back to the default behavior by hiding the method
26
+ private_class_method :entity_name
27
+ end
28
+ end
29
+ ```
30
+
31
+ - Full class name is modified to use `_` separator (e.g. `A_B_C` instead of `A::B::C`).
32
+
33
+ ### Upgrading to >= 1.1.0
34
+
35
+ Full class name is used for referencing entity by default (e.g. `A::B::C` instead of just `C`). `Entity` and `Entities` suffixes and prefixes are omitted (e.g. if entity name is `Entities::SomeScope::MyFavourite::Entity` only `SomeScope::MyFavourite` will be used).
36
+
3
37
  ### Upgrading to >= 0.26.1
4
38
 
5
39
  The format can now be specified,
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
  s.license = 'MIT'
15
15
 
16
16
  s.required_ruby_version = '>= 2.4'
17
- s.add_runtime_dependency 'grape', '~> 1.3.0'
17
+ s.add_runtime_dependency 'grape', '~> 1.3'
18
18
 
19
19
  s.files = `git ls-files`.split("\n")
20
20
  s.test_files = `git ls-files -- {test,spec}/*`.split("\n")
@@ -29,7 +29,10 @@ module SwaggerRouting
29
29
  route_match = route_path.split(/^.*?#{route.prefix}/).last
30
30
  next unless route_match
31
31
 
32
- route_match = route_match.match('\/([\w|-]*?)[\.\/\(]') || route_match.match('\/([\w|-]*)$')
32
+ # want to match emojis … ;)
33
+ # route_match = route_match
34
+ # .match('\/([\p{Alnum}|\p{Emoji}|\-|\_]*?)[\.\/\(]') || route_match.match('\/([\p{Alpha}|\p{Emoji}|\-|\_]*)$')
35
+ route_match = route_match.match('\/([\p{Alnum}|\-|\_]*?)[\.\/\(]') || route_match.match('\/([\p{Alpha}|\-|\_]*)$')
33
36
  next unless route_match
34
37
 
35
38
  resource = route_match.captures.first
@@ -85,7 +88,7 @@ module SwaggerRouting
85
88
  route_name = name.match(%r{^/?([^/]*).*$})[1]
86
89
  return route_name unless route_name.include? ':'
87
90
 
88
- matches = name.match(/\/[a-z]+/)
91
+ matches = name.match(/\/\p{Alpha}+/)
89
92
  matches.nil? ? route_name : matches[0].delete('/')
90
93
  end
91
94
 
@@ -107,8 +110,8 @@ module SwaggerRouting
107
110
  end
108
111
 
109
112
  module SwaggerDocumentationAdder
110
- attr_accessor :combined_namespaces, :combined_namespace_identifiers
111
- attr_accessor :combined_routes, :combined_namespace_routes
113
+ attr_accessor :combined_namespaces, :combined_namespace_identifiers, :combined_routes, :combined_namespace_routes
114
+
112
115
  include SwaggerRouting
113
116
 
114
117
  def add_swagger_documentation(options = {})
@@ -17,6 +17,61 @@ require 'grape-swagger/doc_methods/version'
17
17
 
18
18
  module GrapeSwagger
19
19
  module DocMethods
20
+ DEFAULTS =
21
+ {
22
+ info: {},
23
+ models: [],
24
+ doc_version: '0.0.1',
25
+ target_class: nil,
26
+ mount_path: '/swagger_doc',
27
+ host: nil,
28
+ base_path: nil,
29
+ add_base_path: false,
30
+ add_version: true,
31
+ add_root: false,
32
+ hide_documentation_path: true,
33
+ format: :json,
34
+ authorizations: nil,
35
+ security_definitions: nil,
36
+ security: nil,
37
+ api_documentation: { desc: 'Swagger compatible API description' },
38
+ specific_api_documentation: { desc: 'Swagger compatible API description for specific API' },
39
+ endpoint_auth_wrapper: nil,
40
+ swagger_endpoint_guard: nil,
41
+ token_owner: nil
42
+ }.freeze
43
+
44
+ FORMATTER_METHOD = %i[format default_format default_error_formatter].freeze
45
+
46
+ def self.output_path_definitions(combi_routes, endpoint, target_class, options)
47
+ output = endpoint.swagger_object(
48
+ target_class,
49
+ endpoint.request,
50
+ options
51
+ )
52
+
53
+ paths, definitions = endpoint.path_and_definition_objects(combi_routes, options)
54
+ tags = tags_from(paths, options)
55
+
56
+ output[:tags] = tags unless tags.empty? || paths.blank?
57
+ output[:paths] = paths unless paths.blank?
58
+ output[:definitions] = definitions unless definitions.blank?
59
+
60
+ output
61
+ end
62
+
63
+ def self.tags_from(paths, options)
64
+ tags = GrapeSwagger::DocMethods::TagNameDescription.build(paths)
65
+
66
+ if options[:tags]
67
+ names = options[:tags].map { |t| t[:name] }
68
+ tags.reject! { |t| names.include?(t[:name]) }
69
+ tags += options[:tags]
70
+ end
71
+
72
+ tags
73
+ end
74
+
20
75
  def hide_documentation_path
21
76
  @@hide_documentation_path
22
77
  end
@@ -26,54 +81,32 @@ module GrapeSwagger
26
81
  end
27
82
 
28
83
  def setup(options)
29
- options = defaults.merge(options)
84
+ options = DEFAULTS.merge(options)
30
85
 
31
86
  # options could be set on #add_swagger_documentation call,
32
87
  # for available options see #defaults
33
88
  target_class = options[:target_class]
34
89
  guard = options[:swagger_endpoint_guard]
35
- formatter = options[:format]
36
90
  api_doc = options[:api_documentation].dup
37
91
  specific_api_doc = options[:specific_api_documentation].dup
38
92
 
39
93
  class_variables_from(options)
40
94
 
41
- if formatter
42
- %i[format default_format default_error_formatter].each do |method|
43
- send(method, formatter)
44
- end
45
- end
95
+ setup_formatter(options[:format])
46
96
 
47
97
  desc api_doc.delete(:desc), api_doc
48
98
 
49
- output_path_definitions = proc do |combi_routes, endpoint|
50
- output = endpoint.swagger_object(
51
- target_class,
52
- endpoint.request,
53
- options
54
- )
55
-
56
- paths, definitions = endpoint.path_and_definition_objects(combi_routes, options)
57
- tags = tags_from(paths, options)
58
-
59
- output[:tags] = tags unless tags.empty? || paths.blank?
60
- output[:paths] = paths unless paths.blank?
61
- output[:definitions] = definitions unless definitions.blank?
62
-
63
- output
64
- end
65
-
66
99
  instance_eval(guard) unless guard.nil?
67
100
 
68
101
  get mount_path do
69
102
  header['Access-Control-Allow-Origin'] = '*'
70
103
  header['Access-Control-Request-Method'] = '*'
71
104
 
72
- output_path_definitions.call(target_class.combined_namespace_routes, self)
105
+ GrapeSwagger::DocMethods
106
+ .output_path_definitions(target_class.combined_namespace_routes, self, target_class, options)
73
107
  end
74
108
 
75
- desc specific_api_doc.delete(:desc), { params:
76
- specific_api_doc.delete(:params) || {} }.merge(specific_api_doc)
109
+ desc specific_api_doc.delete(:desc), { params: specific_api_doc.delete(:params) || {}, **specific_api_doc }
77
110
 
78
111
  params do
79
112
  requires :name, type: String, desc: 'Resource name of mounted API'
@@ -88,51 +121,21 @@ module GrapeSwagger
88
121
  combined_routes = target_class.combined_namespace_routes[params[:name]]
89
122
  error!({ error: 'named resource not exist' }, 400) if combined_routes.nil?
90
123
 
91
- output_path_definitions.call({ params[:name] => combined_routes }, self)
124
+ GrapeSwagger::DocMethods
125
+ .output_path_definitions({ params[:name] => combined_routes }, self, target_class, options)
92
126
  end
93
127
  end
94
128
 
95
- def defaults
96
- {
97
- info: {},
98
- models: [],
99
- doc_version: '0.0.1',
100
- target_class: nil,
101
- mount_path: '/swagger_doc',
102
- host: nil,
103
- base_path: nil,
104
- add_base_path: false,
105
- add_version: true,
106
- add_root: false,
107
- hide_documentation_path: true,
108
- format: :json,
109
- authorizations: nil,
110
- security_definitions: nil,
111
- security: nil,
112
- api_documentation: { desc: 'Swagger compatible API description' },
113
- specific_api_documentation: { desc: 'Swagger compatible API description for specific API' },
114
- endpoint_auth_wrapper: nil,
115
- swagger_endpoint_guard: nil,
116
- token_owner: nil
117
- }
118
- end
119
-
120
129
  def class_variables_from(options)
121
130
  @@mount_path = options[:mount_path]
122
131
  @@class_name = options[:class_name] || options[:mount_path].delete('/')
123
132
  @@hide_documentation_path = options[:hide_documentation_path]
124
133
  end
125
134
 
126
- def tags_from(paths, options)
127
- tags = GrapeSwagger::DocMethods::TagNameDescription.build(paths)
135
+ def setup_formatter(formatter)
136
+ return unless formatter
128
137
 
129
- if options[:tags]
130
- names = options[:tags].map { |t| t[:name] }
131
- tags.reject! { |t| names.include?(t[:name]) }
132
- tags += options[:tags]
133
- end
134
-
135
- tags
138
+ FORMATTER_METHOD.each { |method| send(method, formatter) }
136
139
  end
137
140
  end
138
141
  end
@@ -4,8 +4,8 @@ module GrapeSwagger
4
4
  module DocMethods
5
5
  class BuildModelDefinition
6
6
  class << self
7
- def build(model, properties, required)
8
- definition = { type: 'object', properties: properties }
7
+ def build(model, properties, required, other_def_properties = {})
8
+ definition = { type: 'object', properties: properties }.merge(other_def_properties)
9
9
 
10
10
  if required.nil?
11
11
  required_attrs = required_attributes(model)
@@ -17,6 +17,57 @@ module GrapeSwagger
17
17
  definition
18
18
  end
19
19
 
20
+ def parse_params_from_model(parsed_response, model, model_name)
21
+ if parsed_response.is_a?(Hash) && parsed_response.keys.first == :allOf
22
+ refs_or_models = parsed_response[:allOf]
23
+ parsed = parse_refs_and_models(refs_or_models, model)
24
+
25
+ {
26
+ allOf: parsed
27
+ }
28
+ else
29
+ properties, required = parsed_response
30
+ unless properties&.any?
31
+ raise GrapeSwagger::Errors::SwaggerSpec,
32
+ "Empty model #{model_name}, swagger 2.0 doesn't support empty definitions."
33
+ end
34
+ properties, other_def_properties = parse_properties(properties)
35
+
36
+ build(
37
+ model, properties, required, other_def_properties
38
+ )
39
+ end
40
+ end
41
+
42
+ def parse_properties(properties)
43
+ other_properties = {}
44
+
45
+ discriminator_key, discriminator_value =
46
+ properties.find do |_key, value|
47
+ value[:documentation].try(:[], :is_discriminator)
48
+ end
49
+
50
+ if discriminator_key
51
+ discriminator_value.delete(:documentation)
52
+ properties[discriminator_key] = discriminator_value
53
+
54
+ other_properties[:discriminator] = discriminator_key
55
+ end
56
+
57
+ [properties, other_properties]
58
+ end
59
+
60
+ def parse_refs_and_models(refs_or_models, model)
61
+ refs_or_models.map do |ref_or_models|
62
+ if ref_or_models.is_a?(Hash) && ref_or_models.keys.first == '$ref'
63
+ ref_or_models
64
+ else
65
+ properties, required = ref_or_models
66
+ GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties, required)
67
+ end
68
+ end
69
+ end
70
+
20
71
  private
21
72
 
22
73
  def required_attributes(model)
@@ -48,14 +48,14 @@ module GrapeSwagger
48
48
  end
49
49
 
50
50
  def parse_entity_name(model)
51
- if model.methods(false).include?(:entity_name)
51
+ if model.respond_to?(:entity_name)
52
52
  model.entity_name
53
53
  elsif model.to_s.end_with?('::Entity', '::Entities')
54
- model.to_s.split('::')[-2]
55
- elsif model.respond_to?(:name)
56
- model.name.demodulize.camelize
54
+ model.to_s.split('::')[0..-2].join('_')
55
+ elsif model.to_s.start_with?('Entity::', 'Entities::', 'Representable::')
56
+ model.to_s.split('::')[1..-1].join('_')
57
57
  else
58
- model.to_s.split('::').last
58
+ model.to_s.split('::').join('_')
59
59
  end
60
60
  end
61
61
 
@@ -7,7 +7,7 @@ module GrapeSwagger
7
7
  def to_format(parameters)
8
8
  parameters.reject { |parameter| parameter[:in] == 'body' }.each do |b|
9
9
  related_parameters = parameters.select do |p|
10
- p[:name] != b[:name] && p[:name].to_s.include?(b[:name].to_s.gsub(/\[\]\z/, '') + '[')
10
+ p[:name] != b[:name] && p[:name].to_s.include?("#{b[:name].to_s.gsub(/\[\]\z/, '')}[")
11
11
  end
12
12
  parameters.reject! { |p| p[:name] == b[:name] } if move_down(b, related_parameters)
13
13
  end
@@ -30,7 +30,7 @@ module GrapeSwagger
30
30
 
31
31
  def add_braces(parameter, related_parameters)
32
32
  param_name = parameter[:name].gsub(/\A(.*)\[\]\z/, '\1')
33
- related_parameters.each { |p| p[:name] = p[:name].gsub(param_name, param_name + '[]') }
33
+ related_parameters.each { |p| p[:name] = p[:name].gsub(param_name, "#{param_name}[]") }
34
34
  end
35
35
 
36
36
  def add_array(parameter, related_parameters)
@@ -16,8 +16,8 @@ module GrapeSwagger
16
16
 
17
17
  def manipulate(path)
18
18
  operation = path.split('/').map(&:capitalize).join
19
- operation.gsub!(/\-(\w)/, &:upcase).delete!('-') if operation[/\-(\w)/]
20
- operation.gsub!(/\_(\w)/, &:upcase).delete!('_') if operation.include?('_')
19
+ operation.gsub!(/-(\w)/, &:upcase).delete!('-') if operation[/-(\w)/]
20
+ operation.gsub!(/_(\w)/, &:upcase).delete!('_') if operation.include?('_')
21
21
  operation.gsub!(/\.(\w)/, &:upcase).delete!('.') if operation[/\.(\w)/]
22
22
  if path.include?('{')
23
23
  operation.gsub!(/\{(\w)/, &:upcase)
@@ -26,6 +26,7 @@ module GrapeSwagger
26
26
  document_range_values(settings) unless value_type[:is_array]
27
27
  document_required(settings)
28
28
  document_additional_properties(settings)
29
+ document_add_extensions(settings)
29
30
 
30
31
  @parsed_param
31
32
  end
@@ -62,6 +63,10 @@ module GrapeSwagger
62
63
  @parsed_param[:format] = settings[:format] if settings[:format].present?
63
64
  end
64
65
 
66
+ def document_add_extensions(settings)
67
+ GrapeSwagger::DocMethods::Extensions.add_extensions_to_root(settings, @parsed_param)
68
+ end
69
+
65
70
  def document_array_param(value_type, definitions)
66
71
  if value_type[:documentation].present?
67
72
  param_type = value_type[:documentation][:param_type]
@@ -72,6 +77,19 @@ module GrapeSwagger
72
77
 
73
78
  param_type ||= value_type[:param_type]
74
79
 
80
+ array_items = parse_array_item(
81
+ definitions,
82
+ type,
83
+ value_type
84
+ )
85
+
86
+ @parsed_param[:in] = param_type || 'formData'
87
+ @parsed_param[:items] = array_items
88
+ @parsed_param[:type] = 'array'
89
+ @parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
90
+ end
91
+
92
+ def parse_array_item(definitions, type, value_type)
75
93
  array_items = {}
76
94
  if definitions[value_type[:data_type]]
77
95
  array_items['$ref'] = "#/definitions/#{@parsed_param[:type]}"
@@ -86,10 +104,7 @@ module GrapeSwagger
86
104
 
87
105
  array_items[:default] = value_type[:default] if value_type[:default].present?
88
106
 
89
- @parsed_param[:in] = param_type || 'formData'
90
- @parsed_param[:items] = array_items
91
- @parsed_param[:type] = 'array'
92
- @parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
107
+ array_items
93
108
  end
94
109
 
95
110
  def document_additional_properties(settings)
@@ -11,7 +11,7 @@ module Grape
11
11
 
12
12
  if content_types.empty?
13
13
  formats = [target_class.format, target_class.default_format].compact.uniq
14
- formats = Grape::Formatter.formatters({}).keys if formats.empty?
14
+ formats = Grape::Formatter.formatters(**{}).keys if formats.empty?
15
15
  content_types = Grape::ContentTypes::CONTENT_TYPES.select do |content_type, _mime_type|
16
16
  formats.include? content_type
17
17
  end.values
@@ -78,11 +78,11 @@ module Grape
78
78
  def path_and_definition_objects(namespace_routes, options)
79
79
  @paths = {}
80
80
  @definitions = {}
81
+ add_definitions_from options[:models]
81
82
  namespace_routes.each_value do |routes|
82
83
  path_item(routes, options)
83
84
  end
84
85
 
85
- add_definitions_from options[:models]
86
86
  [@paths, @definitions]
87
87
  end
88
88
 
@@ -175,15 +175,18 @@ module Grape
175
175
  end
176
176
 
177
177
  def params_object(route, options, path)
178
- parameters = partition_params(route, options).map do |param, value|
178
+ parameters = build_request_params(route, options).each_with_object([]) do |(param, value), memo|
179
+ next if hidden_parameter?(value)
180
+
179
181
  value = { required: false }.merge(value) if value.is_a?(Hash)
180
182
  _, value = default_type([[param, value]]).first if value == ''
183
+
181
184
  if value.dig(:documentation, :type)
182
185
  expose_params(value[:documentation][:type])
183
186
  elsif value[:type]
184
187
  expose_params(value[:type])
185
188
  end
186
- GrapeSwagger::DocMethods::ParseParams.call(param, value, path, route, @definitions)
189
+ memo << GrapeSwagger::DocMethods::ParseParams.call(param, value, path, route, @definitions)
187
190
  end
188
191
 
189
192
  if GrapeSwagger::DocMethods::MoveParams.can_be_moved?(route.request_method, parameters)
@@ -196,35 +199,38 @@ module Grape
196
199
  end
197
200
 
198
201
  def response_object(route, options)
199
- codes = http_codes_from_route(route)
200
- codes.map! { |x| x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x }
201
-
202
- codes.each_with_object({}) do |value, memo|
202
+ codes(route).each_with_object({}) do |value, memo|
203
203
  value[:message] ||= ''
204
- memo[value[:code]] = { description: value[:message] }
205
-
204
+ memo[value[:code]] = { description: value[:message] ||= '' } unless memo[value[:code]].present?
206
205
  memo[value[:code]][:headers] = value[:headers] if value[:headers]
207
206
 
208
207
  next build_file_response(memo[value[:code]]) if file_response?(value[:model])
209
208
 
210
- response_model = @item
211
- response_model = expose_params_from_model(value[:model]) if value[:model]
212
-
213
209
  if memo.key?(200) && route.request_method == 'DELETE' && value[:model].nil?
214
210
  memo[204] = memo.delete(200)
215
211
  value[:code] = 204
212
+ next
216
213
  end
217
214
 
218
- next if value[:code] == 204
219
- next unless !response_model.start_with?('Swagger_doc') && (@definitions[response_model] || value[:model])
215
+ # Explicitly request no model with { model: '' }
216
+ next if value[:model] == ''
220
217
 
221
- @definitions[response_model][:description] = description_object(route)
218
+ response_model = value[:model] ? expose_params_from_model(value[:model]) : @item
219
+ next unless @definitions[response_model]
220
+ next if response_model.start_with?('Swagger_doc')
222
221
 
223
- memo[value[:code]][:schema] = build_reference(route, value, response_model, options)
222
+ @definitions[response_model][:description] ||= "#{response_model} model"
223
+ build_memo_schema(memo, route, value, response_model, options)
224
224
  memo[value[:code]][:examples] = value[:examples] if value[:examples]
225
225
  end
226
226
  end
227
227
 
228
+ def codes(route)
229
+ http_codes_from_route(route).map do |x|
230
+ x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x
231
+ end
232
+ end
233
+
228
234
  def success_code?(code)
229
235
  status = code.is_a?(Array) ? code.first : code[:code]
230
236
  status.between?(200, 299)
@@ -250,7 +256,7 @@ module Grape
250
256
 
251
257
  def tag_object(route, path)
252
258
  version = GrapeSwagger::DocMethods::Version.get(route)
253
- version = [version] unless version.is_a?(Array)
259
+ version = Array(version)
254
260
  prefix = route.prefix.to_s.split('/').reject(&:empty?)
255
261
  Array(
256
262
  path.split('{')[0].split('/').reject(&:empty?).delete_if do |i|
@@ -261,15 +267,45 @@ module Grape
261
267
 
262
268
  private
263
269
 
270
+ def build_memo_schema(memo, route, value, response_model, options)
271
+ if memo[value[:code]][:schema] && value[:as]
272
+ memo[value[:code]][:schema][:properties].merge!(build_reference(route, value, response_model, options))
273
+ elsif value[:as]
274
+ memo[value[:code]][:schema] = {
275
+ type: :object,
276
+ properties: build_reference(route, value, response_model, options)
277
+ }
278
+ else
279
+ memo[value[:code]][:schema] = build_reference(route, value, response_model, options)
280
+ end
281
+ end
282
+
264
283
  def build_reference(route, value, response_model, settings)
265
284
  # TODO: proof that the definition exist, if model isn't specified
266
- reference = { '$ref' => "#/definitions/#{response_model}" }
285
+ reference = if value.key?(:as)
286
+ { value[:as] => build_reference_hash(response_model) }
287
+ else
288
+ build_reference_hash(response_model)
289
+ end
267
290
  return reference unless value[:code] < 300
268
291
 
269
- reference = { type: 'array', items: reference } if route.options[:is_array]
292
+ if value.key?(:as) && value.key?(:is_array)
293
+ reference[value[:as]] = build_reference_array(reference[value[:as]])
294
+ elsif route.options[:is_array]
295
+ reference = build_reference_array(reference)
296
+ end
297
+
270
298
  build_root(route, reference, response_model, settings)
271
299
  end
272
300
 
301
+ def build_reference_hash(response_model)
302
+ { '$ref' => "#/definitions/#{response_model}" }
303
+ end
304
+
305
+ def build_reference_array(reference)
306
+ { type: 'array', items: reference }
307
+ end
308
+
273
309
  def build_root(route, reference, response_model, settings)
274
310
  default_root = response_model.underscore
275
311
  default_root = default_root.pluralize if route.options[:is_array]
@@ -293,16 +329,13 @@ module Grape
293
329
  memo['schema'] = { type: 'file' }
294
330
  end
295
331
 
296
- def partition_params(route, settings)
297
- declared_params = route.settings[:declared_params] if route.settings[:declared_params].present?
332
+ def build_request_params(route, settings)
298
333
  required = merge_params(route)
299
334
  required = GrapeSwagger::DocMethods::Headers.parse(route) + required unless route.headers.nil?
300
335
 
301
336
  default_type(required)
302
337
 
303
- request_params = unless declared_params.nil? && route.headers.nil?
304
- GrapeSwagger::Endpoint::ParamsParser.parse_request_params(required, settings, self)
305
- end || {}
338
+ request_params = GrapeSwagger::Endpoint::ParamsParser.parse_request_params(required, settings, self)
306
339
 
307
340
  request_params.empty? ? required : request_params
308
341
  end
@@ -340,13 +373,10 @@ module Grape
340
373
  parser = GrapeSwagger.model_parsers.find(model)
341
374
  raise GrapeSwagger::Errors::UnregisteredParser, "No parser registered for #{model_name}." unless parser
342
375
 
343
- properties, required = parser.new(model, self).call
344
- unless properties&.any?
345
- raise GrapeSwagger::Errors::SwaggerSpec,
346
- "Empty model #{model_name}, swagger 2.0 doesn't support empty definitions."
347
- end
376
+ parsed_response = parser.new(model, self).call
348
377
 
349
- @definitions[model_name] = GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties, required)
378
+ @definitions[model_name] =
379
+ GrapeSwagger::DocMethods::BuildModelDefinition.parse_params_from_model(parsed_response, model, model_name)
350
380
 
351
381
  model_name
352
382
  end
@@ -363,6 +393,16 @@ module Grape
363
393
  options[:token_owner] ? route_hidden.call(send(options[:token_owner].to_sym)) : route_hidden.call
364
394
  end
365
395
 
396
+ def hidden_parameter?(value)
397
+ return false if value[:required]
398
+
399
+ if value.dig(:documentation, :hidden).is_a?(Proc)
400
+ value.dig(:documentation, :hidden).call
401
+ else
402
+ value.dig(:documentation, :hidden)
403
+ end
404
+ end
405
+
366
406
  def success_code_from_entity(route, entity)
367
407
  default_code = GrapeSwagger::DocMethods::StatusCodes.get[route.request_method.downcase.to_sym]
368
408
  if entity.is_a?(Hash)
@@ -371,6 +411,8 @@ module Grape
371
411
  default_code[:message] = entity[:message] || route.description || default_code[:message].sub('{item}', @item)
372
412
  default_code[:examples] = entity[:examples] if entity[:examples]
373
413
  default_code[:headers] = entity[:headers] if entity[:headers]
414
+ default_code[:as] = entity[:as] if entity[:as]
415
+ default_code[:is_array] = entity[:is_array] if entity[:is_array]
374
416
  else
375
417
  default_code = GrapeSwagger::DocMethods::StatusCodes.get[route.request_method.downcase.to_sym]
376
418
  default_code[:model] = entity if entity