grape-swagger 0.33.0 → 0.34.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -7
  3. data/.rubocop_todo.yml +0 -6
  4. data/.travis.yml +10 -11
  5. data/CHANGELOG.md +72 -6
  6. data/Gemfile +4 -5
  7. data/README.md +68 -4
  8. data/grape-swagger.gemspec +2 -1
  9. data/lib/grape-swagger/doc_methods/build_model_definition.rb +0 -17
  10. data/lib/grape-swagger/doc_methods/extensions.rb +6 -1
  11. data/lib/grape-swagger/doc_methods/format_data.rb +51 -0
  12. data/lib/grape-swagger/doc_methods/move_params.rb +22 -49
  13. data/lib/grape-swagger/doc_methods/parse_params.rb +6 -0
  14. data/lib/grape-swagger/doc_methods.rb +2 -0
  15. data/lib/grape-swagger/endpoint/params_parser.rb +10 -17
  16. data/lib/grape-swagger/endpoint.rb +32 -13
  17. data/lib/grape-swagger/version.rb +1 -1
  18. data/lib/grape-swagger.rb +1 -1
  19. data/spec/issues/751_deeply_nested_objects_spec.rb +190 -0
  20. data/spec/lib/endpoint/params_parser_spec.rb +44 -20
  21. data/spec/lib/endpoint_spec.rb +3 -3
  22. data/spec/lib/extensions_spec.rb +10 -0
  23. data/spec/lib/format_data_spec.rb +91 -0
  24. data/spec/lib/move_params_spec.rb +4 -266
  25. data/spec/lib/optional_object_spec.rb +0 -1
  26. data/spec/spec_helper.rb +1 -1
  27. data/spec/swagger_v2/api_swagger_v2_hash_and_array_spec.rb +3 -1
  28. data/spec/swagger_v2/api_swagger_v2_response_with_root_spec.rb +153 -0
  29. data/spec/swagger_v2/description_not_initialized_spec.rb +39 -0
  30. data/spec/swagger_v2/endpoint_versioned_path_spec.rb +33 -0
  31. data/spec/swagger_v2/mounted_target_class_spec.rb +1 -1
  32. data/spec/swagger_v2/namespace_tags_prefix_spec.rb +15 -1
  33. data/spec/swagger_v2/params_array_spec.rb +2 -2
  34. data/spec/swagger_v2/parent_less_namespace_spec.rb +32 -0
  35. data/spec/swagger_v2/{reference_entity.rb → reference_entity_spec.rb} +17 -10
  36. metadata +36 -9
  37. data/spec/swagger_v2/description_not_initialized.rb +0 -39
  38. data/spec/swagger_v2/parent_less_namespace.rb +0 -49
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrapeSwagger
4
+ module DocMethods
5
+ class FormatData
6
+ class << self
7
+ def to_format(parameters)
8
+ parameters.reject { |parameter| parameter[:in] == 'body' }.each do |b|
9
+ related_parameters = parameters.select do |p|
10
+ p[:name] != b[:name] && p[:name].to_s.include?(b[:name].to_s.gsub(/\[\]\z/, '') + '[')
11
+ end
12
+ parameters.reject! { |p| p[:name] == b[:name] } if move_down(b, related_parameters)
13
+ end
14
+ parameters
15
+ end
16
+
17
+ def move_down(parameter, related_parameters)
18
+ case parameter[:type]
19
+ when 'array'
20
+ add_array(parameter, related_parameters)
21
+ unless related_parameters.blank?
22
+ add_braces(parameter, related_parameters) if parameter[:name].match?(/\A.*\[\]\z/)
23
+ return true
24
+ end
25
+ when 'object'
26
+ return true
27
+ end
28
+ false
29
+ end
30
+
31
+ def add_braces(parameter, related_parameters)
32
+ param_name = parameter[:name].gsub(/\A(.*)\[\]\z/, '\1')
33
+ related_parameters.each { |p| p[:name] = p[:name].gsub(param_name, param_name + '[]') }
34
+ end
35
+
36
+ def add_array(parameter, related_parameters)
37
+ related_parameters.each do |p|
38
+ p_type = p[:type] == 'array' ? 'string' : p[:type]
39
+ p[:items] = { type: p_type, format: p[:format], enum: p[:enum], is_array: p[:is_array] }
40
+ p[:items].delete_if { |_k, v| v.nil? }
41
+ p[:type] = 'array'
42
+ p[:is_array] = parameter[:is_array]
43
+ p.delete(:format)
44
+ p.delete(:enum)
45
+ p.delete_if { |_k, v| v.nil? }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -8,7 +8,7 @@ module GrapeSwagger
8
8
  class << self
