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}