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    #[serde(rename = "message_mention")]
1263    MessageMention(SlackRichTextMessageMention),
1264    #[serde(untagged)]
1265    Unknown(serde_json::Value),
1266}
1267
1268#[skip_serializing_none]
1269#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1270pub struct SlackRichTextStyle {
1271    pub bold: Option<bool>,
1272    pub italic: Option<bool>,
1273    pub strike: Option<bool>,
1274    pub code: Option<bool>,
1275    pub underline: Option<bool>,
1276    pub highlight: Option<bool>,
1277    pub client_highlight: Option<bool>,
1278    pub unlink: Option<bool>,
1279}
1280
1281#[skip_serializing_none]
1282#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1283pub struct SlackRichTextText {
1284    pub text: String,
1285    pub style: Option<SlackRichTextStyle>,
1286}
1287
1288#[skip_serializing_none]
1289#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1290pub struct SlackRichTextLink {
1291    pub url: SlackRelaxedUrl,
1292    pub text: Option<String>,
1293    #[serde(rename = "unsafe")]
1294    pub unsafe_: Option<bool>,
1295    pub style: Option<SlackRichTextStyle>,
1296}
1297
1298#[skip_serializing_none]
1299#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1300pub struct SlackRichTextUser {
1301    pub user_id: SlackUserId,
1302    pub style: Option<SlackRichTextStyle>,
1303}
1304
1305#[skip_serializing_none]
1306#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1307pub struct SlackRichTextChannel {
1308    pub channel_id: SlackChannelId,
1309    pub style: Option<SlackRichTextStyle>,
1310}
1311
1312#[skip_serializing_none]
1313#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1314pub struct SlackRichTextUserGroup {
1315    pub usergroup_id: SlackUserGroupId,
1316    pub style: Option<SlackRichTextStyle>,
1317}
1318
1319#[skip_serializing_none]
1320#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1321pub struct SlackRichTextEmoji {
1322    pub name: SlackEmojiName,
1323    pub unicode: Option<String>,
1324}
1325
1326#[skip_serializing_none]
1327#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1328pub struct SlackRichTextDate {
1329    pub timestamp: SlackDateTime,
1330    pub format: String,
1331    pub fallback: Option<String>,
1332    pub style: Option<SlackRichTextStyle>,
1333}
1334
1335#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1336#[serde(rename_all = "snake_case")]
1337pub enum SlackRichTextBroadcastRange {
1338    Here,
1339    Channel,
1340    Everyone,
1341}
1342
1343#[skip_serializing_none]
1344#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1345pub struct SlackRichTextBroadcast {
1346    pub range: SlackRichTextBroadcastRange,
1347    pub style: Option<SlackRichTextStyle>,
1348}
1349
1350#[skip_serializing_none]
1351#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1352pub struct SlackRichTextColor {
1353    pub value: String,
1354}
1355
1356#[skip_serializing_none]
1357#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1358pub struct SlackRichTextMessageMention {
1359    pub url: SlackRelaxedUrl,
1360    pub text: Option<String>,
1361    pub channel_id: Option<SlackChannelId>,
1362    pub author_id: Option<SlackUserId>,
1363    pub message_ts: Option<SlackTs>,
1364    pub thread_ts: Option<SlackTs>,
1365    pub style: Option<SlackRichTextStyle>,
1366}
1367
1368/**
1369 * https://api.slack.com/reference/block-kit/block-elements#rich_text_input
1370 */
1371#[skip_serializing_none]
1372#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1373pub struct SlackBlockRichTextInputElement {
1374    pub action_id: SlackActionId,
1375    pub initial_value: Option<SlackRichTextBlock>,
1376    pub focus_on_load: Option<bool>,
1377    pub placeholder: Option<SlackBlockPlainTextOnly>,
1378}
1379
1380impl From<SlackBlockRichTextInputElement> for SlackInputBlockElement {
1381    fn from(element: SlackBlockRichTextInputElement) -> Self {
1382        SlackInputBlockElement::RichTextInput(element)
1383    }
1384}
1385
1386#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1387#[serde(untagged)]
1388pub enum SlackImageUrlOrFile {
1389    ImageUrl { image_url: Url },
1390    SlackFile { slack_file: SlackFileIdOrUrl },
1391}
1392
1393impl SlackImageUrlOrFile {
1394    pub fn image_url(&self) -> Option<&Url> {
1395        match self {
1396            SlackImageUrlOrFile::ImageUrl { image_url } => Some(image_url),
1397            SlackImageUrlOrFile::SlackFile { slack_file } => match slack_file {
1398                SlackFileIdOrUrl::Url { url } => Some(url),
1399                _ => None,
1400            },
1401        }
1402    }
1403}
1404
1405impl From<Url> for SlackImageUrlOrFile {
1406    fn from(value: Url) -> Self {
1407        SlackImageUrlOrFile::ImageUrl { image_url: value }
1408    }
1409}
1410
1411#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1412#[serde(untagged)]
1413pub enum SlackFileIdOrUrl {
1414    Id { id: SlackFileId },
1415    Url { url: Url },
1416}
1417
1418impl From<SlackFileId> for SlackFileIdOrUrl {
1419    fn from(value: SlackFileId) -> Self {
1420        SlackFileIdOrUrl::Id { id: value }
1421    }
1422}
1423
1424/**
1425 * https://docs.slack.dev/reference/block-kit/blocks/table-block
1426 */
1427#[skip_serializing_none]
1428#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1429pub struct SlackTableBlock {
1430    pub block_id: Option<SlackBlockId>,
1431    pub rows: Vec<Vec<SlackTableCell>>,
1432    pub column_settings: Option<Vec<SlackTableColumnSetting>>,
1433}
1434
1435impl From<SlackTableBlock> for SlackBlock {
1436    fn from(block: SlackTableBlock) -> Self {
1437        SlackBlock::Table(block)
1438    }
1439}
1440
1441#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1442#[serde(tag = "type")]
1443pub enum SlackTableCell {
1444    #[serde(rename = "raw_text")]
1445    RawText(SlackTableRawTextCell),
1446    #[serde(rename = "rich_text")]
1447    RichText(SlackTableRichTextCell),
1448}
1449
1450#[skip_serializing_none]
1451#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1452pub struct SlackTableRawTextCell {
1453    pub text: String,
1454}
1455
1456#[skip_serializing_none]
1457#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1458pub struct SlackTableRichTextCell {
1459    pub elements: Vec<SlackRichTextElement>,
1460}
1461
1462#[skip_serializing_none]
1463#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1464pub struct SlackTableColumnSetting {
1465    pub align: Option<SlackTableColumnAlign>,
1466    pub is_wrapped: Option<bool>,
1467}
1468
1469#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1470#[serde(rename_all = "snake_case")]
1471pub enum SlackTableColumnAlign {
1472    Left,
1473    Center,
1474    Right,
1475}
1476
1477/**
1478 * https://docs.slack.dev/reference/block-kit/blocks/task-card-block
1479 */
1480#[skip_serializing_none]
1481#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1482pub struct SlackTaskCardBlock {
1483    pub task_id: SlackTaskId,
1484    pub title: String,
1485    pub block_id: Option<SlackBlockId>,
1486    pub status: Option<SlackTaskCardStatus>,
1487    #[serde(rename = "details")]
1488    pub details: Option<SlackRichTextInlineContent>,
1489    #[serde(rename = "output")]
1490    pub output: Option<SlackRichTextInlineContent>,
1491    pub sources: Option<Vec<SlackTaskCardSource>>,
1492}
1493
1494impl From<SlackTaskCardBlock> for SlackBlock {
1495    fn from(block: SlackTaskCardBlock) -> Self {
1496        SlackBlock::TaskCard(block)
1497    }
1498}
1499
1500#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1501#[serde(rename_all = "snake_case")]
1502pub enum SlackTaskCardStatus {
1503    Pending,
1504    InProgress,
1505    Complete,
1506    Error,
1507}
1508
1509/**
1510 * https://docs.slack.dev/reference/block-kit/block-elements/url-source-element
1511 */
1512#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1513pub struct SlackUrlSourceElement {
1514    pub url: Url,
1515    pub text: String,
1516}
1517
1518#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1519#[serde(tag = "type")]
1520pub enum SlackTaskCardSource {
1521    #[serde(rename = "url")]
1522    Url(SlackUrlSourceElement),
1523}
1524
1525impl From<SlackUrlSourceElement> for SlackTaskCardSource {
1526    fn from(element: SlackUrlSourceElement) -> Self {
1527        SlackTaskCardSource::Url(element)
1528    }
1529}
1530
1531/**
1532 * https://docs.slack.dev/reference/block-kit/block-elements/file-input-element
1533 */
1534#[skip_serializing_none]
1535#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1536pub struct SlackBlockFileInputElement {
1537    pub action_id: SlackActionId,
1538    pub filetypes: Option<Vec<String>>,
1539    pub max_files: Option<u64>,
1540}
1541
1542impl From<SlackBlockFileInputElement> for SlackInputBlockElement {
1543    fn from(element: SlackBlockFileInputElement) -> Self {
1544        SlackInputBlockElement::FileInput(element)
1545    }
1546}
1547
1548/**
1549 * https://docs.slack.dev/reference/block-kit/blocks/alert-block
1550 */
1551#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1552#[serde(rename_all = "snake_case")]
1553pub enum SlackAlertLevel {
1554    Warning,
1555    Error,
1556    Info,
1557    Success,
1558}
1559
1560#[skip_serializing_none]
1561#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1562pub struct SlackAlertBlock {
1563    pub block_id: Option<SlackBlockId>,
1564    pub text: SlackBlockText,
1565    pub level: Option<SlackAlertLevel>,
1566}
1567
1568impl From<SlackAlertBlock> for SlackBlock {
1569    fn from(block: SlackAlertBlock) -> Self {
1570        SlackBlock::Alert(block)
1571    }
1572}
1573
1574/**
1575 * https://docs.slack.dev/reference/block-kit/blocks/card-block
1576 */
1577#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1578#[serde(tag = "type")]
1579pub enum SlackCardImageElement {
1580    #[serde(rename = "image")]
1581    Image(SlackBlockImageElement),
1582}
1583
1584impl From<SlackBlockImageElement> for SlackCardImageElement {
1585    fn from(element: SlackBlockImageElement) -> Self {
1586        SlackCardImageElement::Image(element)
1587    }
1588}
1589
1590#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1591#[serde(tag = "type")]
1592pub enum SlackCardActionBlockElement {
1593    #[serde(rename = "button")]
1594    Button(SlackBlockButtonElement),
1595}
1596
1597impl From<SlackBlockButtonElement> for SlackCardActionBlockElement {
1598    fn from(element: SlackBlockButtonElement) -> Self {
1599        SlackCardActionBlockElement::Button(element)
1600    }
1601}
1602
1603#[skip_serializing_none]
1604#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1605pub struct SlackCardBlock {
1606    pub block_id: Option<SlackBlockId>,
1607    pub title: Option<SlackBlockText>,
1608    pub subtitle: Option<SlackBlockText>,
1609    pub body: Option<SlackBlockText>,
1610    pub hero_image: Option<SlackCardImageElement>,
1611    pub icon: Option<SlackCardImageElement>,
1612    pub actions: Option<Vec<SlackCardActionBlockElement>>,
1613}
1614
1615impl From<SlackCardBlock> for SlackBlock {
1616    fn from(block: SlackCardBlock) -> Self {
1617        SlackBlock::Card(block)
1618    }
1619}
1620
1621/**
1622 * https://docs.slack.dev/reference/block-kit/blocks/carousel-block
1623 */
1624#[skip_serializing_none]
1625#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1626pub struct SlackCarouselBlock {
1627    pub block_id: Option<SlackBlockId>,
1628    pub elements: Vec<SlackBlock>,
1629}
1630
1631impl From<SlackCarouselBlock> for SlackBlock {
1632    fn from(block: SlackCarouselBlock) -> Self {
1633        SlackBlock::Carousel(block)
1634    }
1635}
1636
1637/**
1638 * https://docs.slack.dev/reference/block-kit/blocks/context-actions-block
1639 * https://docs.slack.dev/reference/block-kit/block-elements/feedback-buttons-element
1640 * https://docs.slack.dev/reference/block-kit/block-elements/icon-button-element
1641 */
1642#[skip_serializing_none]
1643#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1644pub struct SlackFeedbackButtonItem {
1645    pub action_id: SlackActionId,
1646    pub value: String,
1647    pub text: SlackBlockPlainTextOnly,
1648    pub confirm: Option<SlackBlockConfirmItem>,
1649}
1650
1651#[skip_serializing_none]
1652#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1653pub struct SlackBlockFeedbackButtonsElement {
1654    pub action_id: SlackActionId,
1655    pub positive: SlackFeedbackButtonItem,
1656    pub negative: SlackFeedbackButtonItem,
1657}
1658
1659#[skip_serializing_none]
1660#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1661pub struct SlackBlockIconButtonElement {
1662    pub action_id: SlackActionId,
1663    pub icon: String,
1664    pub text: SlackBlockPlainTextOnly,
1665    pub value: Option<String>,
1666    pub confirm: Option<SlackBlockConfirmItem>,
1667    pub accessibility_label: Option<SlackAccessibilityLabel>,
1668    pub visible_to_user_ids: Option<Vec<SlackUserId>>,
1669}
1670
1671#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1672#[serde(tag = "type")]
1673pub enum SlackContextActionBlockElement {
1674    #[serde(rename = "feedback_buttons")]
1675    FeedbackButtons(SlackBlockFeedbackButtonsElement),
1676    #[serde(rename = "icon_button")]
1677    IconButton(SlackBlockIconButtonElement),
1678}
1679
1680impl From<SlackBlockFeedbackButtonsElement> for SlackContextActionBlockElement {
1681    fn from(element: SlackBlockFeedbackButtonsElement) -> Self {
1682        SlackContextActionBlockElement::FeedbackButtons(element)
1683    }
1684}
1685
1686impl From<SlackBlockIconButtonElement> for SlackContextActionBlockElement {
1687    fn from(element: SlackBlockIconButtonElement) -> Self {
1688        SlackContextActionBlockElement::IconButton(element)
1689    }
1690}
1691
1692#[skip_serializing_none]
1693#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1694pub struct SlackContextActionsBlock {
1695    pub block_id: Option<SlackBlockId>,
1696    pub elements: Vec<SlackContextActionBlockElement>,
1697}
1698
1699impl From<SlackContextActionsBlock> for SlackBlock {
1700    fn from(block: SlackContextActionsBlock) -> Self {
1701        SlackBlock::ContextActions(block)
1702    }
1703}
1704
1705#[cfg(test)]
1706mod test {
1707    use super::*;
1708    use crate::blocks::SlackHomeView;
1709
1710    #[test]
1711    fn test_conversation_filter_deserialize() -> Result<(), Box<dyn std::error::Error>> {
1712        let payload = include_str!("./fixtures/slack_conversations_select_with_filter.json");
1713        let block: SlackBlock = serde_json::from_str(payload)?;
1714        match block {
1715            SlackBlock::Section(section) => match section.accessory {
1716                Some(SlackSectionBlockElement::ConversationsSelect(elem)) => {
1717                    let filter = elem.filter.expect("filter should be present");
1718                    let include = filter.include.expect("include should be present");
1719                    assert_eq!(include.len(), 2);
1720                    assert_eq!(include[0], SlackConversationFilterInclude::Public);
1721                    assert_eq!(include[1], SlackConversationFilterInclude::Private);
1722                    assert_eq!(filter.exclude_external_shared_channels, Some(true));
1723                    assert_eq!(filter.exclude_bot_users, Some(true));
1724                }
1725                _ => panic!("Expected ConversationsSelect accessory"),
1726            },
1727            _ => panic!("Expected Section block"),
1728        }
1729        Ok(())
1730    }
1731
1732    #[test]
1733    fn test_conversation_filter_serialize() -> Result<(), Box<dyn std::error::Error>> {
1734        let filter = SlackBlockConversationFilter::new()
1735            .with_include(vec![
1736                SlackConversationFilterInclude::Im,
1737                SlackConversationFilterInclude::Mpim,
1738            ])
1739            .with_exclude_bot_users(true);
1740
1741        let json = serde_json::to_value(&filter)?;
1742        assert_eq!(
1743            json,
1744            serde_json::json!({
1745                "include": ["im", "mpim"],
1746                "exclude_bot_users": true
1747            })
1748        );
1749        Ok(())
1750    }
1751
1752    #[test]
1753    fn test_conversation_filter_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
1754        let elem = SlackBlockConversationsSelectElement::new(SlackActionId("test_action".into()))
1755            .with_filter(
1756                SlackBlockConversationFilter::new()
1757                    .with_include(vec![SlackConversationFilterInclude::Public])
1758                    .with_exclude_external_shared_channels(true),
1759            );
1760
1761        let json = serde_json::to_string(&elem)?;
1762        let parsed: SlackBlockConversationsSelectElement = serde_json::from_str(&json)?;
1763        assert_eq!(elem, parsed);
1764        Ok(())
1765    }
1766
1767    #[test]
1768    fn test_multi_conversations_select_filter() -> Result<(), Box<dyn std::error::Error>> {
1769        let elem =
1770            SlackBlockMultiConversationsSelectElement::new(SlackActionId("multi_action".into()))
1771                .with_filter(
1772                    SlackBlockConversationFilter::new()
1773                        .with_include(vec![
1774                            SlackConversationFilterInclude::Public,
1775                            SlackConversationFilterInclude::Private,
1776                        ])
1777                        .with_exclude_bot_users(true),
1778                );
1779
1780        let json = serde_json::to_string(&elem)?;
1781        let parsed: SlackBlockMultiConversationsSelectElement = serde_json::from_str(&json)?;
1782        assert_eq!(elem, parsed);
1783        Ok(())
1784    }
1785
1786    #[test]
1787    fn test_conversation_filter_none_omitted() -> Result<(), Box<dyn std::error::Error>> {
1788        let elem = SlackBlockConversationsSelectElement::new(SlackActionId("no_filter".into()));
1789
1790        let json = serde_json::to_value(&elem)?;
1791        assert!(json.get("filter").is_none());
1792        Ok(())
1793    }
1794
1795    #[test]
1796    fn test_slack_image_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
1797        let payload = include_str!("./fixtures/slack_image_blocks.json");
1798        let content: SlackMessageContent = serde_json::from_str(payload)?;
1799        let blocks = content.blocks.expect("Blocks should not be empty");
1800        match blocks.first() {
1801            Some(SlackBlock::Section(section)) => match &section.accessory {
1802                Some(SlackSectionBlockElement::Image(image)) => {
1803                    assert_eq!(image.alt_text, "alt text for image");
1804                    match &image.image_url_or_file {
1805                        SlackImageUrlOrFile::ImageUrl { image_url } => {
1806                            assert_eq!(image_url.as_str(), "https://s3-media3.fl.yelpcdn.com/bphoto/c7ed05m9lC2EmA3Aruue7A/o.jpg");
1807                        }
1808                        SlackImageUrlOrFile::SlackFile { slack_file } => {
1809                            panic!("Expected an image URL, not a Slack file: {:?}", slack_file);
1810                        }
1811                    }
1812                }
1813                _ => panic!("Expected a section block with an image accessory"),
1814            },
1815            _ => panic!("Expected a section block"),
1816        }
1817        Ok(())
1818    }
1819
1820    #[test]
1821    fn test_rich_text_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
1822        let payload = include_str!("./fixtures/slack_rich_text_block.json");
1823        let block: SlackBlock = serde_json::from_str(payload)?;
1824
1825        let rich = match block {
1826            SlackBlock::RichText(r) => r,
1827            _ => panic!("Expected a RichText block"),
1828        };
1829
1830        assert_eq!(rich.block_id, Some(SlackBlockId("test_block".into())));
1831        assert_eq!(rich.elements.len(), 4);
1832
1833        // section
1834        let section = match &rich.elements[0] {
1835            SlackRichTextElement::Section(s) => s,
1836            _ => panic!("Expected a Section element"),
1837        };
1838        assert_eq!(section.elements.len(), 7);
1839
1840        // bold text
1841        let text = match &section.elements[0] {
1842            SlackRichTextInlineElement::Text(t) => t,
1843            _ => panic!("Expected a Text element"),
1844        };
1845        assert_eq!(text.text, "Hello ");
1846        assert_eq!(text.style.as_ref().and_then(|s| s.bold), Some(true));
1847
1848        // user
1849        assert!(matches!(
1850            &section.elements[1],
1851            SlackRichTextInlineElement::User(_)
1852        ));
1853
1854        // emoji — name should deserialize as SlackEmojiName
1855        let emoji = match &section.elements[4] {
1856            SlackRichTextInlineElement::Emoji(e) => e,
1857            _ => panic!("Expected an Emoji element"),
1858        };
1859        assert_eq!(emoji.name, SlackEmojiName::new("wave".into()));
1860
1861        // list
1862        let list = match &rich.elements[1] {
1863            SlackRichTextElement::List(l) => l,
1864            _ => panic!("Expected a List element"),
1865        };
1866        assert_eq!(list.style, SlackRichTextListStyle::Bullet);
1867        assert_eq!(list.elements.len(), 2);
1868
1869        // list items are SlackRichTextElement::Section
1870        assert!(matches!(
1871            &list.elements[0],
1872            SlackRichTextListElement::Section(_)
1873        ));
1874        assert!(matches!(
1875            &list.elements[1],
1876            SlackRichTextListElement::Section(_)
1877        ));
1878
1879        // preformatted
1880        assert!(matches!(
1881            &rich.elements[2],
1882            SlackRichTextElement::Preformatted(_)
1883        ));
1884
1885        // quote
1886        assert!(matches!(&rich.elements[3], SlackRichTextElement::Quote(_)));
1887
1888        Ok(())
1889    }
1890
1891    #[test]
1892    fn test_rich_text_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
1893        let payload = include_str!("./fixtures/slack_rich_text_block.json");
1894        let block: SlackBlock = serde_json::from_str(payload)?;
1895        let serialized = serde_json::to_string(&block)?;
1896        let block2: SlackBlock = serde_json::from_str(&serialized)?;
1897        assert_eq!(block, block2);
1898        Ok(())
1899    }
1900
1901    #[test]
1902    fn test_slack_table_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
1903        let payload = include_str!("./fixtures/slack_table_block.json");
1904        let block: SlackBlock = serde_json::from_str(payload)?;
1905
1906        let table = match block {
1907            SlackBlock::Table(t) => t,
1908            _ => panic!("Expected a Table block"),
1909        };
1910
1911        assert_eq!(table.block_id, Some(SlackBlockId("table_block_1".into())));
1912        assert_eq!(table.rows.len(), 2);
1913        assert_eq!(table.rows[0].len(), 2);
1914
1915        // first row, first cell is raw_text
1916        match &table.rows[0][0] {
1917            SlackTableCell::RawText(c) => assert_eq!(c.text, "Header A"),
1918            _ => panic!("Expected RawText cell"),
1919        }
1920
1921        // second row, second cell is rich_text
1922        match &table.rows[1][1] {
1923            SlackTableCell::RichText(c) => assert_eq!(c.elements.len(), 1),
1924            _ => panic!("Expected RichText cell"),
1925        }
1926
1927        let settings = table
1928            .column_settings
1929            .expect("column_settings should be present");
1930        assert_eq!(settings.len(), 2);
1931        assert_eq!(settings[0].is_wrapped, Some(true));
1932        assert_eq!(settings[1].align, Some(SlackTableColumnAlign::Right));
1933
1934        Ok(())
1935    }
1936
1937    #[test]
1938    fn test_slack_table_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
1939        let payload = include_str!("./fixtures/slack_table_block.json");
1940        let block: SlackBlock = serde_json::from_str(payload)?;
1941        let serialized = serde_json::to_string(&block)?;
1942        let block2: SlackBlock = serde_json::from_str(&serialized)?;
1943        assert_eq!(block, block2);
1944        Ok(())
1945    }
1946
1947    #[test]
1948    fn test_slack_task_card_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
1949        let payload = include_str!("./fixtures/slack_task_card_block.json");
1950        let block: SlackBlock = serde_json::from_str(payload)?;
1951
1952        let task_card = match block {
1953            SlackBlock::TaskCard(t) => t,
1954            _ => panic!("Expected a TaskCard block"),
1955        };
1956
1957        assert_eq!(task_card.task_id, SlackTaskId("task_1".into()));
1958        assert_eq!(task_card.title, "Fetching weather data");
1959        assert_eq!(
1960            task_card.block_id,
1961            Some(SlackBlockId("task_card_block_1".into()))
1962        );
1963        assert_eq!(task_card.status, Some(SlackTaskCardStatus::InProgress));
1964
1965        let output = task_card.output.expect("output should be present");
1966        let output_block = match output {
1967            SlackRichTextInlineContent::RichText(b) => b,
1968        };
1969        assert_eq!(output_block.elements.len(), 1);
1970
1971        let sources = task_card.sources.expect("sources should be present");
1972        assert_eq!(sources.len(), 2);
1973        match &sources[0] {
1974            SlackTaskCardSource::Url(u) => assert_eq!(u.text, "weather.com"),
1975        }
1976
1977        Ok(())
1978    }
1979
1980    #[test]
1981    fn test_slack_task_card_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
1982        let payload = include_str!("./fixtures/slack_task_card_block.json");
1983        let block: SlackBlock = serde_json::from_str(payload)?;
1984        let serialized = serde_json::to_string(&block)?;
1985        let block2: SlackBlock = serde_json::from_str(&serialized)?;
1986        assert_eq!(block, block2);
1987        Ok(())
1988    }
1989
1990    #[test]
1991    fn test_slack_alert_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
1992        let payload = include_str!("./fixtures/slack_alert_block.json");
1993        let block: SlackBlock = serde_json::from_str(payload)?;
1994        match block {
1995            SlackBlock::Alert(alert) => {
1996                assert_eq!(alert.level, Some(SlackAlertLevel::Warning));
1997            }
1998            _ => panic!("Expected Alert block"),
1999        }
2000        Ok(())
2001    }
2002
2003    #[test]
2004    fn test_slack_alert_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2005        let payload = include_str!("./fixtures/slack_alert_block.json");
2006        let block: SlackBlock = serde_json::from_str(payload)?;
2007        let serialized = serde_json::to_string(&block)?;
2008        let block2: SlackBlock = serde_json::from_str(&serialized)?;
2009        assert_eq!(block, block2);
2010        Ok(())
2011    }
2012
2013    #[test]
2014    fn test_slack_card_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2015        let payload = include_str!("./fixtures/slack_card_block.json");
2016        let block: SlackBlock = serde_json::from_str(payload)?;
2017        match block {
2018            SlackBlock::Card(card) => {
2019                assert!(card.hero_image.is_some());
2020                assert!(card.actions.is_some());
2021                let actions = card.actions.unwrap();
2022                assert_eq!(actions.len(), 1);
2023                match &actions[0] {
2024                    SlackCardActionBlockElement::Button(btn) => {
2025                        assert_eq!(btn.style, Some(SlackBlockButtonStyle::Primary));
2026                    }
2027                }
2028            }
2029            _ => panic!("Expected Card block"),
2030        }
2031        Ok(())
2032    }
2033
2034    #[test]
2035    fn test_slack_card_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2036        let payload = include_str!("./fixtures/slack_card_block.json");
2037        let block: SlackBlock = serde_json::from_str(payload)?;
2038        let serialized = serde_json::to_string(&block)?;
2039        let block2: SlackBlock = serde_json::from_str(&serialized)?;
2040        assert_eq!(block, block2);
2041        Ok(())
2042    }
2043
2044    #[test]
2045    fn test_slack_context_actions_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2046        let payload = include_str!("./fixtures/slack_context_actions_block.json");
2047        let block: SlackBlock = serde_json::from_str(payload)?;
2048        match block {
2049            SlackBlock::ContextActions(ctx) => {
2050                assert_eq!(ctx.elements.len(), 1);
2051                match &ctx.elements[0] {
2052                    SlackContextActionBlockElement::IconButton(btn) => {
2053                        assert_eq!(btn.icon, "trash");
2054                    }
2055                    _ => panic!("Expected IconButton element"),
2056                }
2057            }
2058            _ => panic!("Expected ContextActions block"),
2059        }
2060        Ok(())
2061    }
2062
2063    #[test]
2064    fn test_slack_context_actions_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2065        let payload = include_str!("./fixtures/slack_context_actions_block.json");
2066        let block: SlackBlock = serde_json::from_str(payload)?;
2067        let serialized = serde_json::to_string(&block)?;
2068        let block2: SlackBlock = serde_json::from_str(&serialized)?;
2069        assert_eq!(block, block2);
2070        Ok(())
2071    }
2072
2073    #[test]
2074    fn test_slack_workflow_button_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2075        let payload = include_str!("./fixtures/slack_workflow_button.json");
2076        let block: SlackBlock = serde_json::from_str(payload)?;
2077        match block {
2078            SlackBlock::Actions(actions) => {
2079                assert_eq!(actions.elements.len(), 1);
2080                match &actions.elements[0] {
2081                    SlackActionBlockElement::WorkflowButton(btn) => {
2082                        assert_eq!(btn.style, Some(SlackBlockButtonStyle::Primary));
2083                        let params = btn
2084                            .workflow
2085                            .trigger
2086                            .customizable_input_parameters
2087                            .as_ref()
2088                            .expect("params should be present");
2089                        assert_eq!(params.len(), 1);
2090                        assert_eq!(params[0].name, "user_input");
2091                    }
2092                    _ => panic!("Expected WorkflowButton element"),
2093                }
2094            }
2095            _ => panic!("Expected Actions block"),
2096        }
2097        Ok(())
2098    }
2099
2100    #[test]
2101    fn test_slack_workflow_button_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2102        let payload = include_str!("./fixtures/slack_workflow_button.json");
2103        let block: SlackBlock = serde_json::from_str(payload)?;
2104        let serialized = serde_json::to_string(&block)?;
2105        let block2: SlackBlock = serde_json::from_str(&serialized)?;
2106        assert_eq!(block, block2);
2107        Ok(())
2108    }
2109
2110    #[test]
2111    fn test_rich_text_message_mention_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2112        let payload = serde_json::json!({
2113            "type": "rich_text",
2114            "block_id": "msgm1",
2115            "elements": [
2116                {
2117                    "type": "rich_text_section",
2118                    "elements": [
2119                        {
2120                            "type": "message_mention",
2121                            "url": "https://acme.slack.com/archives/C12345678/p1784153496441789?thread_ts=1784153496.441789&cid=C12345678",
2122                            "text": "a message",
2123                            "channel_id": "C12345678",
2124                            "author_id": "U12345678",
2125                            "message_ts": "1784153496.441789",
2126                            "thread_ts": "1784153496.441789"
2127                        }
2128                    ]
2129                }
2130            ]
2131        })
2132        .to_string();
2133        let block: SlackBlock = serde_json::from_str(&payload)?;
2134        match block {
2135            SlackBlock::RichText(rich_text) => match &rich_text.elements[0] {
2136                SlackRichTextElement::Section(section) => match &section.elements[0] {
2137                    SlackRichTextInlineElement::MessageMention(mention) => {
2138                        assert_eq!(mention.channel_id, Some(SlackChannelId("C12345678".into())));
2139                        assert_eq!(mention.author_id, Some(SlackUserId("U12345678".into())));
2140                        assert_eq!(
2141                            mention.message_ts,
2142                            Some(SlackTs("1784153496.441789".into()))
2143                        );
2144                    }
2145                    other => panic!("Expected MessageMention element, got {other:?}"),
2146                },
2147                _ => panic!("Expected Section element"),
2148            },
2149            _ => panic!("Expected RichText block"),
2150        }
2151        Ok(())
2152    }
2153
2154    #[test]
2155    fn test_rich_text_unknown_inline_element_deserialize() -> Result<(), Box<dyn std::error::Error>>
2156    {
2157        let payload = serde_json::json!({
2158            "type": "rich_text_section",
2159            "elements": [
2160                {
2161                    "type": "some_future_element",
2162                    "foo": "bar"
2163                }
2164            ]
2165        })
2166        .to_string();
2167        let section: SlackRichTextElement = serde_json::from_str(&payload)?;
2168        match section {
2169            SlackRichTextElement::Section(section) => match &section.elements[0] {
2170                SlackRichTextInlineElement::Unknown(value) => {
2171                    assert_eq!(value["type"], "some_future_element");
2172                }
2173                other => panic!("Expected Unknown element, got {other:?}"),
2174            },
2175            _ => panic!("Expected Section element"),
2176        }
2177        Ok(())
2178    }
2179}