9
9
  attr_accessor :definitions
10
10
 
11
- def can_be_moved?(params, http_verb)
11
+ def can_be_moved?(http_verb, params)
12
12
  move_methods.include?(http_verb) && includes_body_param?(params)
13
13
  end
14
14
 
@@ -51,22 +51,33 @@ module GrapeSwagger
51
51
 
52
52
  def move_params_to_new(definition, params)
53
53
  params, nested_params = params.partition { |x| !x[:name].to_s.include?('[') }
54
-
55
- unless params.blank?
56
- properties, required = build_properties(params)
57
- add_properties_to_definition(definition, properties, required)
54
+ params.each do |param|
55
+ property = param[:name]
56
+ param_properties, param_required = build_properties([param])
57
+ add_properties_to_definition(definition, param_properties, param_required)
58
+ related_nested_params, nested_params = nested_params.partition { |x| x[:name].start_with?("#{property}[") }
59
+ prepare_nested_names(property, related_nested_params)
60
+
61
+ next if related_nested_params.blank?
62
+
63
+ nested_definition = if should_expose_as_array?([param])
64
+ move_params_to_new(array_type, related_nested_params)
65
+ else
66
+ move_params_to_new(object_type, related_nested_params)
67
+ end
68
+ if definition.key?(:items)
69
+ definition[:items][:properties][property.to_sym].deep_merge!(nested_definition)
70
+ else
71
+ definition[:properties][property.to_sym].deep_merge!(nested_definition)
72
+ end
58
73
  end
59
-
60
- nested_properties = build_nested_properties(nested_params) unless nested_params.blank?
61
- add_properties_to_definition(definition, nested_properties, []) unless nested_params.blank?
74
+ definition
62
75
  end
63
76
 
64
77
  def build_properties(params)
65
78
  properties = {}
66
79
  required = []
67
80
 
68
- prepare_nested_types(params) if should_expose_as_array?(params)
69
-
70
81
  params.each do |param|
71
82
  name = param[:name].to_sym
72
83
 
@@ -103,28 +114,6 @@ module GrapeSwagger
103
114
  end
104
115
  end
105
116
 
106
- def build_nested_properties(params, properties = {})
107
- property = params.bsearch { |x| x[:name].include?('[') }[:name].split('[').first
108
-
109
- nested_params, params = params.partition { |x| x[:name].start_with?("#{property}[") }
110
- prepare_nested_names(property, nested_params)
111
-
112
- recursive_call(properties, property, nested_params) unless nested_params.empty?
113
- build_nested_properties(params, properties) unless params.empty?
114
-
115
- properties
116
- end
117
-
118
- def recursive_call(properties, property, nested_params)
119
- if should_expose_as_array?(nested_params)
120
- properties[property.to_sym] = array_type
121
- move_params_to_new(properties[property.to_sym][:items], nested_params)
122
- else
123
- properties[property.to_sym] = object_type
124
- move_params_to_new(properties[property.to_sym], nested_params)
125
- end
126
- end
127
-
128
117
  def movable_params(params)
129
118
  to_delete = params.each_with_object([]) { |x, memo| memo << x if deletable?(x) }
130
119
  delete_from(params, to_delete)
@@ -177,22 +166,6 @@ module GrapeSwagger
177
166
  { type: 'object', properties: {} }
178
167
  end
179
168
 
