slack_messaging/blocks/elements/
overflow_menu.rs

1use crate::composition_objects::{ConfirmationDialog, Opt, Plain, Text, types::UrlAvailable};
2use crate::validators::*;
3
4use serde::Serialize;
5use slack_messaging_derive::Builder;
6
7/// [Overflow menu element](https://docs.slack.dev/reference/block-kit/block-elements/overflow-menu-element)
8/// representation.
9///
10/// # Fields and Validations
11///
12/// For more details, see the [official
13/// documentation](https://docs.slack.dev/reference/block-kit/block-elements/overflow-menu-element).
14///
15/// | Field | Type | Required | Validation |
16/// |-------|------|----------|------------|
17/// | action_id | String | No | Max length 255 characters |
18/// | options | Vec<[Opt]<[Text]<[Plain]>, [UrlAvailable]>> | Yes | Max 5 items |
19/// | confirm | [ConfirmationDialog] | No | N/A |
20///
21/// # Example
22///
23/// ```
24/// use slack_messaging::plain_text;
25/// use slack_messaging::blocks::elements::OverflowMenu;
26/// use slack_messaging::composition_objects::Opt;
27/// # use std::error::Error;
28///
29/// # fn try_main() -> Result<(), Box<dyn Error>> {
30/// let menu = OverflowMenu::builder()
31///     .action_id("overflow_0")
32///     .option(
33///         Opt::builder()
34///             .text(plain_text!("option-0")?)
35///             .value("value-0")
36///             .build()?
37///     )
38///     .option(
39///         Opt::builder()
40///             .text(plain_text!("option-1")?)
41///             .value("value-1")
42///             .build()?
43///     )
44///     .build()?;
45///
46/// let expected = serde_json::json!({
47///     "type": "overflow",
48///     "action_id": "overflow_0",
49///     "options": [
50///         {
51///             "text": {
52///                 "type": "plain_text",
53///                 "text": "option-0"
54///             },
55///             "value": "value-0"
56///         },
57///         {
58///             "text": {
59///                 "type": "plain_text",
60///                 "text": "option-1"
61///             },
62///             "value": "value-1"
63///         }
64///     ]
65/// });
66///
67/// let json = serde_json::to_value(menu).unwrap();
68///
69/// assert_eq!(json, expected);
70///
71/// // If your object has any validation errors, the build method returns Result::Err
72/// let menu = OverflowMenu::builder()
73///     .action_id("overflow_0")
74///     .build();
75///
76/// assert!(menu.is_err());
77/// #     Ok(())
78/// # }
79/// # fn main() {
80/// #     try_main().unwrap()
81/// # }
82/// ```
83#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
84#[serde(tag = "type", rename = "overflow")]
85pub struct OverflowMenu {
86    #[serde(skip_serializing_if = "Option::is_none")]
87    #[builder(validate("text::max_255"))]
88    pub(crate) action_id: Option<String>,
89
90    #[builder(push_item = "option", validate("required", "list::max_item_5"))]
91    pub(crate) options: Option<Vec<Opt<Text<Plain>, UrlAvailable>>>,
92
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub(crate) confirm: Option<ConfirmationDialog>,
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use crate::composition_objects::test_helpers::{confirm, plain_text};
101    use crate::errors::*;
102
103    #[test]
104    fn it_implements_builder() {
105        let expected = OverflowMenu {
106            action_id: Some("overflow_menu_0".into()),
107            options: Some(vec![option("opt0", "val0"), option("opt1", "val1")]),
108            confirm: Some(confirm()),
109        };
110
111        let val = OverflowMenu::builder()
112            .set_action_id(Some("overflow_menu_0"))
113            .set_options(Some(vec![option("opt0", "val0"), option("opt1", "val1")]))
114            .set_confirm(Some(confirm()))
115            .build()
116            .unwrap();
117
118        assert_eq!(val, expected);
119
120        let val = OverflowMenu::builder()
121            .action_id("overflow_menu_0")
122            .options(vec![option("opt0", "val0"), option("opt1", "val1")])
123            .confirm(confirm())
124            .build()
125            .unwrap();
126
127        assert_eq!(val, expected);
128    }
129
130    #[test]
131    fn it_implements_push_item_method() {
132        let expected = OverflowMenu {
133            action_id: None,
134            options: Some(vec![option("opt0", "val0"), option("opt1", "val1")]),
135            confirm: None,
136        };
137
138        let val = OverflowMenu::builder()
139            .option(option("opt0", "val0"))
140            .option(option("opt1", "val1"))
141            .build()
142            .unwrap();
143
144        assert_eq!(val, expected);
145    }
146
147    #[test]
148    fn it_requires_action_id_less_than_255_characters_long() {
149        let err = OverflowMenu::builder()
150            .option(option("opt0", "val0"))
151            .action_id("a".repeat(256))
152            .build()
153            .unwrap_err();
154        assert_eq!(err.object(), "OverflowMenu");
155
156        let errors = err.field("action_id");
157        assert!(errors.includes(ValidationErrorKind::MaxTextLength(255)));
158    }
159
160    #[test]
161    fn it_requires_options_field() {
162        let err = OverflowMenu::builder().build().unwrap_err();
163        assert_eq!(err.object(), "OverflowMenu");
164
165        let errors = err.field("options");
166        assert!(errors.includes(ValidationErrorKind::Required));
167    }
168
169    #[test]
170    fn it_requires_options_item_size_less_than_5() {
171        let err = OverflowMenu::builder()
172            .options(vec![
173                option("opt0", "val0"),
174                option("opt1", "val1"),
175                option("opt2", "val2"),
176                option("opt3", "val3"),
177                option("opt4", "val4"),
178                option("opt5", "val5"),
179            ])
180            .build()
181            .unwrap_err();
182        assert_eq!(err.object(), "OverflowMenu");
183
184        let errors = err.field("options");
185        assert!(errors.includes(ValidationErrorKind::MaxArraySize(5)));
186    }
187
188    fn option(text: impl Into<String>, value: impl Into<String>) -> Opt<Text<Plain>, UrlAvailable> {
189        Opt {
190            phantom: std::marker::PhantomData,
191            text: Some(plain_text(text)),
192            value: Some(value.into()),
193            description: None,
194            url: None,
195        }
196    }
197}