slack_messaging/blocks/actions.rs
1use crate::blocks::elements::{
2 Button, Checkboxes, DatePicker, DatetimePicker, MultiSelectMenuConversations,
3 MultiSelectMenuExternalDataSource, MultiSelectMenuPublicChannels, MultiSelectMenuStaticOptions,
4 MultiSelectMenuUsers, OverflowMenu, RadioButtonGroup, SelectMenuConversations,
5 SelectMenuExternalDataSource, SelectMenuPublicChannels, SelectMenuStaticOptions,
6 SelectMenuUsers, TimePicker, WorkflowButton,
7};
8use crate::validators::*;
9
10use serde::Serialize;
11use slack_messaging_derive::Builder;
12
13/// [Actions block](https://docs.slack.dev/reference/block-kit/blocks/actions-block)
14/// representation.
15///
16/// # Fields and Validations
17///
18/// For more details, see the [official
19/// documentation](https://docs.slack.dev/reference/block-kit/blocks/actions-block).
20///
21/// | Field | Type | Required | Validation |
22/// |-------|------|----------|------------|
23/// | elements | Vec<[ActionsElement]> | Yes | Maximum of 25 items |
24/// | block_id | String | No | Maximum 255 characters |
25///
26/// # Example
27///
28/// The following is reproduction of [the 1st sample actions](https://docs.slack.dev/reference/block-kit/blocks/actions-block#examples).
29///
30/// ```
31/// use slack_messaging::plain_text;
32/// use slack_messaging::blocks::Actions;
33/// use slack_messaging::blocks::elements::{Button, SelectMenuStaticOptions};
34/// use slack_messaging::composition_objects::Opt;
35/// # use std::error::Error;
36///
37/// # fn try_main() -> Result<(), Box<dyn Error>> {
38/// let actions = Actions::builder()
39/// .block_id("actions1")
40/// .element(
41/// SelectMenuStaticOptions::builder()
42/// .action_id("select_2")
43/// .placeholder(plain_text!("Which witch is the witchiest witch?")?)
44/// .options(
45/// vec![
46/// Opt::builder()
47/// .text(plain_text!("Matilda")?)
48/// .value("matilda")
49/// .build()?,
50/// Opt::builder()
51/// .text(plain_text!("Glinda")?)
52/// .value("glinda")
53/// .build()?,
54/// Opt::builder()
55/// .text(plain_text!("Granny Weatherwax")?)
56/// .value("grannyWeatherwax")
57/// .build()?,
58/// Opt::builder()
59/// .text(plain_text!("Hermione")?)
60/// .value("hermione")
61/// .build()?,
62/// ]
63/// )
64/// .build()?
65/// )
66/// .element(
67/// Button::builder()
68/// .action_id("button_1")
69/// .value("cancel")
70/// .text(plain_text!("Cancel")?)
71/// .build()?
72/// )
73/// .build()?;
74///
75/// let expected = serde_json::json!({
76/// "type": "actions",
77/// "block_id": "actions1",
78/// "elements": [
79/// {
80/// "type": "static_select",
81/// "action_id": "select_2",
82/// "placeholder": {
83/// "type": "plain_text",
84/// "text": "Which witch is the witchiest witch?"
85/// },
86/// "options": [
87/// {
88/// "text": {
89/// "type": "plain_text",
90/// "text": "Matilda"
91/// },
92/// "value": "matilda"
93/// },
94/// {
95/// "text": {
96/// "type": "plain_text",
97/// "text": "Glinda"
98/// },
99/// "value": "glinda"
100/// },
101/// {
102/// "text": {
103/// "type": "plain_text",
104/// "text": "Granny Weatherwax"
105/// },
106/// "value": "grannyWeatherwax"
107/// },
108/// {
109/// "text": {
110/// "type": "plain_text",
111/// "text": "Hermione"
112/// },
113/// "value": "hermione"
114/// }
115/// ]
116/// },
117/// {
118/// "type": "button",
119/// "text": {
120/// "type": "plain_text",
121/// "text": "Cancel"
122/// },
123/// "value": "cancel",
124/// "action_id": "button_1"
125/// }
126/// ]
127/// });
128///
129/// let json = serde_json::to_value(actions).unwrap();
130///
131/// assert_eq!(json, expected);
132/// # Ok(())
133/// # }
134/// # fn main() {
135/// # try_main().unwrap()
136/// # }
137/// ```
138///
139/// And the following is the [2nd sample actions](https://docs.slack.dev/reference/block-kit/blocks/actions-block#examples).
140///
141/// ```
142/// use slack_messaging::plain_text;
143/// use slack_messaging::blocks::Actions;
144/// use slack_messaging::blocks::elements::{Button, DatePicker, OverflowMenu};
145/// use slack_messaging::composition_objects::Opt;
146/// # use std::error::Error;
147///
148/// # fn try_main() -> Result<(), Box<dyn Error>> {
149/// let actions = Actions::builder()
150/// .block_id("actionblock789")
151/// .element(
152/// DatePicker::builder()
153/// .action_id("datepicker123")
154/// .initial_date("1990-04-28")
155/// .placeholder(plain_text!("Select a date")?)
156/// .build()?
157/// )
158/// .element(
159/// OverflowMenu::builder()
160/// .action_id("overflow")
161/// .option(
162/// Opt::builder()
163/// .text(plain_text!("*this is plain_text text*")?)
164/// .value("value-0")
165/// .build()?
166/// )
167/// .option(
168/// Opt::builder()
169/// .text(plain_text!("*this is plain_text text*")?)
170/// .value("value-1")
171/// .build()?
172/// )
173/// .option(
174/// Opt::builder()
175/// .text(plain_text!("*this is plain_text text*")?)
176/// .value("value-2")
177/// .build()?
178/// )
179/// .option(
180/// Opt::builder()
181/// .text(plain_text!("*this is plain_text text*")?)
182/// .value("value-3")
183/// .build()?
184/// )
185/// .option(
186/// Opt::builder()
187/// .text(plain_text!("*this is plain_text text*")?)
188/// .value("value-4")
189/// .build()?
190/// )
191/// .build()?
192/// )
193/// .element(
194/// Button::builder()
195/// .action_id("button")
196/// .value("click_me_123")
197/// .text(plain_text!("Click Me")?)
198/// .build()?
199/// )
200/// .build()?;
201///
202/// let expected = serde_json::json!({
203/// "type": "actions",
204/// "block_id": "actionblock789",
205/// "elements": [
206/// {
207/// "type": "datepicker",
208/// "action_id": "datepicker123",
209/// "initial_date": "1990-04-28",
210/// "placeholder": {
211/// "type": "plain_text",
212/// "text": "Select a date"
213/// }
214/// },
215/// {
216/// "type": "overflow",
217/// "action_id": "overflow",
218/// "options": [
219/// {
220/// "text": {
221/// "type": "plain_text",
222/// "text": "*this is plain_text text*"
223/// },
224/// "value": "value-0"
225/// },
226/// {
227/// "text": {
228/// "type": "plain_text",
229/// "text": "*this is plain_text text*"
230/// },
231/// "value": "value-1"
232/// },
233/// {
234/// "text": {
235/// "type": "plain_text",
236/// "text": "*this is plain_text text*"
237/// },
238/// "value": "value-2"
239/// },
240/// {
241/// "text": {
242/// "type": "plain_text",
243/// "text": "*this is plain_text text*"
244/// },
245/// "value": "value-3"
246/// },
247/// {
248/// "text": {
249/// "type": "plain_text",
250/// "text": "*this is plain_text text*"
251/// },
252/// "value": "value-4"
253/// }
254/// ]
255/// },
256/// {
257/// "type": "button",
258/// "text": {
259/// "type": "plain_text",
260/// "text": "Click Me"
261/// },
262/// "value": "click_me_123",
263/// "action_id": "button"
264/// }
265/// ]
266/// });
267///
268/// let json = serde_json::to_value(actions).unwrap();
269///
270/// assert_eq!(json, expected);
271/// # Ok(())
272/// # }
273/// # fn main() {
274/// # try_main().unwrap()
275/// # }
276/// ```
277///
278#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
279#[serde(tag = "type", rename = "actions")]
280pub struct Actions {
281 #[builder(push_item = "element", validate("required", "list::max_item_25"))]
282 pub(crate) elements: Option<Vec<ActionsElement>>,
283
284 #[serde(skip_serializing_if = "Option::is_none")]
285 #[builder(validate("text::max_255"))]
286 pub(crate) block_id: Option<String>,
287}
288
289/// Objects that can be an element of the [Actions]'s elements field.
290#[derive(Debug, Clone, Serialize, PartialEq)]
291#[serde(untagged)]
292pub enum ActionsElement {
293 /// [Button element](https://docs.slack.dev/reference/block-kit/block-elements/button-element)
294 /// representation
295 Button(Box<Button>),
296
297 /// [Checkbox group](https://docs.slack.dev/reference/block-kit/block-elements/checkboxes-element)
298 /// representation
299 Checkboxes(Box<Checkboxes>),
300
301 /// [Date picker element](https://docs.slack.dev/reference/block-kit/block-elements/date-picker-element)
302 /// representation
303 DatePicker(Box<DatePicker>),
304
305 /// [Datetime picker element](https://docs.slack.dev/reference/block-kit/block-elements/datetime-picker-element)
306 /// representation
307 DatetimePicker(Box<DatetimePicker>),
308
309 /// [Multi select menu of static options](https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#static_multi_select)
310 /// representation
311 MultiSelectMenuStaticOptions(Box<MultiSelectMenuStaticOptions>),
312
313 /// [Multi select menu of external data source](https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select)
314 /// representation
315 MultiSelectMenuExternalDataSource(Box<MultiSelectMenuExternalDataSource>),
316
317 /// [Multi select menu of users](https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#users_multi_select)
318 /// representation
319 MultiSelectMenuUsers(Box<MultiSelectMenuUsers>),
320
321 /// [Multi select menu of conversations](https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#conversation_multi_select)
322 /// representation
323 MultiSelectMenuConversations(Box<MultiSelectMenuConversations>),
324
325 /// [Multi select menu of public channels](https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#channel_multi_select)
326 /// representation
327 MultiSelectMenuPublicChannels(Box<MultiSelectMenuPublicChannels>),
328
329 /// [Overflow menu element](https://docs.slack.dev/reference/block-kit/block-elements/overflow-menu-element)
330 /// representation
331 OverflowMenu(Box<OverflowMenu>),
332
333 /// [Radio buton group element](https://docs.slack.dev/reference/block-kit/block-elements/radio-button-group-element)
334 /// representation
335 RadioButtonGroup(Box<RadioButtonGroup>),
336
337 /// [Select menu of static options](https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#static_select)
338 /// representation
339 SelectMenuStaticOptions(Box<SelectMenuStaticOptions>),
340
341 /// [Select menu of external data source](https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select)
342 /// representation
343 SelectMenuExternalDataSource(Box<SelectMenuExternalDataSource>),
344
345 /// [Select menu of users](https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#users_select)
346 /// representation
347 SelectMenuUsers(Box<SelectMenuUsers>),
348
349 /// [Select menu of conversations](https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#conversations_select)
350 /// representation
351 SelectMenuConversations(Box<SelectMenuConversations>),
352
353 /// [Select menu of public channels](https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#channels_select)
354 /// representation
355 SelectMenuPublicChannels(Box<SelectMenuPublicChannels>),
356
357 /// [Time picker element](https://docs.slack.dev/reference/block-kit/block-elements/time-picker-element)
358 /// representation
359 TimePicker(Box<TimePicker>),
360
361 /// [Workflow button element](https://docs.slack.dev/reference/block-kit/block-elements/workflow-button-element)
362 /// representation
363 WorkflowButton(Box<WorkflowButton>),
364}
365
366macro_rules! actions_from {
367 ($($ty:ident,)*) => {
368 $(
369 impl From<$ty> for ActionsElement {
370 fn from(value: $ty) -> Self {
371 Self::$ty(Box::new(value))
372 }
373 }
374 )*
375 }
376}
377
378actions_from! {
379 Button,
380 Checkboxes,
381 DatePicker,
382 DatetimePicker,
383 MultiSelectMenuStaticOptions,
384 MultiSelectMenuExternalDataSource,
385 MultiSelectMenuUsers,
386 MultiSelectMenuConversations,
387 MultiSelectMenuPublicChannels,
388 OverflowMenu,
389 RadioButtonGroup,
390 SelectMenuStaticOptions,
391 SelectMenuExternalDataSource,
392 SelectMenuUsers,
393 SelectMenuConversations,
394 SelectMenuPublicChannels,
395 TimePicker,
396 WorkflowButton,
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402 use crate::blocks::elements::test_helpers::*;
403 use crate::errors::*;
404
405 #[test]
406 fn it_implements_builder() {
407 let expected = Actions {
408 block_id: Some("actions_0".into()),
409 elements: Some(vec![datepicker().into(), btn("button_0", "value_0").into()]),
410 };
411
412 let val = Actions::builder()
413 .set_block_id(Some("actions_0"))
414 .set_elements(Some(vec![
415 datepicker().into(),
416 btn("button_0", "value_0").into(),
417 ]))
418 .build()
419 .unwrap();
420
421 assert_eq!(val, expected);
422
423 let val = Actions::builder()
424 .block_id("actions_0")
425 .elements(vec![datepicker().into(), btn("button_0", "value_0").into()])
426 .build()
427 .unwrap();
428
429 assert_eq!(val, expected);
430 }
431
432 #[test]
433 fn it_implements_push_item_method() {
434 let expected = Actions {
435 block_id: None,
436 elements: Some(vec![datepicker().into(), btn("button_0", "value_0").into()]),
437 };
438
439 let val = Actions::builder()
440 .element(datepicker())
441 .element(btn("button_0", "value_0"))
442 .build()
443 .unwrap();
444
445 assert_eq!(val, expected);
446 }
447
448 #[test]
449 fn it_requries_elements_field() {
450 let err = Actions::builder().build().unwrap_err();
451 assert_eq!(err.object(), "Actions");
452
453 let errors = err.field("elements");
454 assert!(errors.includes(ValidationErrorKind::Required));
455 }
456
457 #[test]
458 fn it_requires_elements_list_size_less_than_25() {
459 let elements: Vec<ActionsElement> = (0..26).map(|_| btn("name", "value").into()).collect();
460 let err = Actions::builder().elements(elements).build().unwrap_err();
461 assert_eq!(err.object(), "Actions");
462
463 let errors = err.field("elements");
464 assert!(errors.includes(ValidationErrorKind::MaxArraySize(25)));
465 }
466
467 #[test]
468 fn it_requires_block_id_less_than_255_characters_long() {
469 let err = Actions::builder()
470 .block_id("a".repeat(256))
471 .element(datepicker())
472 .build()
473 .unwrap_err();
474 assert_eq!(err.object(), "Actions");
475
476 let errors = err.field("block_id");
477 assert!(errors.includes(ValidationErrorKind::MaxTextLength(255)));
478 }
479}