180
- def prepare_nested_types(params)
181
- params.each do |param|
182
- next unless param[:items]
183
-
184
- param[:type] = if param[:items][:type] == 'array'
185
- 'string'
186
- elsif param[:items].key?('$ref')
187
- param[:type] = 'object'
188
- else
189
- param[:items][:type]
190
- end
191
- param[:format] = param[:items][:format] if param[:items][:format]
192
- param.delete(:items) if param[:type] != 'object'
193
- end
194
- end
195
-
196
169
  def prepare_nested_names(property, params)
197
170
  params.each { |x| x[:name] = x[:name].sub(property, '').sub('[', '').sub(']', '') }
198
171
  end
@@ -208,7 +181,7 @@ module GrapeSwagger
208
181
  end
209
182
 
210
183
  def property_keys
211
- %i[type format description minimum maximum items enum default]
184
+ %i[type format description minimum maximum items enum default additionalProperties]
212
185
  end
213
186
 
214
187
  def deletable?(param)
@@ -25,6 +25,7 @@ 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
29
 
29
30
  @parsed_param
30
31
  end
@@ -91,6 +92,11 @@ module GrapeSwagger
91
92
  @parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
92
93
  end
93
94
 
95
+ def document_additional_properties(settings)
96
+ additional_properties = settings[:additionalProperties]
97
+ @parsed_param[:additionalProperties] = additional_properties if additional_properties
98
+ end
99
+
94
100
  def param_type(value_type)
95
101
  param_type = value_type[:param_type] || value_type[:in]
96
102
  if value_type[:path].include?("{#{value_type[:param_name]}}")
@@ -4,6 +4,7 @@ require 'grape-swagger/doc_methods/status_codes'
4
4
  require 'grape-swagger/doc_methods/produces_consumes'
5
5
  require 'grape-swagger/doc_methods/data_type'
6
6
  require 'grape-swagger/doc_methods/extensions'
7
+ require 'grape-swagger/doc_methods/format_data'
7
8
  require 'grape-swagger/doc_methods/operation_id'
8
9
  require 'grape-swagger/doc_methods/optional_object'
9
10
  require 'grape-swagger/doc_methods/path_string'
@@ -102,6 +103,7 @@ module GrapeSwagger
102
103
  base_path: nil,
103
104
  add_base_path: false,
104
105
  add_version: true,
106
+ add_root: false,
105
107
  hide_documentation_path: true,
106
108
  format: :json,
107
109
  authorizations: nil,
@@ -15,37 +15,24 @@ module GrapeSwagger
15
15
  end
16
16
 
17
17
  def parse_request_params
18
- array_keys = []
19
18
  public_params.each_with_object({}) do |(name, options), memo|
20
19
  name = name.to_s
21
20
  param_type = options[:type]
22
21
  param_type = param_type.to_s unless param_type.nil?
23
22
 
24
23
  if param_type_is_array?(param_type)
25
- array_keys << name
26
24
  options[:is_array] = true
27
-
28
- name += '[]' if array_use_braces?(options)
29
- else
30
- keys = array_keys.find_all { |key| name.start_with? "#{key}[" }
31
- if keys.any?
32
- options[:is_array] = true
33
- if array_use_braces?(options)
34
- keys.sort.reverse_each do |key|
35
- name = name.sub(key, "#{key}[]")
36
- end
37
- end
38
- end
25
+ name += '[]' if array_use_braces?
39
26
  end
40
27
 
41
- memo[name] = options unless %w[Hash Array].include?(param_type) && !options.key?(:documentation)
28
+ memo[name] = options
42
29
  end
43
30
  end
44
31
 
45
32
  private
46
33
 
47
- def array_use_braces?(options)
48
- settings[:array_use_braces] && !(options[:documentation] && options[:documentation][:param_type] == 'body')
34
+ def array_use_braces?
35
+ @array_use_braces ||= settings[:array_use_braces] && !includes_body_param?
49
36
  end
50
37
 
51
38
  def param_type_is_array?(param_type)
@@ -71,6 +58,12 @@ module GrapeSwagger
71
58
  param_hidden = param_hidden.call if param_hidden.is_a?(Proc)
72
59
  !param_hidden
73
60
  end
