grape 1.8.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -1
  3. data/README.md +377 -334
  4. data/UPGRADING.md +231 -6
  5. data/grape.gemspec +6 -10
  6. data/lib/grape/api/instance.rb +13 -10
  7. data/lib/grape/api.rb +17 -8
  8. data/lib/grape/content_types.rb +0 -2
  9. data/lib/grape/cookies.rb +2 -1
  10. data/lib/grape/dry_types.rb +0 -2
  11. data/lib/grape/dsl/desc.rb +23 -21
  12. data/lib/grape/dsl/headers.rb +1 -1
  13. data/lib/grape/dsl/inside_route.rb +47 -22
  14. data/lib/grape/dsl/parameters.rb +4 -3
  15. data/lib/grape/dsl/routing.rb +20 -4
  16. data/lib/grape/dsl/validations.rb +13 -0
  17. data/lib/grape/endpoint.rb +15 -10
  18. data/lib/grape/{util/env.rb → env.rb} +0 -5
  19. data/lib/grape/error_formatter/txt.rb +11 -10
  20. data/lib/grape/exceptions/base.rb +3 -3
  21. data/lib/grape/exceptions/missing_group_type.rb +1 -1
  22. data/lib/grape/exceptions/unsupported_group_type.rb +1 -1
  23. data/lib/grape/exceptions/validation.rb +0 -2
  24. data/lib/grape/exceptions/validation_array_errors.rb +1 -0
  25. data/lib/grape/exceptions/validation_errors.rb +1 -3
  26. data/lib/grape/extensions/hash.rb +5 -1
  27. data/lib/grape/http/headers.rb +18 -24
  28. data/lib/grape/{util/json.rb → json.rb} +1 -3
  29. data/lib/grape/locale/en.yml +3 -0
  30. data/lib/grape/middleware/auth/base.rb +0 -2
  31. data/lib/grape/middleware/auth/dsl.rb +0 -2
  32. data/lib/grape/middleware/auth/strategies.rb +1 -2
  33. data/lib/grape/middleware/base.rb +0 -2
  34. data/lib/grape/middleware/error.rb +55 -50
  35. data/lib/grape/middleware/formatter.rb +21 -18
  36. data/lib/grape/middleware/globals.rb +1 -3
  37. data/lib/grape/middleware/stack.rb +2 -3
  38. data/lib/grape/middleware/versioner/accept_version_header.rb +0 -2
  39. data/lib/grape/middleware/versioner/header.rb +17 -163
  40. data/lib/grape/middleware/versioner/param.rb +2 -4
  41. data/lib/grape/middleware/versioner/path.rb +1 -3
  42. data/lib/grape/namespace.rb +3 -4
  43. data/lib/grape/path.rb +24 -29
  44. data/lib/grape/railtie.rb +9 -0
  45. data/lib/grape/request.rb +3 -5
  46. data/lib/grape/router/base_route.rb +39 -0
  47. data/lib/grape/router/greedy_route.rb +20 -0
  48. data/lib/grape/router/pattern.rb +39 -30
  49. data/lib/grape/router/route.rb +22 -59
  50. data/lib/grape/router.rb +30 -36
  51. data/lib/grape/util/accept/header.rb +19 -0
  52. data/lib/grape/util/accept_header_handler.rb +105 -0
  53. data/lib/grape/util/base_inheritable.rb +4 -4
  54. data/lib/grape/util/cache.rb +0 -3
  55. data/lib/grape/util/endpoint_configuration.rb +1 -1
  56. data/lib/grape/util/header.rb +13 -0
  57. data/lib/grape/util/inheritable_values.rb +0 -2
  58. data/lib/grape/util/lazy/block.rb +29 -0
  59. data/lib/grape/util/lazy/object.rb +45 -0
  60. data/lib/grape/util/lazy/value.rb +38 -0
  61. data/lib/grape/util/lazy/value_array.rb +21 -0
  62. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  63. data/lib/grape/util/lazy/value_hash.rb +21 -0
  64. data/lib/grape/util/media_type.rb +70 -0
  65. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  66. data/lib/grape/util/stackable_values.rb +1 -6
  67. data/lib/grape/util/strict_hash_configuration.rb +3 -3
  68. data/lib/grape/validations/attributes_doc.rb +38 -36
  69. data/lib/grape/validations/contract_scope.rb +71 -0
  70. data/lib/grape/validations/params_scope.rb +10 -9
  71. data/lib/grape/validations/types/array_coercer.rb +0 -2
  72. data/lib/grape/validations/types/build_coercer.rb +69 -71
  73. data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
  74. data/lib/grape/validations/types/json.rb +0 -2
  75. data/lib/grape/validations/types/primitive_coercer.rb +0 -2
  76. data/lib/grape/validations/types/set_coercer.rb +0 -3
  77. data/lib/grape/validations/types.rb +0 -3
  78. data/lib/grape/validations/validators/base.rb +2 -1
  79. data/lib/grape/validations/validators/default_validator.rb +5 -1
  80. data/lib/grape/validations/validators/length_validator.rb +42 -0
  81. data/lib/grape/validations/validators/values_validator.rb +8 -3
  82. data/lib/grape/validations.rb +3 -7
  83. data/lib/grape/version.rb +1 -1
  84. data/lib/grape/{util/xml.rb → xml.rb} +1 -1
  85. data/lib/grape.rb +38 -269
  86. metadata +33 -274
  87. data/lib/grape/eager_load.rb +0 -20
  88. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  89. data/lib/grape/router/attribute_translator.rb +0 -63
  90. data/lib/grape/util/lazy_block.rb +0 -27
  91. data/lib/grape/util/lazy_object.rb +0 -43
  92. data/lib/grape/util/lazy_value.rb +0 -91
  93. data/spec/grape/api/custom_validations_spec.rb +0 -213
  94. data/spec/grape/api/deeply_included_options_spec.rb +0 -56
  95. data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
  96. data/spec/grape/api/documentation_spec.rb +0 -59
  97. data/spec/grape/api/inherited_helpers_spec.rb +0 -114
  98. data/spec/grape/api/instance_spec.rb +0 -103
  99. data/spec/grape/api/invalid_format_spec.rb +0 -45
  100. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
  101. data/spec/grape/api/nested_helpers_spec.rb +0 -50
  102. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
  103. data/spec/grape/api/parameters_modification_spec.rb +0 -41
  104. data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
  105. data/spec/grape/api/recognize_path_spec.rb +0 -21
  106. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
  107. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
  108. data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
  109. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
  110. data/spec/grape/api/shared_helpers_spec.rb +0 -36
  111. data/spec/grape/api_remount_spec.rb +0 -509
  112. data/spec/grape/api_spec.rb +0 -4356
  113. data/spec/grape/dsl/callbacks_spec.rb +0 -45
  114. data/spec/grape/dsl/desc_spec.rb +0 -98
  115. data/spec/grape/dsl/headers_spec.rb +0 -62
  116. data/spec/grape/dsl/helpers_spec.rb +0 -100
  117. data/spec/grape/dsl/inside_route_spec.rb +0 -531
  118. data/spec/grape/dsl/logger_spec.rb +0 -24
  119. data/spec/grape/dsl/middleware_spec.rb +0 -60
  120. data/spec/grape/dsl/parameters_spec.rb +0 -180
  121. data/spec/grape/dsl/request_response_spec.rb +0 -225
  122. data/spec/grape/dsl/routing_spec.rb +0 -275
  123. data/spec/grape/dsl/settings_spec.rb +0 -261
  124. data/spec/grape/dsl/validations_spec.rb +0 -55
  125. data/spec/grape/endpoint/declared_spec.rb +0 -846
  126. data/spec/grape/endpoint_spec.rb +0 -1085
  127. data/spec/grape/entity_spec.rb +0 -336
  128. data/spec/grape/exceptions/base_spec.rb +0 -81
  129. data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
  130. data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
  131. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
  132. data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
  133. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
  134. data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
  135. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
  136. data/spec/grape/exceptions/missing_option_spec.rb +0 -15
  137. data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
  138. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
  139. data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
  140. data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
  141. data/spec/grape/exceptions/validation_spec.rb +0 -19
  142. data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
  143. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
  144. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
  145. data/spec/grape/grape_spec.rb +0 -9
  146. data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
  147. data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
  148. data/spec/grape/integration/rack_spec.rb +0 -51
  149. data/spec/grape/loading_spec.rb +0 -44
  150. data/spec/grape/middleware/auth/base_spec.rb +0 -31
  151. data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
  152. data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
  153. data/spec/grape/middleware/base_spec.rb +0 -221
  154. data/spec/grape/middleware/error_spec.rb +0 -85
  155. data/spec/grape/middleware/exception_spec.rb +0 -294
  156. data/spec/grape/middleware/formatter_spec.rb +0 -461
  157. data/spec/grape/middleware/globals_spec.rb +0 -30
  158. data/spec/grape/middleware/stack_spec.rb +0 -155
  159. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
  160. data/spec/grape/middleware/versioner/header_spec.rb +0 -345
  161. data/spec/grape/middleware/versioner/param_spec.rb +0 -171
  162. data/spec/grape/middleware/versioner/path_spec.rb +0 -62
  163. data/spec/grape/middleware/versioner_spec.rb +0 -21
  164. data/spec/grape/named_api_spec.rb +0 -19
  165. data/spec/grape/parser_spec.rb +0 -86
  166. data/spec/grape/path_spec.rb +0 -252
  167. data/spec/grape/presenters/presenter_spec.rb +0 -71
  168. data/spec/grape/request_spec.rb +0 -126
  169. data/spec/grape/util/inheritable_setting_spec.rb +0 -242
  170. data/spec/grape/util/inheritable_values_spec.rb +0 -79
  171. data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
  172. data/spec/grape/util/stackable_values_spec.rb +0 -128
  173. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
  174. data/spec/grape/validations/attributes_doc_spec.rb +0 -153
  175. data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
  176. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
  177. data/spec/grape/validations/params_scope_spec.rb +0 -1420
  178. data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
  179. data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
  180. data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
  181. data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
  182. data/spec/grape/validations/types_spec.rb +0 -111
  183. data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
  184. data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
  185. data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
  186. data/spec/grape/validations/validators/base_spec.rb +0 -38
  187. data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
  188. data/spec/grape/validations/validators/default_spec.rb +0 -463
  189. data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
  190. data/spec/grape/validations/validators/except_values_spec.rb +0 -192
  191. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
  192. data/spec/grape/validations/validators/presence_spec.rb +0 -315
  193. data/spec/grape/validations/validators/regexp_spec.rb +0 -161
  194. data/spec/grape/validations/validators/same_as_spec.rb +0 -57
  195. data/spec/grape/validations/validators/values_spec.rb +0 -733
  196. data/spec/grape/validations/validators/zh-CN.yml +0 -10
  197. data/spec/grape/validations_spec.rb +0 -2030
  198. data/spec/integration/eager_load/eager_load_spec.rb +0 -15
  199. data/spec/integration/multi_json/json_spec.rb +0 -7
  200. data/spec/integration/multi_xml/xml_spec.rb +0 -7
  201. data/spec/shared/deprecated_class_examples.rb +0 -16
  202. data/spec/shared/versioning_examples.rb +0 -215
  203. data/spec/spec_helper.rb +0 -52
  204. data/spec/support/basic_auth_encode_helpers.rb +0 -11
  205. data/spec/support/chunks.rb +0 -14
  206. data/spec/support/content_type_helpers.rb +0 -15
  207. data/spec/support/endpoint_faker.rb +0 -25
  208. data/spec/support/file_streamer.rb +0 -13
  209. data/spec/support/integer_helpers.rb +0 -13
  210. data/spec/support/versioned_helpers.rb +0 -55
