grape-swagger 1.1.0 → 1.4.2

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +14 -0
  3. data/.github/workflows/rubocop.yml +26 -0
  4. data/.github/workflows/ruby.yml +30 -0
  5. data/.gitignore +1 -0
  6. data/.rspec +2 -0
  7. data/.rubocop.yml +65 -2
  8. data/.rubocop_todo.yml +1 -1
  9. data/CHANGELOG.md +66 -0
  10. data/Gemfile +9 -4
  11. data/README.md +213 -13
  12. data/UPGRADING.md +38 -0
  13. data/grape-swagger.gemspec +4 -4
  14. data/lib/grape-swagger/doc_methods/build_model_definition.rb +53 -2
  15. data/lib/grape-swagger/doc_methods/data_type.rb +4 -4
  16. data/lib/grape-swagger/doc_methods/format_data.rb +4 -2
  17. data/lib/grape-swagger/doc_methods/move_params.rb +6 -7
  18. data/lib/grape-swagger/doc_methods/operation_id.rb +2 -2
  19. data/lib/grape-swagger/doc_methods/parse_params.rb +51 -9
  20. data/lib/grape-swagger/doc_methods.rb +65 -62
  21. data/lib/grape-swagger/endpoint.rb +83 -32
  22. data/lib/grape-swagger/errors.rb +2 -0
  23. data/lib/grape-swagger/model_parsers.rb +2 -2
  24. data/lib/grape-swagger/rake/oapi_tasks.rb +3 -1
  25. data/lib/grape-swagger/version.rb +1 -1
  26. data/lib/grape-swagger.rb +7 -4
  27. data/spec/issues/427_entity_as_string_spec.rb +1 -1
  28. data/spec/issues/430_entity_definitions_spec.rb +7 -5
  29. data/spec/issues/537_enum_values_spec.rb +1 -0
  30. data/spec/issues/776_multiple_presents_spec.rb +59 -0
  31. data/spec/issues/809_utf8_routes_spec.rb +55 -0
  32. data/spec/issues/832_array_hash_float_decimal_spec.rb +111 -0
  33. data/spec/lib/data_type_spec.rb +12 -0
  34. data/spec/lib/format_data_spec.rb +24 -0
  35. data/spec/lib/move_params_spec.rb +2 -2
  36. data/spec/spec_helper.rb +1 -1
  37. data/spec/support/empty_model_parser.rb +3 -2
  38. data/spec/support/mock_parser.rb +1 -2
  39. data/spec/support/model_parsers/entity_parser.rb +8 -8
  40. data/spec/support/model_parsers/mock_parser.rb +24 -8
  41. data/spec/support/model_parsers/representable_parser.rb +8 -8
  42. data/spec/support/namespace_tags.rb +3 -0
  43. data/spec/support/the_paths_definitions.rb +4 -4
  44. data/spec/swagger_v2/api_swagger_v2_additional_properties_spec.rb +83 -0
  45. data/spec/swagger_v2/api_swagger_v2_hide_param_spec.rb +1 -1
  46. data/spec/swagger_v2/api_swagger_v2_mounted_spec.rb +1 -0
  47. data/spec/swagger_v2/api_swagger_v2_param_type_body_nested_spec.rb +73 -1
  48. data/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb +2 -2
  49. data/spec/swagger_v2/api_swagger_v2_response_with_headers_spec.rb +4 -2
  50. data/spec/swagger_v2/api_swagger_v2_response_with_models_spec.rb +53 -0
  51. data/spec/swagger_v2/api_swagger_v2_spec.rb +1 -0
  52. data/spec/swagger_v2/boolean_params_spec.rb +4 -1
  53. data/spec/swagger_v2/float_api_spec.rb +1 -0
  54. data/spec/swagger_v2/inheritance_and_discriminator_spec.rb +57 -0
  55. data/spec/swagger_v2/namespace_tags_prefix_spec.rb +1 -0
  56. data/spec/swagger_v2/param_multi_type_spec.rb +2 -0
  57. data/spec/swagger_v2/param_type_spec.rb +3 -0
  58. data/spec/swagger_v2/param_values_spec.rb +6 -0
  59. data/spec/swagger_v2/{params_array_collection_fromat_spec.rb → params_array_collection_format_spec.rb} +0 -0
  60. data/spec/swagger_v2/params_example_spec.rb +40 -0
  61. data/spec/swagger_v2/reference_entity_spec.rb +74 -29
  62. data/spec/swagger_v2/security_requirement_spec.rb +2 -2
  63. data/spec/swagger_v2/simple_mounted_api_spec.rb +3 -0
  64. metadata +31 -13
  65. data/.travis.yml +0 -35
