Skip to main content

slack_web_api/blocks/
block.rs

1use serde::{Deserialize, Serialize};
2
3use super::chart::DataVisualizationBlock;
4use super::composition::{SlackFile, SlackIcon, TextObject};
5use super::element::{
6    ActionsElement, ButtonElement, ContextActionsElement, ContextElement, ImageElement,
7    InputElement, SectionAccessory, UrlSourceElement,
8};
9use super::rich_text::RichTextBlock;
10use super::table::{DataTableBlock, TableBlock};
11use super::tag::{tagged_enum, Tag, Tagged};
12
13tagged_enum! {
14    /// A layout block. <https://docs.slack.dev/reference/block-kit/blocks>
15    pub enum Block("type") {
16        Actions(ActionsBlock),
17        Alert(AlertBlock),
18        Card(CardBlock),
19        Carousel(CarouselBlock),
20        Container(ContainerBlock),
21        Context(ContextBlock),
22        ContextActions(ContextActionsBlock),
23        DataTable(DataTableBlock),
24        DataVisualization(DataVisualizationBlock),
25        Divider(DividerBlock),
26        File(FileBlock),
27        Header(HeaderBlock),
28        Image(ImageBlock),
29        Input(InputBlock),
30        Markdown(MarkdownBlock),
31        Plan(PlanBlock),
32        RichText(RichTextBlock),
33        Section(SectionBlock),
34        Table(TableBlock),
35        TaskCard(TaskCardBlock),
36        Video(VideoBlock),
37    }
38}
39
40/// A block that holds interactive elements. <https://docs.slack.dev/reference/block-kit/blocks/actions-block>
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub struct ActionsBlock {
43    r#type: Tag<Self>,
44    pub elements: Vec<ActionsElement>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub block_id: Option<String>,
47}
48
49impl Tagged for ActionsBlock {
50    const TAG: &'static str = "actions";
51}
52
53impl ActionsBlock {
54    pub fn new(elements: Vec<ActionsElement>) -> Self {
55        Self {
56            r#type: Tag::default(),
57            elements,
58            block_id: None,
59        }
60    }
61
62    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
63        self.block_id = Some(block_id.into());
64        self
65    }
66}
67
68/// A block that shows an alert, warning or informational message (modals only). <https://docs.slack.dev/reference/block-kit/blocks/alert-block>
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70pub struct AlertBlock {
71    r#type: Tag<Self>,
72    pub text: TextObject,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub level: Option<AlertLevel>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub block_id: Option<String>,
77}
78
79impl Tagged for AlertBlock {
80    const TAG: &'static str = "alert";
81}
82
83impl AlertBlock {
84    pub fn new(text: impl Into<TextObject>) -> Self {
85        Self {
86            r#type: Tag::default(),
87            text: text.into(),
88            level: None,
89            block_id: None,
90        }
91    }
92
93    pub fn level(mut self, level: AlertLevel) -> Self {
94        self.level = Some(level);
95        self
96    }
97
98    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
99        self.block_id = Some(block_id.into());
100        self
101    }
102}
103
104/// A card. <https://docs.slack.dev/reference/block-kit/blocks/card-block>
105#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
106pub struct CardBlock {
107    r#type: Tag<Self>,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub block_id: Option<String>,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub hero_image: Option<ImageElement>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub icon: Option<ImageElement>,
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub title: Option<TextObject>,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub subtitle: Option<TextObject>,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub body: Option<TextObject>,
120    #[serde(default, skip_serializing_if = "Vec::is_empty")]
121    pub actions: Vec<ButtonElement>,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub slack_icon: Option<SlackIcon>,
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub subtext: Option<TextObject>,
126}
127
128impl Tagged for CardBlock {
129    const TAG: &'static str = "card";
130}
131
132impl CardBlock {
133    pub fn new() -> Self {
134        Self::default()
135    }
136
137    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
138        self.block_id = Some(block_id.into());
139        self
140    }
141
142    pub fn hero_image(mut self, hero_image: ImageElement) -> Self {
143        self.hero_image = Some(hero_image);
144        self
145    }
146
147    pub fn icon(mut self, icon: ImageElement) -> Self {
148        self.icon = Some(icon);
149        self
150    }
151
152    pub fn title(mut self, title: impl Into<TextObject>) -> Self {
153        self.title = Some(title.into());
154        self
155    }
156
157    pub fn subtitle(mut self, subtitle: impl Into<TextObject>) -> Self {
158        self.subtitle = Some(subtitle.into());
159        self
160    }
161
162    pub fn body(mut self, body: impl Into<TextObject>) -> Self {
163        self.body = Some(body.into());
164        self
165    }
166
167    pub fn actions(mut self, actions: Vec<ButtonElement>) -> Self {
168        self.actions = actions;
169        self
170    }
171
172    pub fn slack_icon(mut self, slack_icon: SlackIcon) -> Self {
173        self.slack_icon = Some(slack_icon);
174        self
175    }
176
177    pub fn subtext(mut self, subtext: impl Into<TextObject>) -> Self {
178        self.subtext = Some(subtext.into());
179        self
180    }
181}
182
183/// A horizontally scrolling row of cards. <https://docs.slack.dev/reference/block-kit/blocks/carousel-block>
184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
185pub struct CarouselBlock {
186    r#type: Tag<Self>,
187    pub elements: Vec<CardBlock>,
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub block_id: Option<String>,
190}
191
192impl Tagged for CarouselBlock {
193    const TAG: &'static str = "carousel";
194}
195
196impl CarouselBlock {
197    pub fn new(elements: Vec<CardBlock>) -> Self {
198        Self {
199            r#type: Tag::default(),
200            elements,
201            block_id: None,
202        }
203    }
204
205    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
206        self.block_id = Some(block_id.into());
207        self
208    }
209}
210
211/// A container that groups child blocks. <https://docs.slack.dev/reference/block-kit/blocks/container-block>
212#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
213pub struct ContainerBlock {
214    r#type: Tag<Self>,
215    pub title: TextObject,
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub rich_text_title: Option<RichTextBlock>,
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub subtitle: Option<TextObject>,
220    pub child_blocks: Vec<Block>,
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub block_id: Option<String>,
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub width: Option<ContainerWidth>,
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub icon: Option<ImageElement>,
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub is_collapsible: Option<bool>,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub default_collapsed: Option<bool>,
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub has_header_divider: Option<bool>,
233}
234
235impl Tagged for ContainerBlock {
236    const TAG: &'static str = "container";
237}
238
239impl ContainerBlock {
240    pub fn new(title: impl Into<TextObject>, child_blocks: Vec<Block>) -> Self {
241        Self {
242            r#type: Tag::default(),
243            title: title.into(),
244            rich_text_title: None,
245            subtitle: None,
246            child_blocks,
247            block_id: None,
248            width: None,
249            icon: None,
250            is_collapsible: None,
251            default_collapsed: None,
252            has_header_divider: None,
253        }
254    }
255
256    pub fn rich_text_title(mut self, rich_text_title: RichTextBlock) -> Self {
257        self.rich_text_title = Some(rich_text_title);
258        self
259    }
260
261    pub fn subtitle(mut self, subtitle: impl Into<TextObject>) -> Self {
262        self.subtitle = Some(subtitle.into());
263        self
264    }
265
266    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
267        self.block_id = Some(block_id.into());
268        self
269    }
270
271    pub fn width(mut self, width: ContainerWidth) -> Self {
272        self.width = Some(width);
273        self
274    }
275
276    pub fn icon(mut self, icon: ImageElement) -> Self {
277        self.icon = Some(icon);
278        self
279    }
280
281    pub fn is_collapsible(mut self, is_collapsible: bool) -> Self {
282        self.is_collapsible = Some(is_collapsible);
283        self
284    }
285
286    pub fn default_collapsed(mut self, default_collapsed: bool) -> Self {
287        self.default_collapsed = Some(default_collapsed);
288        self
289    }
290
291    pub fn has_header_divider(mut self, has_header_divider: bool) -> Self {
292        self.has_header_divider = Some(has_header_divider);
293        self
294    }
295}
296
297/// A block that shows supplementary info in small text and images. <https://docs.slack.dev/reference/block-kit/blocks/context-block>
298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
299pub struct ContextBlock {
300    r#type: Tag<Self>,
301    pub elements: Vec<ContextElement>,
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub block_id: Option<String>,
304}
305
306impl Tagged for ContextBlock {
307    const TAG: &'static str = "context";
308}
309
310impl ContextBlock {
311    pub fn new(elements: Vec<ContextElement>) -> Self {
312        Self {
313            r#type: Tag::default(),
314            elements,
315            block_id: None,
316        }
317    }
318
319    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
320        self.block_id = Some(block_id.into());
321        self
322    }
323}
324
325/// A block that shows feedback buttons and icon buttons as contextual actions. <https://docs.slack.dev/reference/block-kit/blocks/context-actions-block>
326#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
327pub struct ContextActionsBlock {
328    r#type: Tag<Self>,
329    pub elements: Vec<ContextActionsElement>,
330    #[serde(skip_serializing_if = "Option::is_none")]
331    pub block_id: Option<String>,
332}
333
334impl Tagged for ContextActionsBlock {
335    const TAG: &'static str = "context_actions";
336}
337
338impl ContextActionsBlock {
339    pub fn new(elements: Vec<ContextActionsElement>) -> Self {
340        Self {
341            r#type: Tag::default(),
342            elements,
343            block_id: None,
344        }
345    }
346
347    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
348        self.block_id = Some(block_id.into());
349        self
350    }
351}
352
353/// A divider line. <https://docs.slack.dev/reference/block-kit/blocks/divider-block>
354#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
355pub struct DividerBlock {
356    r#type: Tag<Self>,
357    #[serde(skip_serializing_if = "Option::is_none")]
358    pub block_id: Option<String>,
359}
360
361impl Tagged for DividerBlock {
362    const TAG: &'static str = "divider";
363}
364
365impl DividerBlock {
366    pub fn new() -> Self {
367        Self::default()
368    }
369
370    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
371        self.block_id = Some(block_id.into());
372        self
373    }
374}
375
376/// A remote file (only appears in retrieved messages). <https://docs.slack.dev/reference/block-kit/blocks/file-block>
377#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
378pub struct FileBlock {
379    r#type: Tag<Self>,
380    pub external_id: String,
381    pub source: String,
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub block_id: Option<String>,
384}
385
386impl Tagged for FileBlock {
387    const TAG: &'static str = "file";
388}
389
390impl FileBlock {
391    pub fn new(external_id: impl Into<String>, source: impl Into<String>) -> Self {
392        Self {
393            r#type: Tag::default(),
394            external_id: external_id.into(),
395            source: source.into(),
396            block_id: None,
397        }
398    }
399
400    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
401        self.block_id = Some(block_id.into());
402        self
403    }
404}
405
406/// A header. <https://docs.slack.dev/reference/block-kit/blocks/header-block>
407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
408pub struct HeaderBlock {
409    r#type: Tag<Self>,
410    pub text: TextObject,
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub block_id: Option<String>,
413    #[serde(skip_serializing_if = "Option::is_none")]
414    pub level: Option<u8>,
415}
416
417impl Tagged for HeaderBlock {
418    const TAG: &'static str = "header";
419}
420
421impl HeaderBlock {
422    pub fn new(text: impl Into<TextObject>) -> Self {
423        Self {
424            r#type: Tag::default(),
425            text: text.into(),
426            block_id: None,
427            level: None,
428        }
429    }
430
431    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
432        self.block_id = Some(block_id.into());
433        self
434    }
435
436    pub fn level(mut self, level: u8) -> Self {
437        self.level = Some(level);
438        self
439    }
440}
441
442/// An image block (either `image_url` or `slack_file`). <https://docs.slack.dev/reference/block-kit/blocks/image-block>
443#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
444pub struct ImageBlock {
445    r#type: Tag<Self>,
446    pub alt_text: String,
447    #[serde(skip_serializing_if = "Option::is_none")]
448    pub image_url: Option<String>,
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub slack_file: Option<SlackFile>,
451    #[serde(skip_serializing_if = "Option::is_none")]
452    pub title: Option<TextObject>,
453    #[serde(skip_serializing_if = "Option::is_none")]
454    pub block_id: Option<String>,
455}
456
457impl Tagged for ImageBlock {
458    const TAG: &'static str = "image";
459}
460
461impl ImageBlock {
462    pub fn new(image_url: impl Into<String>, alt_text: impl Into<String>) -> Self {
463        Self {
464            r#type: Tag::default(),
465            alt_text: alt_text.into(),
466            image_url: Some(image_url.into()),
467            slack_file: None,
468            title: None,
469            block_id: None,
470        }
471    }
472
473    pub fn from_slack_file(slack_file: SlackFile, alt_text: impl Into<String>) -> Self {
474        Self {
475            r#type: Tag::default(),
476            alt_text: alt_text.into(),
477            image_url: None,
478            slack_file: Some(slack_file),
479            title: None,
480            block_id: None,
481        }
482    }
483
484    pub fn image_url(mut self, image_url: impl Into<String>) -> Self {
485        self.image_url = Some(image_url.into());
486        self
487    }
488
489    pub fn slack_file(mut self, slack_file: SlackFile) -> Self {
490        self.slack_file = Some(slack_file);
491        self
492    }
493
494    pub fn title(mut self, title: impl Into<TextObject>) -> Self {
495        self.title = Some(title.into());
496        self
497    }
498
499    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
500        self.block_id = Some(block_id.into());
501        self
502    }
503}
504
505/// An input field. <https://docs.slack.dev/reference/block-kit/blocks/input-block>
506#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
507pub struct InputBlock {
508    r#type: Tag<Self>,
509    pub label: TextObject,
510    pub element: InputElement,
511    #[serde(skip_serializing_if = "Option::is_none")]
512    pub dispatch_action: Option<bool>,
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub block_id: Option<String>,
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub hint: Option<TextObject>,
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub optional: Option<bool>,
519}
520
521impl Tagged for InputBlock {
522    const TAG: &'static str = "input";
523}
524
525impl InputBlock {
526    pub fn new(label: impl Into<TextObject>, element: impl Into<InputElement>) -> Self {
527        Self {
528            r#type: Tag::default(),
529            label: label.into(),
530            element: element.into(),
531            dispatch_action: None,
532            block_id: None,
533            hint: None,
534            optional: None,
535        }
536    }
537
538    pub fn dispatch_action(mut self, dispatch_action: bool) -> Self {
539        self.dispatch_action = Some(dispatch_action);
540        self
541    }
542
543    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
544        self.block_id = Some(block_id.into());
545        self
546    }
547
548    pub fn hint(mut self, hint: impl Into<TextObject>) -> Self {
549        self.hint = Some(hint.into());
550        self
551    }
552
553    pub fn optional(mut self, optional: bool) -> Self {
554        self.optional = Some(optional);
555        self
556    }
557}
558
559/// A block that renders standard Markdown. <https://docs.slack.dev/reference/block-kit/blocks/markdown-block>
560#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
561pub struct MarkdownBlock {
562    r#type: Tag<Self>,
563    pub text: String,
564    #[serde(skip_serializing_if = "Option::is_none")]
565    pub block_id: Option<String>,
566}
567
568impl Tagged for MarkdownBlock {
569    const TAG: &'static str = "markdown";
570}
571
572impl MarkdownBlock {
573    pub fn new(text: impl Into<String>) -> Self {
574        Self {
575            r#type: Tag::default(),
576            text: text.into(),
577            block_id: None,
578        }
579    }
580
581    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
582        self.block_id = Some(block_id.into());
583        self
584    }
585}
586
587/// A collection of related tasks. <https://docs.slack.dev/reference/block-kit/blocks/plan-block>
588#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
589pub struct PlanBlock {
590    r#type: Tag<Self>,
591    pub title: String,
592    pub tasks: Vec<PlanTask>,
593    #[serde(skip_serializing_if = "Option::is_none")]
594    pub block_id: Option<String>,
595}
596
597impl Tagged for PlanBlock {
598    const TAG: &'static str = "plan";
599}
600
601impl PlanBlock {
602    pub fn new(title: impl Into<String>, tasks: Vec<PlanTask>) -> Self {
603        Self {
604            r#type: Tag::default(),
605            title: title.into(),
606            tasks,
607            block_id: None,
608        }
609    }
610
611    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
612        self.block_id = Some(block_id.into());
613        self
614    }
615}
616
617/// A task inside a plan block (has no `type`). <https://docs.slack.dev/reference/block-kit/blocks/plan-block>
618#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
619pub struct PlanTask {
620    pub task_id: String,
621    pub title: String,
622    pub status: TaskStatus,
623    #[serde(skip_serializing_if = "Option::is_none")]
624    pub details: Option<RichTextBlock>,
625    #[serde(skip_serializing_if = "Option::is_none")]
626    pub output: Option<RichTextBlock>,
627    #[serde(default, skip_serializing_if = "Vec::is_empty")]
628    pub sources: Vec<UrlSourceElement>,
629}
630
631impl PlanTask {
632    pub fn new(task_id: impl Into<String>, title: impl Into<String>, status: TaskStatus) -> Self {
633        Self {
634            task_id: task_id.into(),
635            title: title.into(),
636            status,
637            details: None,
638            output: None,
639            sources: Vec::new(),
640        }
641    }
642
643    pub fn details(mut self, details: RichTextBlock) -> Self {
644        self.details = Some(details);
645        self
646    }
647
648    pub fn output(mut self, output: RichTextBlock) -> Self {
649        self.output = Some(output);
650        self
651    }
652
653    pub fn sources(mut self, sources: Vec<UrlSourceElement>) -> Self {
654        self.sources = sources;
655        self
656    }
657}
658
659/// The basic block for text, optionally alongside an element. <https://docs.slack.dev/reference/block-kit/blocks/section-block>
660#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
661pub struct SectionBlock {
662    r#type: Tag<Self>,
663    #[serde(skip_serializing_if = "Option::is_none")]
664    pub text: Option<TextObject>,
665    #[serde(skip_serializing_if = "Option::is_none")]
666    pub block_id: Option<String>,
667    #[serde(default, skip_serializing_if = "Vec::is_empty")]
668    pub fields: Vec<TextObject>,
669    #[serde(skip_serializing_if = "Option::is_none")]
670    pub accessory: Option<SectionAccessory>,
671    #[serde(skip_serializing_if = "Option::is_none")]
672    pub expand: Option<bool>,
673}
674
675impl Tagged for SectionBlock {
676    const TAG: &'static str = "section";
677}
678
679impl SectionBlock {
680    pub fn new() -> Self {
681        Self::default()
682    }
683
684    pub fn text(mut self, text: impl Into<TextObject>) -> Self {
685        self.text = Some(text.into());
686        self
687    }
688
689    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
690        self.block_id = Some(block_id.into());
691        self
692    }
693
694    pub fn fields(mut self, fields: Vec<TextObject>) -> Self {
695        self.fields = fields;
696        self
697    }
698
699    pub fn accessory(mut self, accessory: impl Into<SectionAccessory>) -> Self {
700        self.accessory = Some(accessory.into());
701        self
702    }
703
704    pub fn expand(mut self, expand: bool) -> Self {
705        self.expand = Some(expand);
706        self
707    }
708}
709
710/// A single task. <https://docs.slack.dev/reference/block-kit/blocks/task-card-block>
711#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
712pub struct TaskCardBlock {
713    r#type: Tag<Self>,
714    pub task_id: String,
715    pub title: String,
716    pub status: TaskStatus,
717    #[serde(skip_serializing_if = "Option::is_none")]
718    pub details: Option<RichTextBlock>,
719    #[serde(skip_serializing_if = "Option::is_none")]
720    pub output: Option<RichTextBlock>,
721    #[serde(default, skip_serializing_if = "Vec::is_empty")]
722    pub sources: Vec<UrlSourceElement>,
723    #[serde(skip_serializing_if = "Option::is_none")]
724    pub block_id: Option<String>,
725    #[serde(skip_serializing_if = "Option::is_none")]
726    pub icon: Option<SlackIcon>,
727    #[serde(skip_serializing_if = "Option::is_none")]
728    pub hide_title: Option<bool>,
729}
730
731impl Tagged for TaskCardBlock {
732    const TAG: &'static str = "task_card";
733}
734
735impl TaskCardBlock {
736    pub fn new(task_id: impl Into<String>, title: impl Into<String>, status: TaskStatus) -> Self {
737        Self {
738            r#type: Tag::default(),
739            task_id: task_id.into(),
740            title: title.into(),
741            status,
742            details: None,
743            output: None,
744            sources: Vec::new(),
745            block_id: None,
746            icon: None,
747            hide_title: None,
748        }
749    }
750
751    pub fn details(mut self, details: RichTextBlock) -> Self {
752        self.details = Some(details);
753        self
754    }
755
756    pub fn output(mut self, output: RichTextBlock) -> Self {
757        self.output = Some(output);
758        self
759    }
760
761    pub fn sources(mut self, sources: Vec<UrlSourceElement>) -> Self {
762        self.sources = sources;
763        self
764    }
765
766    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
767        self.block_id = Some(block_id.into());
768        self
769    }
770
771    pub fn icon(mut self, icon: SlackIcon) -> Self {
772        self.icon = Some(icon);
773        self
774    }
775
776    pub fn hide_title(mut self, hide_title: bool) -> Self {
777        self.hide_title = Some(hide_title);
778        self
779    }
780}
781
782/// An embedded video. <https://docs.slack.dev/reference/block-kit/blocks/video-block>
783#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
784pub struct VideoBlock {
785    r#type: Tag<Self>,
786    pub alt_text: String,
787    #[serde(skip_serializing_if = "Option::is_none")]
788    pub author_name: Option<String>,
789    #[serde(skip_serializing_if = "Option::is_none")]
790    pub block_id: Option<String>,
791    #[serde(skip_serializing_if = "Option::is_none")]
792    pub description: Option<TextObject>,
793    #[serde(skip_serializing_if = "Option::is_none")]
794    pub provider_icon_url: Option<String>,
795    #[serde(skip_serializing_if = "Option::is_none")]
796    pub provider_name: Option<String>,
797    pub title: TextObject,
798    #[serde(skip_serializing_if = "Option::is_none")]
799    pub title_url: Option<String>,
800    pub thumbnail_url: String,
801    pub video_url: String,
802}
803
804impl Tagged for VideoBlock {
805    const TAG: &'static str = "video";
806}
807
808impl VideoBlock {
809    pub fn new(
810        alt_text: impl Into<String>,
811        title: impl Into<TextObject>,
812        thumbnail_url: impl Into<String>,
813        video_url: impl Into<String>,
814    ) -> Self {
815        Self {
816            r#type: Tag::default(),
817            alt_text: alt_text.into(),
818            author_name: None,
819            block_id: None,
820            description: None,
821            provider_icon_url: None,
822            provider_name: None,
823            title: title.into(),
824            title_url: None,
825            thumbnail_url: thumbnail_url.into(),
826            video_url: video_url.into(),
827        }
828    }
829
830    pub fn author_name(mut self, author_name: impl Into<String>) -> Self {
831        self.author_name = Some(author_name.into());
832        self
833    }
834
835    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
836        self.block_id = Some(block_id.into());
837        self
838    }
839
840    pub fn description(mut self, description: impl Into<TextObject>) -> Self {
841        self.description = Some(description.into());
842        self
843    }
844
845    pub fn provider_icon_url(mut self, provider_icon_url: impl Into<String>) -> Self {
846        self.provider_icon_url = Some(provider_icon_url.into());
847        self
848    }
849
850    pub fn provider_name(mut self, provider_name: impl Into<String>) -> Self {
851        self.provider_name = Some(provider_name.into());
852        self
853    }
854
855    pub fn title_url(mut self, title_url: impl Into<String>) -> Self {
856        self.title_url = Some(title_url.into());
857        self
858    }
859}
860
861/// The level of an alert block. Unknown values go to `Other`.
862#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
863#[serde(rename_all = "snake_case")]
864pub enum AlertLevel {
865    Default,
866    Info,
867    Warning,
868    Error,
869    Success,
870    #[serde(untagged)]
871    Other(String),
872}
873
874/// The width of a container block. Unknown values go to `Other`.
875#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
876#[serde(rename_all = "snake_case")]
877pub enum ContainerWidth {
878    Narrow,
879    Standard,
880    Wide,
881    Full,
882    #[serde(untagged)]
883    Other(String),
884}
885
886/// The status of a task. Unknown values go to `Other`.
887#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
888#[serde(rename_all = "snake_case")]
889pub enum TaskStatus {
890    Pending,
891    InProgress,
892    Complete,
893    Error,
894    #[serde(untagged)]
895    Other(String),
896}