61
+
62
+ def includes_body_param?
63
+ params.any? do |_, options|
64
+ options.dig(:documentation, :param_type) == 'body' || options.dig(:documentation, :in) == 'body'
65
+ end
66
+ end
74
67
  end
75
68
  end
76
69
  end
@@ -78,8 +78,7 @@ module Grape
78
78
  def path_and_definition_objects(namespace_routes, options)
79
79
  @paths = {}
80
80
  @definitions = {}
81
- namespace_routes.each_key do |key|
82
- routes = namespace_routes[key]
81
+ namespace_routes.each_value do |routes|
83
82
  path_item(routes, options)
84
83
  end
85
84
 
@@ -121,7 +120,7 @@ module Grape
121
120
  method[:consumes] = consumes_object(route, options[:format])
122
121
  method[:parameters] = params_object(route, options, path)
123
122
  method[:security] = security_object(route)
124
- method[:responses] = response_object(route)
123
+ method[:responses] = response_object(route, options)
125
124
  method[:tags] = route.options.fetch(:tags, tag_object(route, path))
126
125
  method[:operationId] = GrapeSwagger::DocMethods::OperationId.build(route, path)
127
126
  method[:deprecated] = deprecated_object(route)
@@ -179,22 +178,24 @@ module Grape
179
178
  parameters = partition_params(route, options).map do |param, value|
180
179
  value = { required: false }.merge(value) if value.is_a?(Hash)
181
180
  _, value = default_type([[param, value]]).first if value == ''
182
- if value[:type]
183
- expose_params(value[:type])
184
- elsif value[:documentation]
181
+ if value.dig(:documentation, :type)
185
182
  expose_params(value[:documentation][:type])
183
+ elsif value[:type]
184
+ expose_params(value[:type])
186
185
  end
187
186
  GrapeSwagger::DocMethods::ParseParams.call(param, value, path, route, @definitions)
188
187
  end
189
188
 
190
- if GrapeSwagger::DocMethods::MoveParams.can_be_moved?(parameters, route.request_method)
189
+ if GrapeSwagger::DocMethods::MoveParams.can_be_moved?(route.request_method, parameters)
191
190
  parameters = GrapeSwagger::DocMethods::MoveParams.to_definition(path, parameters, route, @definitions)
192
191
  end
193
192
 
193
+ GrapeSwagger::DocMethods::FormatData.to_format(parameters)
194
+
194
195
  parameters.presence
195
196
  end
196
197
 
197
- def response_object(route)
198
+ def response_object(route, options)
198
199
  codes = http_codes_from_route(route)
199
200
  codes.map! { |x| x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x }
200
201
 
@@ -219,7 +220,7 @@ module Grape
219
220
 
220
221
  @definitions[response_model][:description] = description_object(route)
221
222
 
222
- memo[value[:code]][:schema] = build_reference(route, value, response_model)
223
+ memo[value[:code]][:schema] = build_reference(route, value, response_model, options)
223
224
  memo[value[:code]][:examples] = value[:examples] if value[:examples]
224
225
  end
225
226
  end
@@ -250,20 +251,38 @@ module Grape
250
251
  def tag_object(route, path)
251
252
  version = GrapeSwagger::DocMethods::Version.get(route)
252
253
  version = [version] unless version.is_a?(Array)
253
-
254
+ prefix = route.prefix.to_s.split('/').reject(&:empty?)
254
255
  Array(
255
256
  path.split('{')[0].split('/').reject(&:empty?).delete_if do |i|
256
- i == route.prefix.to_s || version.map(&:to_s).include?(i)
257
+ prefix.include?(i) || version.map(&:to_s).include?(i)
257
258
  end.first
258
259
  ).presence
259
260
  end
260
261
 
261
262
  private
262
263
 
263
- def build_reference(route, value, response_model)
264
+ def build_reference(route, value, response_model, settings)
264
265
  # TODO: proof that the definition exist, if model isn't specified
265
266
  reference = { '$ref' => "#/definitions/#{response_model}" }
