committee 5.6.1 → 5.6.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/committee/drivers/open_api_3/driver.rb +5 -0
  3. data/lib/committee/drivers.rb +2 -3
  4. data/lib/committee/errors.rb +11 -0
  5. data/lib/committee/middleware/base.rb +11 -5
  6. data/lib/committee/middleware/options/base.rb +107 -0
  7. data/lib/committee/middleware/options/request_validation.rb +80 -0
  8. data/lib/committee/middleware/options/response_validation.rb +46 -0
  9. data/lib/committee/middleware/options.rb +12 -0
  10. data/lib/committee/middleware/request_validation.rb +7 -1
  11. data/lib/committee/middleware/response_validation.rb +6 -2
  12. data/lib/committee/middleware.rb +1 -0
  13. data/lib/committee/schema_validator/hyper_schema/response_generator.rb +1 -3
  14. data/lib/committee/schema_validator/hyper_schema/response_validator.rb +14 -1
  15. data/lib/committee/schema_validator/hyper_schema/router.rb +1 -1
  16. data/lib/committee/schema_validator/hyper_schema.rb +2 -13
  17. data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +43 -13
  18. data/lib/committee/schema_validator/open_api_3/parameter_deserializer.rb +495 -0
  19. data/lib/committee/schema_validator/open_api_3/response_validator.rb +16 -2
  20. data/lib/committee/schema_validator/open_api_3.rb +18 -12
  21. data/lib/committee/schema_validator/option.rb +6 -17
  22. data/lib/committee/schema_validator.rb +5 -1
  23. data/lib/committee/test/except_parameter.rb +416 -0
  24. data/lib/committee/test/methods.rb +27 -2
  25. data/lib/committee/version.rb +1 -1
  26. data/lib/committee.rb +1 -1
  27. data/test/drivers/open_api_2/driver_test.rb +4 -16
  28. data/test/drivers/open_api_2/parameter_schema_builder_test.rb +4 -50
  29. data/test/drivers_test.rb +35 -21
  30. data/test/middleware/options/base_test.rb +120 -0
  31. data/test/middleware/options/request_validation_test.rb +177 -0
  32. data/test/middleware/options/response_validation_test.rb +121 -0
  33. data/test/middleware/request_validation_open_api_3_test.rb +113 -80
  34. data/test/middleware/request_validation_test.rb +13 -70
  35. data/test/middleware/response_validation_open_api_3_test.rb +33 -17
  36. data/test/middleware/response_validation_test.rb +3 -14
  37. data/test/request_unpacker_test.rb +2 -10
  38. data/test/schema_validator/hyper_schema/parameter_coercer_test.rb +1 -37
  39. data/test/schema_validator/hyper_schema/request_validator_test.rb +6 -30
  40. data/test/schema_validator/hyper_schema/router_test.rb +5 -0
  41. data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +1 -37
  42. data/test/schema_validator/open_api_3/operation_wrapper_test.rb +40 -43
  43. data/test/schema_validator/open_api_3/parameter_deserializer_test.rb +457 -0
  44. data/test/schema_validator/open_api_3/request_validator_test.rb +1 -2
  45. data/test/schema_validator/open_api_3/response_validator_test.rb +3 -11
  46. data/test/schema_validator_test.rb +8 -0
  47. data/test/test/methods_test.rb +222 -105
  48. data/test/test/schema_coverage_test.rb +8 -155
  49. metadata +12 -2
@@ -53,43 +53,7 @@ describe Committee::SchemaValidator::HyperSchema::StringParamsCoercer do
53
53
  end
54
54
 
55
55
  it "pass array property" do