@@ -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('::')[0..-2].join('::')
54
+ model.to_s.split('::')[0..-2].join('_')
55
55
  elsif model.to_s.start_with?('Entity::', 'Entities::', 'Representable::')
56
- model.to_s.split('::')[1..-1].join('::')
56
+ model.to_s.split('::')[1..-1].join('_')
57
57
  else
58
- model.to_s
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.start_with?("#{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,11 +30,13 @@ 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)
37
37
  related_parameters.each do |p|
38
+ next if p.key?(:items)
39
+
38
40
  p_type = p[:type] == 'array' ? 'string' : p[:type]
39
41
  p[:items] = { type: p_type, format: p[:format], enum: p[:enum], is_array: p[:is_array] }
40
42
  p[:items].delete_if { |_k, v| v.nil? }
@@ -103,9 +103,9 @@ module GrapeSwagger
103
103
 
104
104
  def document_as_property(param)
105
105
  property_keys.each_with_object({}) do |x, memo|
106
- value = param[x]
107
- next if value.blank?
106
+ next unless param.key?(x)
108
107
 
108
+ value = param[x]
109
109
  if x == :type && @definitions[value].present?
110
110
  memo['$ref'] = "#/definitions/#{value}"
111
111
  else
@@ -181,7 +181,8 @@ module GrapeSwagger
181
181
  end
182
182
 
183
183
  def property_keys
184
- %i[type format description minimum maximum items enum default additionalProperties]
184
+ %i[type format description minimum maximum items enum default additional_properties additionalProperties
185
+ example]
185
186
  end
186
187
 
187
188
  def deletable?(param)
@@ -193,8 +194,7 @@ module GrapeSwagger
193
194
  end
194
195
 
195
196
  def includes_body_param?(params)
196
- params.map { |x| return true if x[:in] == 'body' || x[:param_type] == 'body' }
197
- false
197
+ params.any? { |x| x[:in] == 'body' || x[:param_type] == 'body' }
198
198
  end
199
199
 
200
200
  def should_expose_as_array?(params)
@@ -202,8 +202,7 @@ module GrapeSwagger
202
202
  end
203
203
 
204
204
  def should_exposed_as(params)
205
- params.map { |x| return 'object' if x[:type] && x[:type] != 'array' }
206
- 'array'
205
+ params.any? { |x| x[:type] && x[:type] != 'array' } ? 'object' : 'array'
207
206
  end
208
207
  end
209
208
  end
@@ -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)
@@ -25,8 +25,9 @@ module GrapeSwagger
25
25
  document_default_value(settings) unless value_type[:is_array]
26
26
  document_range_values(settings) unless value_type[:is_array]
27
27
  document_required(settings)
28
- document_additional_properties(settings)
28
+ document_additional_properties(definitions, settings) unless value_type[:is_array]
29
29
  document_add_extensions(settings)
30
+ document_example(settings)
30
31
 
31
32
  @parsed_param
32
33
  end
@@ -50,7 +51,7 @@ module GrapeSwagger
50
51
  end
51
52
 
52
53
  def document_default_value(settings)
53
- @parsed_param[:default] = settings[:default] if settings[:default].present?
54
+ @parsed_param[:default] = settings[:default] if settings.key?(:default)
54
55
  end
55
56
 
56
57
  def document_type_and_format(settings, data_type)
@@ -77,6 +78,19 @@ module GrapeSwagger
77
78
 
78
79
  param_type ||= value_type[:param_type]
