slack_messaging/blocks/
context_actions.rs

1use crate::blocks::elements::{FeedbackButtons, IconButton};
2use crate::validators::*;
3
4use serde::Serialize;
5use slack_messaging_derive::Builder;
6
7/// [Context actions block](https://docs.slack.dev/reference/block-kit/blocks/context-actions-block)
8/// representation.
9///
10/// # Fields and Validations
11///
12/// For more details, see the [official
13/// documentation](https://docs.slack.dev/reference/block-kit/blocks/context-actions-block).
14///
15/// | Field | Type | Required | Validation |
16/// |-------|------|----------|------------|
17/// | elements | Vec<[ContextActionsElement]> | Yes | Maximum 5 items |
18/// | block_id | String | No | Maximum 255 characters |
19///
20/// # Example
21///
22/// The following is reproduction of [the sample context actions block](https://docs.slack.dev/reference/block-kit/blocks/context-actions-block#examples).
23///
24/// ## Context actions block with feedback buttons.
25///
26/// ```
27/// use slack_messaging::plain_text;
28/// use slack_messaging::blocks::ContextActions;
29/// use slack_messaging::blocks::elements::{types::FeedbackButton, FeedbackButtons};
30/// # use std::error::Error;
31///
32/// # fn try_main() -> Result<(), Box<dyn Error>> {
33/// let actions = ContextActions::builder()
34///     .element(
35///         FeedbackButtons::builder()
36///             .action_id("feedback_buttons_1")
37///             .positive_button(
38///                 FeedbackButton::builder()
39///                     .text(plain_text!("👍")?)
40///                     .value("positive_feedback")
41///                     .build()?
42///             )
43///             .negative_button(
44///                 FeedbackButton::builder()
45///                     .text(plain_text!("👎")?)
46///                     .value("negative_feedback")
47///                     .build()?
48///             )
49///             .build()?
50///     )
51///     .build()?;
52///
53/// let expected = serde_json::json!({
54///     "type": "context_actions",
55///     "elements": [
56///         {
57///             "type": "feedback_buttons",
58///             "action_id": "feedback_buttons_1",
59///             "positive_button": {
60///                 "text": {
61///                     "type": "plain_text",
62///                     "text": "👍"
63///                 },
64///                 "value": "positive_feedback"
65///             },
66///             "negative_button": {
67///                 "text": {
68///                     "type": "plain_text",
69///                     "text": "👎"
70///                 },
71///                 "value": "negative_feedback"
72///             }
73///         }
74///     ]
75/// });
76///
77/// let json = serde_json::to_value(actions).unwrap();
78///
79/// assert_eq!(json, expected);
80/// #     Ok(())
81/// # }
82/// # fn main() {
83/// #     try_main().unwrap()
84/// # }
85/// ```
86///
87/// ## Context actions block with an icon button.
88///
89/// ```
90/// use slack_messaging::plain_text;
91/// use slack_messaging::blocks::ContextActions;
92/// use slack_messaging::blocks::elements::{IconButton, types::Icon};
93/// # use std::error::Error;
94///
95/// # fn try_main() -> Result<(), Box<dyn Error>> {
96/// let actions = ContextActions::builder()
97///     .element(
98///         IconButton::builder()
99///             .icon(Icon::Trash)
100///             .text(plain_text!("Delete")?)
101///             .action_id("delete_button_1")
102///             .value("delete_item")
103///             .build()?
104///     )
105///     .build()?;
106///
107/// let expected = serde_json::json!({
108///     "type": "context_actions",
109///     "elements": [
110///         {
111///             "type": "icon_button",
112///             "icon": "trash",
113///             "text": {
114///                 "type": "plain_text",
115///                 "text": "Delete"
116///             },
117///             "action_id": "delete_button_1",
118///             "value": "delete_item"
119///         }
120///     ]
121/// });
122///
123/// let json = serde_json::to_value(actions).unwrap();
124///
125/// assert_eq!(json, expected);
126/// #     Ok(())
127/// # }
128/// # fn main() {
129/// #     try_main().unwrap()
130/// # }
131/// ```
132#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
133#[serde(tag = "type", rename = "context_actions")]
134pub struct ContextActions {
135    #[builder(push_item = "element", validate("required", "list::max_item_5"))]
136    pub(super) elements: Option<Vec<ContextActionsElement>>,
137
138    #[serde(skip_serializing_if = "Option::is_none")]
139    #[builder(validate("text::max_255"))]
140    pub(super) block_id: Option<String>,
141}
142
143/// Objects that can be an element of the [ContextActions] block.
144#[derive(Debug, Clone, Serialize, PartialEq)]
145#[serde(untagged)]
146pub enum ContextActionsElement {
147    /// [Feedback buttons element](https://docs.slack.dev/reference/block-kit/block-elements/feedback-buttons-element) representation
148    FeedbackButtons(Box<FeedbackButtons>),
149
150    /// [Icon button element](https://docs.slack.dev/reference/block-kit/block-elements/icon-button-element) representation
151    IconButton(Box<IconButton>),
152}
153
154macro_rules! context_actions_from {
155    ($($ty:ident,)*) => {
156        $(
157            impl From<$ty> for ContextActionsElement {
158                fn from(value: $ty) -> Self {
159                    Self::$ty(Box::new(value))
160                }
161            }
162         )*
163    }
164}
165
166context_actions_from! {
167    FeedbackButtons,
168    IconButton,
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174    use crate::blocks::elements::test_helpers::*;
175    use crate::errors::*;
176
177    #[test]
178    fn it_implements_builder() {
179        let expected = ContextActions {
180            block_id: Some("context_actions_0".into()),
181            elements: Some(vec![fb_buttons().into()]),
182        };
183
184        let val = ContextActions::builder()
185            .set_block_id(Some("context_actions_0"))
186            .set_elements(Some(vec![fb_buttons().into()]))
187            .build()
188            .unwrap();
189
190        assert_eq!(val, expected);
191
192        let val = ContextActions::builder()
193            .block_id("context_actions_0")
194            .elements(vec![fb_buttons().into()])
195            .build()
196            .unwrap();
197
198        assert_eq!(val, expected);
199    }
200
201    #[test]
202    fn it_implements_push_item_method() {
203        let expected = ContextActions {
204            block_id: None,
205            elements: Some(vec![fb_buttons().into()]),
206        };
207
208        let val = ContextActions::builder()
209            .element(fb_buttons())
210            .build()
211            .unwrap();
212
213        assert_eq!(val, expected);
214    }
215
216    #[test]
217    fn it_requires_elements_field() {
218        let err = ContextActions::builder().build().unwrap_err();
219        assert_eq!(err.object(), "ContextActions");
220
221        let errors = err.field("elements");
222        assert!(errors.includes(ValidationErrorKind::Required));
223    }
224
225    #[test]
226    fn it_requires_elements_list_size_less_than_5() {
227        let elements: Vec<ContextActionsElement> = (0..6).map(|_| fb_buttons().into()).collect();
228        let err = ContextActions::builder()
229            .elements(elements)
230            .build()
231            .unwrap_err();
232        assert_eq!(err.object(), "ContextActions");
233
234        let errors = err.field("elements");
235        assert!(errors.includes(ValidationErrorKind::MaxArraySize(5)));
236    }
237
238    #[test]
239    fn it_requires_block_id_less_than_255_characters_long() {
240        let err = ContextActions::builder()
241            .block_id("a".repeat(256))
242            .element(fb_buttons())
243            .build()
244            .unwrap_err();
245        assert_eq!(err.object(), "ContextActions");
246
247        let errors = err.field("block_id");
248        assert!(errors.includes(ValidationErrorKind::MaxTextLength(255)));
249    }
250}