266
- route.options[:is_array] && value[:code] < 300 ? { type: 'array', items: reference } : reference
267
+ return reference unless value[:code] < 300
268
+
269
+ reference = { type: 'array', items: reference } if route.options[:is_array]
270
+ build_root(route, reference, response_model, settings)
271
+ end
272
+
273
+ def build_root(route, reference, response_model, settings)
274
+ default_root = response_model.underscore
275
+ default_root = default_root.pluralize if route.options[:is_array]
276
+ case route.settings.dig(:swagger, :root)
277
+ when true
278
+ { type: 'object', properties: { default_root => reference } }
279
+ when false
280
+ reference
281
+ when nil
282
+ settings[:add_root] ? { type: 'object', properties: { default_root => reference } } : reference
283
+ else
284
+ { type: 'object', properties: { route.settings.dig(:swagger, :root) => reference } }
285
+ end
267
286
  end
268
287
 
269
288
  def file_response?(value)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GrapeSwagger
4
- VERSION = '0.33.0'
4
+ VERSION = '0.34.2'
5
5
  end
data/lib/grape-swagger.rb CHANGED
@@ -26,7 +26,7 @@ module SwaggerRouting
26
26
  def combine_routes(app, doc_klass)
27
27
  app.routes.each do |route|
28
28
  route_path = route.path