56
- params = {
57
- "array_property" => [
58
- {
59
- "update_time" => "2016-04-01T16:00:00.000+09:00",
60
- "per_page" => "1",
61
- "nested_coercer_object" => {
62
- "update_time" => "2016-04-01T16:00:00.000+09:00",
63
- "threshold" => "1.5"
64
- },
65
- "nested_no_coercer_object" => {
66
- "per_page" => "1",
67
- "threshold" => "1.5"
68
- },
69
- "nested_coercer_array" => [
70
- {
71
- "update_time" => "2016-04-01T16:00:00.000+09:00",
72
- "threshold" => "1.5"
73
- }
74
- ],
75
- "nested_no_coercer_array" => [
76
- {
77
- "per_page" => "1",
78
- "threshold" => "1.5"
79
- }
80
- ]
81
- },
82
- {
83
- "update_time" => "2016-04-01T16:00:00.000+09:00",
84
- "per_page" => "1",
85
- "threshold" => "1.5"
86
- },
87
- {
88
- "threshold" => "1.5",
89
- "per_page" => "1"
90
- }
91
- ],
92
- }
56
+ params = { "array_property" => [{ "update_time" => "2016-04-01T16:00:00.000+09:00", "per_page" => "1", "nested_coercer_object" => { "update_time" => "2016-04-01T16:00:00.000+09:00", "threshold" => "1.5" }, "nested_no_coercer_object" => { "per_page" => "1", "threshold" => "1.5" }, "nested_coercer_array" => [{ "update_time" => "2016-04-01T16:00:00.000+09:00", "threshold" => "1.5" }], "nested_no_coercer_array" => [{ "per_page" => "1", "threshold" => "1.5" }] }, { "update_time" => "2016-04-01T16:00:00.000+09:00", "per_page" => "1", "threshold" => "1.5" }, { "threshold" => "1.5", "per_page" => "1" }], }
93
57
  call(params, coerce_recursive: true)
94
58
 
95
59
  first_data = params["array_property"][0]
@@ -18,13 +18,7 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
18
18
 
19
19
  HEADER = { 'Content-Type' => 'application/json' }
20
20
 
21
- SCHEMA_PROPERTIES_PAIR = [
22
- ['string', 'str'],
23
- ['integer', 1],
24
- ['boolean', true],
25
- ['boolean', false],
26
- ['number', 0.1],
27
- ]
21
+ SCHEMA_PROPERTIES_PAIR = [['string', 'str'], ['integer', 1], ['boolean', true], ['boolean', false], ['number', 0.1],]
28
22
 
29
23
  it 'correct data' do
30
24
  operation_object.validate_request_params({}, {}, SCHEMA_PROPERTIES_PAIR.to_h, HEADER, @validator_option)
@@ -32,20 +26,7 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
32
26
  end
33
27
 
34
28
  it 'correct object data' do
35
- operation_object.validate_request_params(
36
- {},
37
- {},
38
- {
39
- "object_1" =>
40
- {
41
- "string_1" => nil,
42
- "integer_1" => nil,
43
- "boolean_1" => nil,
44
- "number_1" => nil
45
- }
46
- },
47
- HEADER,
48
- @validator_option)
29
+ operation_object.validate_request_params({}, {}, { "object_1" => { "string_1" => nil, "integer_1" => nil, "boolean_1" => nil, "number_1" => nil } }, HEADER, @validator_option)
49
30
 
50
31
  assert true
51
32
  end
@@ -93,21 +74,9 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
93
74
  end
94
75
 
95
76
  it 'correct' do
96
- operation_object.validate_request_params(
97
- {},
98
- { "query_string" => "query", "query_integer_list" => [1, 2] },
99
- {},
100
- HEADER,
101
- @validator_option
102
- )
103
-
104
- operation_object.validate_request_params(
105
- {},
106
- { "query_string" => "query", "query_integer_list" => [1, 2], "optional_integer" => 1 },
107
- {},
108
- HEADER,
109
- @validator_option
110
- )
77
+ operation_object.validate_request_params({}, { "query_string" => "query", "query_integer_list" => [1, 2] }, {}, HEADER, @validator_option)
78
+
79
+ operation_object.validate_request_params({}, { "query_string" => "query", "query_integer_list" => [1, 2], "optional_integer" => 1 }, {}, HEADER, @validator_option)
111
80
 
112
81
  assert true
113
82
  end
@@ -123,13 +92,7 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
123
92
 
124
93
  it 'invalid type' do
125
94
  e = assert_raises(Committee::InvalidRequest) {
126
- operation_object.validate_request_params(
127
- {},
128
- { "query_string" => 1, "query_integer_list" => [1, 2], "optional_integer" => 1 },
129
- {},
130
- HEADER,
131
- @validator_option
132
- )
95
+ operation_object.validate_request_params({}, { "query_string" => 1, "query_integer_list" => [1, 2], "optional_integer" => 1 }, {}, HEADER, @validator_option)
133
96
  }
134
97
 
135
98
  assert_match(/expected string, but received Integer: 1/i, e.message)
@@ -208,5 +171,39 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
208
171
  assert_equal [], operation_object.request_content_types
209
172
  end
