grape-swagger 1.1.0 → 1.4.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 (58) 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 +32 -0
  5. data/.rubocop.yml +65 -2
  6. data/.rubocop_todo.yml +1 -1
  7. data/CHANGELOG.md +50 -0
  8. data/Gemfile +8 -3
  9. data/README.md +182 -13
  10. data/UPGRADING.md +34 -0
  11. data/grape-swagger.gemspec +4 -4
  12. data/lib/grape-swagger.rb +7 -4
  13. data/lib/grape-swagger/doc_methods.rb +65 -62
  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 +2 -2
  17. data/lib/grape-swagger/doc_methods/operation_id.rb +2 -2
  18. data/lib/grape-swagger/doc_methods/parse_params.rb +20 -4
  19. data/lib/grape-swagger/endpoint.rb +83 -32
  20. data/lib/grape-swagger/errors.rb +2 -0
  21. data/lib/grape-swagger/model_parsers.rb +2 -2
  22. data/lib/grape-swagger/rake/oapi_tasks.rb +2 -0
  23. data/lib/grape-swagger/version.rb +1 -1
  24. data/spec/issues/427_entity_as_string_spec.rb +1 -1
  25. data/spec/issues/430_entity_definitions_spec.rb +7 -5
  26. data/spec/issues/537_enum_values_spec.rb +1 -0
  27. data/spec/issues/776_multiple_presents_spec.rb +59 -0
  28. data/spec/issues/809_utf8_routes_spec.rb +55 -0
  29. data/spec/lib/data_type_spec.rb +12 -0
  30. data/spec/lib/format_data_spec.rb +24 -0
  31. data/spec/lib/move_params_spec.rb +2 -2
  32. data/spec/spec_helper.rb +1 -1
  33. data/spec/support/empty_model_parser.rb +3 -2
  34. data/spec/support/mock_parser.rb +1 -2
  35. data/spec/support/model_parsers/entity_parser.rb +8 -8
  36. data/spec/support/model_parsers/mock_parser.rb +24 -8
  37. data/spec/support/model_parsers/representable_parser.rb +8 -8
  38. data/spec/support/namespace_tags.rb +3 -0
  39. data/spec/swagger_v2/api_swagger_v2_hide_param_spec.rb +1 -1
  40. data/spec/swagger_v2/api_swagger_v2_mounted_spec.rb +1 -0
  41. data/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb +2 -2
  42. data/spec/swagger_v2/api_swagger_v2_response_with_headers_spec.rb +4 -2
  43. data/spec/swagger_v2/api_swagger_v2_response_with_models_spec.rb +53 -0
  44. data/spec/swagger_v2/api_swagger_v2_spec.rb +1 -0
  45. data/spec/swagger_v2/boolean_params_spec.rb +1 -0
  46. data/spec/swagger_v2/float_api_spec.rb +1 -0
  47. data/spec/swagger_v2/inheritance_and_discriminator_spec.rb +57 -0
  48. data/spec/swagger_v2/namespace_tags_prefix_spec.rb +1 -0
  49. data/spec/swagger_v2/param_multi_type_spec.rb +2 -0
  50. data/spec/swagger_v2/param_type_spec.rb +3 -0
  51. data/spec/swagger_v2/param_values_spec.rb +6 -0
  52. data/spec/swagger_v2/{params_array_collection_fromat_spec.rb → params_array_collection_format_spec.rb} +0 -0
  53. data/spec/swagger_v2/params_example_spec.rb +40 -0
  54. data/spec/swagger_v2/reference_entity_spec.rb +74 -29
  55. data/spec/swagger_v2/security_requirement_spec.rb +2 -2
  56. data/spec/swagger_v2/simple_mounted_api_spec.rb +3 -0
  57. metadata +27 -13
  58. data/.travis.yml +0 -35
data/UPGRADING.md CHANGED
@@ -1,5 +1,39 @@
1
1
  ## Upgrading Grape-swagger
2
2
 
3
+ ### Upgrading to >= 1.4.0
4
+
5
+ - Official support for ruby < 2.5 removed, ruby 2.5 only in testing mode, but no support.
6
+
7
+ ### Upgrading to >= 1.3.0
8
+
9
+ - The model (entity) description no longer comes from the route description. It will have a default value: `<<EntityName>> model`.
10
+
11
+ ### Upgrading to >= 1.2.0
12
+
13
+ - The entity_name class method is now called on parent classes for inherited entities. Now you can do this
14
+
15
+ ```ruby
16
+ module Some::Long::Module
17
+ class Base < Grape::Entity
18
+ # ... other shared logic
19
+ def self.entity_name
20
+ "V2::#{self.to_s.demodulize}"
21
+ end
22
+ end
23
+
24
+ def MyEntity < Base
25
+ # ....
26
+ end
27
+
28
+ def OtherEntity < Base
29
+ # revert back to the default behavior by hiding the method
30
+ private_class_method :entity_name
31
+ end
32
+ end
33
+ ```
34
+
35
+ - Full class name is modified to use `_` separator (e.g. `A_B_C` instead of `A::B::C`).
36
+
3
37
  ### Upgrading to >= 1.1.0
4
38
 
5
39
  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).
@@ -7,14 +7,14 @@ Gem::Specification.new do |s|
7
7
  s.name = 'grape-swagger'
8
8
  s.version = GrapeSwagger::VERSION
9
9
  s.platform = Gem::Platform::RUBY
10
- s.authors = ['Tim Vandecasteele']
11
- s.email = ['tim.vandecasteele@gmail.com']
10
+ s.authors = ['LeFnord', 'Tim Vandecasteele']
11
+ s.email = ['pscholz.le@gmail.com', 'tim.vandecasteele@gmail.com']
12
12
  s.homepage = 'https://github.com/ruby-grape/grape-swagger'
13
13
  s.summary = 'Add auto generated documentation to your Grape API that can be displayed with Swagger.'
14
14
  s.license = 'MIT'
15
15
 
16
- s.required_ruby_version = '>= 2.4'
17
- s.add_runtime_dependency 'grape', '~> 1.3.0'
16
+ s.required_ruby_version = '>= 2.5'
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")
data/lib/grape-swagger.rb CHANGED
@@ -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('::')[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,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)
@@ -27,6 +27,7 @@ module GrapeSwagger
27
27
  document_required(settings)
28
28
  document_additional_properties(settings)
29
29
  document_add_extensions(settings)
30
+ document_example(settings)
30
31
 
31
32
  @parsed_param
32
33
  end
@@ -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,10 +105,7 @@ 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
+ array_items
98
109
  end
99
110
 
100
111
  def document_additional_properties(settings)
@@ -102,6 +113,11 @@ module GrapeSwagger
102
113
  @parsed_param[:additionalProperties] = additional_properties if additional_properties
103
114
  end
104
115
 
116
+ def document_example(settings)
117
+ example = settings[:example]
118
+ @parsed_param[:example] = example if example
119
+ end
120
+
105
121
  def param_type(value_type)
106
122
  param_type = value_type[:param_type] || value_type[:in]
107
123
  if value_type[:path].include?("{#{value_type[:param_name]}}")
@@ -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