slack_messaging/blocks/elements/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/// [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/select-menu-element#static_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_option | [Opt] | No | N/A |
22/// | confirm | [ConfirmationDialog] | No | N/A |
23/// | focus_on_load | bool | No | N/A |
24/// | placeholder | [Text]<[Plain]> | No | Max length 150 characters |
25///
26/// # Validation Across Fields
27///
28/// * Either `options` or `option_groups` is required. Both fields cannot be set simultaneously.
29///
30/// # Example
31///
32/// ```
33/// use slack_messaging::plain_text;
34/// use slack_messaging::blocks::elements::SelectMenuStaticOptions;
35/// use slack_messaging::composition_objects::Opt;
36/// # use std::error::Error;
37///
38/// # fn try_main() -> Result<(), Box<dyn Error>> {
39/// let menu = SelectMenuStaticOptions::builder()
40///     .action_id("text1234")
41///     .option(
42///         Opt::builder()
43///             .text(plain_text!("option-0")?)
44///             .value("value-0")
45///             .build()?
46///     )
47///     .option(
48///         Opt::builder()
49///             .text(plain_text!("option-1")?)
50///             .value("value-1")
51///             .build()?
52///     )
53///     .placeholder(plain_text!("Select an item")?)
54///     .build()?;
55///
56/// let expected = serde_json::json!({
57///     "type": "static_select",
58///     "action_id": "text1234",
59///     "options": [
60///         {
61///             "text": {
62///                 "type": "plain_text",
63///                 "text": "option-0"
64///             },
65///             "value": "value-0"
66///         },
67///         {
68///             "text": {
69///                 "type": "plain_text",
70///                 "text": "option-1"
71///             },
72///             "value": "value-1"
73///         }
74///     ],
75///     "placeholder": {
76///         "type": "plain_text",
77///         "text": "Select an item"
78///     }
79/// });
80///
81/// let json = serde_json::to_value(menu).unwrap();
82///
83/// assert_eq!(json, expected);
84///
85/// // If your object has any validation errors, the build method returns Result::Err
86/// let menu = SelectMenuStaticOptions::builder()
87///     .action_id("text1234")
88///     .placeholder(plain_text!("Select an item")?)
89///     .build();
90///
91/// assert!(menu.is_err());
92/// #     Ok(())
93/// # }
94/// # fn main() {
95/// #     try_main().unwrap()
96/// # }
97/// ```
98#[derive(Debug, Default, Clone, Serialize, PartialEq, Builder)]
99#[serde(tag = "type", rename = "static_select")]
100#[builder(validate = "validate")]
101pub struct SelectMenuStaticOptions {
102    #[serde(skip_serializing_if = "Option::is_none")]
103    #[builder(validate("text::max_255"))]
104    pub(crate) action_id: Option<String>,
105
106    #[serde(skip_serializing_if = "Option::is_none")]
107    #[builder(push_item = "option", validate("list::max_item_100"))]
108    pub(crate) options: Option<Vec<Opt>>,
109
110    #[serde(skip_serializing_if = "Option::is_none")]
111    #[builder(push_item = "option_group", validate("list::max_item_100"))]
112    pub(crate) option_groups: Option<Vec<OptGroup>>,
113
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub(crate) initial_option: Option<Opt>,
116
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub(crate) confirm: Option<ConfirmationDialog>,
119
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub(crate) focus_on_load: Option<bool>,
122
123    #[serde(skip_serializing_if = "Option::is_none")]
124    #[builder(validate("text_object::max_150"))]
125    pub(crate) placeholder: Option<Text<Plain>>,
126}
127
128fn validate(val: &SelectMenuStaticOptions) -> Vec<ValidationErrorKind> {
129    match (val.options.as_ref(), val.option_groups.as_ref()) {
130        (Some(_), Some(_)) => {
131            vec![ValidationErrorKind::ExclusiveField(
132                "options",
133                "option_groups",
134            )]
135        }
136        (None, None) => {
137            vec![ValidationErrorKind::EitherRequired(
138                "options",
139                "option_groups",
140            )]
141        }
142        _ => vec![],
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use crate::composition_objects::test_helpers::*;
150
151    #[test]
152    fn it_implements_builder() {
153        // using options field
154        let expected = SelectMenuStaticOptions {
155            action_id: Some("select_0".into()),
156            options: Some(vec![option("opt0", "val0"), option("opt1", "val1")]),
157            option_groups: None,
158            initial_option: Some(option("opt0", "val0")),
159            confirm: Some(confirm()),
160            focus_on_load: Some(true),
161            placeholder: Some(plain_text("Select item")),
162        };
163
164        let val = SelectMenuStaticOptions::builder()
165            .set_action_id(Some("select_0"))
166            .set_options(Some(vec![option("opt0", "val0"), option("opt1", "val1")]))
167            .set_initial_option(Some(option("opt0", "val0")))
168            .set_confirm(Some(confirm()))
169            .set_focus_on_load(Some(true))
170            .set_placeholder(Some(plain_text("Select item")))
171            .build()
172            .unwrap();
173
174        assert_eq!(val, expected);
175
176        let val = SelectMenuStaticOptions::builder()
177            .action_id("select_0")
178            .options(vec![option("opt0", "val0"), option("opt1", "val1")])
179            .initial_option(option("opt0", "val0"))
180            .confirm(confirm())
181            .focus_on_load(true)
182            .placeholder(plain_text("Select item"))
183            .build()
184            .unwrap();
185
186        assert_eq!(val, expected);
187
188        // using option_groups field
189        let expected = SelectMenuStaticOptions {
190            action_id: Some("select_0".into()),
191            options: None,
192            option_groups: Some(vec![
193                option_group(
194                    "group0",
195                    vec![option("opt00", "val00"), option("opt01", "val01")],
196                ),
197                option_group(
198                    "group1",
199                    vec![option("opt10", "val10"), option("opt11", "val11")],
200                ),
201            ]),
202            initial_option: Some(option("opt00", "val00")),
203            confirm: Some(confirm()),
204            focus_on_load: Some(true),
205            placeholder: Some(plain_text("Select item")),
206        };
207
208        let val = SelectMenuStaticOptions::builder()
209            .set_action_id(Some("select_0"))
210            .set_option_groups(Some(vec![
211                option_group(
212                    "group0",
213                    vec![option("opt00", "val00"), option("opt01", "val01")],
214                ),
215                option_group(
216                    "group1",
217                    vec![option("opt10", "val10"), option("opt11", "val11")],
218                ),
219            ]))
220            .set_initial_option(Some(option("opt00", "val00")))
221            .set_confirm(Some(confirm()))
222            .set_focus_on_load(Some(true))
223            .set_placeholder(Some(plain_text("Select item")))
224            .build()
225            .unwrap();
226
227        assert_eq!(val, expected);
228
229        let val = SelectMenuStaticOptions::builder()
230            .action_id("select_0")
231            .option_groups(vec![
232                option_group(
233                    "group0",
234                    vec![option("opt00", "val00"), option("opt01", "val01")],
235                ),
236                option_group(
237                    "group1",
238                    vec![option("opt10", "val10"), option("opt11", "val11")],
239                ),
240            ])
241            .initial_option(option("opt00", "val00"))
242            .confirm(confirm())
243            .focus_on_load(true)
244            .placeholder(plain_text("Select item"))
245            .build()
246            .unwrap();
247
248        assert_eq!(val, expected);
249    }
250
251    #[test]
252    fn it_implements_push_item_method() {
253        let expected = SelectMenuStaticOptions {
254            action_id: None,
255            options: Some(vec![option("opt0", "val0"), option("opt1", "val1")]),
256            option_groups: None,
257            initial_option: None,
258            confirm: None,
259            focus_on_load: None,
260            placeholder: None,
261        };
262
263        let val = SelectMenuStaticOptions::builder()
264            .option(option("opt0", "val0"))
265            .option(option("opt1", "val1"))
266            .build()
267            .unwrap();
268
269        assert_eq!(val, expected);
270
271        let expected = SelectMenuStaticOptions {
272            action_id: None,
273            options: None,
274            option_groups: Some(vec![
275                option_group(
276                    "group0",
277                    vec![option("opt00", "val00"), option("opt01", "val01")],
278                ),
279                option_group(
280                    "group1",
281                    vec![option("opt10", "val10"), option("opt11", "val11")],
282                ),
283            ]),
284            initial_option: None,
285            confirm: None,
286            focus_on_load: None,
287            placeholder: None,
288        };
289
290        let val = SelectMenuStaticOptions::builder()
291            .option_group(option_group(
292                "group0",
293                vec![option("opt00", "val00"), option("opt01", "val01")],
294            ))
295            .option_group(option_group(
296                "group1",
297                vec![option("opt10", "val10"), option("opt11", "val11")],
298            ))
299            .build()
300            .unwrap();
301
302        assert_eq!(val, expected);
303    }
304
305    #[test]
306    fn it_requires_action_id_less_than_255_characters_long() {
307        let err = SelectMenuStaticOptions::builder()
308            .option(option("opt", "val"))
309            .action_id("a".repeat(256))
310            .build()
311            .unwrap_err();
312        assert_eq!(err.object(), "SelectMenuStaticOptions");
313
314        let errors = err.field("action_id");
315        assert!(errors.includes(ValidationErrorKind::MaxTextLength(255)));
316    }
317
318    #[test]
319    fn it_requires_options_list_size_less_than_100() {
320        let options: Vec<Opt> = (0..101).map(|_| option("opt", "val")).collect();
321
322        let err = SelectMenuStaticOptions::builder()
323            .options(options)
324            .build()
325            .unwrap_err();
326        assert_eq!(err.object(), "SelectMenuStaticOptions");
327
328        let errors = err.field("options");
329        assert!(errors.includes(ValidationErrorKind::MaxArraySize(100)));
330    }
331
332    #[test]
333    fn it_requires_option_groups_list_size_less_than_100() {
334        let option_groups: Vec<OptGroup> = (0..101)
335            .map(|_| option_group("group", vec![option("opt", "val")]))
336            .collect();
337
338        let err = SelectMenuStaticOptions::builder()
339            .option_groups(option_groups)
340            .build()
341            .unwrap_err();
342        assert_eq!(err.object(), "SelectMenuStaticOptions");
343
344        let errors = err.field("option_groups");
345        assert!(errors.includes(ValidationErrorKind::MaxArraySize(100)));
346    }
347
348    #[test]
349    fn it_requires_placeholder_text_less_than_150_characters_long() {
350        let err = SelectMenuStaticOptions::builder()
351            .option(option("opt", "val"))
352            .placeholder(plain_text("a".repeat(151)))
353            .build()
354            .unwrap_err();
355        assert_eq!(err.object(), "SelectMenuStaticOptions");
356
357        let errors = err.field("placeholder");
358        assert!(errors.includes(ValidationErrorKind::MaxTextLength(150)));
359    }
360
361    #[test]
362    fn it_requires_either_options_or_option_groups_is_set() {
363        let err = SelectMenuStaticOptions::builder().build().unwrap_err();
364        assert_eq!(err.object(), "SelectMenuStaticOptions");
365
366        let errors = err.across_fields();
367        assert!(errors.includes(ValidationErrorKind::EitherRequired(
368            "options",
369            "option_groups"
370        )));
371    }
372
373    #[test]
374    fn it_prevents_from_both_options_or_option_groups_are_set() {
375        let err = SelectMenuStaticOptions::builder()
376            .option(option("opt", "val"))
377            .option_group(option_group("group", vec![option("opt", "val")]))
378            .build()
379            .unwrap_err();
380        assert_eq!(err.object(), "SelectMenuStaticOptions");
381
382        let errors = err.across_fields();
383        assert!(errors.includes(ValidationErrorKind::ExclusiveField(
384            "options",
385            "option_groups"
386        )));
387    }
388}