210
173
  end
174
+
175
+ describe 'coercion option wiring' do
176
+ before do
177
+ @path = '/coerce_path_params/1'
178
+ @method = 'get'
179
+ end
180
+
181
+ it 'uses coerce_path_params for explicit path coercion' do
182
+ disabled = Committee::SchemaValidator::Option.new({ coerce_path_params: false, coerce_query_params: true }, open_api_3_schema, :open_api_3)
183
+ enabled = Committee::SchemaValidator::Option.new({ coerce_path_params: true, coerce_query_params: false }, open_api_3_schema, :open_api_3)
184
+
185
+ error = assert_raises(Committee::InvalidRequest) do
186
+ operation_object.coerce_path_parameter(disabled)
187
+ end
188
+ assert_match(/expected integer, but received String: "1"/i, error.message)
189
+
190
+ coerced = operation_object.coerce_path_parameter(enabled)
191
+ assert_kind_of(Integer, coerced['integer'])
192
+ end
193
+
194
+ it 'uses coerce_query_params for request parameter validation' do
195
+ query_coercion_disabled = Committee::SchemaValidator::Option.new({ coerce_query_params: false }, open_api_3_schema, :open_api_3)
196
+ query_coercion_enabled = Committee::SchemaValidator::Option.new({ coerce_query_params: true }, open_api_3_schema, :open_api_3)
197
+
198
+ error = assert_raises(Committee::InvalidRequest) do
199
+ @path = '/characters'
200
+ operation_object.validate_request_params({}, { "limit" => "1" }, {}, HEADER, query_coercion_disabled)
201
+ end
202
+ assert_match(/expected integer, but received String: "1"/i, error.message)
203
+
204
+ @path = '/characters'
205
+ operation_object.validate_request_params({}, { "limit" => "1" }, {}, HEADER, query_coercion_enabled)
206
+ end
207
+ end
211
208
  end
212
209
  end