29
- route_match = route_path.split(/^.*?#{route.prefix.to_s}/).last
29
+ route_match = route_path.split(/^.*?#{route.prefix}/).last
30
30
  next unless route_match
31
31
 
32
32
  route_match = route_match.match('\/([\w|-]*?)[\.\/\(]') || route_match.match('\/([\w|-]*)$')
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe '751 deeply nested objects' do
6
+ let(:app) do
7
+ Class.new(Grape::API) do
8
+ content_type :json, 'application/json; charset=UTF-8'
9
+ default_format :json
10
+ class Vrp < Grape::API
11
+ def self.vrp_request_timewindow(this)
12
+ this.optional(:start, types: [String, Float, Integer])
13
+ this.optional(:end, types: [String, Float, Integer])
14
+ end
15
+
16
+ def self.vrp_request_point(this)
17
+ this.requires(:id, type: String, allow_blank: false)
18
+ this.optional(:matrix_index, type: Integer)
19
+ this.optional(:location, type: Hash) do
20
+ requires(:lat, type: Float, allow_blank: false)
21
+ requires(:lon, type: Float, allow_blank: false)
22
+ end
23
+ this.at_least_one_of :matrix_index, :location
24
+ end
25
+
26
+ def self.vrp_request_activity(this)
27
+ this.optional(:duration, types: [String, Float, Integer])
28
+ this.requires(:point_id, type: String, allow_blank: false)
29
+ this.optional(:timewindows, type: Array) do
30
+ Vrp.vrp_request_timewindow(self)
31
+ end
32
+ end
33
+
34
+ def self.vrp_request_service(this)
35
+ this.requires(:id, type: String, allow_blank: false)
36
+ this.optional(:skills, type: Array[String])
37
+
38
+ this.optional(:activity, type: Hash) do
39
+ Vrp.vrp_request_activity(self)
40
+ end
41
+ this.optional(:activities, type: Array) do
42
+ Vrp.vrp_request_activity(self)
43
+ end
44
+ this.mutually_exclusive :activity, :activities
45
+ end
46
+ end
47
+
48
+ namespace :vrp do
49
+ resource :submit do
50
+ desc 'Submit Problems', nickname: 'vrp'
51
+ params do
52
+ optional(:vrp, type: Hash, documentation: { param_type: 'body' }) do
53
+ optional(:points, type: Array) do
54
+ Vrp.vrp_request_point(self)
55
+ end
56
+
57
+ optional(:services, type: Array) do
58
+ Vrp.vrp_request_service(self)
59
+ end
60
+ end
61
+ end
62
+ post do
63
+ { vrp: params[:vrp] }.to_json
64
+ end
65
+ end
66
+ end
67
+
68
+ add_swagger_documentation format: :json
69
+ end
70
+ end
71
+
72
+ subject do
73
+ get '/swagger_doc'
74
+ JSON.parse(last_response.body)
75
+ end
76
+
77
+ describe 'Correctness of vrp Points' do
78
+ let(:get_points_response) { subject['definitions']['postVrpSubmit']['properties']['vrp']['properties']['points'] }
79
+ specify do
80
+ expect(get_points_response).to eql(
81
+ 'type' => 'array',
82
+ 'items' => {
83
+ 'type' => 'object',
84
+ 'properties' => {
85
+ 'id' => {
86
+ 'type' => 'string'
87
+ },
88
+ 'matrix_index' => {
89
+ 'type' => 'integer',
90
+ 'format' => 'int32'
91
+ },
92
+ 'location' => {
93
+ 'type' => 'object',
94
+ 'properties' => {
95
+ 'lat' => {
96
+ 'type' => 'number',
97
+ 'format' => 'float'
98
+ },
99
+ 'lon' => {
100
+ 'type' => 'number',
101
+ 'format' => 'float'
102
+ }
103
+ },
104
+ 'required' => %w[lat lon]
105
+ }
106
+ },
107
+ 'required' => ['id']
108
+ }
109
+ )
110
+ end
111
+ end
112
+
113
+ describe 'Correctness of vrp Services' do
114
+ let(:get_service_response) { subject['definitions']['postVrpSubmit']['properties']['vrp']['properties']['services'] }
115
+ specify do
116
+ expect(get_service_response).to include(
117
+ 'type' => 'array',
118
+ 'items' => {
119
+ 'type' => 'object',
120
+ 'properties' => {
121
+ 'id' => {
122
+ 'type' => 'string'
123
+ },
124
+ 'skills' => {
125
+ 'type' => 'array',
126
+ 'items' => {
127
+ 'type' => 'string'
128
+ }
129
+ },
130
+ 'activity' => {
131
+ 'type' => 'object',
132
+ 'properties' => {
133
+ 'duration' => {
134
+ 'type' => 'string'
135
+ },
136
+ 'point_id' => {
137
+ 'type' => 'string'
138
+ },
139
+ 'timewindows' => {
140
+ 'type' => 'array',
141
+ 'items' => {
142
+ 'type' => 'object',
143
+ 'properties' => {
144
+ 'start' => {
145
+ 'type' => 'string'
146
+ },
147
+ 'end' => {
148
+ 'type' => 'string'
149
+ }
150
+ }
151
+ }
152
+ }
153
+ },
154
+ 'required' => ['point_id']
155
+ }, 'activities' => {
156
+ 'type' => 'array',
157
+ 'items' => {
158
+ 'type' => 'object',
159
+ 'properties' => {
160
+ 'duration' => {
161
+ 'type' => 'string'
162
+ },
163
+ 'point_id' => {
164
+ 'type' => 'string'
165
+ },
166
+ 'timewindows' => {
167
+ 'type' => 'array',
168
+ 'items' => {
169
+ 'type' => 'object',
170
+ 'properties' => {
171
+ 'start' => {
172
+ 'type' => 'string'
173
+ },
174
+ 'end' => {
175
+ 'type' => 'string'
176
+ }
177
+ }
178
+ }
179
+ }
180
+ },
181
+ 'required' => ['point_id']
182
+ }
183
+ }
184
+ },
185
+ 'required' => ['id']
186
+ }
187
+ )
188
+ end
189
+ end
190
+ end
@@ -46,39 +46,63 @@ describe GrapeSwagger::Endpoint::ParamsParser do
46
46
  context 'when param is nested in a param of array type' do
47
47
  let(:params) { [['param_1', { type: 'Array' }], ['param_1[param_2]', { type: 'String' }]] }
48
48
 
49
- it 'skips root parameter' do
50
- is_expected.not_to have_key 'param_1'
51
- end
52
-
53
- it 'adds is_array option to the nested param' do
54
- expect(parse_request_params['param_1[param_2]']).to eq(type: 'String', is_array: true)
55
- end
56
-
57
49
  context 'and array_use_braces setting set to true' do
58
50
  let(:settings) { { array_use_braces: true } }
59
51
 
60
52
  it 'adds braces to the param key' do
61
- expect(parse_request_params.keys.first).to eq 'param_1[][param_2]'
53
+ expect(parse_request_params.keys.last).to eq 'param_1[param_2]'
62
54
  end
63
55
  end
64
56
  end
65
57
 
66
58
  context 'when param is nested in a param of hash type' do
67
- let(:params) { [['param_1', { type: 'Hash' }], ['param_1[param_2]', { type: 'String' }]] }
68
-
69
- it 'skips root parameter' do
70
- is_expected.not_to have_key 'param_1'
71
- end
72
-
73
- it 'does not change options to the nested param' do
74
- expect(parse_request_params['param_1[param_2]']).to eq(type: 'String')
75
- end
59
+ let(:params) { [param_1, param_2] }
60
+ let(:param_1) { ['param_1', { type: 'Hash' }] }
61
+ let(:param_2) { ['param_1[param_2]', { type: 'String' }] }
76
62
 
77
63
  context 'and array_use_braces setting set to true' do
78
64
  let(:settings) { { array_use_braces: true } }
79
65
 
80
- it 'does not add braces to the param key' do
81
- expect(parse_request_params.keys.first).to eq 'param_1[param_2]'
66
+ context 'and param is of simple type' do
67
+ it 'does not add braces to the param key' do
68
+ expect(parse_request_params.keys.last).to eq 'param_1[param_2]'
69
+ end
70
+ end
71
+
72
+ context 'and param is of array type' do
73
+ let(:param_2) { ['param_1[param_2]', { type: 'Array[String]' }] }
74
+
75
+ it 'adds braces to the param key' do
76
+ expect(parse_request_params.keys.last).to eq 'param_1[param_2][]'
77
+ end
78
+
79
+ context 'and `param_type` option is set to body' do
80
+ let(:param_2) do
81
+ ['param_1[param_2]', { type: 'Array[String]', documentation: { param_type: 'body' } }]
82
+ end
83
+
84
+ it 'does not add braces to the param key' do
85
+ expect(parse_request_params.keys.last).to eq 'param_1[param_2]'
86
+ end
87
+ end
88
+
89
+ context 'and `in` option is set to body' do
90
+ let(:param_2) do
91
+ ['param_1[param_2]', { type: 'Array[String]', documentation: { in: 'body' } }]
92
+ end
93
+
94
+ it 'does not add braces to the param key' do
95
+ expect(parse_request_params.keys.last).to eq 'param_1[param_2]'
96
+ end
97
+ end
98
+
99
+ context 'and hash `param_type` option is set to body' do
100
+ let(:param_1) { ['param_1', { type: 'Hash', documentation: { param_type: 'body' } }] }
101
+
102
+ it 'does not add braces to the param key' do
103
+ expect(parse_request_params.keys.last).to eq 'param_1[param_2]'
104
+ end
105
+ end
82
106
  end
83
107
  end
84
108
  end
@@ -109,7 +109,7 @@ describe Grape::Endpoint do
109
109
  ['id', { required: true, type: 'String' }],
110
110
  ['description', { required: false, type: 'String' }],
111
111
  ['stuffs', { required: true, type: 'Array', is_array: true }],
112
- ['stuffs[id]', { required: true, type: 'String', is_array: true }]
112
+ ['stuffs[id]', { required: true, type: 'String' }]
113
113
  ]
114
114
  end
115
115
 
@@ -138,8 +138,8 @@ describe Grape::Endpoint do
138
138
  ['stuffs', { required: true, type: 'Array', is_array: true }],
139
139
  ['stuffs[owners]', { required: true, type: 'Array', is_array: true }],
140
140
  ['stuffs[creators]', { required: true, type: 'Array', is_array: true }],
141
- ['stuffs[owners][id]', { required: true, type: 'String', is_array: true }],
142
- ['stuffs[creators][id]', { required: true, type: 'String', is_array: true }],
141
+ ['stuffs[owners][id]', { required: true, type: 'String' }],
142
+ ['stuffs[creators][id]', { required: true, type: 'String' }],
143
143
  ['stuffs_and_things', { required: true, type: 'String' }]
144
144
  ]
145
145
  end