slack_messaging/blocks/elements/multi_select_menus/
static_options.rs

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/// [Multi select menu of static options](https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#static_multi_select)
9/// representation
10///
11/// # Fields and Validations
12///
13/// For more details, see the [official
14/// documentation](https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#static_multi_select).
15///
16/// | Field | Type | Required | Validation |
17/// |-------|------|----------|------------|
18/// | action_id | String | No | Max length 255 characters |
19/// | options | Vec<[Opt]> | Conditionally* | Max items 100 |
20/// | option_groups | Vec<[OptGroup]> | Conditionally* | Max items 100 |
21/// | initial_options | Vec<[Opt]> | No | N/A |
22/// | confirm | [ConfirmationDialog] | No | N/A |
23/// | max_selected_items | i64 | No | Min value 1 |
24/// | focus_on_load | bool | No | N/A |
25/// | placeholder | [Text]<[Plain]> | No | Max length 150 characters |
26///
27/// # Validation Across Fields
28///
29/// * Either `options` or `option_groups` must be specified, but not both.
30///
31/// # Example
32///
33/// ```
34/// use slack_messaging::plain_text;
35/// use slack_messaging::blocks::elements::MultiSelectMenuStaticOptions;
36/// use slack_messaging::composition_objects::Opt;
37/// # use std::error::Error;
38///
39/// # fn try_main() -> Result<(), Box<dyn Error>> {
40/// let menu = MultiSelectMenuStaticOptions::builder()
41///     .action_id("text1234")
42///     .option(
43///         Opt::builder()
44///             .text(plain_text!("option-0")?)
45///             .value("value-0")
46///             .build()?
47///     )
48///     .option(
49///         Opt::builder()
50///             .text(plain_text!("option-1")?)
51///             .value("value-1")
52///             .build()?
53///     )
54///     .placeholder(plain_text!("Select items")?)
55///     .build()?;
56///
57/// let expected = serde_json::json!({
58///     "type": "multi_static_select",
59///     "action_id": "text1234",
60///     "options": [
61///         {
62///             "text": {
63///                 "type": "plain_text",
64///                 "text": "option-0"
65///             },
66///             "value": "value-0"
67///         },
68///         {
69///             "text": {
70///                 "type": "plain_text",
71///                 "text": "option-1"
72///             },
73///             "value": "value-1"
74///         }
75///     ],
76///     "placeholder": {
77///         "type": "plain_text",
78///         "text": "Select items"
79///     }
80/// });
81///
82/// let json = serde_json::to_value(menu).unwrap();
83///
84/// assert_eq!(json, expected);
85///
86/// // If your object has any validation errors, the build method returns Result::Err
87/// let menu = MultiSelectMenuStaticOptions::builder()
88///     .action_id("text1234")
89///     .placeholder(plain_text!("Select items")?)
90///     .build();
91///
92/// assert!(menu.is_err());
93/// #     Ok(())
94/// # }
95/// # fn main() {
96/// #     try_main().unwrap()
97/// # }
98/// ```
99#[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        // using options field
160        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        // using option_groups field
198        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}