1use crate::composition_objects::{ConfirmationDialog, Opt, OptGroup, Plain, Text};
2use crate::errors::ValidationErrorKind;
3use crate::validators::*;
4
5use serde::Serialize;
6use slack_messaging_derive::Builder;
7
8#[derive(Debug, Default, Clone, Serialize, PartialEq, Builder)]
100#[serde(tag = "type", rename = "multi_static_select")]
101#[builder(validate = "validate")]
102pub struct MultiSelectMenuStaticOptions {
103 #[serde(skip_serializing_if = "Option::is_none")]
104 #[builder(validate("text::max_255"))]
105 pub(crate) action_id: Option<String>,
106
107 #[serde(skip_serializing_if = "Option::is_none")]
108 #[builder(push_item = "option", validate("list::max_item_100"))]
109 pub(crate) options: Option<Vec<Opt>>,
110
111 #[serde(skip_serializing_if = "Option::is_none")]
112 #[builder(push_item = "option_group", validate("list::max_item_100"))]
113 pub(crate) option_groups: Option<Vec<OptGroup>>,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
116 #[builder(push_item = "initial_option")]
117 pub(crate) initial_options: Option<Vec<Opt>>,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub(crate) confirm: Option<ConfirmationDialog>,
121
122 #[serde(skip_serializing_if = "Option::is_none")]
123 #[builder(validate("integer::min_1"))]
124 pub(crate) max_selected_items: Option<i64>,
125
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub(crate) focus_on_load: Option<bool>,
128
129 #[serde(skip_serializing_if = "Option::is_none")]
130 #[builder(validate("text_object::max_150"))]
131 pub(crate) placeholder: Option<Text<Plain>>,
132}
133
134fn validate(val: &MultiSelectMenuStaticOptions) -> Vec<ValidationErrorKind> {
135 match (val.options.as_ref(), val.option_groups.as_ref()) {
136 (Some(_), Some(_)) => {
137 vec![ValidationErrorKind::ExclusiveField(
138 "options",
139 "option_groups",
140 )]
141 }
142 (None, None) => {
143 vec![ValidationErrorKind::EitherRequired(
144 "options",
145 "option_groups",
146 )]
147 }
148 _ => vec![],
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::composition_objects::test_helpers::*;
156
157 #[test]
158 fn it_implements_builder() {
159 let expected = MultiSelectMenuStaticOptions {
161 action_id: Some("multi_select_0".into()),
162 options: Some(vec![option("opt0", "val0"), option("opt1", "val1")]),
163 option_groups: None,
164 initial_options: Some(vec![option("opt0", "val0")]),
165 confirm: Some(confirm()),
166 max_selected_items: Some(2),
167 focus_on_load: Some(true),
168 placeholder: Some(plain_text("Select items")),
169 };
170
171 let val = MultiSelectMenuStaticOptions::builder()
172 .set_action_id(Some("multi_select_0"))
173 .set_options(Some(vec![option("opt0", "val0"), option("opt1", "val1")]))
174 .set_initial_options(Some(vec![option("opt0", "val0")]))
175 .set_confirm(Some(confirm()))
176 .set_max_selected_items(Some(2))
177 .set_focus_on_load(Some(true))
178 .set_placeholder(Some(plain_text("Select items")))
179 .build()
180 .unwrap();
181
182 assert_eq!(val, expected);
183
184 let val = MultiSelectMenuStaticOptions::builder()
185 .action_id("multi_select_0")
186 .options(vec![option("opt0", "val0"), option("opt1", "val1")])
187 .initial_options(vec![option("opt0", "val0")])
188 .confirm(confirm())
189 .max_selected_items(2)
190 .focus_on_load(true)
191 .placeholder(plain_text("Select items"))
192 .build()
193 .unwrap();
194
195 assert_eq!(val, expected);
196
197 let expected = MultiSelectMenuStaticOptions {
199 action_id: Some("multi_select_0".into()),
200 options: None,
201 option_groups: Some(vec![
202 option_group(
203 "group0",
204 vec![option("opt00", "val00"), option("opt01", "val01")],
205 ),
206 option_group(
207 "group1",
208 vec![option("opt10", "val10"), option("opt11", "val11")],
209 ),
210 ]),
211 initial_options: Some(vec![option("opt00", "val00")]),
212 confirm: Some(confirm()),
213 max_selected_items: Some(2),
214 focus_on_load: Some(true),
215 placeholder: Some(plain_text("Select items")),
216 };
217
218 let val = MultiSelectMenuStaticOptions::builder()
219 .set_action_id(Some("multi_select_0"))
220 .set_option_groups(Some(vec![
221 option_group(
222 "group0",
223 vec![option("opt00", "val00"), option("opt01", "val01")],
224 ),
225 option_group(
226 "group1",
227 vec![option("opt10", "val10"), option("opt11", "val11")],
228 ),
229 ]))
230 .set_initial_options(Some(vec![option("opt00", "val00")]))
231 .set_confirm(Some(confirm()))
232 .set_max_selected_items(Some(2))
233 .set_focus_on_load(Some(true))
234 .set_placeholder(Some(plain_text("Select items")))
235 .build()
236 .unwrap();
237
238 assert_eq!(val, expected);
239
240 let val = MultiSelectMenuStaticOptions::builder()
241 .action_id("multi_select_0")
242 .option_groups(vec![
243 option_group(
244 "group0",
245 vec![option("opt00", "val00"), option("opt01", "val01")],
246 ),
247 option_group(
248 "group1",
249 vec![option("opt10", "val10"), option("opt11", "val11")],
250 ),
251 ])
252 .initial_options(vec![option("opt00", "val00")])
253 .confirm(confirm())
254 .max_selected_items(2)
255 .focus_on_load(true)
256 .placeholder(plain_text("Select items"))
257 .build()
258 .unwrap();
259
260 assert_eq!(val, expected);
261 }
262
263 #[test]
264 fn it_implements_push_item_method() {
265 let expected = MultiSelectMenuStaticOptions {
266 action_id: None,
267 options: Some(vec![option("opt0", "val0"), option("opt1", "val1")]),
268 option_groups: None,
269 initial_options: None,
270 confirm: None,
271 max_selected_items: None,
272 focus_on_load: None,
273 placeholder: None,
274 };
275
276 let val = MultiSelectMenuStaticOptions::builder()
277 .option(option("opt0", "val0"))
278 .option(option("opt1", "val1"))
279 .build()
280 .unwrap();
281
282 assert_eq!(val, expected);
283
284 let expected = MultiSelectMenuStaticOptions {
285 action_id: None,
286 options: None,
287 option_groups: Some(vec![
288 option_group(
289 "group0",
290 vec![option("opt00", "val00"), option("opt01", "val01")],
291 ),
292 option_group(
293 "group1",
294 vec![option("opt10", "val10"), option("opt11", "val11")],
295 ),
296 ]),
297 initial_options: None,
298 confirm: None,
299 max_selected_items: None,
300 focus_on_load: None,
301 placeholder: None,
302 };
303
304 let val = MultiSelectMenuStaticOptions::builder()
305 .option_group(option_group(
306 "group0",
307 vec![option("opt00", "val00"), option("opt01", "val01")],
308 ))
309 .option_group(option_group(
310 "group1",
311 vec![option("opt10", "val10"), option("opt11", "val11")],
312 ))
313 .build()
314 .unwrap();
315
316 assert_eq!(val, expected);
317
318 let expected = MultiSelectMenuStaticOptions {
319 action_id: None,
320 options: Some(vec![option("opt", "val")]),
321 option_groups: None,
322 initial_options: Some(vec![option("opt0", "val0"), option("opt1", "val1")]),
323 confirm: None,
324 max_selected_items: None,
325 focus_on_load: None,
326 placeholder: None,
327 };
328
329 let val = MultiSelectMenuStaticOptions::builder()
330 .option(option("opt", "val"))
331 .initial_option(option("opt0", "val0"))
332 .initial_option(option("opt1", "val1"))
333 .build()
334 .unwrap();
335
336 assert_eq!(val, expected);
337 }
338
339 #[test]
340 fn it_requires_action_id_less_than_255_characters_long() {
341 let err = MultiSelectMenuStaticOptions::builder()
342 .option(option("opt", "val"))
343 .action_id("a".repeat(256))
344 .build()
345 .unwrap_err();
346 assert_eq!(err.object(), "MultiSelectMenuStaticOptions");
347
348 let errors = err.field("action_id");
349 assert!(errors.includes(ValidationErrorKind::MaxTextLength(255)));
350 }
351
352 #[test]
353 fn it_requires_options_list_size_less_than_100() {
354 let options: Vec<Opt> = (0..101).map(|_| option("opt", "val")).collect();
355
356 let err = MultiSelectMenuStaticOptions::builder()
357 .options(options)
358 .build()
359 .unwrap_err();
360 assert_eq!(err.object(), "MultiSelectMenuStaticOptions");
361
362 let errors = err.field("options");
363 assert!(errors.includes(ValidationErrorKind::MaxArraySize(100)));
364 }
365
366 #[test]
367 fn it_requires_option_groups_list_size_less_than_100() {
368 let option_groups: Vec<OptGroup> = (0..101)
369 .map(|_| option_group("group", vec![option("opt", "val")]))
370 .collect();
371
372 let err = MultiSelectMenuStaticOptions::builder()
373 .option_groups(option_groups)
374 .build()
375 .unwrap_err();
376 assert_eq!(err.object(), "MultiSelectMenuStaticOptions");
377
378 let errors = err.field("option_groups");
379 assert!(errors.includes(ValidationErrorKind::MaxArraySize(100)));
380 }
381
382 #[test]
383 fn it_requires_max_selected_items_greater_than_1() {
384 let err = MultiSelectMenuStaticOptions::builder()
385 .option(option("opt", "val"))
386 .max_selected_items(0)
387 .build()
388 .unwrap_err();
389 assert_eq!(err.object(), "MultiSelectMenuStaticOptions");
390
391 let errors = err.field("max_selected_items");
392 assert!(errors.includes(ValidationErrorKind::MinIntegerValue(1)));
393 }
394
395 #[test]
396 fn it_requires_placeholder_text_less_than_150_characters_long() {
397 let err = MultiSelectMenuStaticOptions::builder()
398 .option(option("opt", "val"))
399 .placeholder(plain_text("a".repeat(151)))
400 .build()
401 .unwrap_err();
402 assert_eq!(err.object(), "MultiSelectMenuStaticOptions");
403
404 let errors = err.field("placeholder");
405 assert!(errors.includes(ValidationErrorKind::MaxTextLength(150)));
406 }
407
408 #[test]
409 fn it_requires_either_options_or_option_groups_is_set() {
410 let err = MultiSelectMenuStaticOptions::builder().build().unwrap_err();
411 assert_eq!(err.object(), "MultiSelectMenuStaticOptions");
412
413 let errors = err.across_fields();
414 assert!(errors.includes(ValidationErrorKind::EitherRequired(
415 "options",
416 "option_groups"
417 )));
418 }
419
420 #[test]
421 fn it_prevents_from_both_options_or_option_groups_are_set() {
422 let err = MultiSelectMenuStaticOptions::builder()
423 .option(option("opt", "val"))
424 .option_group(option_group("group", vec![option("opt", "val")]))
425 .build()
426 .unwrap_err();
427 assert_eq!(err.object(), "MultiSelectMenuStaticOptions");
428
429 let errors = err.across_fields();
430 assert!(errors.includes(ValidationErrorKind::ExclusiveField(
431 "options",
432 "option_groups"
433 )));
434 }
435}