79
80
 
81
+ array_items = parse_array_item(
82
+ definitions,
83
+ type,
84
+ value_type
85
+ )
86
+
87
+ @parsed_param[:in] = param_type || 'formData'
88
+ @parsed_param[:items] = array_items
89
+ @parsed_param[:type] = 'array'
90
+ @parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
91
+ end
92
+
93
+ def parse_array_item(definitions, type, value_type)
80
94
  array_items = {}
81
95
  if definitions[value_type[:data_type]]
82
96
  array_items['$ref'] = "#/definitions/#{@parsed_param[:type]}"
@@ -91,15 +105,43 @@ module GrapeSwagger
91
105
 
92
106
  array_items[:default] = value_type[:default] if value_type[:default].present?
93
107
 
94
- @parsed_param[:in] = param_type || 'formData'
95
- @parsed_param[:items] = array_items
96
- @parsed_param[:type] = 'array'
97
- @parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
108
+ set_additional_properties, additional_properties = parse_additional_properties(definitions, value_type)
109
+ array_items[:additionalProperties] = additional_properties if set_additional_properties
110
+
111
+ array_items
112
+ end
113
+
114
+ def document_additional_properties(definitions, settings)
115
+ set_additional_properties, additional_properties = parse_additional_properties(definitions, settings)
116
+ @parsed_param[:additionalProperties] = additional_properties if set_additional_properties
117
+ end
118
+
119
+ def parse_additional_properties(definitions, settings)
120
+ return false unless settings.key?(:additionalProperties) || settings.key?(:additional_properties)
121
+
122
+ value =
123
+ if settings.key?(:additionalProperties)
124
+ GrapeSwagger::Errors::SwaggerSpecDeprecated.tell!(:additionalProperties)
125
+ settings[:additionalProperties]
126
+ else
127
+ settings[:additional_properties]
128
+ end
129
+
130
+ parsed_value =
131
+ if definitions[value.to_s]
132
+ { '$ref': "#/definitions/#{value}" }
133
+ elsif value.is_a?(Class)
134
+ { type: DataType.call(value) }
135
+ else
136
+ value
137
+ end
138
+
139
+ [true, parsed_value]
98
140
  end
99
141
 
100
- def document_additional_properties(settings)
101
- additional_properties = settings[:additionalProperties]
102
- @parsed_param[:additionalProperties] = additional_properties if additional_properties
142
+ def document_example(settings)
143
+ example = settings[:example]
144
+ @parsed_param[:example] = example if example
103
145
  end
104
146
 
105
147
  def param_type(value_type)
@@ -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
@@ -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,52 @@ 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
+
274
+ if value[:required]
275
+ memo[value[:code]][:schema][:required] ||= []
276
+ memo[value[:code]][:schema][:required] << value[:as].to_s
277
+ end
278
+
279
+ elsif value[:as]
280
+ memo[value[:code]][:schema] = {
281
+ type: :object,
282
+ properties: build_reference(route, value, response_model, options)
283
+ }
284
+ memo[value[:code]][:schema][:required] = [value[:as].to_s] if value[:required]
285
+ else
286
+ memo[value[:code]][:schema] = build_reference(route, value, response_model, options)
287
+ end
288
+ end
289
+
264
290
  def build_reference(route, value, response_model, settings)
265
291
  # TODO: proof that the definition exist, if model isn't specified
266
- reference = { '$ref' => "#/definitions/#{response_model}" }
292
+ reference = if value.key?(:as)
293
+ { value[:as] => build_reference_hash(response_model) }
294
+ else
295
+ build_reference_hash(response_model)
296
+ end
267
297
  return reference unless value[:code] < 300
268
298
 
269
- reference = { type: 'array', items: reference } if route.options[:is_array]
299
+ if value.key?(:as) && value.key?(:is_array)
300
+ reference[value[:as]] = build_reference_array(reference[value[:as]])
301
+ elsif route.options[:is_array]
302
+ reference = build_reference_array(reference)
303
+ end
304
+
270
305
  build_root(route, reference, response_model, settings)