@@ -1,2030 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- describe Grape::Validations do
4
- subject { Class.new(Grape::API) }
5
-
6
- def app
7
- subject
8
- end
9
-
10
- def declared_params
11
- subject.namespace_stackable(:declared_params).flatten
12
- end
13
-
14
- describe 'params' do
15
- context 'optional' do
16
- before do
17
- subject.params do
18
- optional :a_number, regexp: /^[0-9]+$/
19
- optional :attachment, type: File
20
- end
21
- subject.get '/optional' do
22
- 'optional works!'
23
- end
24
- end
25
-
26
- it 'validates when params is present' do
27
- get '/optional', a_number: 'string'
28
- expect(last_response.status).to eq(400)
29
- expect(last_response.body).to eq('a_number is invalid')
30
-
31
- get '/optional', a_number: 45
32
- expect(last_response.status).to eq(200)
33
- expect(last_response.body).to eq('optional works!')
34
- end
35
-
36
- it "doesn't validate when param not present" do
37
- get '/optional', a_number: nil, attachment: nil
38
- expect(last_response.status).to eq(200)
39
- expect(last_response.body).to eq('optional works!')
40
- end
41
-
42
- it 'adds to declared parameters' do
43
- subject.params do
44
- optional :some_param
45
- end
46
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq([:some_param])
47
- end
48
- end
49
-
50
- context 'optional using Grape::Entity documentation' do
51
- def define_optional_using
52
- documentation = { field_a: { type: String }, field_b: { type: String } }
53
- subject.params do
54
- optional :all, using: documentation
55
- end
56
- end
57
- before do
58
- define_optional_using
59
- subject.get '/optional' do
60
- 'optional with using works'
61
- end
62
- end
63
-
64
- it 'adds entity documentation to declared params' do
65
- define_optional_using
66
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq(%i[field_a field_b])
67
- end
68
-
69
- it 'works when field_a and field_b are not present' do
70
- get '/optional'
71
- expect(last_response.status).to eq(200)
72
- expect(last_response.body).to eq('optional with using works')
73
- end
74
-
75
- it 'works when field_a is present' do
76
- get '/optional', field_a: 'woof'
77
- expect(last_response.status).to eq(200)
78
- expect(last_response.body).to eq('optional with using works')
79
- end
80
-
81
- it 'works when field_b is present' do
82
- get '/optional', field_b: 'woof'
83
- expect(last_response.status).to eq(200)
84
- expect(last_response.body).to eq('optional with using works')
85
- end
86
- end
87
-
88
- context 'required' do
89
- before do
90
- subject.params do
91
- requires :key, type: String
92
- end
93
- subject.get('/required') { 'required works' }
94
- subject.put('/required') { { key: params[:key] }.to_json }
95
- end
96
-
97
- it 'errors when param not present' do
98
- get '/required'
99
- expect(last_response.status).to eq(400)
100
- expect(last_response.body).to eq('key is missing')
101
- end
102
-
103
- it "doesn't throw a missing param when param is present" do
104
- get '/required', key: 'cool'
105
- expect(last_response.status).to eq(200)
106
- expect(last_response.body).to eq('required works')
107
- end
108
-
109
- it 'adds to declared parameters' do
110
- subject.params do
111
- requires :some_param
112
- end
113
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq([:some_param])
114
- end
115
-
116
- it 'works when required field is present but nil' do
117
- put '/required', { key: nil }.to_json, 'CONTENT_TYPE' => 'application/json'
118
- expect(last_response.status).to eq(200)
119
- expect(JSON.parse(last_response.body)).to eq('key' => nil)
120
- end
121
- end
122
-
123
- context 'requires with nested params' do
124
- before do
125
- subject.params do
126
- requires :first_level, type: Hash do
127
- optional :second_level, type: Array do
128
- requires :value, type: Integer
129
- optional :name, type: String
130
- optional :third_level, type: Array do
131
- requires :value, type: Integer
132
- optional :name, type: String
133
- optional :fourth_level, type: Array do
134
- requires :value, type: Integer
135
- optional :name, type: String
136
- end
137
- end
138
- end
139
- end
140
- end
141
- subject.put('/required') { 'required works' }
142
- end
143
-
144
- let(:request_params) do
145
- {
146
- first_level: {
147
- second_level: [
148
- { value: 1, name: 'Lisa' },
149
- {
150
- value: 2,
151
- name: 'James',
152
- third_level: [
153
- { value: 'three', name: 'Sophie' },
154
- {
155
- value: 4,
156
- name: 'Jenny',
157
- fourth_level: [
158
- { name: 'Samuel' }, { value: 6, name: 'Jane' }
159
- ]
160
- }
161
- ]
162
- }
163
- ]
164
- }
165
- }
166
- end
167
-
168
- it 'validates correctly in deep nested params' do
169
- put '/required', request_params.to_json, 'CONTENT_TYPE' => 'application/json'
170
-
171
- expect(last_response.status).to eq(400)
172
- expect(last_response.body).to eq(
173
- 'first_level[second_level][1][third_level][0][value] is invalid, ' \
174
- 'first_level[second_level][1][third_level][1][fourth_level][0][value] is missing'
175
- )
176
- end
177
- end
178
-
179
- context 'requires :all using Grape::Entity documentation' do
180
- def define_requires_all
181
- documentation = {
182
- required_field: { type: String, required: true, param_type: 'query' },
183
- optional_field: { type: String },
184
- optional_array_field: { type: Array[String], is_array: true }
185
- }
186
- subject.params do
187
- requires :all, except: %i[optional_field optional_array_field], using: documentation
188
- end
189
- end
190
- before do
191
- define_requires_all
192
- subject.get '/required' do
193
- 'required works'
194
- end
195
- end
196
-
197
- it 'adds entity documentation to declared params' do
198
- define_requires_all
199
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq(%i[required_field optional_field optional_array_field])
200
- end
201
-
202
- it 'errors when required_field is not present' do
203
- get '/required'
204
- expect(last_response.status).to eq(400)
205
- expect(last_response.body).to eq('required_field is missing')
206
- end
207
-
208
- it 'works when required_field is present' do
209
- get '/required', required_field: 'woof'
210
- expect(last_response.status).to eq(200)
211
- expect(last_response.body).to eq('required works')
212
- end
213
- end
214
-
215
- context 'requires :none using Grape::Entity documentation' do
216
- def define_requires_none
217
- documentation = {
218
- required_field: { type: String, example: 'Foo' },
219
- optional_field: { type: Integer, format: 'int64' }
220
- }
221
- subject.params do
222
- requires :none, except: :required_field, using: documentation
223
- end
224
- end
225
- before do
226
- define_requires_none
227
- subject.get '/required' do
228
- 'required works'
229
- end
230
- end
231
-
232
- it 'adds entity documentation to declared params' do
233
- define_requires_none
234
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq(%i[required_field optional_field])
235
- end
236
-
237
- it 'errors when required_field is not present' do
238
- get '/required'
239
- expect(last_response.status).to eq(400)
240
- expect(last_response.body).to eq('required_field is missing')
241
- end
242
-
243
- it 'works when required_field is present' do
244
- get '/required', required_field: 'woof'
245
- expect(last_response.status).to eq(200)
246
- expect(last_response.body).to eq('required works')
247
- end
248
- end
249
-
250
- context 'requires :all or :none but except a non-existent field using Grape::Entity documentation' do
251
- context 'requires :all' do
252
- def define_requires_all
253
- documentation = {
254
- required_field: { type: String },
255
- optional_field: { type: String }
256
- }
257
- subject.params do
258
- requires :all, except: :non_existent_field, using: documentation
259
- end
260
- end
261
-
262
- it 'adds only the entity documentation to declared params, nothing more' do
263
- define_requires_all
264
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq(%i[required_field optional_field])
265
- end
266
- end
267
-
268
- context 'requires :none' do
269
- def define_requires_none
270
- documentation = {
271
- required_field: { type: String },
272
- optional_field: { type: String }
273
- }
274
- subject.params do
275
- requires :none, except: :non_existent_field, using: documentation
276
- end
277
- end
278
-
279
- it 'adds only the entity documentation to declared params, nothing more' do
280
- expect { define_requires_none }.to raise_error(ArgumentError)
281
- end
282
- end
283
- end
284
-
285
- context 'required with an Array block' do
286
- before do
287
- subject.params do
288
- requires :items, type: Array do
289
- requires :key
290
- end
291
- end
292
- subject.get('/required') { 'required works' }
293
- subject.put('/required') { { items: params[:items] }.to_json }
294
- end
295
-
296
- it 'errors when param not present' do
297
- get '/required'
298
- expect(last_response.status).to eq(400)
299
- expect(last_response.body).to eq('items is missing')
300
- end
301
-
302
- it 'errors when param is not an Array' do
303
- get '/required', items: 'hello'
304
- expect(last_response.status).to eq(400)
305
- expect(last_response.body).to eq('items is invalid')
306
-
307
- get '/required', items: { key: 'foo' }
308
- expect(last_response.status).to eq(400)
309
- expect(last_response.body).to eq('items is invalid')
310
- end
311
-
312
- it "doesn't throw a missing param when param is present" do
313
- get '/required', items: [{ key: 'hello' }, { key: 'world' }]
314
- expect(last_response.status).to eq(200)
315
- expect(last_response.body).to eq('required works')
316
- end
317
-
318
- it "doesn't throw a missing param when param is present but empty" do
319
- put '/required', { items: [] }.to_json, 'CONTENT_TYPE' => 'application/json'
320
- expect(last_response.status).to eq(200)
321
- expect(JSON.parse(last_response.body)).to eq('items' => [])
322
- end
323
-
324
- it 'adds to declared parameters' do
325
- subject.params do
326
- requires :items, type: Array do
327
- requires :key
328
- end
329
- end
330
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq([items: [:key]])
331
- end
332
- end
333
-
334
- # Ensure there is no leakage between declared Array types and
335
- # subsequent Hash types
336
- context 'required with an Array and a Hash block' do
337
- before do
338
- subject.params do
339
- requires :cats, type: Array[String], default: []
340
- requires :items, type: Hash do
341
- requires :key
342
- end
343
- end
344
- subject.get '/required' do
345
- 'required works'
346
- end
347
- end
348
-
349
- it 'does not output index [0] for Hash types' do
350
- get '/required', cats: ['Garfield'], items: { foo: 'bar' }
351
- expect(last_response.status).to eq(400)
352
- expect(last_response.body).to eq('items[key] is missing')
353
- end
354
- end
355
-
356
- context 'required with a Hash block' do
357
- before do
358
- subject.params do
359
- requires :items, type: Hash do
360
- requires :key
361
- end
362
- end
363
- subject.get '/required' do
364
- 'required works'
365
- end
366
- end
367
-
368
- it 'errors when param not present' do
369
- get '/required'
370
- expect(last_response.status).to eq(400)
371
- expect(last_response.body).to eq('items is missing, items[key] is missing')
372
- end
373
-
374
- it 'errors when nested param not present' do
375
- get '/required', items: { foo: 'bar' }
376
- expect(last_response.status).to eq(400)
377
- expect(last_response.body).to eq('items[key] is missing')
378
- end
379
-
380
- it 'errors when param is not a Hash' do
381
- get '/required', items: 'hello'
382
- expect(last_response.status).to eq(400)
383
- expect(last_response.body).to eq('items is invalid, items[key] is missing')
384
-
385
- get '/required', items: [{ key: 'foo' }]
386
- expect(last_response.status).to eq(400)
387
- expect(last_response.body).to eq('items is invalid')
388
- end
389
-
390
- it "doesn't throw a missing param when param is present" do
391
- get '/required', items: { key: 'hello' }
392
- expect(last_response.status).to eq(200)
393
- expect(last_response.body).to eq('required works')
394
- end
395
-
396
- it 'adds to declared parameters' do
397
- subject.params do
398
- requires :items, type: Array do
399
- requires :key
400
- end
401
- end
402
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq([items: [:key]])
403
- end
404
- end
405
-
406
- context 'hash with a required param with validation' do
407
- before do
408
- subject.params do
409
- requires :items, type: Hash do
410
- requires :key, type: String, values: %w[a b]
411
- end
412
- end
413
- subject.get '/required' do
414
- 'required works'
415
- end
416
- end
417
-
418
- it 'errors when param is not a Hash' do
419
- get '/required', items: 'not a hash'
420
- expect(last_response.status).to eq(400)
421
- expect(last_response.body).to eq('items is invalid, items[key] is missing, items[key] is invalid')
422
-
423
- get '/required', items: [{ key: 'hash in array' }]
424
- expect(last_response.status).to eq(400)
425
- expect(last_response.body).to eq('items is invalid, items[key] does not have a valid value')
426
- end
427
-
428
- it 'works when all params match' do
429
- get '/required', items: { key: 'a' }
430
- expect(last_response.status).to eq(200)
431
- expect(last_response.body).to eq('required works')
432
- end
433
- end
434
-
435
- context 'group' do
436
- before do
437
- subject.params do
438
- group :items, type: Array do
439
- requires :key
440
- end
441
- end
442
- subject.get '/required' do
443
- 'required works'
444
- end
445
- end
446
-
447
- it 'errors when param not present' do
448
- get '/required'
449
- expect(last_response.status).to eq(400)
450
- expect(last_response.body).to eq('items is missing')
451
- end
452
-
453
- it "doesn't throw a missing param when param is present" do
454
- get '/required', items: [key: 'hello']
455
- expect(last_response.status).to eq(200)
456
- expect(last_response.body).to eq('required works')
457
- end
458
-
459
- it 'adds to declared parameters' do
460
- subject.params do
461
- group :items, type: Array do
462
- requires :key
463
- end
464
- end
465
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq([items: [:key]])
466
- end
467
- end
468
-
469
- context 'group params with nested params which has a type' do
470
- let(:invalid_items) { { items: '' } }
471
-
472
- before do
473
- subject.params do
474
- optional :items, type: Array do
475
- optional :key1, type: String
476
- optional :key2, type: String
477
- end
478
- end
479
- subject.post '/group_with_nested' do
480
- 'group with nested works'
481
- end
482
- end
483
-
484
- it 'errors when group param is invalid' do
485
- post '/group_with_nested', items: invalid_items
486
- expect(last_response.status).to eq(400)
487
- end
488
- end
489
-
490
- context 'custom validator for a Hash' do
491
- let(:date_range_validator) do
492
- Class.new(Grape::Validations::Validators::Base) do
493
- def validate_param!(attr_name, params)
494
- return if params[attr_name][:from] <= params[attr_name][:to]
495
-
496
- raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: "'from' must be lower or equal to 'to'")
497
- end
498
- end
499
- end
500
-
501
- before do
502
- described_class.register_validator('date_range', date_range_validator)
503
- end
504
-
505
- after do
506
- described_class.deregister_validator('date_range')
507
- end
508
-
509
- before do
510
- subject.params do
511
- optional :date_range, date_range: true, type: Hash do
512
- requires :from, type: Integer
513
- requires :to, type: Integer
514
- end
515
- end
516
- subject.get('/optional') do
517
- 'optional works'
518
- end
519
- subject.params do
520
- requires :date_range, date_range: true, type: Hash do
521
- requires :from, type: Integer
522
- requires :to, type: Integer
523
- end
524
- end
525
- subject.get('/required') do
526
- 'required works'
527
- end
528
- end
529
-
530
- context 'which is optional' do
531
- it "doesn't throw an error if the validation passes" do
532
- get '/optional', date_range: { from: 1, to: 2 }
533
- expect(last_response.status).to eq(200)
534
- end
535
-
536
- it 'errors if the validation fails' do
537
- get '/optional', date_range: { from: 2, to: 1 }
538
- expect(last_response.status).to eq(400)
539
- end
540
- end
541
-
542
- context 'which is required' do
543
- it "doesn't throw an error if the validation passes" do
544
- get '/required', date_range: { from: 1, to: 2 }
545
- expect(last_response.status).to eq(200)
546
- end
547
-
548
- it 'errors if the validation fails' do
549
- get '/required', date_range: { from: 2, to: 1 }
550
- expect(last_response.status).to eq(400)
551
- end
552
- end
553
- end
554
-
555
- context 'validation within arrays' do
556
- before do
557
- subject.params do
558
- group :children, type: Array do
559
- requires :name
560
- group :parents, type: Array do
561
- requires :name, allow_blank: false
562
- end
563
- end
564
- end
565
- subject.get '/within_array' do
566
- 'within array works'
567
- end
568
- end
569
-
570
- it 'can handle new scopes within child elements' do
571
- get '/within_array', children: [
572
- { name: 'John', parents: [{ name: 'Jane' }, { name: 'Bob' }] },
573
- { name: 'Joe', parents: [{ name: 'Josie' }] }
574
- ]
575
- expect(last_response.status).to eq(200)
576
- expect(last_response.body).to eq('within array works')
577
- end
578
-
579
- it 'errors when a parameter is not present' do
580
- get '/within_array', children: [
581
- { name: 'Jim', parents: [{ name: 'Joy' }] },
582
- { name: 'Job', parents: [{}] }
583
- ]
584
- # NOTE: with body parameters in json or XML or similar this
585
- # should actually fail with: children[parents][name] is missing.
586
- expect(last_response.status).to eq(400)
587
- expect(last_response.body).to eq('children[1][parents] is missing, children[0][parents][1][name] is missing, children[0][parents][1][name] is empty')
588
- end
589
-
590
- it 'errors when a parameter is not present in array within array' do
591
- get '/within_array', children: [
592
- { name: 'Jim', parents: [{ name: 'Joy' }] },
593
- { name: 'Job', parents: [{ name: 'Bill' }, { name: '' }] }
594
- ]
595
-
596
- expect(last_response.status).to eq(400)
597
- expect(last_response.body).to eq('children[1][parents][1][name] is empty')
598
- end
599
-
600
- it 'handle errors for all array elements' do
601
- get '/within_array', children: [
602
- { name: 'Jim', parents: [] },
603
- { name: 'Job', parents: [] }
604
- ]
605
-
606
- expect(last_response.status).to eq(400)
607
- expect(last_response.body).to eq(
608
- 'children[0][parents][0][name] is missing, ' \
609
- 'children[1][parents][0][name] is missing'
610
- )
611
- end
612
-
613
- it 'safely handles empty arrays and blank parameters' do
614
- # NOTE: with body parameters in json or XML or similar this
615
- # should actually return 200, since an empty array is valid.
616
- get '/within_array', children: []
617
- expect(last_response.status).to eq(400)
618
- expect(last_response.body).to eq(
619
- 'children[0][name] is missing, ' \
620
- 'children[0][parents] is missing, ' \
621
- 'children[0][parents] is invalid, ' \
622
- 'children[0][parents][0][name] is missing, ' \
623
- 'children[0][parents][0][name] is empty'
624
- )
625
-
626
- get '/within_array', children: [name: 'Jay']
627
- expect(last_response.status).to eq(400)
628
- expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing, children[0][parents][0][name] is empty')
629
- end
630
-
631
- it 'errors when param is not an Array' do
632
- get '/within_array', children: 'hello'
633
- expect(last_response.status).to eq(400)
634
- expect(last_response.body).to eq('children is invalid')
635
-
636
- get '/within_array', children: { name: 'foo' }
637
- expect(last_response.status).to eq(400)
638
- expect(last_response.body).to eq('children is invalid')
639
-
640
- get '/within_array', children: [name: 'Jay', parents: { name: 'Fred' }]
641
- expect(last_response.status).to eq(400)
642
- expect(last_response.body).to eq('children[0][parents] is invalid')
643
- end
644
- end
645
-
646
- context 'with block param' do
647
- before do
648
- subject.params do
649
- requires :planets, type: Array do
650
- requires :name
651
- end
652
- end
653
- subject.get '/req' do
654
- 'within array works'
655
- end
656
- subject.put '/req' do
657
- ''
658
- end
659
-
660
- subject.params do
661
- group :stars, type: Array do
662
- requires :name
663
- end
664
- end
665
- subject.get '/grp' do
666
- 'within array works'
667
- end
668
- subject.put '/grp' do
669
- ''
670
- end
671
-
672
- subject.params do
673
- requires :name
674
- optional :moons, type: Array do
675
- requires :name
676
- end
677
- end
678
- subject.get '/opt' do
679
- 'within array works'
680
- end
681
- subject.put '/opt' do
682
- ''
683
- end
684
- end
685
-
686
- it 'requires defaults to Array type' do
687
- get '/req', planets: 'Jupiter, Saturn'
688
- expect(last_response.status).to eq(400)
689
- expect(last_response.body).to eq('planets is invalid')
690
-
691
- get '/req', planets: { name: 'Jupiter' }
692
- expect(last_response.status).to eq(400)
693
- expect(last_response.body).to eq('planets is invalid')
694
-
695
- get '/req', planets: [{ name: 'Venus' }, { name: 'Mars' }]
696
- expect(last_response.status).to eq(200)
697
-
698
- put_with_json '/req', planets: []
699
- expect(last_response.status).to eq(200)
700
- end
701
-
702
- it 'optional defaults to Array type' do
703
- get '/opt', name: 'Jupiter', moons: 'Europa, Ganymede'
704
- expect(last_response.status).to eq(400)
705
- expect(last_response.body).to eq('moons is invalid')
706
-
707
- get '/opt', name: 'Jupiter', moons: { name: 'Ganymede' }
708
- expect(last_response.status).to eq(400)
709
- expect(last_response.body).to eq('moons is invalid')
710
-
711
- get '/opt', name: 'Jupiter', moons: [{ name: 'Io' }, { name: 'Callisto' }]
712
- expect(last_response.status).to eq(200)
713
-
714
- put_with_json '/opt', name: 'Venus'
715
- expect(last_response.status).to eq(200)
716
-
717
- put_with_json '/opt', name: 'Mercury', moons: []
718
- expect(last_response.status).to eq(200)
719
- end
720
-
721
- it 'group defaults to Array type' do
722
- get '/grp', stars: 'Sun'
723
- expect(last_response.status).to eq(400)
724
- expect(last_response.body).to eq('stars is invalid')
725
-
726
- get '/grp', stars: { name: 'Sun' }
727
- expect(last_response.status).to eq(400)
728
- expect(last_response.body).to eq('stars is invalid')
729
-
730
- get '/grp', stars: [{ name: 'Sun' }]
731
- expect(last_response.status).to eq(200)
732
-
733
- put_with_json '/grp', stars: []
734
- expect(last_response.status).to eq(200)
735
- end
736
- end
737
-
738
- context 'validation within arrays with JSON' do
739
- before do
740
- subject.params do
741
- group :children, type: Array do
742
- requires :name
743
- group :parents, type: Array do
744
- requires :name
745
- end
746
- end
747
- end
748
- subject.put '/within_array' do
749
- 'within array works'
750
- end
751
- end
752
-
753
- it 'can handle new scopes within child elements' do
754
- put_with_json '/within_array', children: [
755
- { name: 'John', parents: [{ name: 'Jane' }, { name: 'Bob' }] },
756
- { name: 'Joe', parents: [{ name: 'Josie' }] }
757
- ]
758
- expect(last_response.status).to eq(200)
759
- expect(last_response.body).to eq('within array works')
760
- end
761
-
762
- it 'errors when a parameter is not present' do
763
- put_with_json '/within_array', children: [
764
- { name: 'Jim', parents: [{}] },
765
- { name: 'Job', parents: [{ name: 'Joy' }] }
766
- ]
767
- expect(last_response.status).to eq(400)
768
- expect(last_response.body).to eq('children[0][parents][0][name] is missing')
769
- end
770
-
771
- it 'safely handles empty arrays and blank parameters' do
772
- put_with_json '/within_array', children: []
773
- expect(last_response.status).to eq(200)
774
- put_with_json '/within_array', children: [name: 'Jay']
775
- expect(last_response.status).to eq(400)
776
- expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing')
777
- end
778
- end
779
-
780
- context 'optional with an Array block' do
781
- before do
782
- subject.params do
783
- optional :items, type: Array do
784
- requires :key
785
- end
786
- end
787
- subject.get '/optional_group' do
788
- 'optional group works'
789
- end
790
- end
791
-
792
- it "doesn't throw a missing param when the group isn't present" do
793
- get '/optional_group'
794
- expect(last_response.status).to eq(200)
795
- expect(last_response.body).to eq('optional group works')
796
- end
797
-
798
- it "doesn't throw a missing param when both group and param are given" do
799
- get '/optional_group', items: [{ key: 'foo' }]
800
- expect(last_response.status).to eq(200)
801
- expect(last_response.body).to eq('optional group works')
802
- end
803
-
804
- it 'errors when group is present, but required param is not' do
805
- get '/optional_group', items: [{ not_key: 'foo' }]
806
- expect(last_response.status).to eq(400)
807
- expect(last_response.body).to eq('items[0][key] is missing')
808
- end
809
-
810
- it "errors when param is present but isn't an Array" do
811
- get '/optional_group', items: 'hello'
812
- expect(last_response.status).to eq(400)
813
- expect(last_response.body).to eq('items is invalid')
814
-
815
- get '/optional_group', items: { key: 'foo' }
816
- expect(last_response.status).to eq(400)
817
- expect(last_response.body).to eq('items is invalid')
818
- end
819
-
820
- it 'adds to declared parameters' do
821
- subject.params do
822
- optional :items, type: Array do
823
- requires :key
824
- end
825
- end
826
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq([items: [:key]])
827
- end
828
- end
829
-
830
- context 'nested optional Array blocks' do
831
- before do
832
- subject.params do
833
- optional :items, type: Array do
834
- requires :key
835
- optional(:optional_subitems, type: Array) { requires :value }
836
- requires(:required_subitems, type: Array) { requires :value }
837
- end
838
- end
839
- subject.get('/nested_optional_group') { 'nested optional group works' }
840
- end
841
-
842
- it 'does no internal validations if the outer group is blank' do
843
- get '/nested_optional_group'
844
- expect(last_response.status).to eq(200)
845
- expect(last_response.body).to eq('nested optional group works')
846
- end
847
-
848
- it 'does internal validations if the outer group is present' do
849
- get '/nested_optional_group', items: [{ key: 'foo' }]
850
- expect(last_response.status).to eq(400)
851
- expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing')
852
-
853
- get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
854
- expect(last_response.status).to eq(200)
855
- expect(last_response.body).to eq('nested optional group works')
856
- end
857
-
858
- it 'handles deep nesting' do
859
- get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }]
860
- expect(last_response.status).to eq(400)
861
- expect(last_response.body).to eq('items[0][optional_subitems][0][value] is missing')
862
-
863
- get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ value: 'baz' }] }]
864
- expect(last_response.status).to eq(200)
865
- expect(last_response.body).to eq('nested optional group works')
866
- end
867
-
868
- it 'handles validation within arrays' do
869
- get '/nested_optional_group', items: [{ key: 'foo' }]
870
- expect(last_response.status).to eq(400)
871
- expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing')
872
-
873
- get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
874
- expect(last_response.status).to eq(200)
875
- expect(last_response.body).to eq('nested optional group works')
876
-
877
- get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }]
878
- expect(last_response.status).to eq(400)
879
- expect(last_response.body).to eq('items[0][optional_subitems][0][value] is missing')
880
- end
881
-
882
- it 'adds to declared parameters' do
883
- subject.params do
884
- optional :items, type: Array do
885
- requires :key
886
- optional(:optional_subitems, type: Array) { requires :value }
887
- requires(:required_subitems, type: Array) { requires :value }
888
- end
889
- end
890
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]])
891
- end
892
-
893
- context <<~DESC do
894
- Issue occurs whenever:
895
- * param structure with at least three levels
896
- * 1st level item is a required Array that has >1 entry with an optional item present and >1 entry with an optional item missing#{' '}
897
- * 2nd level is an optional Array or Hash#{' '}
898
- * 3rd level is a required item (can be any type)
899
- * additional levels do not effect the issue from occuring
900
- DESC
901
-
902
- it 'example based off actual real world use case' do
903
- subject.params do
904
- requires :orders, type: Array do
905
- requires :id, type: Integer
906
- optional :drugs, type: Array do
907
- requires :batches, type: Array do
908
- requires :batch_no, type: String
909
- end
910
- end
911
- end
912
- end
913
-
914
- subject.get '/validate_required_arrays_under_optional_arrays' do
915
- 'validate_required_arrays_under_optional_arrays works!'
916
- end
917
-
918
- data = {
919
- orders: [
920
- { id: 77, drugs: [{ batches: [{ batch_no: 'A1234567' }] }] },
921
- { id: 70 }
922
- ]
923
- }
924
-
925
- get '/validate_required_arrays_under_optional_arrays', data
926
- expect(last_response.body).to eq('validate_required_arrays_under_optional_arrays works!')
927
- expect(last_response.status).to eq(200)
928
- end
929
-
930
- it 'simplest example using Array -> Array -> Hash -> String' do
931
- subject.params do
932
- requires :orders, type: Array do
933
- requires :id, type: Integer
934
- optional :drugs, type: Array do
935
- requires :batch_no, type: String
936
- end
937
- end
938
- end
939
-
940
- subject.get '/validate_required_arrays_under_optional_arrays' do
941
- 'validate_required_arrays_under_optional_arrays works!'
942
- end
943
-
944
- data = {
945
- orders: [
946
- { id: 77, drugs: [{ batch_no: 'A1234567' }] },
947
- { id: 70 }
948
- ]
949
- }
950
-
951
- get '/validate_required_arrays_under_optional_arrays', data
952
- expect(last_response.body).to eq('validate_required_arrays_under_optional_arrays works!')
953
- expect(last_response.status).to eq(200)
954
- end
955
-
956
- it 'simplest example using Array -> Hash -> String' do
957
- subject.params do
958
- requires :orders, type: Array do
959
- requires :id, type: Integer
960
- optional :drugs, type: Hash do
961
- requires :batch_no, type: String
962
- end
963
- end
964
- end
965
-
966
- subject.get '/validate_required_arrays_under_optional_arrays' do
967
- 'validate_required_arrays_under_optional_arrays works!'
968
- end
969
-
970
- data = {
971
- orders: [
972
- { id: 77, drugs: { batch_no: 'A1234567' } },
973
- { id: 70 }
974
- ]
975
- }
976
-
977
- get '/validate_required_arrays_under_optional_arrays', data
978
- expect(last_response.body).to eq('validate_required_arrays_under_optional_arrays works!')
979
- expect(last_response.status).to eq(200)
980
- end
981
-
982
- it 'correctly indexes invalida data' do
983
- subject.params do
984
- requires :orders, type: Array do
985
- requires :id, type: Integer
986
- optional :drugs, type: Array do
987
- requires :batch_no, type: String
988
- requires :quantity, type: Integer
989
- end
990
- end
991
- end
992
-
993
- subject.get '/correctly_indexes' do
994
- 'correctly_indexes works!'
995
- end
996
-
997
- data = {
998
- orders: [
999
- { id: 70 },
1000
- { id: 77, drugs: [{ batch_no: 'A1234567', quantity: 12 }, { batch_no: 'B222222' }] }
1001
- ]
1002
- }
1003
-
1004
- get '/correctly_indexes', data
1005
- expect(last_response.body).to eq('orders[1][drugs][1][quantity] is missing')
1006
- expect(last_response.status).to eq(400)
1007
- end
1008
-
1009
- context 'multiple levels of optional and requires settings' do
1010
- before do
1011
- subject.params do
1012
- requires :top, type: Array do
1013
- requires :top_id, type: Integer, allow_blank: false
1014
- optional :middle_1, type: Array do
1015
- requires :middle_1_id, type: Integer, allow_blank: false
1016
- optional :middle_2, type: Array do
1017
- requires :middle_2_id, type: String, allow_blank: false
1018
- optional :bottom, type: Array do
1019
- requires :bottom_id, type: Integer, allow_blank: false
1020
- end
1021
- end
1022
- end
1023
- end
1024
- end
1025
-
1026
- subject.get '/multi_level' do
1027
- 'multi_level works!'
1028
- end
1029
- end
1030
-
1031
- it 'with valid data' do
1032
- data = {
1033
- top: [
1034
- { top_id: 1, middle_1: [
1035
- { middle_1_id: 11 }, { middle_1_id: 12, middle_2: [
1036
- { middle_2_id: 121 }, { middle_2_id: 122, bottom: [{ bottom_id: 1221 }] }
1037
- ] }
1038
- ] },
1039
- { top_id: 2, middle_1: [
1040
- { middle_1_id: 21 }, { middle_1_id: 22, middle_2: [
1041
- { middle_2_id: 221 }
1042
- ] }
1043
- ] },
1044
- { top_id: 3, middle_1: [
1045
- { middle_1_id: 31 }, { middle_1_id: 32 }
1046
- ] },
1047
- { top_id: 4 }
1048
- ]
1049
- }
1050
-
1051
- get '/multi_level', data
1052
- expect(last_response.body).to eq('multi_level works!')
1053
- expect(last_response.status).to eq(200)
1054
- end
1055
-
1056
- it 'with invalid data' do
1057
- data = {
1058
- top: [
1059
- { top_id: 1, middle_1: [
1060
- { middle_1_id: 11 }, { middle_1_id: 12, middle_2: [
1061
- { middle_2_id: 121 }, { middle_2_id: 122, bottom: [{ bottom_id: nil }] }
1062
- ] }
1063
- ] },
1064
- { top_id: 2, middle_1: [
1065
- { middle_1_id: 21 }, { middle_1_id: 22, middle_2: [{ middle_2_id: nil }] }
1066
- ] },
1067
- { top_id: 3, middle_1: [
1068
- { middle_1_id: nil }, { middle_1_id: 32 }
1069
- ] },
1070
- { top_id: nil, missing_top_id: 4 }
1071
- ]
1072
- }
1073
- # debugger
1074
- get '/multi_level', data
1075
- expect(last_response.body.split(', ')).to contain_exactly(
1076
- 'top[3][top_id] is empty',
1077
- 'top[2][middle_1][0][middle_1_id] is empty',
1078
- 'top[1][middle_1][1][middle_2][0][middle_2_id] is empty',
1079
- 'top[0][middle_1][1][middle_2][1][bottom][0][bottom_id] is empty'
1080
- )
1081
- expect(last_response.status).to eq(400)
1082
- end
1083
- end
1084
- end
1085
-
1086
- it 'exactly_one_of' do
1087
- subject.params do
1088
- requires :orders, type: Array do
1089
- requires :id, type: Integer
1090
- optional :drugs, type: Hash do
1091
- optional :batch_no, type: String
1092
- optional :batch_id, type: String
1093
- exactly_one_of :batch_no, :batch_id
1094
- end
1095
- end
1096
- end
1097
-
1098
- subject.get '/exactly_one_of' do
1099
- 'exactly_one_of works!'
1100
- end
1101
-
1102
- data = {
1103
- orders: [
1104
- { id: 77, drugs: { batch_no: 'A1234567' } },
1105
- { id: 70 }
1106
- ]
1107
- }
1108
-
1109
- get '/exactly_one_of', data
1110
- expect(last_response.body).to eq('exactly_one_of works!')
1111
- expect(last_response.status).to eq(200)
1112
- end
1113
-
1114
- it 'at_least_one_of' do
1115
- subject.params do
1116
- requires :orders, type: Array do
1117
- requires :id, type: Integer
1118
- optional :drugs, type: Hash do
1119
- optional :batch_no, type: String
1120
- optional :batch_id, type: String
1121
- at_least_one_of :batch_no, :batch_id
1122
- end
1123
- end
1124
- end
1125
-
1126
- subject.get '/at_least_one_of' do
1127
- 'at_least_one_of works!'
1128
- end
1129
-
1130
- data = {
1131
- orders: [
1132
- { id: 77, drugs: { batch_no: 'A1234567' } },
1133
- { id: 70 }
1134
- ]
1135
- }
1136
-
1137
- get '/at_least_one_of', data
1138
- expect(last_response.body).to eq('at_least_one_of works!')
1139
- expect(last_response.status).to eq(200)
1140
- end
1141
-
1142
- it 'all_or_none_of' do
1143
- subject.params do
1144
- requires :orders, type: Array do
1145
- requires :id, type: Integer
1146
- optional :drugs, type: Hash do
1147
- optional :batch_no, type: String
1148
- optional :batch_id, type: String
1149
- all_or_none_of :batch_no, :batch_id
1150
- end
1151
- end
1152
- end
1153
-
1154
- subject.get '/all_or_none_of' do
1155
- 'all_or_none_of works!'
1156
- end
1157
-
1158
- data = {
1159
- orders: [
1160
- { id: 77, drugs: { batch_no: 'A1234567', batch_id: '12' } },
1161
- { id: 70 }
1162
- ]
1163
- }
1164
-
1165
- get '/all_or_none_of', data
1166
- expect(last_response.body).to eq('all_or_none_of works!')
1167
- expect(last_response.status).to eq(200)
1168
- end
1169
- end
1170
-
1171
- context 'multiple validation errors' do
1172
- before do
1173
- subject.params do
1174
- requires :yolo
1175
- requires :swag
1176
- end
1177
- subject.get '/two_required' do
1178
- 'two required works'
1179
- end
1180
- end
1181
-
1182
- it 'throws the validation errors' do
1183
- get '/two_required'
1184
- expect(last_response.status).to eq(400)
1185
- expect(last_response.body).to match(/yolo is missing/)
1186
- expect(last_response.body).to match(/swag is missing/)
1187
- end
1188
- end
1189
-
1190
- context 'custom validation' do
1191
- let(:custom_validator) do
1192
- Class.new(Grape::Validations::Validators::Base) do
1193
- def validate_param!(attr_name, params)
1194
- return if params[attr_name] == 'im custom'
1195
-
1196
- raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: 'is not custom!')
1197
- end
1198
- end
1199
- end
1200
-
1201
- before do
1202
- described_class.register_validator('customvalidator', custom_validator)
1203
- end
1204
-
1205
- after do
1206
- described_class.deregister_validator('customvalidator')
1207
- end
1208
-
1209
- context 'when using optional with a custom validator' do
1210
- before do
1211
- subject.params do
1212
- optional :custom, customvalidator: true
1213
- end
1214
- subject.get '/optional_custom' do
1215
- 'optional with custom works!'
1216
- end
1217
- end
1218
-
1219
- it 'validates when param is present' do
1220
- get '/optional_custom', custom: 'im custom'
1221
- expect(last_response.status).to eq(200)
1222
- expect(last_response.body).to eq('optional with custom works!')
1223
-
1224
- get '/optional_custom', custom: 'im wrong'
1225
- expect(last_response.status).to eq(400)
1226
- expect(last_response.body).to eq('custom is not custom!')
1227
- end
1228
-
1229
- it "skips validation when parameter isn't present" do
1230
- get '/optional_custom'
1231
- expect(last_response.status).to eq(200)
1232
- expect(last_response.body).to eq('optional with custom works!')
1233
- end
1234
-
1235
- it 'validates with custom validator when param present and incorrect type' do
1236
- subject.params do
1237
- optional :custom, type: String, customvalidator: true
1238
- end
1239
-
1240
- get '/optional_custom', custom: 123
1241
- expect(last_response.status).to eq(400)
1242
- expect(last_response.body).to eq('custom is not custom!')
1243
- end
1244
- end
1245
-
1246
- context 'when using requires with a custom validator' do
1247
- before do
1248
- subject.params do
1249
- requires :custom, customvalidator: true
1250
- end
1251
- subject.get '/required_custom' do
1252
- 'required with custom works!'
1253
- end
1254
- end
1255
-
1256
- it 'validates when param is present' do
1257
- get '/required_custom', custom: 'im wrong, validate me'
1258
- expect(last_response.status).to eq(400)
1259
- expect(last_response.body).to eq('custom is not custom!')
1260
-
1261
- get '/required_custom', custom: 'im custom'
1262
- expect(last_response.status).to eq(200)
1263
- expect(last_response.body).to eq('required with custom works!')
1264
- end
1265
-
1266
- it 'validates when param is not present' do
1267
- get '/required_custom'
1268
- expect(last_response.status).to eq(400)
1269
- expect(last_response.body).to eq('custom is missing, custom is not custom!')
1270
- end
1271
-
1272
- context 'nested namespaces' do
1273
- before do
1274
- subject.params do
1275
- requires :custom, customvalidator: true
1276
- end
1277
- subject.namespace 'nested' do
1278
- get 'one' do
1279
- 'validation failed'
1280
- end
1281
- namespace 'nested' do
1282
- get 'two' do
1283
- 'validation failed'
1284
- end
1285
- end
1286
- end
1287
- subject.namespace 'peer' do
1288
- get 'one' do
1289
- 'no validation required'
1290
- end
1291
- namespace 'nested' do
1292
- get 'two' do
1293
- 'no validation required'
1294
- end
1295
- end
1296
- end
1297
-
1298
- subject.namespace 'unrelated' do
1299
- params do
1300
- requires :name
1301
- end
1302
- get 'one' do
1303
- 'validation required'
1304
- end
1305
-
1306
- namespace 'double' do
1307
- get 'two' do
1308
- 'no validation required'
1309
- end
1310
- end
1311
- end
1312
- end
1313
-
1314
- specify 'the parent namespace uses the validator' do
1315
- get '/nested/one', custom: 'im wrong, validate me'
1316
- expect(last_response.status).to eq(400)
1317
- expect(last_response.body).to eq('custom is not custom!')
1318
- end
1319
-
1320
- specify 'the nested namespace inherits the custom validator' do
1321
- get '/nested/nested/two', custom: 'im wrong, validate me'
1322
- expect(last_response.status).to eq(400)
1323
- expect(last_response.body).to eq('custom is not custom!')
1324
- end
1325
-
1326
- specify 'peer namespaces does not have the validator' do
1327
- get '/peer/one', custom: 'im not validated'
1328
- expect(last_response.status).to eq(200)
1329
- expect(last_response.body).to eq('no validation required')
1330
- end
1331
-
1332
- specify 'namespaces nested in peers should also not have the validator' do
1333
- get '/peer/nested/two', custom: 'im not validated'
1334
- expect(last_response.status).to eq(200)
1335
- expect(last_response.body).to eq('no validation required')
1336
- end
1337
-
1338
- specify 'when nested, specifying a route should clear out the validations for deeper nested params' do
1339
- get '/unrelated/one'
1340
- expect(last_response.status).to eq(400)
1341
- get '/unrelated/double/two'
1342
- expect(last_response.status).to eq(200)
1343
- end
1344
- end
1345
- end
1346
-
1347
- context 'when using options on param' do
1348
- let(:custom_validator_with_options) do
1349
- Class.new(Grape::Validations::Validators::Base) do
1350
- def validate_param!(attr_name, params)
1351
- return if params[attr_name] == @option[:text]
1352
-
1353
- raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message)
1354
- end
1355
- end
1356
- end
1357
-
1358
- before do
1359
- described_class.register_validator('customvalidator_with_options', custom_validator_with_options)
1360
- end
1361
-
1362
- after do
1363
- described_class.deregister_validator('customvalidator_with_options')
1364
- end
1365
-
1366
- before do
1367
- subject.params do
1368
- optional :custom, customvalidator_with_options: { text: 'im custom with options', message: 'is not custom with options!' }
1369
- end
1370
- subject.get '/optional_custom' do
1371
- 'optional with custom works!'
1372
- end
1373
- end
1374
-
1375
- it 'validates param with custom validator with options' do
1376
- get '/optional_custom', custom: 'im custom with options'
1377
- expect(last_response.status).to eq(200)
1378
- expect(last_response.body).to eq('optional with custom works!')
1379
-
1380
- get '/optional_custom', custom: 'im wrong'
1381
- expect(last_response.status).to eq(400)
1382
- expect(last_response.body).to eq('custom is not custom with options!')
1383
- end
1384
- end
1385
- end
1386
-
1387
- context 'named' do
1388
- context 'can be defined' do
1389
- it 'in helpers' do
1390
- subject.helpers do
1391
- params :pagination do
1392
- end
1393
- end
1394
- end
1395
-
1396
- it 'in helper module which kind of Grape::DSL::Helpers::BaseHelper' do
1397
- shared_params = Module.new do
1398
- extend Grape::DSL::Helpers::BaseHelper
1399
- params :pagination do
1400
- end
1401
- end
1402
- subject.helpers shared_params
1403
- end
1404
- end
1405
-
1406
- context 'can be included in usual params' do
1407
- before do
1408
- shared_params = Module.new do
1409
- extend Grape::DSL::Helpers::BaseHelper
1410
- params :period do
1411
- optional :start_date
1412
- optional :end_date
1413
- end
1414
- end
1415
-
1416
- subject.helpers shared_params
1417
-
1418
- subject.helpers do
1419
- params :pagination do
1420
- optional :page, type: Integer
1421
- optional :per_page, type: Integer
1422
- end
1423
- end
1424
- end
1425
-
1426
- it 'by #use' do
1427
- subject.params do
1428
- use :pagination
1429
- end
1430
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq %i[page per_page]
1431
- end
1432
-
1433
- it 'by #use with multiple params' do
1434
- subject.params do
1435
- use :pagination, :period
1436
- end
1437
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq %i[page per_page start_date end_date]
1438
- end
1439
- end
1440
-
1441
- context 'with block' do
1442
- before do
1443
- subject.helpers do
1444
- params :order do |options|
1445
- optional :order, type: Symbol, values: %i[asc desc], default: options[:default_order]
1446
- optional :order_by, type: Symbol, values: options[:order_by], default: options[:default_order_by]
1447
- end
1448
- end
1449
- subject.format :json
1450
- subject.params do
1451
- use :order, default_order: :asc, order_by: %i[name created_at], default_order_by: :created_at
1452
- end
1453
- subject.get '/order' do
1454
- {
1455
- order: params[:order],
1456
- order_by: params[:order_by]
1457
- }
1458
- end
1459
- end
1460
-
1461
- it 'returns defaults' do
1462
- get '/order'
1463
- expect(last_response.status).to eq(200)
1464
- expect(last_response.body).to eq({ order: :asc, order_by: :created_at }.to_json)
1465
- end
1466
-
1467
- it 'overrides default value for order' do
1468
- get '/order?order=desc'
1469
- expect(last_response.status).to eq(200)
1470
- expect(last_response.body).to eq({ order: :desc, order_by: :created_at }.to_json)
1471
- end
1472
-
1473
- it 'overrides default value for order_by' do
1474
- get '/order?order_by=name'
1475
- expect(last_response.status).to eq(200)
1476
- expect(last_response.body).to eq({ order: :asc, order_by: :name }.to_json)
1477
- end
1478
-
1479
- it 'fails with invalid value' do
1480
- get '/order?order=invalid'
1481
- expect(last_response.status).to eq(400)
1482
- expect(last_response.body).to eq('{"error":"order does not have a valid value"}')
1483
- end
1484
- end
1485
- end
1486
-
1487
- context 'with block and keyword argument' do
1488
- before do
1489
- subject.helpers do
1490
- params :shared_params do |type:|
1491
- optional :param, default: type
1492
- end
1493
- end
1494
- subject.format :json
1495
- subject.params do
1496
- use :shared_params, type: 'value'
1497
- end
1498
- subject.get '/shared_params' do
1499
- {
1500
- param: params[:param]
1501
- }
1502
- end
1503
- end
1504
-
1505
- it 'works' do
1506
- get '/shared_params'
1507
-
1508
- expect(last_response.status).to eq(200)
1509
- expect(last_response.body).to eq({ param: 'value' }.to_json)
1510
- end
1511
- end
1512
-
1513
- context 'with block and empty args' do
1514
- before do
1515
- subject.helpers do
1516
- params :shared_params do |empty_args|
1517
- optional :param, default: empty_args[:some]
1518
- end
1519
- end
1520
- subject.format :json
1521
- subject.params do
1522
- use :shared_params
1523
- end
1524
- subject.get '/shared_params' do
1525
- :ok
1526
- end
1527
- end
1528
-
1529
- it 'works' do
1530
- get '/shared_params'
1531
-
1532
- expect(last_response.status).to eq(200)
1533
- end
1534
- end
1535
-
1536
- context 'all or none' do
1537
- context 'optional params' do
1538
- before do
1539
- subject.resource :custom_message do
1540
- params do
1541
- optional :beer
1542
- optional :wine
1543
- optional :juice
1544
- all_or_none_of :beer, :wine, :juice, message: 'all params are required or none is required'
1545
- end
1546
- get '/all_or_none' do
1547
- 'all_or_none works!'
1548
- end
1549
- end
1550
- end
1551
-
1552
- context 'with a custom validation message' do
1553
- it 'errors when any one is present' do
1554
- get '/custom_message/all_or_none', beer: 'string'
1555
- expect(last_response.status).to eq(400)
1556
- expect(last_response.body).to eq 'beer, wine, juice all params are required or none is required'
1557
- end
1558
-
1559
- it 'works when all params are present' do
1560
- get '/custom_message/all_or_none', beer: 'string', wine: 'anotherstring', juice: 'anotheranotherstring'
1561
- expect(last_response.status).to eq(200)
1562
- expect(last_response.body).to eq 'all_or_none works!'
1563
- end
1564
-
1565
- it 'works when none are present' do
1566
- get '/custom_message/all_or_none'
1567
- expect(last_response.status).to eq(200)
1568
- expect(last_response.body).to eq 'all_or_none works!'
1569
- end
1570
- end
1571
- end
1572
- end
1573
-
1574
- context 'mutually exclusive' do
1575
- context 'optional params' do
1576
- context 'with custom validation message' do
1577
- it 'errors when two or more are present' do
1578
- subject.resources :custom_message do
1579
- params do
1580
- optional :beer
1581
- optional :wine
1582
- optional :juice
1583
- mutually_exclusive :beer, :wine, :juice, message: 'are mutually exclusive cannot pass both params'
1584
- end
1585
- get '/mutually_exclusive' do
1586
- 'mutually_exclusive works!'
1587
- end
1588
- end
1589
- get '/custom_message/mutually_exclusive', beer: 'string', wine: 'anotherstring'
1590
- expect(last_response.status).to eq(400)
1591
- expect(last_response.body).to eq 'beer, wine are mutually exclusive cannot pass both params'
1592
- end
1593
- end
1594
-
1595
- it 'errors when two or more are present' do
1596
- subject.params do
1597
- optional :beer
1598
- optional :wine
1599
- optional :juice
1600
- mutually_exclusive :beer, :wine, :juice
1601
- end
1602
- subject.get '/mutually_exclusive' do
1603
- 'mutually_exclusive works!'
1604
- end
1605
-
1606
- get '/mutually_exclusive', beer: 'string', wine: 'anotherstring'
1607
- expect(last_response.status).to eq(400)
1608
- expect(last_response.body).to eq 'beer, wine are mutually exclusive'
1609
- end
1610
- end
1611
-
1612
- context 'more than one set of mutually exclusive params' do
1613
- context 'with a custom validation message' do
1614
- it 'errors for all sets' do
1615
- subject.resources :custom_message do
1616
- params do
1617
- optional :beer
1618
- optional :wine
1619
- mutually_exclusive :beer, :wine, message: 'are mutually exclusive pass only one'
1620
- optional :nested, type: Hash do
1621
- optional :scotch
1622
- optional :aquavit
1623
- mutually_exclusive :scotch, :aquavit, message: 'are mutually exclusive pass only one'
1624
- end
1625
- optional :nested2, type: Array do
1626
- optional :scotch2
1627
- optional :aquavit2
1628
- mutually_exclusive :scotch2, :aquavit2, message: 'are mutually exclusive pass only one'
1629
- end
1630
- end
1631
- get '/mutually_exclusive' do
1632
- 'mutually_exclusive works!'
1633
- end
1634
- end
1635
- get '/custom_message/mutually_exclusive', beer: 'true', wine: 'true', nested: { scotch: 'true', aquavit: 'true' }, nested2: [{ scotch2: 'true' }, { scotch2: 'true', aquavit2: 'true' }]
1636
- expect(last_response.status).to eq(400)
1637
- expect(last_response.body).to eq(
1638
- 'beer, wine are mutually exclusive pass only one, nested[scotch], nested[aquavit] are mutually exclusive pass only one, nested2[1][scotch2], nested2[1][aquavit2] are mutually exclusive pass only one'
1639
- )
1640
- end
1641
- end
1642
-
1643
- it 'errors for all sets' do
1644
- subject.params do
1645
- optional :beer
1646
- optional :wine
1647
- mutually_exclusive :beer, :wine
1648
- optional :nested, type: Hash do
1649
- optional :scotch
1650
- optional :aquavit
1651
- mutually_exclusive :scotch, :aquavit
1652
- end
1653
- optional :nested2, type: Array do
1654
- optional :scotch2
1655
- optional :aquavit2
1656
- mutually_exclusive :scotch2, :aquavit2
1657
- end
1658
- end
1659
- subject.get '/mutually_exclusive' do
1660
- 'mutually_exclusive works!'
1661
- end
1662
-
1663
- get '/mutually_exclusive', beer: 'true', wine: 'true', nested: { scotch: 'true', aquavit: 'true' }, nested2: [{ scotch2: 'true' }, { scotch2: 'true', aquavit2: 'true' }]
1664
- expect(last_response.status).to eq(400)
1665
- expect(last_response.body).to eq 'beer, wine are mutually exclusive, nested[scotch], nested[aquavit] are mutually exclusive, nested2[1][scotch2], nested2[1][aquavit2] are mutually exclusive'
1666
- end
1667
- end
1668
-
1669
- context 'in a group' do
1670
- it 'works when only one from the set is present' do
1671
- subject.params do
1672
- group :drink, type: Hash do
1673
- optional :wine
1674
- optional :beer
1675
- optional :juice
1676
- mutually_exclusive :beer, :wine, :juice
1677
- end
1678
- end
1679
- subject.get '/mutually_exclusive_group' do
1680
- 'mutually_exclusive_group works!'
1681
- end
1682
-
1683
- get '/mutually_exclusive_group', drink: { beer: 'true' }
1684
- expect(last_response.status).to eq(200)
1685
- end
1686
-
1687
- it 'errors when more than one from the set is present' do
1688
- subject.params do
1689
- group :drink, type: Hash do
1690
- optional :wine
1691
- optional :beer
1692
- optional :juice
1693
-
1694
- mutually_exclusive :beer, :wine, :juice
1695
- end
1696
- end
1697
- subject.get '/mutually_exclusive_group' do
1698
- 'mutually_exclusive_group works!'
1699
- end
1700
-
1701
- get '/mutually_exclusive_group', drink: { beer: 'true', juice: 'true', wine: 'true' }
1702
- expect(last_response.status).to eq(400)
1703
- end
1704
- end
1705
-
1706
- context 'mutually exclusive params inside Hash group' do
1707
- it 'invalidates if request param is invalid type' do
1708
- subject.params do
1709
- optional :wine, type: Hash do
1710
- optional :grape
1711
- optional :country
1712
- mutually_exclusive :grape, :country
1713
- end
1714
- end
1715
- subject.post '/mutually_exclusive' do
1716
- 'mutually_exclusive works!'
1717
- end
1718
-
1719
- post '/mutually_exclusive', wine: '2015 sauvignon'
1720
- expect(last_response.status).to eq(400)
1721
- expect(last_response.body).to eq 'wine is invalid'
1722
- end
1723
- end
1724
- end
1725
-
1726
- context 'exactly one of' do
1727
- context 'params' do
1728
- before do
1729
- subject.resources :custom_message do
1730
- params do
1731
- optional :beer
1732
- optional :wine
1733
- optional :juice
1734
- exactly_one_of :beer, :wine, :juice, message: 'are missing, exactly one parameter is required'
1735
- end
1736
- get '/exactly_one_of' do
1737
- 'exactly_one_of works!'
1738
- end
1739
- end
1740
-
1741
- subject.params do
1742
- optional :beer
1743
- optional :wine
1744
- optional :juice
1745
- exactly_one_of :beer, :wine, :juice
1746
- end
1747
- subject.get '/exactly_one_of' do
1748
- 'exactly_one_of works!'
1749
- end
1750
- end
1751
-
1752
- context 'with a custom validation message' do
1753
- it 'errors when none are present' do
1754
- get '/custom_message/exactly_one_of'
1755
- expect(last_response.status).to eq(400)
1756
- expect(last_response.body).to eq 'beer, wine, juice are missing, exactly one parameter is required'
1757
- end
1758
-
1759
- it 'succeeds when one is present' do
1760
- get '/custom_message/exactly_one_of', beer: 'string'
1761
- expect(last_response.status).to eq(200)
1762
- expect(last_response.body).to eq 'exactly_one_of works!'
1763
- end
1764
-
1765
- it 'errors when two or more are present' do
1766
- get '/custom_message/exactly_one_of', beer: 'string', wine: 'anotherstring'
1767
- expect(last_response.status).to eq(400)
1768
- expect(last_response.body).to eq 'beer, wine are missing, exactly one parameter is required'
1769
- end
1770
- end
1771
-
1772
- it 'errors when none are present' do
1773
- get '/exactly_one_of'
1774
- expect(last_response.status).to eq(400)
1775
- expect(last_response.body).to eq 'beer, wine, juice are missing, exactly one parameter must be provided'
1776
- end
1777
-
1778
- it 'succeeds when one is present' do
1779
- get '/exactly_one_of', beer: 'string'
1780
- expect(last_response.status).to eq(200)
1781
- expect(last_response.body).to eq 'exactly_one_of works!'
1782
- end
1783
-
1784
- it 'errors when two or more are present' do
1785
- get '/exactly_one_of', beer: 'string', wine: 'anotherstring'
1786
- expect(last_response.status).to eq(400)
1787
- expect(last_response.body).to eq 'beer, wine are mutually exclusive'
1788
- end
1789
- end
1790
-
1791
- context 'nested params' do
1792
- before do
1793
- subject.params do
1794
- requires :nested, type: Hash do
1795
- optional :beer_nested
1796
- optional :wine_nested
1797
- optional :juice_nested
1798
- exactly_one_of :beer_nested, :wine_nested, :juice_nested
1799
- end
1800
- optional :nested2, type: Array do
1801
- optional :beer_nested2
1802
- optional :wine_nested2
1803
- optional :juice_nested2
1804
- exactly_one_of :beer_nested2, :wine_nested2, :juice_nested2
1805
- end
1806
- end
1807
- subject.get '/exactly_one_of_nested' do
1808
- 'exactly_one_of works!'
1809
- end
1810
- end
1811
-
1812
- it 'errors when none are present' do
1813
- get '/exactly_one_of_nested'
1814
- expect(last_response.status).to eq(400)
1815
- expect(last_response.body).to eq 'nested is missing, nested[beer_nested], nested[wine_nested], nested[juice_nested] are missing, exactly one parameter must be provided'
1816
- end
1817
-
1818
- it 'succeeds when one is present' do
1819
- get '/exactly_one_of_nested', nested: { beer_nested: 'string' }
1820
- expect(last_response.status).to eq(200)
1821
- expect(last_response.body).to eq 'exactly_one_of works!'
1822
- end
1823
-
1824
- it 'errors when two or more are present' do
1825
- get '/exactly_one_of_nested', nested: { beer_nested: 'string' }, nested2: [{ beer_nested2: 'string', wine_nested2: 'anotherstring' }]
1826
- expect(last_response.status).to eq(400)
1827
- expect(last_response.body).to eq 'nested2[0][beer_nested2], nested2[0][wine_nested2] are mutually exclusive'
1828
- end
1829
- end
1830
- end
1831
-
1832
- context 'at least one of' do
1833
- context 'params' do
1834
- before do
1835
- subject.resources :custom_message do
1836
- params do
1837
- optional :beer
1838
- optional :wine
1839
- optional :juice
1840
- at_least_one_of :beer, :wine, :juice, message: 'are missing, please specify at least one param'
1841
- end
1842
- get '/at_least_one_of' do
1843
- 'at_least_one_of works!'
1844
- end
1845
- end
1846
-
1847
- subject.params do
1848
- optional :beer
1849
- optional :wine
1850
- optional :juice
1851
- at_least_one_of :beer, :wine, :juice
1852
- end
1853
- subject.get '/at_least_one_of' do
1854
- 'at_least_one_of works!'
1855
- end
1856
- end
1857
-
1858
- context 'with a custom validation message' do
1859
- it 'errors when none are present' do
1860
- get '/custom_message/at_least_one_of'
1861
- expect(last_response.status).to eq(400)
1862
- expect(last_response.body).to eq 'beer, wine, juice are missing, please specify at least one param'
1863
- end
1864
-
1865
- it 'does not error when one is present' do
1866
- get '/custom_message/at_least_one_of', beer: 'string'
1867
- expect(last_response.status).to eq(200)
1868
- expect(last_response.body).to eq 'at_least_one_of works!'
1869
- end
1870
-
1871
- it 'does not error when two are present' do
1872
- get '/custom_message/at_least_one_of', beer: 'string', wine: 'string'
1873
- expect(last_response.status).to eq(200)
1874
- expect(last_response.body).to eq 'at_least_one_of works!'
1875
- end
1876
- end
1877
-
1878
- it 'errors when none are present' do
1879
- get '/at_least_one_of'
1880
- expect(last_response.status).to eq(400)
1881
- expect(last_response.body).to eq 'beer, wine, juice are missing, at least one parameter must be provided'
1882
- end
1883
-
1884
- it 'does not error when one is present' do
1885
- get '/at_least_one_of', beer: 'string'
1886
- expect(last_response.status).to eq(200)
1887
- expect(last_response.body).to eq 'at_least_one_of works!'
1888
- end
1889
-
1890
- it 'does not error when two are present' do
1891
- get '/at_least_one_of', beer: 'string', wine: 'string'
1892
- expect(last_response.status).to eq(200)
1893
- expect(last_response.body).to eq 'at_least_one_of works!'
1894
- end
1895
- end
1896
-
1897
- context 'nested params' do
1898
- before do
1899
- subject.params do
1900
- requires :nested, type: Hash do
1901
- optional :beer
1902
- optional :wine
1903
- optional :juice
1904
- at_least_one_of :beer, :wine, :juice
1905
- end
1906
- optional :nested2, type: Array do
1907
- optional :beer
1908
- optional :wine
1909
- optional :juice
1910
- at_least_one_of :beer, :wine, :juice
1911
- end
1912
- end
1913
- subject.get '/at_least_one_of_nested' do
1914
- 'at_least_one_of works!'
1915
- end
1916
- end
1917
-
1918
- it 'errors when none are present' do
1919
- get '/at_least_one_of_nested'
1920
- expect(last_response.status).to eq(400)
1921
- expect(last_response.body).to eq 'nested is missing, nested[beer], nested[wine], nested[juice] are missing, at least one parameter must be provided'
1922
- end
1923
-
1924
- it 'does not error when one is present' do
1925
- get '/at_least_one_of_nested', nested: { beer: 'string' }, nested2: [{ beer: 'string' }]
1926
- expect(last_response.status).to eq(200)
1927
- expect(last_response.body).to eq 'at_least_one_of works!'
1928
- end
1929
-
1930
- it 'does not error when two are present' do
1931
- get '/at_least_one_of_nested', nested: { beer: 'string', wine: 'string' }, nested2: [{ beer: 'string', wine: 'string' }]
1932
- expect(last_response.status).to eq(200)
1933
- expect(last_response.body).to eq 'at_least_one_of works!'
1934
- end
1935
- end
1936
- end
1937
-
1938
- context 'in a group' do
1939
- it 'works when only one from the set is present' do
1940
- subject.params do
1941
- group :drink, type: Hash do
1942
- optional :wine
1943
- optional :beer
1944
- optional :juice
1945
-
1946
- exactly_one_of :beer, :wine, :juice
1947
- end
1948
- end
1949
- subject.get '/exactly_one_of_group' do
1950
- 'exactly_one_of_group works!'
1951
- end
1952
-
1953
- get '/exactly_one_of_group', drink: { beer: 'true' }
1954
- expect(last_response.status).to eq(200)
1955
- end
1956
-
1957
- it 'errors when no parameter from the set is present' do
1958
- subject.params do
1959
- group :drink, type: Hash do
1960
- optional :wine
1961
- optional :beer
1962
- optional :juice
1963
-
1964
- exactly_one_of :beer, :wine, :juice
1965
- end
1966
- end
1967
- subject.get '/exactly_one_of_group' do
1968
- 'exactly_one_of_group works!'
1969
- end
1970
-
1971
- get '/exactly_one_of_group', drink: {}
1972
- expect(last_response.status).to eq(400)
1973
- end
1974
-
1975
- it 'errors when more than one from the set is present' do
1976
- subject.params do
1977
- group :drink, type: Hash do
1978
- optional :wine
1979
- optional :beer
1980
- optional :juice
1981
-
1982
- exactly_one_of :beer, :wine, :juice
1983
- end
1984
- end
1985
- subject.get '/exactly_one_of_group' do
1986
- 'exactly_one_of_group works!'
1987
- end
1988
-
1989
- get '/exactly_one_of_group', drink: { beer: 'true', juice: 'true', wine: 'true' }
1990
- expect(last_response.status).to eq(400)
1991
- end
1992
-
1993
- it 'does not falsely think the param is there if it is provided outside the block' do
1994
- subject.params do
1995
- group :drink, type: Hash do
1996
- optional :wine
1997
- optional :beer
1998
- optional :juice
1999
-
2000
- exactly_one_of :beer, :wine, :juice
2001
- end
2002
- end
2003
- subject.get '/exactly_one_of_group' do
2004
- 'exactly_one_of_group works!'
2005
- end
2006
-
2007
- get '/exactly_one_of_group', drink: { foo: 'bar' }, beer: 'true'
2008
- expect(last_response.status).to eq(400)
2009
- end
2010
- end
2011
- end
2012
-
2013
- describe 'require_validator' do
2014
- subject { described_class.require_validator(short_name) }
2015
-
2016
- context 'when found' do
2017
- let(:short_name) { :presence }
2018
-
2019
- it { is_expected.to be(Grape::Validations::Validators::PresenceValidator) }
2020
- end
2021
-
2022
- context 'when not found' do
2023
- let(:short_name) { :test }
2024
-
2025
- it 'raises an error' do
2026
- expect { subject }.to raise_error(Grape::Exceptions::UnknownValidator)
2027
- end
2028
- end
2029
- end
2030
- end