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.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -7
- data/.rubocop_todo.yml +0 -6
- data/.travis.yml +8 -9
- data/CHANGELOG.md +67 -5
- data/Gemfile +4 -4
- data/README.md +66 -3
- data/grape-swagger.gemspec +1 -1
- data/lib/grape-swagger.rb +1 -1
- data/lib/grape-swagger/doc_methods.rb +2 -0
- data/lib/grape-swagger/doc_methods/build_model_definition.rb +0 -17
- data/lib/grape-swagger/doc_methods/extensions.rb +6 -1
- data/lib/grape-swagger/doc_methods/format_data.rb +51 -0
- data/lib/grape-swagger/doc_methods/move_params.rb +22 -49
- data/lib/grape-swagger/doc_methods/parse_params.rb +6 -0
- data/lib/grape-swagger/endpoint.rb +32 -13
- data/lib/grape-swagger/endpoint/params_parser.rb +10 -17
- data/lib/grape-swagger/version.rb +1 -1
- data/spec/issues/751_deeply_nested_objects_spec.rb +190 -0
- data/spec/lib/endpoint/params_parser_spec.rb +44 -20
- data/spec/lib/endpoint_spec.rb +3 -3
- data/spec/lib/extensions_spec.rb +10 -0
- data/spec/lib/format_data_spec.rb +91 -0
- data/spec/lib/move_params_spec.rb +4 -266
- data/spec/lib/optional_object_spec.rb +0 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/swagger_v2/api_swagger_v2_hash_and_array_spec.rb +3 -1
- data/spec/swagger_v2/api_swagger_v2_response_with_root_spec.rb +153 -0
- data/spec/swagger_v2/description_not_initialized_spec.rb +39 -0
- data/spec/swagger_v2/endpoint_versioned_path_spec.rb +33 -0
- data/spec/swagger_v2/mounted_target_class_spec.rb +1 -1
- data/spec/swagger_v2/namespace_tags_prefix_spec.rb +15 -1
- data/spec/swagger_v2/params_array_spec.rb +2 -2
- data/spec/swagger_v2/parent_less_namespace_spec.rb +32 -0
- data/spec/swagger_v2/{reference_entity.rb → reference_entity_spec.rb} +17 -10
- metadata +22 -9
- data/spec/swagger_v2/description_not_initialized.rb +0 -39
- 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
|
28
|
+
memo[name] = options
|
42
29
|
end
|
43
30
|
end
|
44
31
|
|
45
32
|
private
|
46
33
|
|
47
|
-
def array_use_braces?
|
48
|
-
settings[:array_use_braces] && !
|
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
|
@@ -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.
|
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) { [
|
68
|
-
|
69
|
-
|
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
|
-
|
81
|
-
|
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
|
data/spec/lib/endpoint_spec.rb
CHANGED
@@ -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'
|
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'
|
142
|
-
['stuffs[creators][id]', { required: true, type: 'String'
|
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
|
data/spec/lib/extensions_spec.rb
CHANGED
@@ -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
|