271
306
  end
272
307
 
308
+ def build_reference_hash(response_model)
309
+ { '$ref' => "#/definitions/#{response_model}" }
310
+ end
311
+
312
+ def build_reference_array(reference)
313
+ { type: 'array', items: reference }
314
+ end
315
+
273
316
  def build_root(route, reference, response_model, settings)
274
317
  default_root = response_model.underscore
275
318
  default_root = default_root.pluralize if route.options[:is_array]
@@ -286,23 +329,20 @@ module Grape
286
329
  end
287
330
 
288
331
  def file_response?(value)
289
- value.to_s.casecmp('file').zero? ? true : false
332
+ value.to_s.casecmp('file').zero?
290
333
  end
291
334
 
292
335
  def build_file_response(memo)
293
336
  memo['schema'] = { type: 'file' }
294
337
  end
295
338
 
296
- def partition_params(route, settings)
297
- declared_params = route.settings[:declared_params] if route.settings[:declared_params].present?
339
+ def build_request_params(route, settings)
298
340
  required = merge_params(route)
299
341
  required = GrapeSwagger::DocMethods::Headers.parse(route) + required unless route.headers.nil?
300
342
 
301
343
  default_type(required)
302
344
 
303
- request_params = unless declared_params.nil? && route.headers.nil?
304
- GrapeSwagger::Endpoint::ParamsParser.parse_request_params(required, settings, self)
305
- end || {}
345
+ request_params = GrapeSwagger::Endpoint::ParamsParser.parse_request_params(required, settings, self)
306
346
 
307
347
  request_params.empty? ? required : request_params
308
348
  end
@@ -340,12 +380,10 @@ module Grape
340
380
  parser = GrapeSwagger.model_parsers.find(model)
341
381
  raise GrapeSwagger::Errors::UnregisteredParser, "No parser registered for #{model_name}." unless parser
342
382
 
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
348
- @definitions[model_name] = GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties, required)
383
+ parsed_response = parser.new(model, self).call
384
+
385
+ @definitions[model_name] =
386
+ GrapeSwagger::DocMethods::BuildModelDefinition.parse_params_from_model(parsed_response, model, model_name)
349
387
 
350
388
  model_name
351
389
  end
@@ -362,6 +400,16 @@ module Grape
362
400
  options[:token_owner] ? route_hidden.call(send(options[:token_owner].to_sym)) : route_hidden.call
363
401
  end
364
402
 
403
+ def hidden_parameter?(value)
404
+ return false if value[:required]
405
+
406
+ if value.dig(:documentation, :hidden).is_a?(Proc)
407
+ value.dig(:documentation, :hidden).call
408
+ else
409
+ value.dig(:documentation, :hidden)
410
+ end
411
+ end
412
+
365
413
  def success_code_from_entity(route, entity)
366
414
  default_code = GrapeSwagger::DocMethods::StatusCodes.get[route.request_method.downcase.to_sym]
367
415
  if entity.is_a?(Hash)
@@ -370,6 +418,9 @@ module Grape
370
418
  default_code[:message] = entity[:message] || route.description || default_code[:message].sub('{item}', @item)
371
419
  default_code[:examples] = entity[:examples] if entity[:examples]
372
420
  default_code[:headers] = entity[:headers] if entity[:headers]
421
+ default_code[:as] = entity[:as] if entity[:as]
422
+ default_code[:is_array] = entity[:is_array] if entity[:is_array]
423
+ default_code[:required] = entity[:required] if entity[:required]
373
424
  else
374
425
  default_code = GrapeSwagger::DocMethods::StatusCodes.get[route.request_method.downcase.to_sym]
375
426
  default_code[:model] = entity if entity
@@ -3,7 +3,9 @@
3
3
  module GrapeSwagger
4
4
  module Errors
5
5
  class UnregisteredParser < StandardError; end
6
+
6
7
  class SwaggerSpec < StandardError; end
8
+
7
9
  class SwaggerSpecDeprecated < SwaggerSpec
8
10
  class << self
9
11
  def tell!(what)