Skip to main content

slack_morphism/models/blocks/
kit.rs

1use rsb_derive::Builder;
2use rvstruct::ValueStruct;
3use serde::{Deserialize, Serialize};
4use serde_with::skip_serializing_none;
5use url::Url;
6
7use super::workflow::SlackBlockWorkflowButtonElement;
8use crate::*;
9
10#[skip_serializing_none]
11#[derive(Debug, PartialEq, Clone, Eq, Hash, Serialize, Deserialize, ValueStruct)]
12pub struct SlackBlockId(pub String);
13
14#[derive(Debug, PartialEq, Clone, Eq, Hash, Serialize, Deserialize, ValueStruct)]
15pub struct SlackTaskId(pub String);
16
17#[derive(Debug, PartialEq, Clone, Eq, Hash, Serialize, Deserialize, ValueStruct)]
18pub struct SlackAccessibilityLabel(pub String);
19
20#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
21#[serde(tag = "type")]
22pub enum SlackBlock {
23    #[serde(rename = "section")]
24    Section(SlackSectionBlock),
25    #[serde(rename = "header")]
26    Header(SlackHeaderBlock),
27    #[serde(rename = "divider")]
28    Divider(SlackDividerBlock),
29    #[serde(rename = "image")]
30    Image(SlackImageBlock),
31    #[serde(rename = "actions")]
32    Actions(SlackActionsBlock),
33    #[serde(rename = "context")]
34    Context(SlackContextBlock),
35    #[serde(rename = "input")]
36    Input(SlackInputBlock),
37    #[serde(rename = "file")]
38    File(SlackFileBlock),
39    #[serde(rename = "video")]
40    Video(SlackVideoBlock),
41    #[serde(rename = "markdown")]
42    Markdown(SlackMarkdownBlock),
43    #[serde(rename = "rich_text")]
44    RichText(SlackRichTextBlock),
45    #[serde(rename = "table")]
46    Table(SlackTableBlock),
47    #[serde(rename = "task_card")]
48    TaskCard(SlackTaskCardBlock),
49    #[serde(rename = "alert")]
50    Alert(SlackAlertBlock),
51    #[serde(rename = "card")]
52    Card(SlackCardBlock),
53    #[serde(rename = "carousel")]
54    Carousel(SlackCarouselBlock),
55    #[serde(rename = "context_actions")]
56    ContextActions(SlackContextActionsBlock),
57    #[serde(rename = "share_shortcut")]
58    ShareShortcut(serde_json::Value),
59    #[serde(rename = "event")]
60    Event(serde_json::Value),
61}
62
63#[skip_serializing_none]
64#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
65pub struct SlackSectionBlock {
66    pub block_id: Option<SlackBlockId>,
67    pub text: Option<SlackBlockText>,
68    pub fields: Option<Vec<SlackBlockText>>,
69    pub accessory: Option<SlackSectionBlockElement>,
70    pub expand: Option<bool>,
71}
72
73impl From<SlackSectionBlock> for SlackBlock {
74    fn from(block: SlackSectionBlock) -> Self {
75        SlackBlock::Section(block)
76    }
77}
78
79#[skip_serializing_none]
80#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
81pub struct SlackHeaderBlock {
82    pub block_id: Option<SlackBlockId>,
83    pub text: SlackBlockPlainTextOnly,
84}
85
86impl From<SlackHeaderBlock> for SlackBlock {
87    fn from(block: SlackHeaderBlock) -> Self {
88        SlackBlock::Header(block)
89    }
90}
91
92#[skip_serializing_none]
93#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
94pub struct SlackDividerBlock {
95    pub block_id: Option<SlackBlockId>,
96}
97
98impl From<SlackDividerBlock> for SlackBlock {
99    fn from(block: SlackDividerBlock) -> Self {
100        SlackBlock::Divider(block)
101    }
102}
103
104#[skip_serializing_none]
105#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
106pub struct SlackImageBlock {
107    pub block_id: Option<SlackBlockId>,
108    #[serde(flatten)]
109    pub image_url_or_file: SlackImageUrlOrFile,
110    pub alt_text: String,
111    pub title: Option<SlackBlockPlainTextOnly>,
112}
113
114impl From<SlackImageBlock> for SlackBlock {
115    fn from(block: SlackImageBlock) -> Self {
116        SlackBlock::Image(block)
117    }
118}
119
120#[skip_serializing_none]
121#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
122pub struct SlackActionsBlock {
123    pub block_id: Option<SlackBlockId>,
124    pub elements: Vec<SlackActionBlockElement>,
125}
126
127impl From<SlackActionsBlock> for SlackBlock {
128    fn from(block: SlackActionsBlock) -> Self {
129        SlackBlock::Actions(block)
130    }
131}
132
133#[skip_serializing_none]
134#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
135pub struct SlackContextBlock {
136    pub block_id: Option<SlackBlockId>,
137    pub elements: Vec<SlackContextBlockElement>,
138}
139
140impl From<SlackContextBlock> for SlackBlock {
141    fn from(block: SlackContextBlock) -> Self {
142        SlackBlock::Context(block)
143    }
144}
145
146#[skip_serializing_none]
147#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
148pub struct SlackInputBlock {
149    pub block_id: Option<SlackBlockId>,
150    pub label: SlackBlockPlainTextOnly,
151    pub element: SlackInputBlockElement,
152    pub hint: Option<SlackBlockPlainTextOnly>,
153    pub optional: Option<bool>,
154    pub dispatch_action: Option<bool>,
155}
156
157impl From<SlackInputBlock> for SlackBlock {
158    fn from(block: SlackInputBlock) -> Self {
159        SlackBlock::Input(block)
160    }
161}
162
163const SLACK_FILE_BLOCK_SOURCE_DEFAULT: &str = "remote";
164
165#[skip_serializing_none]
166#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
167pub struct SlackFileBlock {
168    pub block_id: Option<SlackBlockId>,
169    pub external_id: String,
170    #[default = "SLACK_FILE_BLOCK_SOURCE_DEFAULT.into()"]
171    pub source: String,
172}
173
174impl From<SlackFileBlock> for SlackBlock {
175    fn from(block: SlackFileBlock) -> Self {
176        SlackBlock::File(block)
177    }
178}
179
180#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
181#[serde(tag = "type")]
182pub enum SlackSectionBlockElement {
183    #[serde(rename = "image")]
184    Image(SlackBlockImageElement),
185    #[serde(rename = "button")]
186    Button(SlackBlockButtonElement),
187    #[serde(rename = "static_select")]
188    StaticSelect(SlackBlockStaticSelectElement),
189    #[serde(rename = "multi_static_select")]
190    MultiStaticSelect(SlackBlockMultiStaticSelectElement),
191    #[serde(rename = "external_select")]
192    ExternalSelect(SlackBlockExternalSelectElement),
193    #[serde(rename = "multi_external_select")]
194    MultiExternalSelect(SlackBlockMultiExternalSelectElement),
195    #[serde(rename = "users_select")]
196    UsersSelect(SlackBlockUsersSelectElement),
197    #[serde(rename = "multi_users_select")]
198    MultiUsersSelect(SlackBlockMultiUsersSelectElement),
199    #[serde(rename = "conversations_select")]
200    ConversationsSelect(SlackBlockConversationsSelectElement),
201    #[serde(rename = "multi_conversations_select")]
202    MultiConversationsSelect(SlackBlockMultiConversationsSelectElement),
203    #[serde(rename = "channels_select")]
204    ChannelsSelect(SlackBlockChannelsSelectElement),
205    #[serde(rename = "multi_channels_select")]
206    MultiChannelsSelect(SlackBlockMultiChannelsSelectElement),
207    #[serde(rename = "overflow")]
208    Overflow(SlackBlockOverflowElement),
209    #[serde(rename = "datepicker")]
210    DatePicker(SlackBlockDatePickerElement),
211    #[serde(rename = "timepicker")]
212    TimePicker(SlackBlockTimePickerElement),
213    #[serde(rename = "plain_text_input")]
214    PlainTextInput(SlackBlockPlainTextInputElement),
215    #[serde(rename = "number_input")]
216    NumberInput(SlackBlockNumberInputElement),
217    #[serde(rename = "url_text_input")]
218    UrlInput(SlackBlockUrlInputElement),
219    #[serde(rename = "radio_buttons")]
220    RadioButtons(SlackBlockRadioButtonsElement),
221    #[serde(rename = "checkboxes")]
222    Checkboxes(SlackBlockCheckboxesElement),
223    #[serde(rename = "workflow_button")]
224    WorkflowButton(SlackBlockWorkflowButtonElement),
225}
226
227#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
228#[serde(tag = "type")]
229pub enum SlackActionBlockElement {
230    #[serde(rename = "button")]
231    Button(SlackBlockButtonElement),
232    #[serde(rename = "overflow")]
233    Overflow(SlackBlockOverflowElement),
234    #[serde(rename = "datepicker")]
235    DatePicker(SlackBlockDatePickerElement),
236    #[serde(rename = "timepicker")]
237    TimePicker(SlackBlockTimePickerElement),
238    #[serde(rename = "datetimepicker")]
239    DateTimePicker(SlackBlockDateTimePickerElement),
240    #[serde(rename = "plain_text_input")]
241    PlainTextInput(SlackBlockPlainTextInputElement),
242    #[serde(rename = "number_input")]
243    NumberInput(SlackBlockNumberInputElement),
244    #[serde(rename = "url_text_input")]
245    UrlInput(SlackBlockUrlInputElement),
246    #[serde(rename = "radio_buttons")]
247    RadioButtons(SlackBlockRadioButtonsElement),
248    #[serde(rename = "checkboxes")]
249    Checkboxes(SlackBlockCheckboxesElement),
250    #[serde(rename = "static_select")]
251    StaticSelect(SlackBlockStaticSelectElement),
252    #[serde(rename = "external_select")]
253    ExternalSelect(SlackBlockExternalSelectElement),
254    #[serde(rename = "users_select")]
255    UsersSelect(SlackBlockUsersSelectElement),
256    #[serde(rename = "conversations_select")]
257    ConversationsSelect(SlackBlockConversationsSelectElement),
258    #[serde(rename = "channels_select")]
259    ChannelsSelect(SlackBlockChannelsSelectElement),
260    #[serde(rename = "workflow_button")]
261    WorkflowButton(SlackBlockWorkflowButtonElement),
262}
263
264#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
265#[serde(tag = "type")]
266pub enum SlackContextBlockElement {
267    #[serde(rename = "image")]
268    Image(SlackBlockImageElement),
269    #[serde(rename = "plain_text")]
270    Plain(SlackBlockPlainText),
271    #[serde(rename = "mrkdwn")]
272    MarkDown(SlackBlockMarkDownText),
273}
274
275#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
276#[serde(tag = "type")]
277pub enum SlackInputBlockElement {
278    #[serde(rename = "static_select")]
279    StaticSelect(SlackBlockStaticSelectElement),
280    #[serde(rename = "multi_static_select")]
281    MultiStaticSelect(SlackBlockMultiStaticSelectElement),
282    #[serde(rename = "external_select")]
283    ExternalSelect(SlackBlockExternalSelectElement),
284    #[serde(rename = "multi_external_select")]
285    MultiExternalSelect(SlackBlockMultiExternalSelectElement),
286    #[serde(rename = "users_select")]
287    UsersSelect(SlackBlockUsersSelectElement),
288    #[serde(rename = "multi_users_select")]
289    MultiUsersSelect(SlackBlockMultiUsersSelectElement),
290    #[serde(rename = "conversations_select")]
291    ConversationsSelect(SlackBlockConversationsSelectElement),
292    #[serde(rename = "multi_conversations_select")]
293    MultiConversationsSelect(SlackBlockMultiConversationsSelectElement),
294    #[serde(rename = "channels_select")]
295    ChannelsSelect(SlackBlockChannelsSelectElement),
296    #[serde(rename = "multi_channels_select")]
297    MultiChannelsSelect(SlackBlockMultiChannelsSelectElement),
298    #[serde(rename = "datepicker")]
299    DatePicker(SlackBlockDatePickerElement),
300    #[serde(rename = "timepicker")]
301    TimePicker(SlackBlockTimePickerElement),
302    #[serde(rename = "datetimepicker")]
303    DateTimePicker(SlackBlockDateTimePickerElement),
304    #[serde(rename = "plain_text_input")]
305    PlainTextInput(SlackBlockPlainTextInputElement),
306    #[serde(rename = "number_input")]
307    NumberInput(SlackBlockNumberInputElement),
308    #[serde(rename = "url_text_input")]
309    UrlInput(SlackBlockUrlInputElement),
310    #[serde(rename = "radio_buttons")]
311    RadioButtons(SlackBlockRadioButtonsElement),
312    #[serde(rename = "checkboxes")]
313    Checkboxes(SlackBlockCheckboxesElement),
314    #[serde(rename = "email_text_input")]
315    EmailInput(SlackBlockEmailInputElement),
316    #[serde(rename = "rich_text_input")]
317    RichTextInput(SlackBlockRichTextInputElement),
318    #[serde(rename = "file_input")]
319    FileInput(SlackBlockFileInputElement),
320}
321
322#[skip_serializing_none]
323#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
324pub struct SlackBlockImageElement {
325    #[serde(flatten)]
326    pub image_url_or_file: SlackImageUrlOrFile,
327    pub alt_text: String,
328}
329
330impl From<SlackBlockImageElement> for SlackSectionBlockElement {
331    fn from(element: SlackBlockImageElement) -> Self {
332        SlackSectionBlockElement::Image(element)
333    }
334}
335
336impl From<SlackBlockImageElement> for SlackContextBlockElement {
337    fn from(element: SlackBlockImageElement) -> Self {
338        SlackContextBlockElement::Image(element)
339    }
340}
341
342#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
343#[serde(rename_all = "snake_case")]
344pub enum SlackBlockButtonStyle {
345    Primary,
346    Danger,
347}
348
349#[skip_serializing_none]
350#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
351pub struct SlackBlockButtonElement {
352    pub action_id: SlackActionId,
353    pub text: SlackBlockPlainTextOnly,
354    pub url: Option<Url>,
355    pub value: Option<String>,
356    pub style: Option<SlackBlockButtonStyle>,
357    pub confirm: Option<SlackBlockConfirmItem>,
358    pub accessibility_label: Option<SlackAccessibilityLabel>,
359}
360
361impl From<SlackBlockButtonElement> for SlackSectionBlockElement {
362    fn from(element: SlackBlockButtonElement) -> Self {
363        SlackSectionBlockElement::Button(element)
364    }
365}
366
367impl From<SlackBlockButtonElement> for SlackActionBlockElement {
368    fn from(element: SlackBlockButtonElement) -> Self {
369        SlackActionBlockElement::Button(element)
370    }
371}
372
373#[skip_serializing_none]
374#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
375pub struct SlackBlockConfirmItem {
376    pub title: SlackBlockPlainTextOnly,
377    pub text: SlackBlockText,
378    pub confirm: SlackBlockPlainTextOnly,
379    pub deny: SlackBlockPlainTextOnly,
380    pub style: Option<String>,
381}
382
383#[skip_serializing_none]
384#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
385pub struct SlackBlockChoiceItem<T: Into<SlackBlockText>> {
386    pub text: T,
387    pub value: String,
388    pub url: Option<Url>,
389}
390
391#[skip_serializing_none]
392#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
393pub struct SlackBlockOptionGroup<T: Into<SlackBlockText>> {
394    pub label: SlackBlockPlainTextOnly,
395    pub options: Vec<SlackBlockChoiceItem<T>>,
396}
397
398#[skip_serializing_none]
399#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
400pub struct SlackBlockStaticSelectElement {
401    pub action_id: SlackActionId,
402    pub placeholder: Option<SlackBlockPlainTextOnly>,
403    pub options: Option<Vec<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>>,
404    pub option_groups: Option<Vec<SlackBlockOptionGroup<SlackBlockPlainTextOnly>>>,
405    pub initial_option: Option<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>,
406    pub confirm: Option<SlackBlockConfirmItem>,
407    pub focus_on_load: Option<bool>,
408}
409
410impl From<SlackBlockStaticSelectElement> for SlackSectionBlockElement {
411    fn from(element: SlackBlockStaticSelectElement) -> Self {
412        SlackSectionBlockElement::StaticSelect(element)
413    }
414}
415
416impl From<SlackBlockStaticSelectElement> for SlackInputBlockElement {
417    fn from(element: SlackBlockStaticSelectElement) -> Self {
418        SlackInputBlockElement::StaticSelect(element)
419    }
420}
421
422impl From<SlackBlockStaticSelectElement> for SlackActionBlockElement {
423    fn from(element: SlackBlockStaticSelectElement) -> Self {
424        SlackActionBlockElement::StaticSelect(element)
425    }
426}
427
428#[skip_serializing_none]
429#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
430pub struct SlackBlockMultiStaticSelectElement {
431    pub action_id: SlackActionId,
432    pub placeholder: Option<SlackBlockPlainTextOnly>,
433    pub options: Option<Vec<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>>,
434    pub option_groups: Option<Vec<SlackBlockOptionGroup<SlackBlockPlainTextOnly>>>,
435    pub initial_options: Option<Vec<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>>,
436    pub confirm: Option<SlackBlockConfirmItem>,
437    pub max_selected_items: Option<u64>,
438    pub focus_on_load: Option<bool>,
439}
440
441impl From<SlackBlockMultiStaticSelectElement> for SlackSectionBlockElement {
442    fn from(element: SlackBlockMultiStaticSelectElement) -> Self {
443        SlackSectionBlockElement::MultiStaticSelect(element)
444    }
445}
446
447impl From<SlackBlockMultiStaticSelectElement> for SlackInputBlockElement {
448    fn from(element: SlackBlockMultiStaticSelectElement) -> Self {
449        SlackInputBlockElement::MultiStaticSelect(element)
450    }
451}
452
453#[skip_serializing_none]
454#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
455pub struct SlackBlockExternalSelectElement {
456    pub action_id: SlackActionId,
457    pub placeholder: Option<SlackBlockPlainTextOnly>,
458    pub initial_option: Option<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>,
459    pub confirm: Option<SlackBlockConfirmItem>,
460    pub focus_on_load: Option<bool>,
461    pub min_query_length: Option<u64>,
462}
463
464impl From<SlackBlockExternalSelectElement> for SlackSectionBlockElement {
465    fn from(element: SlackBlockExternalSelectElement) -> Self {
466        SlackSectionBlockElement::ExternalSelect(element)
467    }
468}
469
470impl From<SlackBlockExternalSelectElement> for SlackInputBlockElement {
471    fn from(element: SlackBlockExternalSelectElement) -> Self {
472        SlackInputBlockElement::ExternalSelect(element)
473    }
474}
475
476impl From<SlackBlockExternalSelectElement> for SlackActionBlockElement {
477    fn from(element: SlackBlockExternalSelectElement) -> Self {
478        SlackActionBlockElement::ExternalSelect(element)
479    }
480}
481
482#[skip_serializing_none]
483#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
484pub struct SlackBlockMultiExternalSelectElement {
485    pub action_id: SlackActionId,
486    pub placeholder: Option<SlackBlockPlainTextOnly>,
487    pub initial_options: Option<Vec<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>>,
488    pub confirm: Option<SlackBlockConfirmItem>,
489    pub max_selected_items: Option<u64>,
490    pub focus_on_load: Option<bool>,
491    pub min_query_length: Option<u64>,
492}
493
494impl From<SlackBlockMultiExternalSelectElement> for SlackSectionBlockElement {
495    fn from(element: SlackBlockMultiExternalSelectElement) -> Self {
496        SlackSectionBlockElement::MultiExternalSelect(element)
497    }
498}
499
500impl From<SlackBlockMultiExternalSelectElement> for SlackInputBlockElement {
501    fn from(element: SlackBlockMultiExternalSelectElement) -> Self {
502        SlackInputBlockElement::MultiExternalSelect(element)
503    }
504}
505
506#[skip_serializing_none]
507#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
508pub struct SlackBlockUsersSelectElement {
509    pub action_id: SlackActionId,
510    pub placeholder: Option<SlackBlockPlainTextOnly>,
511    pub initial_user: Option<String>,
512    pub confirm: Option<SlackBlockConfirmItem>,
513    pub focus_on_load: Option<bool>,
514}
515
516impl From<SlackBlockUsersSelectElement> for SlackSectionBlockElement {
517    fn from(element: SlackBlockUsersSelectElement) -> Self {
518        SlackSectionBlockElement::UsersSelect(element)
519    }
520}
521
522impl From<SlackBlockUsersSelectElement> for SlackInputBlockElement {
523    fn from(element: SlackBlockUsersSelectElement) -> Self {
524        SlackInputBlockElement::UsersSelect(element)
525    }
526}
527
528impl From<SlackBlockUsersSelectElement> for SlackActionBlockElement {
529    fn from(element: SlackBlockUsersSelectElement) -> Self {
530        SlackActionBlockElement::UsersSelect(element)
531    }
532}
533
534#[skip_serializing_none]
535#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
536pub struct SlackBlockMultiUsersSelectElement {
537    pub action_id: SlackActionId,
538    pub placeholder: Option<SlackBlockPlainTextOnly>,
539    pub initial_users: Option<Vec<String>>,
540    pub confirm: Option<SlackBlockConfirmItem>,
541    pub max_selected_items: Option<u64>,
542    pub focus_on_load: Option<bool>,
543}
544
545impl From<SlackBlockMultiUsersSelectElement> for SlackSectionBlockElement {
546    fn from(element: SlackBlockMultiUsersSelectElement) -> Self {
547        SlackSectionBlockElement::MultiUsersSelect(element)
548    }
549}
550
551impl From<SlackBlockMultiUsersSelectElement> for SlackInputBlockElement {
552    fn from(element: SlackBlockMultiUsersSelectElement) -> Self {
553        SlackInputBlockElement::MultiUsersSelect(element)
554    }
555}
556
557#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
558pub enum SlackConversationFilterInclude {
559    #[serde(rename = "im")]
560    Im,
561    #[serde(rename = "mpim")]
562    Mpim,
563    #[serde(rename = "public")]
564    Public,
565    #[serde(rename = "private")]
566    Private,
567}
568
569#[skip_serializing_none]
570#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
571pub struct SlackBlockConversationFilter {
572    pub include: Option<Vec<SlackConversationFilterInclude>>,
573    pub exclude_external_shared_channels: Option<bool>,
574    pub exclude_bot_users: Option<bool>,
575}
576
577#[skip_serializing_none]
578#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
579pub struct SlackBlockConversationsSelectElement {
580    pub action_id: SlackActionId,
581    pub placeholder: Option<SlackBlockPlainTextOnly>,
582    pub initial_conversation: Option<SlackConversationId>,
583    pub default_to_current_conversation: Option<bool>,
584    pub confirm: Option<SlackBlockConfirmItem>,
585    pub response_url_enabled: Option<bool>,
586    pub focus_on_load: Option<bool>,
587    pub filter: Option<SlackBlockConversationFilter>,
588}
589
590impl From<SlackBlockConversationsSelectElement> for SlackSectionBlockElement {
591    fn from(element: SlackBlockConversationsSelectElement) -> Self {
592        SlackSectionBlockElement::ConversationsSelect(element)
593    }
594}
595
596impl From<SlackBlockConversationsSelectElement> for SlackInputBlockElement {
597    fn from(element: SlackBlockConversationsSelectElement) -> Self {
598        SlackInputBlockElement::ConversationsSelect(element)
599    }
600}
601
602impl From<SlackBlockConversationsSelectElement> for SlackActionBlockElement {
603    fn from(element: SlackBlockConversationsSelectElement) -> Self {
604        SlackActionBlockElement::ConversationsSelect(element)
605    }
606}
607
608#[skip_serializing_none]
609#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
610pub struct SlackBlockMultiConversationsSelectElement {
611    pub action_id: SlackActionId,
612    pub placeholder: Option<SlackBlockPlainTextOnly>,
613    pub initial_conversations: Option<Vec<SlackConversationId>>,
614    pub default_to_current_conversation: Option<bool>,
615    pub confirm: Option<SlackBlockConfirmItem>,
616    pub max_selected_items: Option<u64>,
617    pub focus_on_load: Option<bool>,
618    pub filter: Option<SlackBlockConversationFilter>,
619}
620
621impl From<SlackBlockMultiConversationsSelectElement> for SlackSectionBlockElement {
622    fn from(element: SlackBlockMultiConversationsSelectElement) -> Self {
623        SlackSectionBlockElement::MultiConversationsSelect(element)
624    }
625}
626
627impl From<SlackBlockMultiConversationsSelectElement> for SlackInputBlockElement {
628    fn from(element: SlackBlockMultiConversationsSelectElement) -> Self {
629        SlackInputBlockElement::MultiConversationsSelect(element)
630    }
631}
632
633#[skip_serializing_none]
634#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
635pub struct SlackBlockChannelsSelectElement {
636    pub action_id: SlackActionId,
637    pub placeholder: Option<SlackBlockPlainTextOnly>,
638    pub initial_channel: Option<SlackChannelId>,
639    pub confirm: Option<SlackBlockConfirmItem>,
640    pub response_url_enabled: Option<bool>,
641    pub focus_on_load: Option<bool>,
642}
643
644impl From<SlackBlockChannelsSelectElement> for SlackSectionBlockElement {
645    fn from(element: SlackBlockChannelsSelectElement) -> Self {
646        SlackSectionBlockElement::ChannelsSelect(element)
647    }
648}
649
650impl From<SlackBlockChannelsSelectElement> for SlackInputBlockElement {
651    fn from(element: SlackBlockChannelsSelectElement) -> Self {
652        SlackInputBlockElement::ChannelsSelect(element)
653    }
654}
655
656impl From<SlackBlockChannelsSelectElement> for SlackActionBlockElement {
657    fn from(element: SlackBlockChannelsSelectElement) -> Self {
658        SlackActionBlockElement::ChannelsSelect(element)
659    }
660}
661
662#[skip_serializing_none]
663#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
664pub struct SlackBlockMultiChannelsSelectElement {
665    pub action_id: SlackActionId,
666    pub placeholder: Option<SlackBlockPlainTextOnly>,
667    pub initial_channels: Option<Vec<SlackChannelId>>,
668    pub confirm: Option<SlackBlockConfirmItem>,
669    pub max_selected_items: Option<u64>,
670    pub focus_on_load: Option<bool>,
671}
672
673impl From<SlackBlockMultiChannelsSelectElement> for SlackSectionBlockElement {
674    fn from(element: SlackBlockMultiChannelsSelectElement) -> Self {
675        SlackSectionBlockElement::MultiChannelsSelect(element)
676    }
677}
678
679impl From<SlackBlockMultiChannelsSelectElement> for SlackInputBlockElement {
680    fn from(element: SlackBlockMultiChannelsSelectElement) -> Self {
681        SlackInputBlockElement::MultiChannelsSelect(element)
682    }
683}
684
685#[skip_serializing_none]
686#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
687pub struct SlackBlockOverflowElement {
688    pub action_id: SlackActionId,
689    pub options: Vec<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>,
690    pub confirm: Option<SlackBlockConfirmItem>,
691}
692
693impl From<SlackBlockOverflowElement> for SlackSectionBlockElement {
694    fn from(element: SlackBlockOverflowElement) -> Self {
695        SlackSectionBlockElement::Overflow(element)
696    }
697}
698
699impl From<SlackBlockOverflowElement> for SlackActionBlockElement {
700    fn from(element: SlackBlockOverflowElement) -> Self {
701        SlackActionBlockElement::Overflow(element)
702    }
703}
704
705#[skip_serializing_none]
706#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
707pub struct SlackBlockDatePickerElement {
708    pub action_id: SlackActionId,
709    pub placeholder: Option<SlackBlockPlainTextOnly>,
710    pub initial_date: Option<String>,
711    pub confirm: Option<SlackBlockConfirmItem>,
712    pub focus_on_load: Option<bool>,
713}
714
715impl From<SlackBlockDatePickerElement> for SlackSectionBlockElement {
716    fn from(element: SlackBlockDatePickerElement) -> Self {
717        SlackSectionBlockElement::DatePicker(element)
718    }
719}
720
721impl From<SlackBlockDatePickerElement> for SlackInputBlockElement {
722    fn from(element: SlackBlockDatePickerElement) -> Self {
723        SlackInputBlockElement::DatePicker(element)
724    }
725}
726
727impl From<SlackBlockDatePickerElement> for SlackActionBlockElement {
728    fn from(element: SlackBlockDatePickerElement) -> Self {
729        SlackActionBlockElement::DatePicker(element)
730    }
731}
732
733#[skip_serializing_none]
734#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
735pub struct SlackBlockTimePickerElement {
736    pub action_id: SlackActionId,
737    pub initial_time: Option<String>,
738    pub confirm: Option<SlackBlockConfirmItem>,
739    pub focus_on_load: Option<bool>,
740    pub placeholder: Option<SlackBlockPlainTextOnly>,
741    pub timezone: Option<String>,
742}
743
744impl From<SlackBlockTimePickerElement> for SlackSectionBlockElement {
745    fn from(element: SlackBlockTimePickerElement) -> Self {
746        SlackSectionBlockElement::TimePicker(element)
747    }
748}
749
750impl From<SlackBlockTimePickerElement> for SlackInputBlockElement {
751    fn from(element: SlackBlockTimePickerElement) -> Self {
752        SlackInputBlockElement::TimePicker(element)
753    }
754}
755
756impl From<SlackBlockTimePickerElement> for SlackActionBlockElement {
757    fn from(element: SlackBlockTimePickerElement) -> Self {
758        SlackActionBlockElement::TimePicker(element)
759    }
760}
761
762#[skip_serializing_none]
763#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
764pub struct SlackBlockDateTimePickerElement {
765    pub action_id: SlackActionId,
766    pub initial_date_time: Option<SlackDateTime>,
767    pub confirm: Option<SlackBlockConfirmItem>,
768    pub focus_on_load: Option<bool>,
769}
770
771impl From<SlackBlockDateTimePickerElement> for SlackInputBlockElement {
772    fn from(element: SlackBlockDateTimePickerElement) -> Self {
773        SlackInputBlockElement::DateTimePicker(element)
774    }
775}
776
777impl From<SlackBlockDateTimePickerElement> for SlackActionBlockElement {
778    fn from(element: SlackBlockDateTimePickerElement) -> Self {
779        SlackActionBlockElement::DateTimePicker(element)
780    }
781}
782
783/**
784 * https://docs.slack.dev/reference/block-kit/composition-objects/dispatch-action-configuration-object
785 */
786#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
787#[serde(rename_all = "snake_case")]
788pub enum SlackDispatchActionTrigger {
789    OnEnterPressed,
790    OnCharacterEntered,
791}
792
793#[skip_serializing_none]
794#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
795pub struct SlackDispatchActionConfig {
796    pub trigger_actions_on: Option<Vec<SlackDispatchActionTrigger>>,
797}
798
799#[skip_serializing_none]
800#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
801pub struct SlackBlockPlainTextInputElement {
802    pub action_id: SlackActionId,
803    pub placeholder: Option<SlackBlockPlainTextOnly>,
804    pub initial_value: Option<String>,
805    pub multiline: Option<bool>,
806    pub min_length: Option<u64>,
807    pub max_length: Option<u64>,
808    pub focus_on_load: Option<bool>,
809    pub dispatch_action_config: Option<SlackDispatchActionConfig>,
810}
811
812impl From<SlackBlockPlainTextInputElement> for SlackSectionBlockElement {
813    fn from(element: SlackBlockPlainTextInputElement) -> Self {
814        SlackSectionBlockElement::PlainTextInput(element)
815    }
816}
817
818impl From<SlackBlockPlainTextInputElement> for SlackInputBlockElement {
819    fn from(element: SlackBlockPlainTextInputElement) -> Self {
820        SlackInputBlockElement::PlainTextInput(element)
821    }
822}
823
824impl From<SlackBlockPlainTextInputElement> for SlackActionBlockElement {
825    fn from(element: SlackBlockPlainTextInputElement) -> Self {
826        SlackActionBlockElement::PlainTextInput(element)
827    }
828}
829
830#[skip_serializing_none]
831#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
832pub struct SlackBlockNumberInputElement {
833    pub action_id: SlackActionId,
834    pub is_decimal_allowed: bool,
835    pub focus_on_load: Option<bool>,
836    pub placeholder: Option<SlackBlockPlainTextOnly>,
837    pub initial_value: Option<String>,
838    pub min_value: Option<String>,
839    pub max_value: Option<String>,
840}
841
842impl From<SlackBlockNumberInputElement> for SlackSectionBlockElement {
843    fn from(element: SlackBlockNumberInputElement) -> Self {
844        SlackSectionBlockElement::NumberInput(element)
845    }
846}
847
848impl From<SlackBlockNumberInputElement> for SlackInputBlockElement {
849    fn from(element: SlackBlockNumberInputElement) -> Self {
850        SlackInputBlockElement::NumberInput(element)
851    }
852}
853
854impl From<SlackBlockNumberInputElement> for SlackActionBlockElement {
855    fn from(element: SlackBlockNumberInputElement) -> Self {
856        SlackActionBlockElement::NumberInput(element)
857    }
858}
859
860#[skip_serializing_none]
861#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
862pub struct SlackBlockUrlInputElement {
863    pub action_id: SlackActionId,
864    pub placeholder: Option<SlackBlockPlainTextOnly>,
865    pub initial_value: Option<String>,
866}
867
868impl From<SlackBlockUrlInputElement> for SlackSectionBlockElement {
869    fn from(element: SlackBlockUrlInputElement) -> Self {
870        SlackSectionBlockElement::UrlInput(element)
871    }
872}
873
874impl From<SlackBlockUrlInputElement> for SlackInputBlockElement {
875    fn from(element: SlackBlockUrlInputElement) -> Self {
876        SlackInputBlockElement::UrlInput(element)
877    }
878}
879
880impl From<SlackBlockUrlInputElement> for SlackActionBlockElement {
881    fn from(element: SlackBlockUrlInputElement) -> Self {
882        SlackActionBlockElement::UrlInput(element)
883    }
884}
885
886#[skip_serializing_none]
887#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
888pub struct SlackBlockEmailInputElement {
889    pub action_id: SlackActionId,
890    pub focus_on_load: Option<bool>,
891    pub placeholder: Option<SlackBlockPlainTextOnly>,
892    pub initial_value: Option<EmailAddress>,
893}
894
895impl From<SlackBlockEmailInputElement> for SlackInputBlockElement {
896    fn from(element: SlackBlockEmailInputElement) -> Self {
897        SlackInputBlockElement::EmailInput(element)
898    }
899}
900
901#[skip_serializing_none]
902#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
903pub struct SlackBlockRadioButtonsElement {
904    pub action_id: SlackActionId,
905    pub options: Vec<SlackBlockChoiceItem<SlackBlockText>>,
906    pub initial_option: Option<SlackBlockChoiceItem<SlackBlockText>>,
907    pub confirm: Option<SlackBlockConfirmItem>,
908    pub focus_on_load: Option<bool>,
909}
910
911impl From<SlackBlockRadioButtonsElement> for SlackSectionBlockElement {
912    fn from(element: SlackBlockRadioButtonsElement) -> Self {
913        SlackSectionBlockElement::RadioButtons(element)
914    }
915}
916
917impl From<SlackBlockRadioButtonsElement> for SlackInputBlockElement {
918    fn from(element: SlackBlockRadioButtonsElement) -> Self {
919        SlackInputBlockElement::RadioButtons(element)
920    }
921}
922
923impl From<SlackBlockRadioButtonsElement> for SlackActionBlockElement {
924    fn from(element: SlackBlockRadioButtonsElement) -> Self {
925        SlackActionBlockElement::RadioButtons(element)
926    }
927}
928
929#[skip_serializing_none]
930#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
931pub struct SlackBlockCheckboxesElement {
932    pub action_id: SlackActionId,
933    pub options: Vec<SlackBlockChoiceItem<SlackBlockText>>,
934    pub initial_options: Option<Vec<SlackBlockChoiceItem<SlackBlockText>>>,
935    pub confirm: Option<SlackBlockConfirmItem>,
936    pub focus_on_load: Option<bool>,
937}
938
939impl From<SlackBlockCheckboxesElement> for SlackSectionBlockElement {
940    fn from(element: SlackBlockCheckboxesElement) -> Self {
941        SlackSectionBlockElement::Checkboxes(element)
942    }
943}
944
945impl From<SlackBlockCheckboxesElement> for SlackInputBlockElement {
946    fn from(element: SlackBlockCheckboxesElement) -> Self {
947        SlackInputBlockElement::Checkboxes(element)
948    }
949}
950
951impl From<SlackBlockCheckboxesElement> for SlackActionBlockElement {
952    fn from(element: SlackBlockCheckboxesElement) -> Self {
953        SlackActionBlockElement::Checkboxes(element)
954    }
955}
956
957/**
958 * 'plain_text' type of https://api.slack.com/reference/block-kit/composition-objects#text
959 */
960#[skip_serializing_none]
961#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
962pub struct SlackBlockPlainText {
963    pub text: String,
964    pub emoji: Option<bool>,
965}
966
967/**
968 * 'mrkdwn' type of https://api.slack.com/reference/block-kit/composition-objects#text
969 */
970#[skip_serializing_none]
971#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
972pub struct SlackBlockMarkDownText {
973    pub text: String,
974    pub verbatim: Option<bool>,
975}
976
977/**
978 * https://api.slack.com/reference/block-kit/composition-objects#text
979 */
980#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
981#[serde(tag = "type")]
982pub enum SlackBlockText {
983    #[serde(rename = "plain_text")]
984    Plain(SlackBlockPlainText),
985    #[serde(rename = "mrkdwn")]
986    MarkDown(SlackBlockMarkDownText),
987}
988
989#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
990#[serde(tag = "type", rename = "plain_text")]
991pub struct SlackBlockPlainTextOnly {
992    #[serde(flatten)]
993    value: SlackBlockPlainText,
994}
995
996impl SlackBlockPlainText {
997    pub fn as_block_text(&self) -> SlackBlockText {
998        SlackBlockText::Plain(self.clone())
999    }
1000}
1001
1002impl From<String> for SlackBlockPlainText {
1003    fn from(value: String) -> Self {
1004        SlackBlockPlainText::new(value)
1005    }
1006}
1007
1008impl From<&str> for SlackBlockPlainText {
1009    fn from(value: &str) -> Self {
1010        SlackBlockPlainText::new(String::from(value))
1011    }
1012}
1013
1014impl SlackBlockMarkDownText {
1015    pub fn as_block_text(&self) -> SlackBlockText {
1016        SlackBlockText::MarkDown(self.clone())
1017    }
1018}
1019
1020impl From<String> for SlackBlockMarkDownText {
1021    fn from(value: String) -> Self {
1022        SlackBlockMarkDownText::new(value)
1023    }
1024}
1025
1026impl From<&str> for SlackBlockMarkDownText {
1027    fn from(value: &str) -> Self {
1028        SlackBlockMarkDownText::new(String::from(value))
1029    }
1030}
1031
1032impl From<SlackBlockPlainText> for SlackBlockPlainTextOnly {
1033    fn from(pt: SlackBlockPlainText) -> Self {
1034        SlackBlockPlainTextOnly { value: pt }
1035    }
1036}
1037
1038impl From<SlackBlockPlainText> for SlackBlockText {
1039    fn from(text: SlackBlockPlainText) -> Self {
1040        SlackBlockText::Plain(text)
1041    }
1042}
1043
1044impl From<SlackBlockMarkDownText> for SlackBlockText {
1045    fn from(text: SlackBlockMarkDownText) -> Self {
1046        SlackBlockText::MarkDown(text)
1047    }
1048}
1049
1050impl From<SlackBlockPlainText> for SlackContextBlockElement {
1051    fn from(text: SlackBlockPlainText) -> Self {
1052        SlackContextBlockElement::Plain(text)
1053    }
1054}
1055
1056impl From<SlackBlockMarkDownText> for SlackContextBlockElement {
1057    fn from(text: SlackBlockMarkDownText) -> Self {
1058        SlackContextBlockElement::MarkDown(text)
1059    }
1060}
1061
1062impl From<SlackBlockPlainTextOnly> for SlackBlockText {
1063    fn from(text: SlackBlockPlainTextOnly) -> Self {
1064        SlackBlockText::Plain(text.value)
1065    }
1066}
1067
1068impl From<String> for SlackBlockPlainTextOnly {
1069    fn from(value: String) -> Self {
1070        SlackBlockPlainTextOnly {
1071            value: value.into(),
1072        }
1073    }
1074}
1075
1076impl From<&str> for SlackBlockPlainTextOnly {
1077    fn from(value: &str) -> Self {
1078        SlackBlockPlainTextOnly {
1079            value: value.into(),
1080        }
1081    }
1082}
1083
1084/**
1085 * https://api.slack.com/reference/block-kit/blocks#video
1086 */
1087#[skip_serializing_none]
1088#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1089pub struct SlackVideoBlock {
1090    pub alt_text: String,
1091    pub author_name: Option<String>,
1092    pub block_id: Option<SlackBlockId>,
1093    pub description: Option<SlackBlockPlainTextOnly>,
1094    pub provider_icon_url: Option<Url>,
1095    pub provider_name: Option<String>,
1096    pub title: SlackBlockPlainTextOnly,
1097    pub title_url: Option<Url>,
1098    pub thumbnail_url: Url,
1099    pub video_url: Url,
1100}
1101
1102impl From<SlackVideoBlock> for SlackBlock {
1103    fn from(block: SlackVideoBlock) -> Self {
1104        SlackBlock::Video(block)
1105    }
1106}
1107
1108/**
1109 * https://api.slack.com/reference/block-kit/blocks#markdown
1110 */
1111#[skip_serializing_none]
1112#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1113pub struct SlackMarkdownBlock {
1114    pub block_id: Option<SlackBlockId>,
1115    pub text: String,
1116}
1117
1118impl From<SlackMarkdownBlock> for SlackBlock {
1119    fn from(block: SlackMarkdownBlock) -> Self {
1120        SlackBlock::Markdown(block)
1121    }
1122}
1123
1124/**
1125 * https://api.slack.com/reference/block-kit/blocks#rich_text
1126 */
1127#[skip_serializing_none]
1128#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1129pub struct SlackRichTextBlock {
1130    pub block_id: Option<SlackBlockId>,
1131    pub elements: Vec<SlackRichTextElement>,
1132}
1133
1134impl From<SlackRichTextBlock> for SlackBlock {
1135    fn from(block: SlackRichTextBlock) -> Self {
1136        SlackBlock::RichText(block)
1137    }
1138}
1139
1140#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1141#[serde(tag = "type", rename_all = "snake_case")]
1142pub enum SlackRichTextInlineContent {
1143    #[serde(rename = "rich_text")]
1144    RichText(SlackRichTextBlock),
1145}
1146
1147impl From<SlackRichTextBlock> for SlackRichTextInlineContent {
1148    fn from(block: SlackRichTextBlock) -> Self {
1149        SlackRichTextInlineContent::RichText(block)
1150    }
1151}
1152
1153#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1154#[serde(tag = "type")]
1155pub enum SlackRichTextElement {
1156    #[serde(rename = "rich_text_section")]
1157    Section(SlackRichTextSection),
1158    #[serde(rename = "rich_text_list")]
1159    List(SlackRichTextList),
1160    #[serde(rename = "rich_text_preformatted")]
1161    Preformatted(SlackRichTextPreformatted),
1162    #[serde(rename = "rich_text_quote")]
1163    Quote(SlackRichTextQuote),
1164}
1165
1166impl From<SlackRichTextSection> for SlackRichTextElement {
1167    fn from(element: SlackRichTextSection) -> Self {
1168        SlackRichTextElement::Section(element)
1169    }
1170}
1171
1172impl From<SlackRichTextList> for SlackRichTextElement {
1173    fn from(list: SlackRichTextList) -> Self {
1174        SlackRichTextElement::List(list)
1175    }
1176}
1177
1178impl From<SlackRichTextPreformatted> for SlackRichTextElement {
1179    fn from(element: SlackRichTextPreformatted) -> Self {
1180        SlackRichTextElement::Preformatted(element)
1181    }
1182}
1183
1184impl From<SlackRichTextQuote> for SlackRichTextElement {
1185    fn from(element: SlackRichTextQuote) -> Self {
1186        SlackRichTextElement::Quote(element)
1187    }
1188}
1189
1190#[skip_serializing_none]
1191#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1192pub struct SlackRichTextSection {
1193    pub elements: Vec<SlackRichTextInlineElement>,
1194}
1195
1196#[skip_serializing_none]
1197#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1198pub struct SlackRichTextList {
1199    pub style: SlackRichTextListStyle,
1200    pub elements: Vec<SlackRichTextListElement>,
1201    pub indent: Option<u64>,
1202    pub offset: Option<u64>,
1203    pub border: Option<u64>,
1204}
1205
1206#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1207#[serde(tag = "type")]
1208pub enum SlackRichTextListElement {
1209    #[serde(rename = "rich_text_section")]
1210    Section(SlackRichTextSection),
1211}
1212
1213impl From<SlackRichTextSection> for SlackRichTextListElement {
1214    fn from(element: SlackRichTextSection) -> Self {
1215        SlackRichTextListElement::Section(element)
1216    }
1217}
1218
1219#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1220#[serde(rename_all = "snake_case")]
1221pub enum SlackRichTextListStyle {
1222    Bullet,
1223    Ordered,
1224}
1225
1226#[skip_serializing_none]
1227#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1228pub struct SlackRichTextPreformatted {
1229    pub elements: Vec<SlackRichTextInlineElement>,
1230    pub border: Option<u64>,
1231    pub language: Option<String>,
1232}
1233
1234#[skip_serializing_none]
1235#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1236pub struct SlackRichTextQuote {
1237    pub elements: Vec<SlackRichTextInlineElement>,
1238    pub border: Option<u64>,
1239}
1240
1241#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1242#[serde(tag = "type")]
1243pub enum SlackRichTextInlineElement {
1244    #[serde(rename = "text")]
1245    Text(SlackRichTextText),
1246    #[serde(rename = "link")]
1247    Link(SlackRichTextLink),
1248    #[serde(rename = "user")]
1249    User(SlackRichTextUser),
1250    #[serde(rename = "channel")]
1251    Channel(SlackRichTextChannel),
1252    #[serde(rename = "usergroup")]
1253    UserGroup(SlackRichTextUserGroup),
1254    #[serde(rename = "emoji")]
1255    Emoji(SlackRichTextEmoji),
1256    #[serde(rename = "date")]
1257    Date(SlackRichTextDate),
1258    #[serde(rename = "broadcast")]
1259    Broadcast(SlackRichTextBroadcast),
1260    #[serde(rename = "color")]
1261    Color(SlackRichTextColor),
1262}
1263
1264#[skip_serializing_none]
1265#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1266pub struct SlackRichTextStyle {
1267    pub bold: Option<bool>,
1268    pub italic: Option<bool>,
1269    pub strike: Option<bool>,
1270    pub code: Option<bool>,
1271    pub underline: Option<bool>,
1272    pub highlight: Option<bool>,
1273    pub client_highlight: Option<bool>,
1274    pub unlink: Option<bool>,
1275}
1276
1277#[skip_serializing_none]
1278#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1279pub struct SlackRichTextText {
1280    pub text: String,
1281    pub style: Option<SlackRichTextStyle>,
1282}
1283
1284#[skip_serializing_none]
1285#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1286pub struct SlackRichTextLink {
1287    pub url: SlackRelaxedUrl,
1288    pub text: Option<String>,
1289    #[serde(rename = "unsafe")]
1290    pub unsafe_: Option<bool>,
1291    pub style: Option<SlackRichTextStyle>,
1292}
1293
1294#[skip_serializing_none]
1295#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1296pub struct SlackRichTextUser {
1297    pub user_id: SlackUserId,
1298    pub style: Option<SlackRichTextStyle>,
1299}
1300
1301#[skip_serializing_none]
1302#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1303pub struct SlackRichTextChannel {
1304    pub channel_id: SlackChannelId,
1305    pub style: Option<SlackRichTextStyle>,
1306}
1307
1308#[skip_serializing_none]
1309#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1310pub struct SlackRichTextUserGroup {
1311    pub usergroup_id: SlackUserGroupId,
1312    pub style: Option<SlackRichTextStyle>,
1313}
1314
1315#[skip_serializing_none]
1316#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1317pub struct SlackRichTextEmoji {
1318    pub name: SlackEmojiName,
1319    pub unicode: Option<String>,
1320}
1321
1322#[skip_serializing_none]
1323#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1324pub struct SlackRichTextDate {
1325    pub timestamp: SlackDateTime,
1326    pub format: String,
1327    pub fallback: Option<String>,
1328    pub style: Option<SlackRichTextStyle>,
1329}
1330
1331#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1332#[serde(rename_all = "snake_case")]
1333pub enum SlackRichTextBroadcastRange {
1334    Here,
1335    Channel,
1336    Everyone,
1337}
1338
1339#[skip_serializing_none]
1340#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1341pub struct SlackRichTextBroadcast {
1342    pub range: SlackRichTextBroadcastRange,
1343    pub style: Option<SlackRichTextStyle>,
1344}
1345
1346#[skip_serializing_none]
1347#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1348pub struct SlackRichTextColor {
1349    pub value: String,
1350}
1351
1352/**
1353 * https://api.slack.com/reference/block-kit/block-elements#rich_text_input
1354 */
1355#[skip_serializing_none]
1356#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1357pub struct SlackBlockRichTextInputElement {
1358    pub action_id: SlackActionId,
1359    pub initial_value: Option<SlackRichTextBlock>,
1360    pub focus_on_load: Option<bool>,
1361    pub placeholder: Option<SlackBlockPlainTextOnly>,
1362}
1363
1364impl From<SlackBlockRichTextInputElement> for SlackInputBlockElement {
1365    fn from(element: SlackBlockRichTextInputElement) -> Self {
1366        SlackInputBlockElement::RichTextInput(element)
1367    }
1368}
1369
1370#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1371#[serde(untagged)]
1372pub enum SlackImageUrlOrFile {
1373    ImageUrl { image_url: Url },
1374    SlackFile { slack_file: SlackFileIdOrUrl },
1375}
1376
1377impl SlackImageUrlOrFile {
1378    pub fn image_url(&self) -> Option<&Url> {
1379        match self {
1380            SlackImageUrlOrFile::ImageUrl { image_url } => Some(image_url),
1381            SlackImageUrlOrFile::SlackFile { slack_file } => match slack_file {
1382                SlackFileIdOrUrl::Url { url } => Some(url),
1383                _ => None,
1384            },
1385        }
1386    }
1387}
1388
1389impl From<Url> for SlackImageUrlOrFile {
1390    fn from(value: Url) -> Self {
1391        SlackImageUrlOrFile::ImageUrl { image_url: value }
1392    }
1393}
1394
1395#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1396#[serde(untagged)]
1397pub enum SlackFileIdOrUrl {
1398    Id { id: SlackFileId },
1399    Url { url: Url },
1400}
1401
1402impl From<SlackFileId> for SlackFileIdOrUrl {
1403    fn from(value: SlackFileId) -> Self {
1404        SlackFileIdOrUrl::Id { id: value }
1405    }
1406}
1407
1408/**
1409 * https://docs.slack.dev/reference/block-kit/blocks/table-block
1410 */
1411#[skip_serializing_none]
1412#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1413pub struct SlackTableBlock {
1414    pub block_id: Option<SlackBlockId>,
1415    pub rows: Vec<Vec<SlackTableCell>>,
1416    pub column_settings: Option<Vec<SlackTableColumnSetting>>,
1417}
1418
1419impl From<SlackTableBlock> for SlackBlock {
1420    fn from(block: SlackTableBlock) -> Self {
1421        SlackBlock::Table(block)
1422    }
1423}
1424
1425#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1426#[serde(tag = "type")]
1427pub enum SlackTableCell {
1428    #[serde(rename = "raw_text")]
1429    RawText(SlackTableRawTextCell),
1430    #[serde(rename = "rich_text")]
1431    RichText(SlackTableRichTextCell),
1432}
1433
1434#[skip_serializing_none]
1435#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1436pub struct SlackTableRawTextCell {
1437    pub text: String,
1438}
1439
1440#[skip_serializing_none]
1441#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1442pub struct SlackTableRichTextCell {
1443    pub elements: Vec<SlackRichTextElement>,
1444}
1445
1446#[skip_serializing_none]
1447#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1448pub struct SlackTableColumnSetting {
1449    pub align: Option<SlackTableColumnAlign>,
1450    pub is_wrapped: Option<bool>,
1451}
1452
1453#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1454#[serde(rename_all = "snake_case")]
1455pub enum SlackTableColumnAlign {
1456    Left,
1457    Center,
1458    Right,
1459}
1460
1461/**
1462 * https://docs.slack.dev/reference/block-kit/blocks/task-card-block
1463 */
1464#[skip_serializing_none]
1465#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1466pub struct SlackTaskCardBlock {
1467    pub task_id: SlackTaskId,
1468    pub title: String,
1469    pub block_id: Option<SlackBlockId>,
1470    pub status: Option<SlackTaskCardStatus>,
1471    #[serde(rename = "details")]
1472    pub details: Option<SlackRichTextInlineContent>,
1473    #[serde(rename = "output")]
1474    pub output: Option<SlackRichTextInlineContent>,
1475    pub sources: Option<Vec<SlackTaskCardSource>>,
1476}
1477
1478impl From<SlackTaskCardBlock> for SlackBlock {
1479    fn from(block: SlackTaskCardBlock) -> Self {
1480        SlackBlock::TaskCard(block)
1481    }
1482}
1483
1484#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1485#[serde(rename_all = "snake_case")]
1486pub enum SlackTaskCardStatus {
1487    Pending,
1488    InProgress,
1489    Complete,
1490    Error,
1491}
1492
1493/**
1494 * https://docs.slack.dev/reference/block-kit/block-elements/url-source-element
1495 */
1496#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1497pub struct SlackUrlSourceElement {
1498    pub url: Url,
1499    pub text: String,
1500}
1501
1502#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1503#[serde(tag = "type")]
1504pub enum SlackTaskCardSource {
1505    #[serde(rename = "url")]
1506    Url(SlackUrlSourceElement),
1507}
1508
1509impl From<SlackUrlSourceElement> for SlackTaskCardSource {
1510    fn from(element: SlackUrlSourceElement) -> Self {
1511        SlackTaskCardSource::Url(element)
1512    }
1513}
1514
1515/**
1516 * https://docs.slack.dev/reference/block-kit/block-elements/file-input-element
1517 */
1518#[skip_serializing_none]
1519#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1520pub struct SlackBlockFileInputElement {
1521    pub action_id: SlackActionId,
1522    pub filetypes: Option<Vec<String>>,
1523    pub max_files: Option<u64>,
1524}
1525
1526impl From<SlackBlockFileInputElement> for SlackInputBlockElement {
1527    fn from(element: SlackBlockFileInputElement) -> Self {
1528        SlackInputBlockElement::FileInput(element)
1529    }
1530}
1531
1532/**
1533 * https://docs.slack.dev/reference/block-kit/blocks/alert-block
1534 */
1535#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1536#[serde(rename_all = "snake_case")]
1537pub enum SlackAlertLevel {
1538    Warning,
1539    Error,
1540    Info,
1541    Success,
1542}
1543
1544#[skip_serializing_none]
1545#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1546pub struct SlackAlertBlock {
1547    pub block_id: Option<SlackBlockId>,
1548    pub text: SlackBlockText,
1549    pub level: Option<SlackAlertLevel>,
1550}
1551
1552impl From<SlackAlertBlock> for SlackBlock {
1553    fn from(block: SlackAlertBlock) -> Self {
1554        SlackBlock::Alert(block)
1555    }
1556}
1557
1558/**
1559 * https://docs.slack.dev/reference/block-kit/blocks/card-block
1560 */
1561#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1562#[serde(tag = "type")]
1563pub enum SlackCardImageElement {
1564    #[serde(rename = "image")]
1565    Image(SlackBlockImageElement),
1566}
1567
1568impl From<SlackBlockImageElement> for SlackCardImageElement {
1569    fn from(element: SlackBlockImageElement) -> Self {
1570        SlackCardImageElement::Image(element)
1571    }
1572}
1573
1574#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1575#[serde(tag = "type")]
1576pub enum SlackCardActionBlockElement {
1577    #[serde(rename = "button")]
1578    Button(SlackBlockButtonElement),
1579}
1580
1581impl From<SlackBlockButtonElement> for SlackCardActionBlockElement {
1582    fn from(element: SlackBlockButtonElement) -> Self {
1583        SlackCardActionBlockElement::Button(element)
1584    }
1585}
1586
1587#[skip_serializing_none]
1588#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1589pub struct SlackCardBlock {
1590    pub block_id: Option<SlackBlockId>,
1591    pub title: Option<SlackBlockText>,
1592    pub subtitle: Option<SlackBlockText>,
1593    pub body: Option<SlackBlockText>,
1594    pub hero_image: Option<SlackCardImageElement>,
1595    pub icon: Option<SlackCardImageElement>,
1596    pub actions: Option<Vec<SlackCardActionBlockElement>>,
1597}
1598
1599impl From<SlackCardBlock> for SlackBlock {
1600    fn from(block: SlackCardBlock) -> Self {
1601        SlackBlock::Card(block)
1602    }
1603}
1604
1605/**
1606 * https://docs.slack.dev/reference/block-kit/blocks/carousel-block
1607 */
1608#[skip_serializing_none]
1609#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1610pub struct SlackCarouselBlock {
1611    pub block_id: Option<SlackBlockId>,
1612    pub elements: Vec<SlackBlock>,
1613}
1614
1615impl From<SlackCarouselBlock> for SlackBlock {
1616    fn from(block: SlackCarouselBlock) -> Self {
1617        SlackBlock::Carousel(block)
1618    }
1619}
1620
1621/**
1622 * https://docs.slack.dev/reference/block-kit/blocks/context-actions-block
1623 * https://docs.slack.dev/reference/block-kit/block-elements/feedback-buttons-element
1624 * https://docs.slack.dev/reference/block-kit/block-elements/icon-button-element
1625 */
1626#[skip_serializing_none]
1627#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1628pub struct SlackFeedbackButtonItem {
1629    pub action_id: SlackActionId,
1630    pub value: String,
1631    pub text: SlackBlockPlainTextOnly,
1632    pub confirm: Option<SlackBlockConfirmItem>,
1633}
1634
1635#[skip_serializing_none]
1636#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1637pub struct SlackBlockFeedbackButtonsElement {
1638    pub action_id: SlackActionId,
1639    pub positive: SlackFeedbackButtonItem,
1640    pub negative: SlackFeedbackButtonItem,
1641}
1642
1643#[skip_serializing_none]
1644#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1645pub struct SlackBlockIconButtonElement {
1646    pub action_id: SlackActionId,
1647    pub icon: String,
1648    pub text: SlackBlockPlainTextOnly,
1649    pub value: Option<String>,
1650    pub confirm: Option<SlackBlockConfirmItem>,
1651    pub accessibility_label: Option<SlackAccessibilityLabel>,
1652    pub visible_to_user_ids: Option<Vec<SlackUserId>>,
1653}
1654
1655#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1656#[serde(tag = "type")]
1657pub enum SlackContextActionBlockElement {
1658    #[serde(rename = "feedback_buttons")]
1659    FeedbackButtons(SlackBlockFeedbackButtonsElement),
1660    #[serde(rename = "icon_button")]
1661    IconButton(SlackBlockIconButtonElement),
1662}
1663
1664impl From<SlackBlockFeedbackButtonsElement> for SlackContextActionBlockElement {
1665    fn from(element: SlackBlockFeedbackButtonsElement) -> Self {
1666        SlackContextActionBlockElement::FeedbackButtons(element)
1667    }
1668}
1669
1670impl From<SlackBlockIconButtonElement> for SlackContextActionBlockElement {
1671    fn from(element: SlackBlockIconButtonElement) -> Self {
1672        SlackContextActionBlockElement::IconButton(element)
1673    }
1674}
1675
1676#[skip_serializing_none]
1677#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1678pub struct SlackContextActionsBlock {
1679    pub block_id: Option<SlackBlockId>,
1680    pub elements: Vec<SlackContextActionBlockElement>,
1681}
1682
1683impl From<SlackContextActionsBlock> for SlackBlock {
1684    fn from(block: SlackContextActionsBlock) -> Self {
1685        SlackBlock::ContextActions(block)
1686    }
1687}
1688
1689#[cfg(test)]
1690mod test {
1691    use super::*;
1692    use crate::blocks::SlackHomeView;
1693
1694    #[test]
1695    fn test_conversation_filter_deserialize() -> Result<(), Box<dyn std::error::Error>> {
1696        let payload = include_str!("./fixtures/slack_conversations_select_with_filter.json");
1697        let block: SlackBlock = serde_json::from_str(payload)?;
1698        match block {
1699            SlackBlock::Section(section) => match section.accessory {
1700                Some(SlackSectionBlockElement::ConversationsSelect(elem)) => {
1701                    let filter = elem.filter.expect("filter should be present");
1702                    let include = filter.include.expect("include should be present");
1703                    assert_eq!(include.len(), 2);
1704                    assert_eq!(include[0], SlackConversationFilterInclude::Public);
1705                    assert_eq!(include[1], SlackConversationFilterInclude::Private);
1706                    assert_eq!(filter.exclude_external_shared_channels, Some(true));
1707                    assert_eq!(filter.exclude_bot_users, Some(true));
1708                }
1709                _ => panic!("Expected ConversationsSelect accessory"),
1710            },
1711            _ => panic!("Expected Section block"),
1712        }
1713        Ok(())
1714    }
1715
1716    #[test]
1717    fn test_conversation_filter_serialize() -> Result<(), Box<dyn std::error::Error>> {
1718        let filter = SlackBlockConversationFilter::new()
1719            .with_include(vec![
1720                SlackConversationFilterInclude::Im,
1721                SlackConversationFilterInclude::Mpim,
1722            ])
1723            .with_exclude_bot_users(true);
1724
1725        let json = serde_json::to_value(&filter)?;
1726        assert_eq!(
1727            json,
1728            serde_json::json!({
1729                "include": ["im", "mpim"],
1730                "exclude_bot_users": true
1731            })
1732        );
1733        Ok(())
1734    }
1735
1736    #[test]
1737    fn test_conversation_filter_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
1738        let elem = SlackBlockConversationsSelectElement::new(SlackActionId("test_action".into()))
1739            .with_filter(
1740                SlackBlockConversationFilter::new()
1741                    .with_include(vec![SlackConversationFilterInclude::Public])
1742                    .with_exclude_external_shared_channels(true),
1743            );
1744
1745        let json = serde_json::to_string(&elem)?;
1746        let parsed: SlackBlockConversationsSelectElement = serde_json::from_str(&json)?;
1747        assert_eq!(elem, parsed);
1748        Ok(())
1749    }
1750
1751    #[test]
1752    fn test_multi_conversations_select_filter() -> Result<(), Box<dyn std::error::Error>> {
1753        let elem =
1754            SlackBlockMultiConversationsSelectElement::new(SlackActionId("multi_action".into()))
1755                .with_filter(
1756                    SlackBlockConversationFilter::new()
1757                        .with_include(vec![
1758                            SlackConversationFilterInclude::Public,
1759                            SlackConversationFilterInclude::Private,
1760                        ])
1761                        .with_exclude_bot_users(true),
1762                );
1763
1764        let json = serde_json::to_string(&elem)?;
1765        let parsed: SlackBlockMultiConversationsSelectElement = serde_json::from_str(&json)?;
1766        assert_eq!(elem, parsed);
1767        Ok(())
1768    }
1769
1770    #[test]
1771    fn test_conversation_filter_none_omitted() -> Result<(), Box<dyn std::error::Error>> {
1772        let elem = SlackBlockConversationsSelectElement::new(SlackActionId("no_filter".into()));
1773
1774        let json = serde_json::to_value(&elem)?;
1775        assert!(json.get("filter").is_none());
1776        Ok(())
1777    }
1778
1779    #[test]
1780    fn test_slack_image_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
1781        let payload = include_str!("./fixtures/slack_image_blocks.json");
1782        let content: SlackMessageContent = serde_json::from_str(payload)?;
1783        let blocks = content.blocks.expect("Blocks should not be empty");
1784        match blocks.first() {
1785            Some(SlackBlock::Section(section)) => match &section.accessory {
1786                Some(SlackSectionBlockElement::Image(image)) => {
1787                    assert_eq!(image.alt_text, "alt text for image");
1788                    match &image.image_url_or_file {
1789                        SlackImageUrlOrFile::ImageUrl { image_url } => {
1790                            assert_eq!(image_url.as_str(), "https://s3-media3.fl.yelpcdn.com/bphoto/c7ed05m9lC2EmA3Aruue7A/o.jpg");
1791                        }
1792                        SlackImageUrlOrFile::SlackFile { slack_file } => {
1793                            panic!("Expected an image URL, not a Slack file: {:?}", slack_file);
1794                        }
1795                    }
1796                }
1797                _ => panic!("Expected a section block with an image accessory"),
1798            },
1799            _ => panic!("Expected a section block"),
1800        }
1801        Ok(())
1802    }
1803
1804    #[test]
1805    fn test_rich_text_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
1806        let payload = include_str!("./fixtures/slack_rich_text_block.json");
1807        let block: SlackBlock = serde_json::from_str(payload)?;
1808
1809        let rich = match block {
1810            SlackBlock::RichText(r) => r,
1811            _ => panic!("Expected a RichText block"),
1812        };
1813
1814        assert_eq!(rich.block_id, Some(SlackBlockId("test_block".into())));
1815        assert_eq!(rich.elements.len(), 4);
1816
1817        // section
1818        let section = match &rich.elements[0] {
1819            SlackRichTextElement::Section(s) => s,
1820            _ => panic!("Expected a Section element"),
1821        };
1822        assert_eq!(section.elements.len(), 7);
1823
1824        // bold text
1825        let text = match &section.elements[0] {
1826            SlackRichTextInlineElement::Text(t) => t,
1827            _ => panic!("Expected a Text element"),
1828        };
1829        assert_eq!(text.text, "Hello ");
1830        assert_eq!(text.style.as_ref().and_then(|s| s.bold), Some(true));
1831
1832        // user
1833        assert!(matches!(
1834            &section.elements[1],
1835            SlackRichTextInlineElement::User(_)
1836        ));
1837
1838        // emoji — name should deserialize as SlackEmojiName
1839        let emoji = match &section.elements[4] {
1840            SlackRichTextInlineElement::Emoji(e) => e,
1841            _ => panic!("Expected an Emoji element"),
1842        };
1843        assert_eq!(emoji.name, SlackEmojiName::new("wave".into()));
1844
1845        // list
1846        let list = match &rich.elements[1] {
1847            SlackRichTextElement::List(l) => l,
1848            _ => panic!("Expected a List element"),
1849        };
1850        assert_eq!(list.style, SlackRichTextListStyle::Bullet);
1851        assert_eq!(list.elements.len(), 2);
1852
1853        // list items are SlackRichTextElement::Section
1854        assert!(matches!(
1855            &list.elements[0],
1856            SlackRichTextListElement::Section(_)
1857        ));
1858        assert!(matches!(
1859            &list.elements[1],
1860            SlackRichTextListElement::Section(_)
1861        ));
1862
1863        // preformatted
1864        assert!(matches!(
1865            &rich.elements[2],
1866            SlackRichTextElement::Preformatted(_)
1867        ));
1868
1869        // quote
1870        assert!(matches!(&rich.elements[3], SlackRichTextElement::Quote(_)));
1871
1872        Ok(())
1873    }
1874
1875    #[test]
1876    fn test_rich_text_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
1877        let payload = include_str!("./fixtures/slack_rich_text_block.json");
1878        let block: SlackBlock = serde_json::from_str(payload)?;
1879        let serialized = serde_json::to_string(&block)?;
1880        let block2: SlackBlock = serde_json::from_str(&serialized)?;
1881        assert_eq!(block, block2);
1882        Ok(())
1883    }
1884
1885    #[test]
1886    fn test_slack_table_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
1887        let payload = include_str!("./fixtures/slack_table_block.json");
1888        let block: SlackBlock = serde_json::from_str(payload)?;
1889
1890        let table = match block {
1891            SlackBlock::Table(t) => t,
1892            _ => panic!("Expected a Table block"),
1893        };
1894
1895        assert_eq!(table.block_id, Some(SlackBlockId("table_block_1".into())));
1896        assert_eq!(table.rows.len(), 2);
1897        assert_eq!(table.rows[0].len(), 2);
1898
1899        // first row, first cell is raw_text
1900        match &table.rows[0][0] {
1901            SlackTableCell::RawText(c) => assert_eq!(c.text, "Header A"),
1902            _ => panic!("Expected RawText cell"),
1903        }
1904
1905        // second row, second cell is rich_text
1906        match &table.rows[1][1] {
1907            SlackTableCell::RichText(c) => assert_eq!(c.elements.len(), 1),
1908            _ => panic!("Expected RichText cell"),
1909        }
1910
1911        let settings = table
1912            .column_settings
1913            .expect("column_settings should be present");
1914        assert_eq!(settings.len(), 2);
1915        assert_eq!(settings[0].is_wrapped, Some(true));
1916        assert_eq!(settings[1].align, Some(SlackTableColumnAlign::Right));
1917
1918        Ok(())
1919    }
1920
1921    #[test]
1922    fn test_slack_table_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
1923        let payload = include_str!("./fixtures/slack_table_block.json");
1924        let block: SlackBlock = serde_json::from_str(payload)?;
1925        let serialized = serde_json::to_string(&block)?;
1926        let block2: SlackBlock = serde_json::from_str(&serialized)?;
1927        assert_eq!(block, block2);
1928        Ok(())
1929    }
1930
1931    #[test]
1932    fn test_slack_task_card_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
1933        let payload = include_str!("./fixtures/slack_task_card_block.json");
1934        let block: SlackBlock = serde_json::from_str(payload)?;
1935
1936        let task_card = match block {
1937            SlackBlock::TaskCard(t) => t,
1938            _ => panic!("Expected a TaskCard block"),
1939        };
1940
1941        assert_eq!(task_card.task_id, SlackTaskId("task_1".into()));
1942        assert_eq!(task_card.title, "Fetching weather data");
1943        assert_eq!(
1944            task_card.block_id,
1945            Some(SlackBlockId("task_card_block_1".into()))
1946        );
1947        assert_eq!(task_card.status, Some(SlackTaskCardStatus::InProgress));
1948
1949        let output = task_card.output.expect("output should be present");
1950        let output_block = match output {
1951            SlackRichTextInlineContent::RichText(b) => b,
1952        };
1953        assert_eq!(output_block.elements.len(), 1);
1954
1955        let sources = task_card.sources.expect("sources should be present");
1956        assert_eq!(sources.len(), 2);
1957        match &sources[0] {
1958            SlackTaskCardSource::Url(u) => assert_eq!(u.text, "weather.com"),
1959        }
1960
1961        Ok(())
1962    }
1963
1964    #[test]
1965    fn test_slack_task_card_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
1966        let payload = include_str!("./fixtures/slack_task_card_block.json");
1967        let block: SlackBlock = serde_json::from_str(payload)?;
1968        let serialized = serde_json::to_string(&block)?;
1969        let block2: SlackBlock = serde_json::from_str(&serialized)?;
1970        assert_eq!(block, block2);
1971        Ok(())
1972    }
1973
1974    #[test]
1975    fn test_slack_alert_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
1976        let payload = include_str!("./fixtures/slack_alert_block.json");
1977        let block: SlackBlock = serde_json::from_str(payload)?;
1978        match block {
1979            SlackBlock::Alert(alert) => {
1980                assert_eq!(alert.level, Some(SlackAlertLevel::Warning));
1981            }
1982            _ => panic!("Expected Alert block"),
1983        }
1984        Ok(())
1985    }
1986
1987    #[test]
1988    fn test_slack_alert_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
1989        let payload = include_str!("./fixtures/slack_alert_block.json");
1990        let block: SlackBlock = serde_json::from_str(payload)?;
1991        let serialized = serde_json::to_string(&block)?;
1992        let block2: SlackBlock = serde_json::from_str(&serialized)?;
1993        assert_eq!(block, block2);
1994        Ok(())
1995    }
1996
1997    #[test]
1998    fn test_slack_card_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
1999        let payload = include_str!("./fixtures/slack_card_block.json");
2000        let block: SlackBlock = serde_json::from_str(payload)?;
2001        match block {
2002            SlackBlock::Card(card) => {
2003                assert!(card.hero_image.is_some());
2004                assert!(card.actions.is_some());
2005                let actions = card.actions.unwrap();
2006                assert_eq!(actions.len(), 1);
2007                match &actions[0] {
2008                    SlackCardActionBlockElement::Button(btn) => {
2009                        assert_eq!(btn.style, Some(SlackBlockButtonStyle::Primary));
2010                    }
2011                }
2012            }
2013            _ => panic!("Expected Card block"),
2014        }
2015        Ok(())
2016    }
2017
2018    #[test]
2019    fn test_slack_card_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2020        let payload = include_str!("./fixtures/slack_card_block.json");
2021        let block: SlackBlock = serde_json::from_str(payload)?;
2022        let serialized = serde_json::to_string(&block)?;
2023        let block2: SlackBlock = serde_json::from_str(&serialized)?;
2024        assert_eq!(block, block2);
2025        Ok(())
2026    }
2027
2028    #[test]
2029    fn test_slack_context_actions_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2030        let payload = include_str!("./fixtures/slack_context_actions_block.json");
2031        let block: SlackBlock = serde_json::from_str(payload)?;
2032        match block {
2033            SlackBlock::ContextActions(ctx) => {
2034                assert_eq!(ctx.elements.len(), 1);
2035                match &ctx.elements[0] {
2036                    SlackContextActionBlockElement::IconButton(btn) => {
2037                        assert_eq!(btn.icon, "trash");
2038                    }
2039                    _ => panic!("Expected IconButton element"),
2040                }
2041            }
2042            _ => panic!("Expected ContextActions block"),
2043        }
2044        Ok(())
2045    }
2046
2047    #[test]
2048    fn test_slack_context_actions_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2049        let payload = include_str!("./fixtures/slack_context_actions_block.json");
2050        let block: SlackBlock = serde_json::from_str(payload)?;
2051        let serialized = serde_json::to_string(&block)?;
2052        let block2: SlackBlock = serde_json::from_str(&serialized)?;
2053        assert_eq!(block, block2);
2054        Ok(())
2055    }
2056
2057    #[test]
2058    fn test_slack_workflow_button_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2059        let payload = include_str!("./fixtures/slack_workflow_button.json");
2060        let block: SlackBlock = serde_json::from_str(payload)?;
2061        match block {
2062            SlackBlock::Actions(actions) => {
2063                assert_eq!(actions.elements.len(), 1);
2064                match &actions.elements[0] {
2065                    SlackActionBlockElement::WorkflowButton(btn) => {
2066                        assert_eq!(btn.style, Some(SlackBlockButtonStyle::Primary));
2067                        let params = btn
2068                            .workflow
2069                            .trigger
2070                            .customizable_input_parameters
2071                            .as_ref()
2072                            .expect("params should be present");
2073                        assert_eq!(params.len(), 1);
2074                        assert_eq!(params[0].name, "user_input");
2075                    }
2076                    _ => panic!("Expected WorkflowButton element"),
2077                }
2078            }
2079            _ => panic!("Expected Actions block"),
2080        }
2081        Ok(())
2082    }
2083
2084    #[test]
2085    fn test_slack_workflow_button_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2086        let payload = include_str!("./fixtures/slack_workflow_button.json");
2087        let block: SlackBlock = serde_json::from_str(payload)?;
2088        let serialized = serde_json::to_string(&block)?;
2089        let block2: SlackBlock = serde_json::from_str(&serialized)?;
2090        assert_eq!(block, block2);
2091        Ok(())
2092    }
2093}