@@ -0,0 +1,457 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::SchemaValidator::OpenAPI3::ParameterDeserializer do
6
+ before do
7
+ @validator_option = Committee::SchemaValidator::Option.new({}, open_api_3_schema, :open_api_3)
8
+ end
9
+
10
+ describe 'deserialize_parameters option' do
11
+ it 'is enabled by default for OpenAPI 3' do
12
+ option = Committee::SchemaValidator::Option.new({}, open_api_3_schema, :open_api_3)
13
+ assert_equal true, option.deserialize_parameters
14
+ end
15
+
16
+ it 'can be disabled' do
17
+ option = Committee::SchemaValidator::Option.new({ deserialize_parameters: false }, open_api_3_schema, :open_api_3)
18
+ assert_equal false, option.deserialize_parameters
19
+ end
20
+ end
21
+
22
+ describe 'parameter deserialization' do
23
+ it 'deserializes form style object with explode=true' do
24
+ raw_params = { 'role' => 'admin', 'status' => 'active' }
25
+ param = create_param('filter', 'query', 'form', true, 'object', { 'role' => 'string', 'status' => 'string' })
26
+ deserializer = create_deserializer([param])
27
+ result = deserializer.deserialize_query_params(raw_params)
28
+
29
+ assert_equal({ 'filter' => { 'role' => 'admin', 'status' => 'active' } }, result)
30
+ assert_equal 'admin', result[:filter][:role]
31
+ end
32
+
33
+ it 'deserializes form style object with explode=false' do
34
+ raw_params = { 'filter' => 'role,admin,status,active' }
35
+ param = create_param('filter', 'query', 'form', false, 'object', { 'role' => 'string', 'status' => 'string' })
36
+ deserializer = create_deserializer([param])
37
+ result = deserializer.deserialize_query_params(raw_params)
38
+
39
+ assert_equal({ 'filter' => { 'role' => 'admin', 'status' => 'active' } }, result)
40
+ end
41
+
42
+ it 'deserializes form style array with explode=true' do
43
+ raw_params = { 'ids' => ['1', '2', '3'] }
44
+ param = create_param('ids', 'query', 'form', true, 'array', 'integer')
45
+ deserializer = create_deserializer([param])
46
+ result = deserializer.deserialize_query_params(raw_params)
47
+
48
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
49
+ end
50
+
51
+ it 'deserializes form style array with explode=false' do
52
+ raw_params = { 'ids' => '1,2,3' }
53
+ param = create_param('ids', 'query', 'form', false, 'array', 'integer')
54
+ deserializer = create_deserializer([param])
55
+ result = deserializer.deserialize_query_params(raw_params)
56
+
57
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
58
+ end
59
+
60
+ it 'deserializes deepObject style' do
61
+ raw_params = { 'filter[role]' => 'admin', 'filter[status]' => 'active' }
62
+ param = create_param('filter', 'query', 'deepObject', true, 'object', { 'role' => 'string', 'status' => 'string' })
63
+ deserializer = create_deserializer([param])
64
+ result = deserializer.deserialize_query_params(raw_params)
65
+
66
+ assert_equal({ 'filter' => { 'role' => 'admin', 'status' => 'active' } }, result)
67
+ end
68
+
69
+ it 'deserializes spaceDelimited style' do
70
+ raw_params = { 'ids' => '1 2 3' }
71
+ param = create_param('ids', 'query', 'spaceDelimited', false, 'array', 'integer')
72
+ deserializer = create_deserializer([param])
73
+ result = deserializer.deserialize_query_params(raw_params)
74
+
75
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
76
+ end
77
+
78
+ it 'deserializes pipeDelimited style' do
79
+ raw_params = { 'ids' => '1|2|3' }
80
+ param = create_param('ids', 'query', 'pipeDelimited', false, 'array', 'integer')
81
+ deserializer = create_deserializer([param])
82
+ result = deserializer.deserialize_query_params(raw_params)
83
+
84
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
85
+ end
86
+
87
+ it 'deserializes simple style array with explode=false for path params' do
88
+ raw_params = { 'ids' => '1,2,3' }
89
+ param = create_param('ids', 'path', 'simple', false, 'array', 'integer')
90
+ deserializer = create_deserializer([param])
91
+ result = deserializer.deserialize_path_params(raw_params)
92
+
93
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
94
+ end
95
+
96
+ it 'deserializes simple style array with explode=true for path params' do
97
+ raw_params = { 'ids' => ['1', '2', '3'] }
98
+ param = create_param('ids', 'path', 'simple', true, 'array', 'integer')
99
+ deserializer = create_deserializer([param])
100
+ result = deserializer.deserialize_path_params(raw_params)
101
+
102
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
103
+ end
104
+
105
+ it 'deserializes simple style object with explode=false for path params' do
106
+ raw_params = { 'filter' => 'role,admin,status,active' }
107
+ param = create_param('filter', 'path', 'simple', false, 'object', { 'role' => 'string', 'status' => 'string' })
108
+ deserializer = create_deserializer([param])
109
+ result = deserializer.deserialize_path_params(raw_params)
110
+
111
+ assert_equal({ 'filter' => { 'role' => 'admin', 'status' => 'active' } }, result)
112
+ end
113
+
114
+ it 'deserializes simple style object with explode=true for path params' do
115
+ raw_params = { 'filter' => 'role=admin,status=active' }
116
+ param = create_param('filter', 'path', 'simple', true, 'object', { 'role' => 'string', 'status' => 'string' })
117
+ deserializer = create_deserializer([param])
118
+ result = deserializer.deserialize_path_params(raw_params)
119
+
120
+ assert_equal({ 'filter' => { 'role' => 'admin', 'status' => 'active' } }, result)
121
+ end
122
+
123
+ it 'deserializes label style array with explode=false' do
124
+ raw_params = { 'ids' => '.1,2,3' }
125
+ param = create_param('ids', 'path', 'label', false, 'array', 'integer')
126
+ deserializer = create_deserializer([param])
127
+ result = deserializer.deserialize_path_params(raw_params)
128
+
129
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
130
+ end
131
+
132
+ it 'deserializes label style array with explode=true' do
133
+ raw_params = { 'ids' => '.1.2.3' }
134
+ param = create_param('ids', 'path', 'label', true, 'array', 'integer')
135
+ deserializer = create_deserializer([param])
136
+ result = deserializer.deserialize_path_params(raw_params)
137
+
138
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
139
+ end
140
+
141
+ it 'deserializes label style object with explode=false' do
142
+ raw_params = { 'filter' => '.role,admin,status,active' }
143
+ param = create_param('filter', 'path', 'label', false, 'object', { 'role' => 'string', 'status' => 'string' })
144
+ deserializer = create_deserializer([param])
145
+ result = deserializer.deserialize_path_params(raw_params)
146
+
147
+ assert_equal({ 'filter' => { 'role' => 'admin', 'status' => 'active' } }, result)
148
+ end
149
+
150
+ it 'deserializes label style object with explode=true' do
151
+ raw_params = { 'filter' => '.role=admin.status=active' }
152
+ param = create_param('filter', 'path', 'label', true, 'object', { 'role' => 'string', 'status' => 'string' })
153
+ deserializer = create_deserializer([param])
154
+ result = deserializer.deserialize_path_params(raw_params)
155
+
156
+ assert_equal({ 'filter' => { 'role' => 'admin', 'status' => 'active' } }, result)
157
+ end
158
+
159
+ it 'deserializes matrix style array with explode=false' do
160
+ raw_params = { 'ids' => ';ids=1,2,3' }
161
+ param = create_param('ids', 'path', 'matrix', false, 'array', 'integer')
162
+ deserializer = create_deserializer([param])
163
+ result = deserializer.deserialize_path_params(raw_params)
164
+
165
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
166
+ end
167
+
168
+ it 'deserializes matrix style array with explode=true' do
169
+ raw_params = { 'ids' => ['1', '2', '3'] }
170
+ param = create_param('ids', 'path', 'matrix', true, 'array', 'integer')
171
+ deserializer = create_deserializer([param])
172
+ result = deserializer.deserialize_path_params(raw_params)
173
+
174
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
175
+ end
176
+
177
+ it 'deserializes matrix style object with explode=false' do
178
+ raw_params = { 'filter' => ';filter=role,admin,status,active' }
179
+ param = create_param('filter', 'path', 'matrix', false, 'object', { 'role' => 'string', 'status' => 'string' })
180
+ deserializer = create_deserializer([param])
181
+ result = deserializer.deserialize_path_params(raw_params)
182
+
183
+ assert_equal({ 'filter' => { 'role' => 'admin', 'status' => 'active' } }, result)
184
+ end
185
+
186
+ it 'deserializes matrix style object with explode=true' do
187
+ raw_params = { 'filter' => ';role=admin;status=active' }
188
+ param = create_param('filter', 'path', 'matrix', true, 'object', { 'role' => 'string', 'status' => 'string' })
189
+ deserializer = create_deserializer([param])
190
+ result = deserializer.deserialize_path_params(raw_params)
191
+
192
+ assert_equal({ 'filter' => { 'role' => 'admin', 'status' => 'active' } }, result)
193
+ end
194
+
195
+ it 'deserializes matrix style primitive' do
196
+ raw_params = { 'id' => ';id=5' }
197
+ param = create_param('id', 'path', 'matrix', false, 'string', nil)
198
+ deserializer = create_deserializer([param])
199
+ result = deserializer.deserialize_path_params(raw_params)
200
+
201
+ assert_equal({ 'id' => '5' }, result)
202
+ end
203
+
204
+ it 'deserializes headers with simple style' do
205
+ raw_params = { 'X-Filter' => 'role,admin,status,active' }
206
+ param = create_param('X-Filter', 'header', 'simple', false, 'object', { 'role' => 'string', 'status' => 'string' })
207
+ deserializer = create_deserializer([param])
208
+ result = deserializer.deserialize_headers(raw_params)
209
+
210
+ assert_equal({ 'X-Filter' => { 'role' => 'admin', 'status' => 'active' } }, result)
211
+ end
212
+
213
+ it 'handles empty params gracefully' do
214
+ raw_params = {}
215
+ param = create_param('filter', 'query', 'form', true, 'object', { 'role' => 'string' })
216
+ deserializer = create_deserializer([param])
217
+ result = deserializer.deserialize_query_params(raw_params)
218
+
219
+ assert_equal({}, result)
220
+ end
221
+
222
+ it 'handles nil param value' do
223
+ raw_params = { 'filter' => nil }
224
+ param = create_param('filter', 'query', 'form', false, 'object', { 'role' => 'string' })
225
+ deserializer = create_deserializer([param])
226
+ result = deserializer.deserialize_query_params(raw_params)
227
+
228
+ assert_equal({ 'filter' => nil }, result)
229
+ end
230
+
231
+ it 'preserves unknown parameters' do
232
+ raw_params = { 'role' => 'admin', 'unknown' => 'value' }
233
+ param = create_param('filter', 'query', 'form', true, 'object', { 'role' => 'string' })
234
+ deserializer = create_deserializer([param])
235
+ result = deserializer.deserialize_query_params(raw_params)
236
+
237
+ assert_equal 'admin', result[:filter][:role]
238
+ assert_equal 'value', result[:unknown]
239
+ end
240
+
241
+ it 'handles empty object with form style explode=true' do
242
+ raw_params = {}
243
+ param = create_param('filter', 'query', 'form', true, 'object', { 'role' => 'string', 'status' => 'string' })
244
+ deserializer = create_deserializer([param])
245
+ result = deserializer.deserialize_query_params(raw_params)
246
+
247
+ assert_equal({}, result)
248
+ end
249
+
250
+ it 'returns indifferent hashes' do
251
+ raw_params = { 'role' => 'admin' }
252
+ param = create_param('filter', 'query', 'form', true, 'object', { 'role' => 'string' })
253
+ deserializer = create_deserializer([param])
254
+ result = deserializer.deserialize_query_params(raw_params)
255
+
256
+ assert_equal({ 'role' => 'admin' }, result['filter'])
257
+ assert_equal 'admin', result['filter']['role']
258
+ assert_equal({ 'role' => 'admin' }, result[:filter])
259
+ assert_equal 'admin', result[:filter][:role]
260
+ end
261
+
262
+ it 'handles primitive values with form style' do
263
+ raw_params = { 'id' => '123' }
264
+ param = create_param('id', 'query', 'form', false, 'string', nil)
265
+ deserializer = create_deserializer([param])
266
+ result = deserializer.deserialize_query_params(raw_params)
267
+
268
+ assert_equal({ 'id' => '123' }, result)
269
+ end
270
+
271
+ it 'handles primitive values with simple style' do
272
+ raw_params = { 'id' => '123' }
273
+ param = create_param('id', 'path', 'simple', false, 'string', nil)
274
+ deserializer = create_deserializer([param])
275
+ result = deserializer.deserialize_path_params(raw_params)
276
+
277
+ assert_equal({ 'id' => '123' }, result)
278
+ end
279
+
280
+ it 'handles unsupported style gracefully' do
281
+ raw_params = { 'id' => '123' }
282
+ param = Struct.new(:name, :in, :style, :explode, :schema, :required).new('id', 'query', 'unsupported', false, create_schema('string', nil), false)
283
+ deserializer = create_deserializer([param])
284
+ result = deserializer.deserialize_query_params(raw_params)
285
+
286
+ assert_equal({ 'id' => '123' }, result)
287
+ end
288
+
289
+ it 'handles form array when value is already array' do
290
+ raw_params = { 'ids' => ['1', '2', '3'] }
291
+ param = create_param('ids', 'query', 'form', false, 'array', 'integer')
292
+ deserializer = create_deserializer([param])
293
+ result = deserializer.deserialize_query_params(raw_params)
294
+
295
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
296
+ end
297
+
298
+ it 'deserializes label style primitive value' do
299
+ raw_params = { 'id' => '.123' }
300
+ param = create_param('id', 'path', 'label', false, 'string', nil)
301
+ deserializer = create_deserializer([param])
302
+ result = deserializer.deserialize_path_params(raw_params)
303
+
304
+ assert_equal({ 'id' => '123' }, result)
305
+ end
306
+
307
+ it 'handles label style without leading dot' do
308
+ raw_params = { 'id' => '123' }
309
+ param = create_param('id', 'path', 'label', false, 'string', nil)
310
+ deserializer = create_deserializer([param])
311
+ result = deserializer.deserialize_path_params(raw_params)
312
+
313
+ assert_equal({ 'id' => '123' }, result)
314
+ end
315
+
316
+ it 'handles matrix style with missing prefix' do
317
+ raw_params = { 'ids' => 'invalid' }
318
+ param = create_param('ids', 'path', 'matrix', false, 'array', 'integer')
319
+ deserializer = create_deserializer([param])
320
+ result = deserializer.deserialize_path_params(raw_params)
321
+
322
+ assert_equal({ 'ids' => [] }, result)
323
+ end
324
+
325
+ it 'handles matrix style object with missing prefix' do
326
+ raw_params = { 'filter' => 'invalid' }
327
+ param = create_param('filter', 'path', 'matrix', false, 'object', { 'role' => 'string' })
328
+ deserializer = create_deserializer([param])
329
+ result = deserializer.deserialize_path_params(raw_params)
330
+
331
+ assert_equal({ 'filter' => {} }, result)
332
+ end
333
+
334
+ it 'handles matrix style primitive without prefix' do
335
+ raw_params = { 'id' => 'invalid' }
336
+ param = create_param('id', 'path', 'matrix', false, 'string', nil)
337
+ deserializer = create_deserializer([param])
338
+ result = deserializer.deserialize_path_params(raw_params)
339
+
340
+ assert_equal({ 'id' => 'invalid' }, result)
341
+ end
342
+
343
+ it 'handles space delimited when value is already array' do
344
+ raw_params = { 'ids' => ['1', '2', '3'] }
345
+ param = create_param('ids', 'query', 'spaceDelimited', false, 'array', 'integer')
346
+ deserializer = create_deserializer([param])
347
+ result = deserializer.deserialize_query_params(raw_params)
348
+
349
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
350
+ end
351
+
352
+ it 'handles pipe delimited when value is already array' do
353
+ raw_params = { 'ids' => ['1', '2', '3'] }
354
+ param = create_param('ids', 'query', 'pipeDelimited', false, 'array', 'integer')
355
+ deserializer = create_deserializer([param])
356
+ result = deserializer.deserialize_query_params(raw_params)
357
+
358
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
359
+ end
360
+
361
+ it 'handles simple array when value is already array' do
362
+ raw_params = { 'ids' => ['1', '2', '3'] }
363
+ param = create_param('ids', 'path', 'simple', false, 'array', 'integer')
364
+ deserializer = create_deserializer([param])
365
+ result = deserializer.deserialize_path_params(raw_params)
366
+
367
+ assert_equal({ 'ids' => ['1', '2', '3'] }, result)
368
+ end
369
+
370
+ it 'handles comma-separated object with odd number of elements' do
371
+ raw_params = { 'filter' => 'role,admin,status' }
372
+ param = create_param('filter', 'query', 'form', false, 'object', { 'role' => 'string', 'status' => 'string' })
373
+ deserializer = create_deserializer([param])
374
+ result = deserializer.deserialize_query_params(raw_params)
375
+
376
+ assert_equal({ 'filter' => { 'role' => 'admin' } }, result)
377
+ end
378
+
379
+ it 'handles matrix object exploded with malformed pairs' do
380
+ raw_params = { 'filter' => ';role=admin;invalid;status=active' }
381
+ param = create_param('filter', 'path', 'matrix', true, 'object', { 'role' => 'string', 'status' => 'string' })
382
+ deserializer = create_deserializer([param])
383
+ result = deserializer.deserialize_path_params(raw_params)
384
+
385
+ assert_equal({ 'filter' => { 'role' => 'admin', 'status' => 'active' } }, result)
386
+ end
387
+
388
+ it 'handles parameter without schema in simple style' do
389
+ raw_params = { 'id' => '123' }
390
+ param = Struct.new(:name, :in, :style, :explode, :schema, :required).new('id', 'path', 'simple', false, nil, false)
391
+ deserializer = create_deserializer([param])
392
+ result = deserializer.deserialize_path_params(raw_params)
393
+
394
+ assert_equal({ 'id' => '123' }, result)
395
+ end
396
+
397
+ it 'handles parameter without schema in label style' do
398
+ raw_params = { 'id' => '.123' }
399
+ param = Struct.new(:name, :in, :style, :explode, :schema, :required).new('id', 'path', 'label', false, nil, false)
400
+ deserializer = create_deserializer([param])
401
+ result = deserializer.deserialize_path_params(raw_params)
402
+
403
+ assert_equal({ 'id' => '123' }, result)
404
+ end
405
+
406
+ it 'handles parameter without schema in matrix style' do
407
+ raw_params = { 'id' => ';id=123' }
408
+ param = Struct.new(:name, :in, :style, :explode, :schema, :required).new('id', 'path', 'matrix', false, nil, false)
409
+ deserializer = create_deserializer([param])
410
+ result = deserializer.deserialize_path_params(raw_params)
411
+
412
+ assert_equal({ 'id' => ';id=123' }, result)
413
+ end
414
+
415
+ it 'handles parameter without schema in form style' do
416
+ raw_params = { 'id' => '123' }
417
+ param = Struct.new(:name, :in, :style, :explode, :schema, :required).new('id', 'query', 'form', false, nil, false)
418
+ deserializer = create_deserializer([param])
419
+ result = deserializer.deserialize_query_params(raw_params)
420
+
421
+ assert_equal({ 'id' => '123' }, result)
422
+ end
423
+
424
+ it 'handles form explode with symbol property keys' do
425
+ raw_params = { 'role' => 'admin', 'status' => 'active' }
426
+ # Create schema with symbol keys for properties
427
+ schema = Struct.new(:type, :properties, :items).new('object', { role: Struct.new(:type).new('string'), status: Struct.new(:type).new('string') }, nil)
428
+ param = Struct.new(:name, :in, :style, :explode, :schema, :required).new('filter', 'query', 'form', true, schema, false)
429
+ deserializer = create_deserializer([param])
430
+ result = deserializer.deserialize_query_params(raw_params)
431
+
432
+ assert_equal({ 'filter' => { role: 'admin', status: 'active' } }, result)
433
+ end
434
+ end
435
+
436
+ private
437
+
438
+ def create_deserializer(params)
439
+ operation = Struct.new(:operation_object).new(Struct.new(:parameters).new(params))
440
+ Committee::SchemaValidator::OpenAPI3::ParameterDeserializer.new(operation)
441
+ end
442
+
443
+ def create_param(name, location, style, explode, type, properties_or_items)
444
+ Struct.new(:name, :in, :style, :explode, :schema, :required).new(name, location, style, explode, create_schema(type, properties_or_items), false)
445
+ end
446
+
447
+ def create_schema(type, properties_or_items)
448
+ if type == 'object'
449
+ props = properties_or_items.transform_values { |v| Struct.new(:type).new(v) }
450
+ Struct.new(:type, :properties, :items).new(type, props, nil)
451
+ elsif type == 'array'
452
+ Struct.new(:type, :properties, :items).new(type, nil, Struct.new(:type).new(properties_or_items))
453
+ else
454
+ Struct.new(:type, :properties, :items).new(type, nil, nil)
455
+ end
456
+ end
457
+ end
@@ -40,8 +40,7 @@ describe Committee::SchemaValidator::OpenAPI3::RequestValidator do
40
40
  assert_equal 400, last_response.status
