grape-swagger 0.33.0 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
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 +8 -9
  5. data/CHANGELOG.md +67 -5
  6. data/Gemfile +4 -4
  7. data/README.md +66 -3
  8. data/grape-swagger.gemspec +1 -1
  9. data/lib/grape-swagger.rb +1 -1
  10. data/lib/grape-swagger/doc_methods.rb +2 -0
  11. data/lib/grape-swagger/doc_methods/build_model_definition.rb +0 -17
  12. data/lib/grape-swagger/doc_methods/extensions.rb +6 -1
  13. data/lib/grape-swagger/doc_methods/format_data.rb +51 -0
  14. data/lib/grape-swagger/doc_methods/move_params.rb +22 -49
  15. data/lib/grape-swagger/doc_methods/parse_params.rb +6 -0
  16. data/lib/grape-swagger/endpoint.rb +32 -13
  17. data/lib/grape-swagger/endpoint/params_parser.rb +10 -17
  18. data/lib/grape-swagger/version.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 +22 -9
  37. data/spec/swagger_v2/description_not_initialized.rb +0 -39
  38. data/spec/swagger_v2/parent_less_namespace.rb +0 -49
@@ -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
@@ -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.0'
5
5
  end
@@ -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
@@ -3,6 +3,16 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe GrapeSwagger::DocMethods::Extensions do
6
+ context 'it should not break method introspection' do
7
+ describe '.method' do
8
+ describe 'method introspection' do
9
+ specify do
10
+ expect(described_class.method(described_class.methods.first)).to be_a(Method)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
6
16
  describe '#find_definition' do
7
17
  subject { described_class }
8
18
 
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe GrapeSwagger::DocMethods::FormatData do
6
+ let(:subject) { GrapeSwagger::DocMethods::FormatData }
7
+
8
+ [true, false].each do |array_use_braces|
9
+ context 'when param is nested in a param of array type' do
10
+ let(:braces) { array_use_braces ? '[]' : '' }
11
+ let(:params) do
12
+ [
13
+ { in: 'formData', name: "param1#{braces}", type: 'array', items: { type: 'string' } },
14
+ { in: 'formData', name: 'param1[param2]', type: 'string' }
15
+ ]
16
+ end
17
+
18
+ it 'skips root parameter' do
19
+ expect(subject.to_format(params).first).not_to have_key "param1#{braces}"
20
+ end
21
+
22
+ it 'Move array type to param2' do
23
+ expect(subject.to_format(params).first).to include(name: "param1#{braces}[param2]", type: 'array')
24
+ end
25
+ end
26
+ end
27
+
28
+ context 'when param is nested in a param of hash type' do
29
+ let(:params) { [{ in: 'formData', type: 'object', name: 'param1' }, { in: 'formData', name: 'param1[param2]', type: 'string' }] }
30
+
31
+ it 'skips root parameter' do
32
+ expect(subject.to_format(params).first).not_to have_key 'param1'
33
+ end
34
+
35
+ it 'Move array type to param2' do
36
+ expect(subject.to_format(params).first).to include(name: 'param1[param2]', type: 'string')
37
+ end
38
+ end
39
+
40
+ context 'when params contain a complex array' do
41
+ let(:params) do
42
+ [
43
+ { name: 'id', required: true, type: 'string' },
44
+ { name: 'description', required: false, type: 'string' },
45
+ { name: 'stuffs', required: true, type: 'array' },
46
+ { name: 'stuffs[id]', required: true, type: 'string' }
47
+ ]
48
+ end
49
+
50
+ let(:expected_params) do
51
+ [
52
+ { name: 'id', required: true, type: 'string' },
53
+ { name: 'description', required: false, type: 'string' },
54
+ { name: 'stuffs[id]', required: true, type: 'array', items: { type: 'string' } }
55
+ ]
56
+ end
57
+
58
+ it 'parses params correctly and adds array type all concerned elements' do
59
+ expect(subject.to_format(params)).to eq expected_params
60
+ end
61
+
62
+ context 'when array params are not contiguous with parent array' do
63
+ let(:params) do
64
+ [
65
+ { name: 'id', required: true, type: 'string' },
66
+ { name: 'description', required: false, type: 'string' },
67
+ { name: 'stuffs', required: true, type: 'array' },
68
+ { name: 'stuffs[owners]', required: true, type: 'array' },
69
+ { name: 'stuffs[creators]', required: true, type: 'array' },
70
+ { name: 'stuffs[owners][id]', required: true, type: 'string' },
71
+ { name: 'stuffs[creators][id]', required: true, type: 'string' },
72
+ { name: 'stuffs_and_things', required: true, type: 'string' }
73
+ ]
74
+ end
75
+
76
+ let(:expected_params) do
77
+ [
78
+ { name: 'id', required: true, type: 'string' },
79
+ { name: 'description', required: false, type: 'string' },
80
+ { name: 'stuffs[owners][id]', required: true, type: 'array', items: { type: 'string' } },
81
+ { name: 'stuffs[creators][id]', required: true, type: 'array', items: { type: 'string' } },
82
+ { name: 'stuffs_and_things', required: true, type: 'string' }
83
+ ]
84
+ end
85
+
86
+ it 'parses params correctly and adds array type all concerned elements' do
87
+ expect(subject.to_format(params)).to eq expected_params
88
+ end
89
+ end
90
+ end
91
+ end