41
41
 
42
42
  body = JSON.parse(last_response.body)
43
- message =
44
- %{"Content-Type" request header must be set to any of the following: ["application/json", "application/binary"].}
43
+ message = %{"Content-Type" request header must be set to any of the following: ["application/json", "application/binary"].}
45
44
 
46
45
  assert_equal message, body['message']
47
46
  end
@@ -101,10 +101,7 @@ describe Committee::SchemaValidator::OpenAPI3::ResponseValidator do
101
101
  end
102
102
 
103
103
  it "passes given an empty date string with allow_empty_date_and_datetime enabled" do
104
- @validator_option = Committee::SchemaValidator::Option.new(
105
- { allow_empty_date_and_datetime: true },
106
- open_api_3_schema,
107
- :open_api_3)
104
+ @validator_option = Committee::SchemaValidator::Option.new({ allow_empty_date_and_datetime: true }, open_api_3_schema, :open_api_3)
108
105
 
109
106
  @path = '/date_time'
110
107
  @method = 'get'
@@ -123,10 +120,7 @@ describe Committee::SchemaValidator::OpenAPI3::ResponseValidator do
123
120
  end
124
121
 
125
122
  it "passes given an empty date-time string with allow_empty_date_and_datetime enabled" do
126
- @validator_option = Committee::SchemaValidator::Option.new(
127
- { allow_empty_date_and_datetime: true },
128
- open_api_3_schema,
129
- :open_api_3)
123
+ @validator_option = Committee::SchemaValidator::Option.new({ allow_empty_date_and_datetime: true }, open_api_3_schema, :open_api_3)
130
124
 
131
125
  @path = '/date_time'
132
126
  @method = 'get'
@@ -149,8 +143,6 @@ describe Committee::SchemaValidator::OpenAPI3::ResponseValidator do
149
143
 
150
144
  def call_response_validator(strict = false)
151
145
  @operation_object = open_api_3_schema.operation_object(@path, @method)
152
- Committee::SchemaValidator::OpenAPI3::ResponseValidator.
153
- new(@operation_object, @validator_option).
154
- call(@status, @headers, @data, strict)
146
+ Committee::SchemaValidator::OpenAPI3::ResponseValidator.new(@operation_object, @validator_option).call(@status, @headers, @data, strict)
155
147
  end
156
148
  end