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(
22 Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize, ValueStruct,
23)]
24pub struct SlackHeaderLevel(pub u8);
25
26#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
31#[serde(tag = "type")]
32pub enum SlackBlock {
33 #[serde(rename = "section")]
34 Section(SlackSectionBlock),
35 #[serde(rename = "header")]
36 Header(SlackHeaderBlock),
37 #[serde(rename = "divider")]
38 Divider(SlackDividerBlock),
39 #[serde(rename = "image")]
40 Image(SlackImageBlock),
41 #[serde(rename = "actions")]
42 Actions(SlackActionsBlock),
43 #[serde(rename = "context")]
44 Context(SlackContextBlock),
45 #[serde(rename = "input")]
46 Input(SlackInputBlock),
47 #[serde(rename = "file")]
48 File(SlackFileBlock),
49 #[serde(rename = "video")]
50 Video(SlackVideoBlock),
51 #[serde(rename = "markdown")]
52 Markdown(SlackMarkdownBlock),
53 #[serde(rename = "rich_text")]
54 RichText(SlackRichTextBlock),
55 #[serde(rename = "table")]
56 Table(SlackTableBlock),
57 #[serde(rename = "task_card")]
58 TaskCard(SlackTaskCardBlock),
59 #[serde(rename = "alert")]
60 Alert(SlackAlertBlock),
61 #[serde(rename = "card")]
62 Card(SlackCardBlock),
63 #[serde(rename = "carousel")]
64 Carousel(SlackCarouselBlock),
65 #[serde(rename = "context_actions")]
66 ContextActions(SlackContextActionsBlock),
67 #[serde(rename = "container")]
68 Container(SlackContainerBlock),
69 #[serde(rename = "plan")]
70 Plan(SlackPlanBlock),
71 #[serde(rename = "data_table")]
72 DataTable(SlackDataTableBlock),
73 #[serde(rename = "data_visualization")]
74 DataVisualization(SlackDataVisualizationBlock),
75 #[serde(rename = "share_shortcut")]
76 ShareShortcut(serde_json::Value),
77 #[serde(rename = "event")]
78 Event(serde_json::Value),
79 #[serde(untagged)]
80 Unknown(serde_json::Value),
81}
82
83#[skip_serializing_none]
84#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
85pub struct SlackSectionBlock {
86 pub block_id: Option<SlackBlockId>,
87 pub text: Option<SlackBlockText>,
88 pub fields: Option<Vec<SlackBlockText>>,
89 pub accessory: Option<SlackSectionBlockElement>,
90 pub expand: Option<bool>,
91}
92
93impl From<SlackSectionBlock> for SlackBlock {
94 fn from(block: SlackSectionBlock) -> Self {
95 SlackBlock::Section(block)
96 }
97}
98
99#[skip_serializing_none]
100#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
101pub struct SlackHeaderBlock {
102 pub block_id: Option<SlackBlockId>,
103 pub text: SlackBlockPlainTextOnly,
104 pub level: Option<SlackHeaderLevel>,
107}
108
109impl From<SlackHeaderBlock> for SlackBlock {
110 fn from(block: SlackHeaderBlock) -> Self {
111 SlackBlock::Header(block)
112 }
113}
114
115#[skip_serializing_none]
116#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
117pub struct SlackDividerBlock {
118 pub block_id: Option<SlackBlockId>,
119}
120
121impl From<SlackDividerBlock> for SlackBlock {
122 fn from(block: SlackDividerBlock) -> Self {
123 SlackBlock::Divider(block)
124 }
125}
126
127#[skip_serializing_none]
128#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
129pub struct SlackImageBlock {
130 pub block_id: Option<SlackBlockId>,
131 #[serde(flatten)]
132 pub image_url_or_file: SlackImageUrlOrFile,
133 pub alt_text: String,
134 pub title: Option<SlackBlockPlainTextOnly>,
135}
136
137impl From<SlackImageBlock> for SlackBlock {
138 fn from(block: SlackImageBlock) -> Self {
139 SlackBlock::Image(block)
140 }
141}
142
143#[skip_serializing_none]
144#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
145pub struct SlackActionsBlock {
146 pub block_id: Option<SlackBlockId>,
147 pub elements: Vec<SlackActionBlockElement>,
148}
149
150impl From<SlackActionsBlock> for SlackBlock {
151 fn from(block: SlackActionsBlock) -> Self {
152 SlackBlock::Actions(block)
153 }
154}
155
156#[skip_serializing_none]
157#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
158pub struct SlackContextBlock {
159 pub block_id: Option<SlackBlockId>,
160 pub elements: Vec<SlackContextBlockElement>,
161}
162
163impl From<SlackContextBlock> for SlackBlock {
164 fn from(block: SlackContextBlock) -> Self {
165 SlackBlock::Context(block)
166 }
167}
168
169#[skip_serializing_none]
170#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
171pub struct SlackInputBlock {
172 pub block_id: Option<SlackBlockId>,
173 pub label: SlackBlockPlainTextOnly,
174 pub element: SlackInputBlockElement,
175 pub hint: Option<SlackBlockPlainTextOnly>,
176 pub optional: Option<bool>,
177 pub dispatch_action: Option<bool>,
178}
179
180impl From<SlackInputBlock> for SlackBlock {
181 fn from(block: SlackInputBlock) -> Self {
182 SlackBlock::Input(block)
183 }
184}
185
186const SLACK_FILE_BLOCK_SOURCE_DEFAULT: &str = "remote";
187
188#[skip_serializing_none]
189#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
190pub struct SlackFileBlock {
191 pub block_id: Option<SlackBlockId>,
192 pub external_id: String,
193 #[default = "SLACK_FILE_BLOCK_SOURCE_DEFAULT.into()"]
194 pub source: String,
195}
196
197impl From<SlackFileBlock> for SlackBlock {
198 fn from(block: SlackFileBlock) -> Self {
199 SlackBlock::File(block)
200 }
201}
202
203#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
204#[serde(tag = "type")]
205pub enum SlackSectionBlockElement {
206 #[serde(rename = "image")]
207 Image(SlackBlockImageElement),
208 #[serde(rename = "button")]
209 Button(SlackBlockButtonElement),
210 #[serde(rename = "static_select")]
211 StaticSelect(SlackBlockStaticSelectElement),
212 #[serde(rename = "multi_static_select")]
213 MultiStaticSelect(SlackBlockMultiStaticSelectElement),
214 #[serde(rename = "external_select")]
215 ExternalSelect(SlackBlockExternalSelectElement),
216 #[serde(rename = "multi_external_select")]
217 MultiExternalSelect(SlackBlockMultiExternalSelectElement),
218 #[serde(rename = "users_select")]
219 UsersSelect(SlackBlockUsersSelectElement),
220 #[serde(rename = "multi_users_select")]
221 MultiUsersSelect(SlackBlockMultiUsersSelectElement),
222 #[serde(rename = "conversations_select")]
223 ConversationsSelect(SlackBlockConversationsSelectElement),
224 #[serde(rename = "multi_conversations_select")]
225 MultiConversationsSelect(SlackBlockMultiConversationsSelectElement),
226 #[serde(rename = "channels_select")]
227 ChannelsSelect(SlackBlockChannelsSelectElement),
228 #[serde(rename = "multi_channels_select")]
229 MultiChannelsSelect(SlackBlockMultiChannelsSelectElement),
230 #[serde(rename = "overflow")]
231 Overflow(SlackBlockOverflowElement),
232 #[serde(rename = "datepicker")]
233 DatePicker(SlackBlockDatePickerElement),
234 #[serde(rename = "timepicker")]
235 TimePicker(SlackBlockTimePickerElement),
236 #[serde(rename = "plain_text_input")]
237 PlainTextInput(SlackBlockPlainTextInputElement),
238 #[serde(rename = "number_input")]
239 NumberInput(SlackBlockNumberInputElement),
240 #[serde(rename = "url_text_input")]
241 UrlInput(SlackBlockUrlInputElement),
242 #[serde(rename = "radio_buttons")]
243 RadioButtons(SlackBlockRadioButtonsElement),
244 #[serde(rename = "checkboxes")]
245 Checkboxes(SlackBlockCheckboxesElement),
246 #[serde(rename = "workflow_button")]
247 WorkflowButton(SlackBlockWorkflowButtonElement),
248}
249
250#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
251#[serde(tag = "type")]
252pub enum SlackActionBlockElement {
253 #[serde(rename = "button")]
254 Button(SlackBlockButtonElement),
255 #[serde(rename = "overflow")]
256 Overflow(SlackBlockOverflowElement),
257 #[serde(rename = "datepicker")]
258 DatePicker(SlackBlockDatePickerElement),
259 #[serde(rename = "timepicker")]
260 TimePicker(SlackBlockTimePickerElement),
261 #[serde(rename = "datetimepicker")]
262 DateTimePicker(SlackBlockDateTimePickerElement),
263 #[serde(rename = "plain_text_input")]
264 PlainTextInput(SlackBlockPlainTextInputElement),
265 #[serde(rename = "number_input")]
266 NumberInput(SlackBlockNumberInputElement),
267 #[serde(rename = "url_text_input")]
268 UrlInput(SlackBlockUrlInputElement),
269 #[serde(rename = "radio_buttons")]
270 RadioButtons(SlackBlockRadioButtonsElement),
271 #[serde(rename = "checkboxes")]
272 Checkboxes(SlackBlockCheckboxesElement),
273 #[serde(rename = "static_select")]
274 StaticSelect(SlackBlockStaticSelectElement),
275 #[serde(rename = "external_select")]
276 ExternalSelect(SlackBlockExternalSelectElement),
277 #[serde(rename = "users_select")]
278 UsersSelect(SlackBlockUsersSelectElement),
279 #[serde(rename = "conversations_select")]
280 ConversationsSelect(SlackBlockConversationsSelectElement),
281 #[serde(rename = "channels_select")]
282 ChannelsSelect(SlackBlockChannelsSelectElement),
283 #[serde(rename = "workflow_button")]
284 WorkflowButton(SlackBlockWorkflowButtonElement),
285}
286
287#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
288#[serde(tag = "type")]
289pub enum SlackContextBlockElement {
290 #[serde(rename = "image")]
291 Image(SlackBlockImageElement),
292 #[serde(rename = "plain_text")]
293 Plain(SlackBlockPlainText),
294 #[serde(rename = "mrkdwn")]
295 MarkDown(SlackBlockMarkDownText),
296}
297
298#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
299#[serde(tag = "type")]
300pub enum SlackInputBlockElement {
301 #[serde(rename = "static_select")]
302 StaticSelect(SlackBlockStaticSelectElement),
303 #[serde(rename = "multi_static_select")]
304 MultiStaticSelect(SlackBlockMultiStaticSelectElement),
305 #[serde(rename = "external_select")]
306 ExternalSelect(SlackBlockExternalSelectElement),
307 #[serde(rename = "multi_external_select")]
308 MultiExternalSelect(SlackBlockMultiExternalSelectElement),
309 #[serde(rename = "users_select")]
310 UsersSelect(SlackBlockUsersSelectElement),
311 #[serde(rename = "multi_users_select")]
312 MultiUsersSelect(SlackBlockMultiUsersSelectElement),
313 #[serde(rename = "conversations_select")]
314 ConversationsSelect(SlackBlockConversationsSelectElement),
315 #[serde(rename = "multi_conversations_select")]
316 MultiConversationsSelect(SlackBlockMultiConversationsSelectElement),
317 #[serde(rename = "channels_select")]
318 ChannelsSelect(SlackBlockChannelsSelectElement),
319 #[serde(rename = "multi_channels_select")]
320 MultiChannelsSelect(SlackBlockMultiChannelsSelectElement),
321 #[serde(rename = "datepicker")]
322 DatePicker(SlackBlockDatePickerElement),
323 #[serde(rename = "timepicker")]
324 TimePicker(SlackBlockTimePickerElement),
325 #[serde(rename = "datetimepicker")]
326 DateTimePicker(SlackBlockDateTimePickerElement),
327 #[serde(rename = "plain_text_input")]
328 PlainTextInput(SlackBlockPlainTextInputElement),
329 #[serde(rename = "number_input")]
330 NumberInput(SlackBlockNumberInputElement),
331 #[serde(rename = "url_text_input")]
332 UrlInput(SlackBlockUrlInputElement),
333 #[serde(rename = "radio_buttons")]
334 RadioButtons(SlackBlockRadioButtonsElement),
335 #[serde(rename = "checkboxes")]
336 Checkboxes(SlackBlockCheckboxesElement),
337 #[serde(rename = "email_text_input")]
338 EmailInput(SlackBlockEmailInputElement),
339 #[serde(rename = "rich_text_input")]
340 RichTextInput(SlackBlockRichTextInputElement),
341 #[serde(rename = "file_input")]
342 FileInput(SlackBlockFileInputElement),
343}
344
345#[skip_serializing_none]
346#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
347pub struct SlackBlockImageElement {
348 #[serde(flatten)]
349 pub image_url_or_file: SlackImageUrlOrFile,
350 pub alt_text: String,
351}
352
353impl From<SlackBlockImageElement> for SlackSectionBlockElement {
354 fn from(element: SlackBlockImageElement) -> Self {
355 SlackSectionBlockElement::Image(element)
356 }
357}
358
359impl From<SlackBlockImageElement> for SlackContextBlockElement {
360 fn from(element: SlackBlockImageElement) -> Self {
361 SlackContextBlockElement::Image(element)
362 }
363}
364
365#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
366#[serde(rename_all = "snake_case")]
367pub enum SlackBlockButtonStyle {
368 Primary,
369 Danger,
370}
371
372#[skip_serializing_none]
373#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
374pub struct SlackBlockButtonElement {
375 pub action_id: Option<SlackActionId>,
376 pub text: SlackBlockPlainTextOnly,
377 pub url: Option<Url>,
378 pub value: Option<String>,
379 pub style: Option<SlackBlockButtonStyle>,
380 pub confirm: Option<SlackBlockConfirmItem>,
381 pub accessibility_label: Option<SlackAccessibilityLabel>,
382}
383
384impl From<SlackBlockButtonElement> for SlackSectionBlockElement {
385 fn from(element: SlackBlockButtonElement) -> Self {
386 SlackSectionBlockElement::Button(element)
387 }
388}
389
390impl From<SlackBlockButtonElement> for SlackActionBlockElement {
391 fn from(element: SlackBlockButtonElement) -> Self {
392 SlackActionBlockElement::Button(element)
393 }
394}
395
396#[skip_serializing_none]
397#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
398pub struct SlackBlockConfirmItem {
399 pub title: SlackBlockPlainTextOnly,
400 pub text: SlackBlockText,
401 pub confirm: SlackBlockPlainTextOnly,
402 pub deny: SlackBlockPlainTextOnly,
403 pub style: Option<String>,
404}
405
406#[skip_serializing_none]
407#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
408pub struct SlackBlockChoiceItem<T: Into<SlackBlockText>> {
409 pub text: T,
410 pub value: String,
411 pub description: Option<SlackBlockPlainTextOnly>,
412 pub url: Option<Url>,
413}
414
415#[skip_serializing_none]
416#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
417pub struct SlackBlockOptionGroup<T: Into<SlackBlockText>> {
418 pub label: SlackBlockPlainTextOnly,
419 pub options: Vec<SlackBlockChoiceItem<T>>,
420}
421
422#[skip_serializing_none]
423#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
424pub struct SlackBlockStaticSelectElement {
425 pub action_id: Option<SlackActionId>,
426 pub placeholder: Option<SlackBlockPlainTextOnly>,
427 pub options: Option<Vec<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>>,
428 pub option_groups: Option<Vec<SlackBlockOptionGroup<SlackBlockPlainTextOnly>>>,
429 pub initial_option: Option<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>,
430 pub confirm: Option<SlackBlockConfirmItem>,
431 pub focus_on_load: Option<bool>,
432}
433
434impl From<SlackBlockStaticSelectElement> for SlackSectionBlockElement {
435 fn from(element: SlackBlockStaticSelectElement) -> Self {
436 SlackSectionBlockElement::StaticSelect(element)
437 }
438}
439
440impl From<SlackBlockStaticSelectElement> for SlackInputBlockElement {
441 fn from(element: SlackBlockStaticSelectElement) -> Self {
442 SlackInputBlockElement::StaticSelect(element)
443 }
444}
445
446impl From<SlackBlockStaticSelectElement> for SlackActionBlockElement {
447 fn from(element: SlackBlockStaticSelectElement) -> Self {
448 SlackActionBlockElement::StaticSelect(element)
449 }
450}
451
452#[skip_serializing_none]
453#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
454pub struct SlackBlockMultiStaticSelectElement {
455 pub action_id: Option<SlackActionId>,
456 pub placeholder: Option<SlackBlockPlainTextOnly>,
457 pub options: Option<Vec<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>>,
458 pub option_groups: Option<Vec<SlackBlockOptionGroup<SlackBlockPlainTextOnly>>>,
459 pub initial_options: Option<Vec<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>>,
460 pub confirm: Option<SlackBlockConfirmItem>,
461 pub max_selected_items: Option<u64>,
462 pub focus_on_load: Option<bool>,
463}
464
465impl From<SlackBlockMultiStaticSelectElement> for SlackSectionBlockElement {
466 fn from(element: SlackBlockMultiStaticSelectElement) -> Self {
467 SlackSectionBlockElement::MultiStaticSelect(element)
468 }
469}
470
471impl From<SlackBlockMultiStaticSelectElement> for SlackInputBlockElement {
472 fn from(element: SlackBlockMultiStaticSelectElement) -> Self {
473 SlackInputBlockElement::MultiStaticSelect(element)
474 }
475}
476
477#[skip_serializing_none]
478#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
479pub struct SlackBlockExternalSelectElement {
480 pub action_id: Option<SlackActionId>,
481 pub placeholder: Option<SlackBlockPlainTextOnly>,
482 pub initial_option: Option<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>,
483 pub confirm: Option<SlackBlockConfirmItem>,
484 pub focus_on_load: Option<bool>,
485 pub min_query_length: Option<u64>,
486}
487
488impl From<SlackBlockExternalSelectElement> for SlackSectionBlockElement {
489 fn from(element: SlackBlockExternalSelectElement) -> Self {
490 SlackSectionBlockElement::ExternalSelect(element)
491 }
492}
493
494impl From<SlackBlockExternalSelectElement> for SlackInputBlockElement {
495 fn from(element: SlackBlockExternalSelectElement) -> Self {
496 SlackInputBlockElement::ExternalSelect(element)
497 }
498}
499
500impl From<SlackBlockExternalSelectElement> for SlackActionBlockElement {
501 fn from(element: SlackBlockExternalSelectElement) -> Self {
502 SlackActionBlockElement::ExternalSelect(element)
503 }
504}
505
506#[skip_serializing_none]
507#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
508pub struct SlackBlockMultiExternalSelectElement {
509 pub action_id: Option<SlackActionId>,
510 pub placeholder: Option<SlackBlockPlainTextOnly>,
511 pub initial_options: Option<Vec<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>>,
512 pub confirm: Option<SlackBlockConfirmItem>,
513 pub max_selected_items: Option<u64>,
514 pub focus_on_load: Option<bool>,
515 pub min_query_length: Option<u64>,
516}
517
518impl From<SlackBlockMultiExternalSelectElement> for SlackSectionBlockElement {
519 fn from(element: SlackBlockMultiExternalSelectElement) -> Self {
520 SlackSectionBlockElement::MultiExternalSelect(element)
521 }
522}
523
524impl From<SlackBlockMultiExternalSelectElement> for SlackInputBlockElement {
525 fn from(element: SlackBlockMultiExternalSelectElement) -> Self {
526 SlackInputBlockElement::MultiExternalSelect(element)
527 }
528}
529
530#[skip_serializing_none]
531#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
532pub struct SlackBlockUsersSelectElement {
533 pub action_id: Option<SlackActionId>,
534 pub placeholder: Option<SlackBlockPlainTextOnly>,
535 pub initial_user: Option<String>,
536 pub confirm: Option<SlackBlockConfirmItem>,
537 pub focus_on_load: Option<bool>,
538}
539
540impl From<SlackBlockUsersSelectElement> for SlackSectionBlockElement {
541 fn from(element: SlackBlockUsersSelectElement) -> Self {
542 SlackSectionBlockElement::UsersSelect(element)
543 }
544}
545
546impl From<SlackBlockUsersSelectElement> for SlackInputBlockElement {
547 fn from(element: SlackBlockUsersSelectElement) -> Self {
548 SlackInputBlockElement::UsersSelect(element)
549 }
550}
551
552impl From<SlackBlockUsersSelectElement> for SlackActionBlockElement {
553 fn from(element: SlackBlockUsersSelectElement) -> Self {
554 SlackActionBlockElement::UsersSelect(element)
555 }
556}
557
558#[skip_serializing_none]
559#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
560pub struct SlackBlockMultiUsersSelectElement {
561 pub action_id: Option<SlackActionId>,
562 pub placeholder: Option<SlackBlockPlainTextOnly>,
563 pub initial_users: Option<Vec<String>>,
564 pub confirm: Option<SlackBlockConfirmItem>,
565 pub max_selected_items: Option<u64>,
566 pub focus_on_load: Option<bool>,
567}
568
569impl From<SlackBlockMultiUsersSelectElement> for SlackSectionBlockElement {
570 fn from(element: SlackBlockMultiUsersSelectElement) -> Self {
571 SlackSectionBlockElement::MultiUsersSelect(element)
572 }
573}
574
575impl From<SlackBlockMultiUsersSelectElement> for SlackInputBlockElement {
576 fn from(element: SlackBlockMultiUsersSelectElement) -> Self {
577 SlackInputBlockElement::MultiUsersSelect(element)
578 }
579}
580
581#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
582pub enum SlackConversationFilterInclude {
583 #[serde(rename = "im")]
584 Im,
585 #[serde(rename = "mpim")]
586 Mpim,
587 #[serde(rename = "public")]
588 Public,
589 #[serde(rename = "private")]
590 Private,
591}
592
593#[skip_serializing_none]
594#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
595pub struct SlackBlockConversationFilter {
596 pub include: Option<Vec<SlackConversationFilterInclude>>,
597 pub exclude_external_shared_channels: Option<bool>,
598 pub exclude_bot_users: Option<bool>,
599}
600
601#[skip_serializing_none]
602#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
603pub struct SlackBlockConversationsSelectElement {
604 pub action_id: Option<SlackActionId>,
605 pub placeholder: Option<SlackBlockPlainTextOnly>,
606 pub initial_conversation: Option<SlackConversationId>,
607 pub default_to_current_conversation: Option<bool>,
608 pub confirm: Option<SlackBlockConfirmItem>,
609 pub response_url_enabled: Option<bool>,
610 pub focus_on_load: Option<bool>,
611 pub filter: Option<SlackBlockConversationFilter>,
612}
613
614impl From<SlackBlockConversationsSelectElement> for SlackSectionBlockElement {
615 fn from(element: SlackBlockConversationsSelectElement) -> Self {
616 SlackSectionBlockElement::ConversationsSelect(element)
617 }
618}
619
620impl From<SlackBlockConversationsSelectElement> for SlackInputBlockElement {
621 fn from(element: SlackBlockConversationsSelectElement) -> Self {
622 SlackInputBlockElement::ConversationsSelect(element)
623 }
624}
625
626impl From<SlackBlockConversationsSelectElement> for SlackActionBlockElement {
627 fn from(element: SlackBlockConversationsSelectElement) -> Self {
628 SlackActionBlockElement::ConversationsSelect(element)
629 }
630}
631
632#[skip_serializing_none]
633#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
634pub struct SlackBlockMultiConversationsSelectElement {
635 pub action_id: Option<SlackActionId>,
636 pub placeholder: Option<SlackBlockPlainTextOnly>,
637 pub initial_conversations: Option<Vec<SlackConversationId>>,
638 pub default_to_current_conversation: Option<bool>,
639 pub confirm: Option<SlackBlockConfirmItem>,
640 pub max_selected_items: Option<u64>,
641 pub focus_on_load: Option<bool>,
642 pub filter: Option<SlackBlockConversationFilter>,
643}
644
645impl From<SlackBlockMultiConversationsSelectElement> for SlackSectionBlockElement {
646 fn from(element: SlackBlockMultiConversationsSelectElement) -> Self {
647 SlackSectionBlockElement::MultiConversationsSelect(element)
648 }
649}
650
651impl From<SlackBlockMultiConversationsSelectElement> for SlackInputBlockElement {
652 fn from(element: SlackBlockMultiConversationsSelectElement) -> Self {
653 SlackInputBlockElement::MultiConversationsSelect(element)
654 }
655}
656
657#[skip_serializing_none]
658#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
659pub struct SlackBlockChannelsSelectElement {
660 pub action_id: Option<SlackActionId>,
661 pub placeholder: Option<SlackBlockPlainTextOnly>,
662 pub initial_channel: Option<SlackChannelId>,
663 pub confirm: Option<SlackBlockConfirmItem>,
664 pub response_url_enabled: Option<bool>,
665 pub focus_on_load: Option<bool>,
666}
667
668impl From<SlackBlockChannelsSelectElement> for SlackSectionBlockElement {
669 fn from(element: SlackBlockChannelsSelectElement) -> Self {
670 SlackSectionBlockElement::ChannelsSelect(element)
671 }
672}
673
674impl From<SlackBlockChannelsSelectElement> for SlackInputBlockElement {
675 fn from(element: SlackBlockChannelsSelectElement) -> Self {
676 SlackInputBlockElement::ChannelsSelect(element)
677 }
678}
679
680impl From<SlackBlockChannelsSelectElement> for SlackActionBlockElement {
681 fn from(element: SlackBlockChannelsSelectElement) -> Self {
682 SlackActionBlockElement::ChannelsSelect(element)
683 }
684}
685
686#[skip_serializing_none]
687#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
688pub struct SlackBlockMultiChannelsSelectElement {
689 pub action_id: Option<SlackActionId>,
690 pub placeholder: Option<SlackBlockPlainTextOnly>,
691 pub initial_channels: Option<Vec<SlackChannelId>>,
692 pub confirm: Option<SlackBlockConfirmItem>,
693 pub max_selected_items: Option<u64>,
694 pub focus_on_load: Option<bool>,
695}
696
697impl From<SlackBlockMultiChannelsSelectElement> for SlackSectionBlockElement {
698 fn from(element: SlackBlockMultiChannelsSelectElement) -> Self {
699 SlackSectionBlockElement::MultiChannelsSelect(element)
700 }
701}
702
703impl From<SlackBlockMultiChannelsSelectElement> for SlackInputBlockElement {
704 fn from(element: SlackBlockMultiChannelsSelectElement) -> Self {
705 SlackInputBlockElement::MultiChannelsSelect(element)
706 }
707}
708
709#[skip_serializing_none]
710#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
711pub struct SlackBlockOverflowElement {
712 pub action_id: Option<SlackActionId>,
713 pub options: Vec<SlackBlockChoiceItem<SlackBlockPlainTextOnly>>,
714 pub confirm: Option<SlackBlockConfirmItem>,
715}
716
717impl From<SlackBlockOverflowElement> for SlackSectionBlockElement {
718 fn from(element: SlackBlockOverflowElement) -> Self {
719 SlackSectionBlockElement::Overflow(element)
720 }
721}
722
723impl From<SlackBlockOverflowElement> for SlackActionBlockElement {
724 fn from(element: SlackBlockOverflowElement) -> Self {
725 SlackActionBlockElement::Overflow(element)
726 }
727}
728
729#[skip_serializing_none]
730#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
731pub struct SlackBlockDatePickerElement {
732 pub action_id: Option<SlackActionId>,
733 pub placeholder: Option<SlackBlockPlainTextOnly>,
734 pub initial_date: Option<String>,
735 pub confirm: Option<SlackBlockConfirmItem>,
736 pub focus_on_load: Option<bool>,
737}
738
739impl From<SlackBlockDatePickerElement> for SlackSectionBlockElement {
740 fn from(element: SlackBlockDatePickerElement) -> Self {
741 SlackSectionBlockElement::DatePicker(element)
742 }
743}
744
745impl From<SlackBlockDatePickerElement> for SlackInputBlockElement {
746 fn from(element: SlackBlockDatePickerElement) -> Self {
747 SlackInputBlockElement::DatePicker(element)
748 }
749}
750
751impl From<SlackBlockDatePickerElement> for SlackActionBlockElement {
752 fn from(element: SlackBlockDatePickerElement) -> Self {
753 SlackActionBlockElement::DatePicker(element)
754 }
755}
756
757#[skip_serializing_none]
758#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
759pub struct SlackBlockTimePickerElement {
760 pub action_id: Option<SlackActionId>,
761 pub initial_time: Option<String>,
762 pub confirm: Option<SlackBlockConfirmItem>,
763 pub focus_on_load: Option<bool>,
764 pub placeholder: Option<SlackBlockPlainTextOnly>,
765 pub timezone: Option<String>,
766}
767
768impl From<SlackBlockTimePickerElement> for SlackSectionBlockElement {
769 fn from(element: SlackBlockTimePickerElement) -> Self {
770 SlackSectionBlockElement::TimePicker(element)
771 }
772}
773
774impl From<SlackBlockTimePickerElement> for SlackInputBlockElement {
775 fn from(element: SlackBlockTimePickerElement) -> Self {
776 SlackInputBlockElement::TimePicker(element)
777 }
778}
779
780impl From<SlackBlockTimePickerElement> for SlackActionBlockElement {
781 fn from(element: SlackBlockTimePickerElement) -> Self {
782 SlackActionBlockElement::TimePicker(element)
783 }
784}
785
786#[skip_serializing_none]
787#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
788pub struct SlackBlockDateTimePickerElement {
789 pub action_id: Option<SlackActionId>,
790 pub initial_date_time: Option<SlackDateTime>,
791 pub confirm: Option<SlackBlockConfirmItem>,
792 pub focus_on_load: Option<bool>,
793}
794
795impl From<SlackBlockDateTimePickerElement> for SlackInputBlockElement {
796 fn from(element: SlackBlockDateTimePickerElement) -> Self {
797 SlackInputBlockElement::DateTimePicker(element)
798 }
799}
800
801impl From<SlackBlockDateTimePickerElement> for SlackActionBlockElement {
802 fn from(element: SlackBlockDateTimePickerElement) -> Self {
803 SlackActionBlockElement::DateTimePicker(element)
804 }
805}
806
807#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
811#[serde(rename_all = "snake_case")]
812pub enum SlackDispatchActionTrigger {
813 OnEnterPressed,
814 OnCharacterEntered,
815}
816
817#[skip_serializing_none]
818#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
819pub struct SlackDispatchActionConfig {
820 pub trigger_actions_on: Option<Vec<SlackDispatchActionTrigger>>,
821}
822
823#[skip_serializing_none]
824#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
825pub struct SlackBlockPlainTextInputElement {
826 pub action_id: Option<SlackActionId>,
827 pub placeholder: Option<SlackBlockPlainTextOnly>,
828 pub initial_value: Option<String>,
829 pub multiline: Option<bool>,
830 pub min_length: Option<u64>,
831 pub max_length: Option<u64>,
832 pub focus_on_load: Option<bool>,
833 pub dispatch_action_config: Option<SlackDispatchActionConfig>,
834}
835
836impl From<SlackBlockPlainTextInputElement> for SlackSectionBlockElement {
837 fn from(element: SlackBlockPlainTextInputElement) -> Self {
838 SlackSectionBlockElement::PlainTextInput(element)
839 }
840}
841
842impl From<SlackBlockPlainTextInputElement> for SlackInputBlockElement {
843 fn from(element: SlackBlockPlainTextInputElement) -> Self {
844 SlackInputBlockElement::PlainTextInput(element)
845 }
846}
847
848impl From<SlackBlockPlainTextInputElement> for SlackActionBlockElement {
849 fn from(element: SlackBlockPlainTextInputElement) -> Self {
850 SlackActionBlockElement::PlainTextInput(element)
851 }
852}
853
854#[skip_serializing_none]
855#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
856pub struct SlackBlockNumberInputElement {
857 pub action_id: Option<SlackActionId>,
858 pub is_decimal_allowed: bool,
859 pub focus_on_load: Option<bool>,
860 pub placeholder: Option<SlackBlockPlainTextOnly>,
861 pub initial_value: Option<String>,
862 pub min_value: Option<String>,
863 pub max_value: Option<String>,
864}
865
866impl From<SlackBlockNumberInputElement> for SlackSectionBlockElement {
867 fn from(element: SlackBlockNumberInputElement) -> Self {
868 SlackSectionBlockElement::NumberInput(element)
869 }
870}
871
872impl From<SlackBlockNumberInputElement> for SlackInputBlockElement {
873 fn from(element: SlackBlockNumberInputElement) -> Self {
874 SlackInputBlockElement::NumberInput(element)
875 }
876}
877
878impl From<SlackBlockNumberInputElement> for SlackActionBlockElement {
879 fn from(element: SlackBlockNumberInputElement) -> Self {
880 SlackActionBlockElement::NumberInput(element)
881 }
882}
883
884#[skip_serializing_none]
885#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
886pub struct SlackBlockUrlInputElement {
887 pub action_id: Option<SlackActionId>,
888 pub placeholder: Option<SlackBlockPlainTextOnly>,
889 pub initial_value: Option<String>,
890}
891
892impl From<SlackBlockUrlInputElement> for SlackSectionBlockElement {
893 fn from(element: SlackBlockUrlInputElement) -> Self {
894 SlackSectionBlockElement::UrlInput(element)
895 }
896}
897
898impl From<SlackBlockUrlInputElement> for SlackInputBlockElement {
899 fn from(element: SlackBlockUrlInputElement) -> Self {
900 SlackInputBlockElement::UrlInput(element)
901 }
902}
903
904impl From<SlackBlockUrlInputElement> for SlackActionBlockElement {
905 fn from(element: SlackBlockUrlInputElement) -> Self {
906 SlackActionBlockElement::UrlInput(element)
907 }
908}
909
910#[skip_serializing_none]
911#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
912pub struct SlackBlockEmailInputElement {
913 pub action_id: Option<SlackActionId>,
914 pub focus_on_load: Option<bool>,
915 pub placeholder: Option<SlackBlockPlainTextOnly>,
916 pub initial_value: Option<EmailAddress>,
917}
918
919impl From<SlackBlockEmailInputElement> for SlackInputBlockElement {
920 fn from(element: SlackBlockEmailInputElement) -> Self {
921 SlackInputBlockElement::EmailInput(element)
922 }
923}
924
925#[skip_serializing_none]
926#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
927pub struct SlackBlockRadioButtonsElement {
928 pub action_id: Option<SlackActionId>,
929 pub options: Vec<SlackBlockChoiceItem<SlackBlockText>>,
930 pub initial_option: Option<SlackBlockChoiceItem<SlackBlockText>>,
931 pub confirm: Option<SlackBlockConfirmItem>,
932 pub focus_on_load: Option<bool>,
933}
934
935impl From<SlackBlockRadioButtonsElement> for SlackSectionBlockElement {
936 fn from(element: SlackBlockRadioButtonsElement) -> Self {
937 SlackSectionBlockElement::RadioButtons(element)
938 }
939}
940
941impl From<SlackBlockRadioButtonsElement> for SlackInputBlockElement {
942 fn from(element: SlackBlockRadioButtonsElement) -> Self {
943 SlackInputBlockElement::RadioButtons(element)
944 }
945}
946
947impl From<SlackBlockRadioButtonsElement> for SlackActionBlockElement {
948 fn from(element: SlackBlockRadioButtonsElement) -> Self {
949 SlackActionBlockElement::RadioButtons(element)
950 }
951}
952
953#[skip_serializing_none]
954#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
955pub struct SlackBlockCheckboxesElement {
956 pub action_id: Option<SlackActionId>,
957 pub options: Vec<SlackBlockChoiceItem<SlackBlockText>>,
958 pub initial_options: Option<Vec<SlackBlockChoiceItem<SlackBlockText>>>,
959 pub confirm: Option<SlackBlockConfirmItem>,
960 pub focus_on_load: Option<bool>,
961}
962
963impl From<SlackBlockCheckboxesElement> for SlackSectionBlockElement {
964 fn from(element: SlackBlockCheckboxesElement) -> Self {
965 SlackSectionBlockElement::Checkboxes(element)
966 }
967}
968
969impl From<SlackBlockCheckboxesElement> for SlackInputBlockElement {
970 fn from(element: SlackBlockCheckboxesElement) -> Self {
971 SlackInputBlockElement::Checkboxes(element)
972 }
973}
974
975impl From<SlackBlockCheckboxesElement> for SlackActionBlockElement {
976 fn from(element: SlackBlockCheckboxesElement) -> Self {
977 SlackActionBlockElement::Checkboxes(element)
978 }
979}
980
981#[skip_serializing_none]
985#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
986pub struct SlackBlockPlainText {
987 pub text: String,
988 pub emoji: Option<bool>,
989}
990
991#[skip_serializing_none]
995#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
996pub struct SlackBlockMarkDownText {
997 pub text: String,
998 pub verbatim: Option<bool>,
999}
1000
1001#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1005#[serde(tag = "type")]
1006pub enum SlackBlockText {
1007 #[serde(rename = "plain_text")]
1008 Plain(SlackBlockPlainText),
1009 #[serde(rename = "mrkdwn")]
1010 MarkDown(SlackBlockMarkDownText),
1011}
1012
1013#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1014#[serde(tag = "type", rename = "plain_text")]
1015pub struct SlackBlockPlainTextOnly {
1016 #[serde(flatten)]
1017 value: SlackBlockPlainText,
1018}
1019
1020impl SlackBlockPlainText {
1021 pub fn as_block_text(&self) -> SlackBlockText {
1022 SlackBlockText::Plain(self.clone())
1023 }
1024}
1025
1026impl From<String> for SlackBlockPlainText {
1027 fn from(value: String) -> Self {
1028 SlackBlockPlainText::new(value)
1029 }
1030}
1031
1032impl From<&str> for SlackBlockPlainText {
1033 fn from(value: &str) -> Self {
1034 SlackBlockPlainText::new(String::from(value))
1035 }
1036}
1037
1038impl SlackBlockMarkDownText {
1039 pub fn as_block_text(&self) -> SlackBlockText {
1040 SlackBlockText::MarkDown(self.clone())
1041 }
1042}
1043
1044impl From<String> for SlackBlockMarkDownText {
1045 fn from(value: String) -> Self {
1046 SlackBlockMarkDownText::new(value)
1047 }
1048}
1049
1050impl From<&str> for SlackBlockMarkDownText {
1051 fn from(value: &str) -> Self {
1052 SlackBlockMarkDownText::new(String::from(value))
1053 }
1054}
1055
1056impl From<SlackBlockPlainText> for SlackBlockPlainTextOnly {
1057 fn from(pt: SlackBlockPlainText) -> Self {
1058 SlackBlockPlainTextOnly { value: pt }
1059 }
1060}
1061
1062impl From<SlackBlockPlainText> for SlackBlockText {
1063 fn from(text: SlackBlockPlainText) -> Self {
1064 SlackBlockText::Plain(text)
1065 }
1066}
1067
1068impl From<SlackBlockMarkDownText> for SlackBlockText {
1069 fn from(text: SlackBlockMarkDownText) -> Self {
1070 SlackBlockText::MarkDown(text)
1071 }
1072}
1073
1074impl From<SlackBlockPlainText> for SlackContextBlockElement {
1075 fn from(text: SlackBlockPlainText) -> Self {
1076 SlackContextBlockElement::Plain(text)
1077 }
1078}
1079
1080impl From<SlackBlockMarkDownText> for SlackContextBlockElement {
1081 fn from(text: SlackBlockMarkDownText) -> Self {
1082 SlackContextBlockElement::MarkDown(text)
1083 }
1084}
1085
1086impl From<SlackBlockPlainTextOnly> for SlackBlockText {
1087 fn from(text: SlackBlockPlainTextOnly) -> Self {
1088 SlackBlockText::Plain(text.value)
1089 }
1090}
1091
1092impl From<String> for SlackBlockPlainTextOnly {
1093 fn from(value: String) -> Self {
1094 SlackBlockPlainTextOnly {
1095 value: value.into(),
1096 }
1097 }
1098}
1099
1100impl From<&str> for SlackBlockPlainTextOnly {
1101 fn from(value: &str) -> Self {
1102 SlackBlockPlainTextOnly {
1103 value: value.into(),
1104 }
1105 }
1106}
1107
1108#[skip_serializing_none]
1112#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1113pub struct SlackVideoBlock {
1114 pub alt_text: String,
1115 pub author_name: Option<String>,
1116 pub block_id: Option<SlackBlockId>,
1117 pub description: Option<SlackBlockPlainTextOnly>,
1118 pub provider_icon_url: Option<Url>,
1119 pub provider_name: Option<String>,
1120 pub title: SlackBlockPlainTextOnly,
1121 pub title_url: Option<Url>,
1122 pub thumbnail_url: Url,
1123 pub video_url: Url,
1124}
1125
1126impl From<SlackVideoBlock> for SlackBlock {
1127 fn from(block: SlackVideoBlock) -> Self {
1128 SlackBlock::Video(block)
1129 }
1130}
1131
1132#[skip_serializing_none]
1136#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1137pub struct SlackMarkdownBlock {
1138 pub block_id: Option<SlackBlockId>,
1139 pub text: String,
1140}
1141
1142impl From<SlackMarkdownBlock> for SlackBlock {
1143 fn from(block: SlackMarkdownBlock) -> Self {
1144 SlackBlock::Markdown(block)
1145 }
1146}
1147
1148#[skip_serializing_none]
1152#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1153pub struct SlackRichTextBlock {
1154 pub block_id: Option<SlackBlockId>,
1155 pub elements: Vec<SlackRichTextElement>,
1156}
1157
1158impl From<SlackRichTextBlock> for SlackBlock {
1159 fn from(block: SlackRichTextBlock) -> Self {
1160 SlackBlock::RichText(block)
1161 }
1162}
1163
1164#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1165#[serde(tag = "type", rename_all = "snake_case")]
1166pub enum SlackRichTextInlineContent {
1167 #[serde(rename = "rich_text")]
1168 RichText(SlackRichTextBlock),
1169}
1170
1171impl From<SlackRichTextBlock> for SlackRichTextInlineContent {
1172 fn from(block: SlackRichTextBlock) -> Self {
1173 SlackRichTextInlineContent::RichText(block)
1174 }
1175}
1176
1177#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1178#[serde(tag = "type")]
1179pub enum SlackRichTextElement {
1180 #[serde(rename = "rich_text_section")]
1181 Section(SlackRichTextSection),
1182 #[serde(rename = "rich_text_list")]
1183 List(SlackRichTextList),
1184 #[serde(rename = "rich_text_preformatted")]
1185 Preformatted(SlackRichTextPreformatted),
1186 #[serde(rename = "rich_text_quote")]
1187 Quote(SlackRichTextQuote),
1188}
1189
1190impl From<SlackRichTextSection> for SlackRichTextElement {
1191 fn from(element: SlackRichTextSection) -> Self {
1192 SlackRichTextElement::Section(element)
1193 }
1194}
1195
1196impl From<SlackRichTextList> for SlackRichTextElement {
1197 fn from(list: SlackRichTextList) -> Self {
1198 SlackRichTextElement::List(list)
1199 }
1200}
1201
1202impl From<SlackRichTextPreformatted> for SlackRichTextElement {
1203 fn from(element: SlackRichTextPreformatted) -> Self {
1204 SlackRichTextElement::Preformatted(element)
1205 }
1206}
1207
1208impl From<SlackRichTextQuote> for SlackRichTextElement {
1209 fn from(element: SlackRichTextQuote) -> Self {
1210 SlackRichTextElement::Quote(element)
1211 }
1212}
1213
1214#[skip_serializing_none]
1215#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1216pub struct SlackRichTextSection {
1217 pub elements: Vec<SlackRichTextInlineElement>,
1218}
1219
1220impl From<&str> for SlackRichTextSection {
1222 fn from(value: &str) -> Self {
1223 SlackRichTextSection::new(vec![value.into()])
1224 }
1225}
1226
1227impl From<String> for SlackRichTextSection {
1228 fn from(value: String) -> Self {
1229 SlackRichTextSection::new(vec![value.into()])
1230 }
1231}
1232
1233#[skip_serializing_none]
1234#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1235pub struct SlackRichTextList {
1236 pub style: SlackRichTextListStyle,
1237 pub elements: Vec<SlackRichTextListElement>,
1238 pub indent: Option<u64>,
1239 pub offset: Option<u64>,
1240 pub border: Option<u64>,
1241}
1242
1243#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1244#[serde(tag = "type")]
1245pub enum SlackRichTextListElement {
1246 #[serde(rename = "rich_text_section")]
1247 Section(SlackRichTextSection),
1248}
1249
1250impl From<SlackRichTextSection> for SlackRichTextListElement {
1251 fn from(element: SlackRichTextSection) -> Self {
1252 SlackRichTextListElement::Section(element)
1253 }
1254}
1255
1256impl From<&str> for SlackRichTextListElement {
1258 fn from(value: &str) -> Self {
1259 SlackRichTextListElement::Section(value.into())
1260 }
1261}
1262
1263impl From<String> for SlackRichTextListElement {
1264 fn from(value: String) -> Self {
1265 SlackRichTextListElement::Section(value.into())
1266 }
1267}
1268
1269#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1270#[serde(rename_all = "snake_case")]
1271pub enum SlackRichTextListStyle {
1272 Bullet,
1273 Ordered,
1274}
1275
1276#[skip_serializing_none]
1277#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1278pub struct SlackRichTextPreformatted {
1279 pub elements: Vec<SlackRichTextInlineElement>,
1280 pub border: Option<u64>,
1281 pub language: Option<String>,
1282}
1283
1284#[skip_serializing_none]
1285#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1286pub struct SlackRichTextQuote {
1287 pub elements: Vec<SlackRichTextInlineElement>,
1288 pub border: Option<u64>,
1289}
1290
1291#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1292#[serde(tag = "type")]
1293pub enum SlackRichTextInlineElement {
1294 #[serde(rename = "text")]
1295 Text(SlackRichTextText),
1296 #[serde(rename = "link")]
1297 Link(SlackRichTextLink),
1298 #[serde(rename = "user")]
1299 User(SlackRichTextUser),
1300 #[serde(rename = "channel")]
1301 Channel(SlackRichTextChannel),
1302 #[serde(rename = "usergroup")]
1303 UserGroup(SlackRichTextUserGroup),
1304 #[serde(rename = "emoji")]
1305 Emoji(SlackRichTextEmoji),
1306 #[serde(rename = "date")]
1307 Date(SlackRichTextDate),
1308 #[serde(rename = "broadcast")]
1309 Broadcast(SlackRichTextBroadcast),
1310 #[serde(rename = "color")]
1311 Color(SlackRichTextColor),
1312 #[serde(rename = "message_mention")]
1313 MessageMention(SlackRichTextMessageMention),
1314 #[serde(untagged)]
1315 Unknown(serde_json::Value),
1316}
1317
1318impl From<&str> for SlackRichTextInlineElement {
1320 fn from(value: &str) -> Self {
1321 SlackRichTextInlineElement::Text(SlackRichTextText::new(value.to_string()))
1322 }
1323}
1324
1325impl From<String> for SlackRichTextInlineElement {
1326 fn from(value: String) -> Self {
1327 SlackRichTextInlineElement::Text(SlackRichTextText::new(value))
1328 }
1329}
1330
1331impl From<SlackRichTextText> for SlackRichTextInlineElement {
1332 fn from(element: SlackRichTextText) -> Self {
1333 SlackRichTextInlineElement::Text(element)
1334 }
1335}
1336
1337impl From<SlackRichTextLink> for SlackRichTextInlineElement {
1338 fn from(element: SlackRichTextLink) -> Self {
1339 SlackRichTextInlineElement::Link(element)
1340 }
1341}
1342
1343impl From<SlackRichTextUser> for SlackRichTextInlineElement {
1344 fn from(element: SlackRichTextUser) -> Self {
1345 SlackRichTextInlineElement::User(element)
1346 }
1347}
1348
1349impl From<SlackRichTextChannel> for SlackRichTextInlineElement {
1350 fn from(element: SlackRichTextChannel) -> Self {
1351 SlackRichTextInlineElement::Channel(element)
1352 }
1353}
1354
1355impl From<SlackRichTextUserGroup> for SlackRichTextInlineElement {
1356 fn from(element: SlackRichTextUserGroup) -> Self {
1357 SlackRichTextInlineElement::UserGroup(element)
1358 }
1359}
1360
1361impl From<SlackRichTextEmoji> for SlackRichTextInlineElement {
1362 fn from(element: SlackRichTextEmoji) -> Self {
1363 SlackRichTextInlineElement::Emoji(element)
1364 }
1365}
1366
1367impl From<SlackRichTextDate> for SlackRichTextInlineElement {
1368 fn from(element: SlackRichTextDate) -> Self {
1369 SlackRichTextInlineElement::Date(element)
1370 }
1371}
1372
1373impl From<SlackRichTextBroadcast> for SlackRichTextInlineElement {
1374 fn from(element: SlackRichTextBroadcast) -> Self {
1375 SlackRichTextInlineElement::Broadcast(element)
1376 }
1377}
1378
1379impl From<SlackRichTextColor> for SlackRichTextInlineElement {
1380 fn from(element: SlackRichTextColor) -> Self {
1381 SlackRichTextInlineElement::Color(element)
1382 }
1383}
1384
1385impl From<SlackRichTextMessageMention> for SlackRichTextInlineElement {
1386 fn from(element: SlackRichTextMessageMention) -> Self {
1387 SlackRichTextInlineElement::MessageMention(element)
1388 }
1389}
1390
1391#[skip_serializing_none]
1392#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1393pub struct SlackRichTextStyle {
1394 pub bold: Option<bool>,
1395 pub italic: Option<bool>,
1396 pub strike: Option<bool>,
1397 pub code: Option<bool>,
1398 pub underline: Option<bool>,
1399 pub highlight: Option<bool>,
1400 pub client_highlight: Option<bool>,
1401 pub unlink: Option<bool>,
1402}
1403
1404#[skip_serializing_none]
1405#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1406pub struct SlackRichTextText {
1407 pub text: String,
1408 pub style: Option<SlackRichTextStyle>,
1409}
1410
1411impl SlackRichTextText {
1412 pub fn bold(mut self) -> Self {
1414 self.style.get_or_insert_with(SlackRichTextStyle::new).bold = Some(true);
1415 self
1416 }
1417
1418 pub fn italic(mut self) -> Self {
1420 self.style
1421 .get_or_insert_with(SlackRichTextStyle::new)
1422 .italic = Some(true);
1423 self
1424 }
1425
1426 pub fn strike(mut self) -> Self {
1428 self.style
1429 .get_or_insert_with(SlackRichTextStyle::new)
1430 .strike = Some(true);
1431 self
1432 }
1433
1434 pub fn code(mut self) -> Self {
1436 self.style.get_or_insert_with(SlackRichTextStyle::new).code = Some(true);
1437 self
1438 }
1439}
1440
1441#[skip_serializing_none]
1442#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1443pub struct SlackRichTextLink {
1444 pub url: SlackRelaxedUrl,
1445 pub text: Option<String>,
1446 #[serde(rename = "unsafe")]
1447 pub unsafe_: Option<bool>,
1448 pub style: Option<SlackRichTextStyle>,
1449}
1450
1451#[skip_serializing_none]
1452#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1453pub struct SlackRichTextUser {
1454 pub user_id: SlackUserId,
1455 pub style: Option<SlackRichTextStyle>,
1456}
1457
1458#[skip_serializing_none]
1459#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1460pub struct SlackRichTextChannel {
1461 pub channel_id: SlackChannelId,
1462 pub style: Option<SlackRichTextStyle>,
1463}
1464
1465#[skip_serializing_none]
1466#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1467pub struct SlackRichTextUserGroup {
1468 pub usergroup_id: SlackUserGroupId,
1469 pub style: Option<SlackRichTextStyle>,
1470}
1471
1472#[skip_serializing_none]
1473#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1474pub struct SlackRichTextEmoji {
1475 pub name: SlackEmojiName,
1476 pub unicode: Option<String>,
1477}
1478
1479#[skip_serializing_none]
1480#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1481pub struct SlackRichTextDate {
1482 pub timestamp: SlackDateTime,
1483 pub format: String,
1484 pub fallback: Option<String>,
1485 pub style: Option<SlackRichTextStyle>,
1486}
1487
1488#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1489#[serde(rename_all = "snake_case")]
1490pub enum SlackRichTextBroadcastRange {
1491 Here,
1492 Channel,
1493 Everyone,
1494}
1495
1496#[skip_serializing_none]
1497#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1498pub struct SlackRichTextBroadcast {
1499 pub range: SlackRichTextBroadcastRange,
1500 pub style: Option<SlackRichTextStyle>,
1501}
1502
1503#[skip_serializing_none]
1504#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1505pub struct SlackRichTextColor {
1506 pub value: String,
1507}
1508
1509#[skip_serializing_none]
1510#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1511pub struct SlackRichTextMessageMention {
1512 pub url: SlackRelaxedUrl,
1513 pub text: Option<String>,
1514 pub channel_id: Option<SlackChannelId>,
1515 pub author_id: Option<SlackUserId>,
1516 pub message_ts: Option<SlackTs>,
1517 pub thread_ts: Option<SlackTs>,
1518 pub style: Option<SlackRichTextStyle>,
1519}
1520
1521#[skip_serializing_none]
1525#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1526pub struct SlackBlockRichTextInputElement {
1527 pub action_id: SlackActionId,
1528 pub initial_value: Option<SlackRichTextBlock>,
1529 pub focus_on_load: Option<bool>,
1530 pub placeholder: Option<SlackBlockPlainTextOnly>,
1531}
1532
1533impl From<SlackBlockRichTextInputElement> for SlackInputBlockElement {
1534 fn from(element: SlackBlockRichTextInputElement) -> Self {
1535 SlackInputBlockElement::RichTextInput(element)
1536 }
1537}
1538
1539#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1540#[serde(untagged)]
1541pub enum SlackImageUrlOrFile {
1542 ImageUrl { image_url: Url },
1543 SlackFile { slack_file: SlackFileIdOrUrl },
1544}
1545
1546impl SlackImageUrlOrFile {
1547 pub fn image_url(&self) -> Option<&Url> {
1548 match self {
1549 SlackImageUrlOrFile::ImageUrl { image_url } => Some(image_url),
1550 SlackImageUrlOrFile::SlackFile { slack_file } => match slack_file {
1551 SlackFileIdOrUrl::Url { url } => Some(url),
1552 _ => None,
1553 },
1554 }
1555 }
1556}
1557
1558impl From<Url> for SlackImageUrlOrFile {
1559 fn from(value: Url) -> Self {
1560 SlackImageUrlOrFile::ImageUrl { image_url: value }
1561 }
1562}
1563
1564#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1565#[serde(untagged)]
1566pub enum SlackFileIdOrUrl {
1567 Id { id: SlackFileId },
1568 Url { url: Url },
1569}
1570
1571impl From<SlackFileId> for SlackFileIdOrUrl {
1572 fn from(value: SlackFileId) -> Self {
1573 SlackFileIdOrUrl::Id { id: value }
1574 }
1575}
1576
1577#[skip_serializing_none]
1581#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1582pub struct SlackTableBlock {
1583 pub block_id: Option<SlackBlockId>,
1584 pub rows: Vec<Vec<SlackTableCell>>,
1585 pub column_settings: Option<Vec<SlackTableColumnSetting>>,
1586}
1587
1588impl From<SlackTableBlock> for SlackBlock {
1589 fn from(block: SlackTableBlock) -> Self {
1590 SlackBlock::Table(block)
1591 }
1592}
1593
1594#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1595#[serde(tag = "type")]
1596pub enum SlackTableCell {
1597 #[serde(rename = "raw_text")]
1598 RawText(SlackTableRawTextCell),
1599 #[serde(rename = "rich_text")]
1600 RichText(SlackTableRichTextCell),
1601}
1602
1603impl From<&str> for SlackTableCell {
1605 fn from(value: &str) -> Self {
1606 SlackTableCell::RawText(SlackTableRawTextCell::new(value.to_string()))
1607 }
1608}
1609
1610impl From<String> for SlackTableCell {
1611 fn from(value: String) -> Self {
1612 SlackTableCell::RawText(SlackTableRawTextCell::new(value))
1613 }
1614}
1615
1616impl From<SlackTableRawTextCell> for SlackTableCell {
1617 fn from(cell: SlackTableRawTextCell) -> Self {
1618 SlackTableCell::RawText(cell)
1619 }
1620}
1621
1622impl From<SlackTableRichTextCell> for SlackTableCell {
1623 fn from(cell: SlackTableRichTextCell) -> Self {
1624 SlackTableCell::RichText(cell)
1625 }
1626}
1627
1628#[skip_serializing_none]
1629#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1630pub struct SlackTableRawTextCell {
1631 pub text: String,
1632}
1633
1634#[skip_serializing_none]
1635#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1636pub struct SlackTableRichTextCell {
1637 pub elements: Vec<SlackRichTextElement>,
1638}
1639
1640#[skip_serializing_none]
1641#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1642pub struct SlackTableColumnSetting {
1643 pub align: Option<SlackTableColumnAlign>,
1644 pub is_wrapped: Option<bool>,
1645}
1646
1647#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1648#[serde(rename_all = "snake_case")]
1649pub enum SlackTableColumnAlign {
1650 Left,
1651 Center,
1652 Right,
1653}
1654
1655#[skip_serializing_none]
1659#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1660pub struct SlackTaskCardBlock {
1661 pub task_id: SlackTaskId,
1662 pub title: String,
1663 pub block_id: Option<SlackBlockId>,
1664 pub status: Option<SlackTaskCardStatus>,
1665 pub icon: Option<SlackTaskCardIcon>,
1666 pub hide_title: Option<bool>,
1667 #[serde(rename = "details")]
1668 pub details: Option<SlackRichTextInlineContent>,
1669 #[serde(rename = "output")]
1670 pub output: Option<SlackRichTextInlineContent>,
1671 pub sources: Option<Vec<SlackTaskCardSource>>,
1672}
1673
1674impl From<SlackTaskCardBlock> for SlackBlock {
1675 fn from(block: SlackTaskCardBlock) -> Self {
1676 SlackBlock::TaskCard(block)
1677 }
1678}
1679
1680#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1681#[serde(rename_all = "snake_case")]
1682pub enum SlackTaskCardStatus {
1683 Pending,
1684 InProgress,
1685 Complete,
1686 Error,
1687}
1688
1689#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1692#[serde(tag = "type", rename = "icon")]
1693pub struct SlackTaskCardIcon {
1694 pub name: String,
1695}
1696
1697#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1701pub struct SlackUrlSourceElement {
1702 pub url: Url,
1703 pub text: String,
1704}
1705
1706#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1707#[serde(tag = "type")]
1708pub enum SlackTaskCardSource {
1709 #[serde(rename = "url")]
1710 Url(SlackUrlSourceElement),
1711}
1712
1713impl From<SlackUrlSourceElement> for SlackTaskCardSource {
1714 fn from(element: SlackUrlSourceElement) -> Self {
1715 SlackTaskCardSource::Url(element)
1716 }
1717}
1718
1719#[skip_serializing_none]
1723#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1724pub struct SlackBlockFileInputElement {
1725 pub action_id: Option<SlackActionId>,
1726 pub filetypes: Option<Vec<String>>,
1727 pub max_files: Option<u64>,
1728}
1729
1730impl From<SlackBlockFileInputElement> for SlackInputBlockElement {
1731 fn from(element: SlackBlockFileInputElement) -> Self {
1732 SlackInputBlockElement::FileInput(element)
1733 }
1734}
1735
1736#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1740#[serde(rename_all = "snake_case")]
1741pub enum SlackAlertLevel {
1742 Warning,
1743 Error,
1744 Info,
1745 Success,
1746}
1747
1748#[skip_serializing_none]
1749#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1750pub struct SlackAlertBlock {
1751 pub block_id: Option<SlackBlockId>,
1752 pub text: SlackBlockText,
1753 pub level: Option<SlackAlertLevel>,
1754}
1755
1756impl From<SlackAlertBlock> for SlackBlock {
1757 fn from(block: SlackAlertBlock) -> Self {
1758 SlackBlock::Alert(block)
1759 }
1760}
1761
1762#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1766#[serde(tag = "type")]
1767pub enum SlackCardImageElement {
1768 #[serde(rename = "image")]
1769 Image(SlackBlockImageElement),
1770}
1771
1772impl From<SlackBlockImageElement> for SlackCardImageElement {
1773 fn from(element: SlackBlockImageElement) -> Self {
1774 SlackCardImageElement::Image(element)
1775 }
1776}
1777
1778#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1779#[serde(tag = "type")]
1780pub enum SlackCardActionBlockElement {
1781 #[serde(rename = "button")]
1782 Button(SlackBlockButtonElement),
1783}
1784
1785impl From<SlackBlockButtonElement> for SlackCardActionBlockElement {
1786 fn from(element: SlackBlockButtonElement) -> Self {
1787 SlackCardActionBlockElement::Button(element)
1788 }
1789}
1790
1791#[skip_serializing_none]
1792#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1793pub struct SlackCardBlock {
1794 pub block_id: Option<SlackBlockId>,
1795 pub title: Option<SlackBlockText>,
1796 pub subtitle: Option<SlackBlockText>,
1797 pub body: Option<SlackBlockText>,
1798 pub hero_image: Option<SlackCardImageElement>,
1799 pub icon: Option<SlackCardImageElement>,
1800 pub actions: Option<Vec<SlackCardActionBlockElement>>,
1801}
1802
1803impl From<SlackCardBlock> for SlackBlock {
1804 fn from(block: SlackCardBlock) -> Self {
1805 SlackBlock::Card(block)
1806 }
1807}
1808
1809#[skip_serializing_none]
1813#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1814pub struct SlackCarouselBlock {
1815 pub block_id: Option<SlackBlockId>,
1816 pub elements: Vec<SlackBlock>,
1817}
1818
1819impl From<SlackCarouselBlock> for SlackBlock {
1820 fn from(block: SlackCarouselBlock) -> Self {
1821 SlackBlock::Carousel(block)
1822 }
1823}
1824
1825#[skip_serializing_none]
1831#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1832pub struct SlackFeedbackButtonItem {
1833 pub text: SlackBlockPlainTextOnly,
1834 pub value: String,
1835 pub accessibility_label: Option<SlackAccessibilityLabel>,
1836}
1837
1838#[skip_serializing_none]
1839#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1840pub struct SlackBlockFeedbackButtonsElement {
1841 pub action_id: Option<SlackActionId>,
1842 pub positive_button: SlackFeedbackButtonItem,
1843 pub negative_button: SlackFeedbackButtonItem,
1844}
1845
1846#[skip_serializing_none]
1847#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1848pub struct SlackBlockIconButtonElement {
1849 pub action_id: Option<SlackActionId>,
1850 pub icon: String,
1851 pub text: SlackBlockPlainTextOnly,
1852 pub value: Option<String>,
1853 pub confirm: Option<SlackBlockConfirmItem>,
1854 pub accessibility_label: Option<SlackAccessibilityLabel>,
1855 pub visible_to_user_ids: Option<Vec<SlackUserId>>,
1856}
1857
1858#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1859#[serde(tag = "type")]
1860pub enum SlackContextActionBlockElement {
1861 #[serde(rename = "feedback_buttons")]
1862 FeedbackButtons(SlackBlockFeedbackButtonsElement),
1863 #[serde(rename = "icon_button")]
1864 IconButton(SlackBlockIconButtonElement),
1865}
1866
1867impl From<SlackBlockFeedbackButtonsElement> for SlackContextActionBlockElement {
1868 fn from(element: SlackBlockFeedbackButtonsElement) -> Self {
1869 SlackContextActionBlockElement::FeedbackButtons(element)
1870 }
1871}
1872
1873impl From<SlackBlockIconButtonElement> for SlackContextActionBlockElement {
1874 fn from(element: SlackBlockIconButtonElement) -> Self {
1875 SlackContextActionBlockElement::IconButton(element)
1876 }
1877}
1878
1879#[skip_serializing_none]
1880#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1881pub struct SlackContextActionsBlock {
1882 pub block_id: Option<SlackBlockId>,
1883 pub elements: Vec<SlackContextActionBlockElement>,
1884}
1885
1886impl From<SlackContextActionsBlock> for SlackBlock {
1887 fn from(block: SlackContextActionsBlock) -> Self {
1888 SlackBlock::ContextActions(block)
1889 }
1890}
1891
1892#[skip_serializing_none]
1903#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1904pub struct SlackContainerBlock {
1905 pub block_id: Option<SlackBlockId>,
1906 pub title: Option<SlackBlockPlainTextOnly>,
1907 pub rich_text_title: Option<SlackRichTextInlineContent>,
1908 pub subtitle: Option<SlackBlockText>,
1909 pub child_blocks: Vec<SlackBlock>,
1910 pub width: Option<SlackContainerWidth>,
1911 pub icon: Option<SlackCardImageElement>,
1912 pub is_collapsible: Option<bool>,
1913 pub default_collapsed: Option<bool>,
1914 pub has_header_divider: Option<bool>,
1915}
1916
1917impl From<SlackContainerBlock> for SlackBlock {
1918 fn from(block: SlackContainerBlock) -> Self {
1919 SlackBlock::Container(block)
1920 }
1921}
1922
1923#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1926#[serde(rename_all = "snake_case")]
1927pub enum SlackContainerWidth {
1928 Narrow,
1929 Standard,
1930 Wide,
1931 Full,
1932}
1933
1934#[skip_serializing_none]
1942#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1943pub struct SlackPlanBlock {
1944 pub title: String,
1945 pub tasks: Vec<SlackTaskCardBlock>,
1946 pub block_id: Option<SlackBlockId>,
1947}
1948
1949impl From<SlackPlanBlock> for SlackBlock {
1950 fn from(block: SlackPlanBlock) -> Self {
1951 SlackBlock::Plan(block)
1952 }
1953}
1954
1955#[skip_serializing_none]
1962#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1963pub struct SlackDataTableBlock {
1964 pub caption: String,
1965 pub rows: Vec<Vec<SlackDataTableCell>>,
1966 pub block_id: Option<SlackBlockId>,
1967 pub page_size: Option<u32>,
1968 pub row_header_column_index: Option<u32>,
1969}
1970
1971impl From<SlackDataTableBlock> for SlackBlock {
1972 fn from(block: SlackDataTableBlock) -> Self {
1973 SlackBlock::DataTable(block)
1974 }
1975}
1976
1977#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1978#[serde(tag = "type")]
1979pub enum SlackDataTableCell {
1980 #[serde(rename = "raw_text")]
1981 RawText(SlackTableRawTextCell),
1982 #[serde(rename = "raw_number")]
1983 RawNumber(SlackDataTableRawNumberCell),
1984 #[serde(rename = "rich_text")]
1985 RichText(SlackTableRichTextCell),
1986 #[serde(rename = "action_cell")]
1987 ActionCell(SlackDataTableActionCell),
1988 #[serde(untagged)]
1989 Unknown(serde_json::Value),
1990}
1991
1992impl From<&str> for SlackDataTableCell {
1994 fn from(value: &str) -> Self {
1995 SlackDataTableCell::RawText(SlackTableRawTextCell::new(value.to_string()))
1996 }
1997}
1998
1999impl From<String> for SlackDataTableCell {
2000 fn from(value: String) -> Self {
2001 SlackDataTableCell::RawText(SlackTableRawTextCell::new(value))
2002 }
2003}
2004
2005impl From<SlackTableRawTextCell> for SlackDataTableCell {
2006 fn from(cell: SlackTableRawTextCell) -> Self {
2007 SlackDataTableCell::RawText(cell)
2008 }
2009}
2010
2011impl From<SlackDataTableRawNumberCell> for SlackDataTableCell {
2012 fn from(cell: SlackDataTableRawNumberCell) -> Self {
2013 SlackDataTableCell::RawNumber(cell)
2014 }
2015}
2016
2017impl From<SlackTableRichTextCell> for SlackDataTableCell {
2018 fn from(cell: SlackTableRichTextCell) -> Self {
2019 SlackDataTableCell::RichText(cell)
2020 }
2021}
2022
2023impl From<SlackDataTableActionCell> for SlackDataTableCell {
2024 fn from(cell: SlackDataTableActionCell) -> Self {
2025 SlackDataTableCell::ActionCell(cell)
2026 }
2027}
2028
2029#[skip_serializing_none]
2032#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2033pub struct SlackDataTableRawNumberCell {
2034 pub value: serde_json::Number,
2035 pub text: String,
2036}
2037
2038#[skip_serializing_none]
2041#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2042pub struct SlackDataTableActionCell {
2043 pub element: SlackDataTableActionElement,
2044 pub fallback: Option<Box<SlackDataTableCell>>,
2045}
2046
2047#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
2048#[serde(tag = "type")]
2049pub enum SlackDataTableActionElement {
2050 #[serde(rename = "button")]
2051 Button(SlackBlockButtonElement),
2052}
2053
2054impl From<SlackBlockButtonElement> for SlackDataTableActionElement {
2055 fn from(element: SlackBlockButtonElement) -> Self {
2056 SlackDataTableActionElement::Button(element)
2057 }
2058}
2059
2060#[skip_serializing_none]
2066#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2067pub struct SlackDataVisualizationBlock {
2068 pub title: String,
2069 pub chart: SlackChart,
2070 pub block_id: Option<SlackBlockId>,
2071}
2072
2073impl From<SlackDataVisualizationBlock> for SlackBlock {
2074 fn from(block: SlackDataVisualizationBlock) -> Self {
2075 SlackBlock::DataVisualization(block)
2076 }
2077}
2078
2079#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
2080#[serde(tag = "type", rename_all = "snake_case")]
2081pub enum SlackChart {
2082 Pie(SlackPieChart),
2083 Bar(SlackSeriesChart),
2084 Area(SlackSeriesChart),
2085 Line(SlackSeriesChart),
2086}
2087
2088#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2090pub struct SlackPieChart {
2091 pub segments: Vec<SlackChartSegment>,
2092}
2093
2094#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2097pub struct SlackSeriesChart {
2098 pub series: Vec<SlackChartDataSeries>,
2099 pub axis_config: SlackChartAxisConfig,
2100}
2101
2102#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2103pub struct SlackChartSegment {
2104 pub label: String,
2105 pub value: serde_json::Number,
2106}
2107
2108#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2109pub struct SlackChartDataSeries {
2110 pub name: String,
2111 pub data: Vec<SlackChartDataPoint>,
2112}
2113
2114#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2115pub struct SlackChartDataPoint {
2116 pub label: String,
2117 pub value: serde_json::Number,
2118}
2119
2120#[skip_serializing_none]
2122#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2123pub struct SlackChartAxisConfig {
2124 pub categories: Vec<String>,
2125 pub x_label: Option<String>,
2126 pub y_label: Option<String>,
2127}
2128
2129#[cfg(test)]
2130mod test {
2131 use super::*;
2132 use crate::blocks::SlackHomeView;
2133
2134 #[test]
2135 fn test_conversation_filter_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2136 let payload = include_str!("./fixtures/slack_conversations_select_with_filter.json");
2137 let block: SlackBlock = serde_json::from_str(payload)?;
2138 match block {
2139 SlackBlock::Section(section) => match section.accessory {
2140 Some(SlackSectionBlockElement::ConversationsSelect(elem)) => {
2141 let filter = elem.filter.expect("filter should be present");
2142 let include = filter.include.expect("include should be present");
2143 assert_eq!(include.len(), 2);
2144 assert_eq!(include[0], SlackConversationFilterInclude::Public);
2145 assert_eq!(include[1], SlackConversationFilterInclude::Private);
2146 assert_eq!(filter.exclude_external_shared_channels, Some(true));
2147 assert_eq!(filter.exclude_bot_users, Some(true));
2148 }
2149 _ => panic!("Expected ConversationsSelect accessory"),
2150 },
2151 _ => panic!("Expected Section block"),
2152 }
2153 Ok(())
2154 }
2155
2156 #[test]
2157 fn test_conversation_filter_serialize() -> Result<(), Box<dyn std::error::Error>> {
2158 let filter = SlackBlockConversationFilter::new()
2159 .with_include(vec![
2160 SlackConversationFilterInclude::Im,
2161 SlackConversationFilterInclude::Mpim,
2162 ])
2163 .with_exclude_bot_users(true);
2164
2165 let json = serde_json::to_value(&filter)?;
2166 assert_eq!(
2167 json,
2168 serde_json::json!({
2169 "include": ["im", "mpim"],
2170 "exclude_bot_users": true
2171 })
2172 );
2173 Ok(())
2174 }
2175
2176 #[test]
2177 fn test_conversation_filter_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2178 let elem = SlackBlockConversationsSelectElement::new()
2179 .with_action_id(SlackActionId("test_action".into()))
2180 .with_filter(
2181 SlackBlockConversationFilter::new()
2182 .with_include(vec![SlackConversationFilterInclude::Public])
2183 .with_exclude_external_shared_channels(true),
2184 );
2185
2186 let json = serde_json::to_string(&elem)?;
2187 let parsed: SlackBlockConversationsSelectElement = serde_json::from_str(&json)?;
2188 assert_eq!(elem, parsed);
2189 Ok(())
2190 }
2191
2192 #[test]
2193 fn test_multi_conversations_select_filter() -> Result<(), Box<dyn std::error::Error>> {
2194 let elem = SlackBlockMultiConversationsSelectElement::new()
2195 .with_action_id(SlackActionId("multi_action".into()))
2196 .with_filter(
2197 SlackBlockConversationFilter::new()
2198 .with_include(vec![
2199 SlackConversationFilterInclude::Public,
2200 SlackConversationFilterInclude::Private,
2201 ])
2202 .with_exclude_bot_users(true),
2203 );
2204
2205 let json = serde_json::to_string(&elem)?;
2206 let parsed: SlackBlockMultiConversationsSelectElement = serde_json::from_str(&json)?;
2207 assert_eq!(elem, parsed);
2208 Ok(())
2209 }
2210
2211 #[test]
2212 fn test_conversation_filter_none_omitted() -> Result<(), Box<dyn std::error::Error>> {
2213 let elem = SlackBlockConversationsSelectElement::new()
2214 .with_action_id(SlackActionId("no_filter".into()));
2215
2216 let json = serde_json::to_value(&elem)?;
2217 assert!(json.get("filter").is_none());
2218 Ok(())
2219 }
2220
2221 #[test]
2222 fn test_slack_image_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2223 let payload = include_str!("./fixtures/slack_image_blocks.json");
2224 let content: SlackMessageContent = serde_json::from_str(payload)?;
2225 let blocks = content.blocks.expect("Blocks should not be empty");
2226 match blocks.first() {
2227 Some(SlackBlock::Section(section)) => match §ion.accessory {
2228 Some(SlackSectionBlockElement::Image(image)) => {
2229 assert_eq!(image.alt_text, "alt text for image");
2230 match &image.image_url_or_file {
2231 SlackImageUrlOrFile::ImageUrl { image_url } => {
2232 assert_eq!(image_url.as_str(), "https://s3-media3.fl.yelpcdn.com/bphoto/c7ed05m9lC2EmA3Aruue7A/o.jpg");
2233 }
2234 SlackImageUrlOrFile::SlackFile { slack_file } => {
2235 panic!("Expected an image URL, not a Slack file: {:?}", slack_file);
2236 }
2237 }
2238 }
2239 _ => panic!("Expected a section block with an image accessory"),
2240 },
2241 _ => panic!("Expected a section block"),
2242 }
2243 Ok(())
2244 }
2245
2246 #[test]
2247 fn test_rich_text_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2248 let payload = include_str!("./fixtures/slack_rich_text_block.json");
2249 let block: SlackBlock = serde_json::from_str(payload)?;
2250
2251 let rich = match block {
2252 SlackBlock::RichText(r) => r,
2253 _ => panic!("Expected a RichText block"),
2254 };
2255
2256 assert_eq!(rich.block_id, Some(SlackBlockId("test_block".into())));
2257 assert_eq!(rich.elements.len(), 4);
2258
2259 let section = match &rich.elements[0] {
2261 SlackRichTextElement::Section(s) => s,
2262 _ => panic!("Expected a Section element"),
2263 };
2264 assert_eq!(section.elements.len(), 7);
2265
2266 let text = match §ion.elements[0] {
2268 SlackRichTextInlineElement::Text(t) => t,
2269 _ => panic!("Expected a Text element"),
2270 };
2271 assert_eq!(text.text, "Hello ");
2272 assert_eq!(text.style.as_ref().and_then(|s| s.bold), Some(true));
2273
2274 assert!(matches!(
2276 §ion.elements[1],
2277 SlackRichTextInlineElement::User(_)
2278 ));
2279
2280 let emoji = match §ion.elements[4] {
2282 SlackRichTextInlineElement::Emoji(e) => e,
2283 _ => panic!("Expected an Emoji element"),
2284 };
2285 assert_eq!(emoji.name, SlackEmojiName::new("wave".into()));
2286
2287 let list = match &rich.elements[1] {
2289 SlackRichTextElement::List(l) => l,
2290 _ => panic!("Expected a List element"),
2291 };
2292 assert_eq!(list.style, SlackRichTextListStyle::Bullet);
2293 assert_eq!(list.elements.len(), 2);
2294
2295 assert!(matches!(
2297 &list.elements[0],
2298 SlackRichTextListElement::Section(_)
2299 ));
2300 assert!(matches!(
2301 &list.elements[1],
2302 SlackRichTextListElement::Section(_)
2303 ));
2304
2305 assert!(matches!(
2307 &rich.elements[2],
2308 SlackRichTextElement::Preformatted(_)
2309 ));
2310
2311 assert!(matches!(&rich.elements[3], SlackRichTextElement::Quote(_)));
2313
2314 Ok(())
2315 }
2316
2317 #[test]
2318 fn test_rich_text_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2319 let payload = include_str!("./fixtures/slack_rich_text_block.json");
2320 let block: SlackBlock = serde_json::from_str(payload)?;
2321 let serialized = serde_json::to_string(&block)?;
2322 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2323 assert_eq!(block, block2);
2324 Ok(())
2325 }
2326
2327 #[test]
2328 fn test_slack_table_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2329 let payload = include_str!("./fixtures/slack_table_block.json");
2330 let block: SlackBlock = serde_json::from_str(payload)?;
2331
2332 let table = match block {
2333 SlackBlock::Table(t) => t,
2334 _ => panic!("Expected a Table block"),
2335 };
2336
2337 assert_eq!(table.block_id, Some(SlackBlockId("table_block_1".into())));
2338 assert_eq!(table.rows.len(), 2);
2339 assert_eq!(table.rows[0].len(), 2);
2340
2341 match &table.rows[0][0] {
2343 SlackTableCell::RawText(c) => assert_eq!(c.text, "Header A"),
2344 _ => panic!("Expected RawText cell"),
2345 }
2346
2347 match &table.rows[1][1] {
2349 SlackTableCell::RichText(c) => assert_eq!(c.elements.len(), 1),
2350 _ => panic!("Expected RichText cell"),
2351 }
2352
2353 let settings = table
2354 .column_settings
2355 .expect("column_settings should be present");
2356 assert_eq!(settings.len(), 2);
2357 assert_eq!(settings[0].is_wrapped, Some(true));
2358 assert_eq!(settings[1].align, Some(SlackTableColumnAlign::Right));
2359
2360 Ok(())
2361 }
2362
2363 #[test]
2364 fn test_slack_table_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2365 let payload = include_str!("./fixtures/slack_table_block.json");
2366 let block: SlackBlock = serde_json::from_str(payload)?;
2367 let serialized = serde_json::to_string(&block)?;
2368 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2369 assert_eq!(block, block2);
2370 Ok(())
2371 }
2372
2373 #[test]
2374 fn test_slack_task_card_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2375 let payload = include_str!("./fixtures/slack_task_card_block.json");
2376 let block: SlackBlock = serde_json::from_str(payload)?;
2377
2378 let task_card = match block {
2379 SlackBlock::TaskCard(t) => t,
2380 _ => panic!("Expected a TaskCard block"),
2381 };
2382
2383 assert_eq!(task_card.task_id, SlackTaskId("task_1".into()));
2384 assert_eq!(task_card.title, "Fetching weather data");
2385 assert_eq!(
2386 task_card.block_id,
2387 Some(SlackBlockId("task_card_block_1".into()))
2388 );
2389 assert_eq!(task_card.status, Some(SlackTaskCardStatus::InProgress));
2390
2391 let output = task_card.output.expect("output should be present");
2392 let SlackRichTextInlineContent::RichText(output_block) = output;
2393 assert_eq!(output_block.elements.len(), 1);
2394
2395 let sources = task_card.sources.expect("sources should be present");
2396 assert_eq!(sources.len(), 2);
2397 match &sources[0] {
2398 SlackTaskCardSource::Url(u) => assert_eq!(u.text, "weather.com"),
2399 }
2400
2401 Ok(())
2402 }
2403
2404 #[test]
2405 fn test_slack_task_card_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2406 let payload = include_str!("./fixtures/slack_task_card_block.json");
2407 let block: SlackBlock = serde_json::from_str(payload)?;
2408 let serialized = serde_json::to_string(&block)?;
2409 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2410 assert_eq!(block, block2);
2411 Ok(())
2412 }
2413
2414 #[test]
2415 fn test_slack_task_card_block_serializes_icon_and_hide_title(
2416 ) -> Result<(), Box<dyn std::error::Error>> {
2417 let task_card = SlackTaskCardBlock::new(SlackTaskId("task_1".into()), "Title".into())
2418 .with_icon(SlackTaskCardIcon::new("check".into()))
2419 .with_hide_title(true);
2420 let json = serde_json::to_value(&task_card)?;
2421 assert_eq!(
2422 json["icon"],
2423 serde_json::json!({"type": "icon", "name": "check"})
2424 );
2425 assert_eq!(json["hide_title"], serde_json::json!(true));
2426 Ok(())
2427 }
2428
2429 #[test]
2430 fn test_slack_alert_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2431 let payload = include_str!("./fixtures/slack_alert_block.json");
2432 let block: SlackBlock = serde_json::from_str(payload)?;
2433 match block {
2434 SlackBlock::Alert(alert) => {
2435 assert_eq!(alert.level, Some(SlackAlertLevel::Warning));
2436 }
2437 _ => panic!("Expected Alert block"),
2438 }
2439 Ok(())
2440 }
2441
2442 #[test]
2443 fn test_slack_alert_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2444 let payload = include_str!("./fixtures/slack_alert_block.json");
2445 let block: SlackBlock = serde_json::from_str(payload)?;
2446 let serialized = serde_json::to_string(&block)?;
2447 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2448 assert_eq!(block, block2);
2449 Ok(())
2450 }
2451
2452 #[test]
2453 fn test_slack_card_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2454 let payload = include_str!("./fixtures/slack_card_block.json");
2455 let block: SlackBlock = serde_json::from_str(payload)?;
2456 match block {
2457 SlackBlock::Card(card) => {
2458 assert!(card.hero_image.is_some());
2459 assert!(card.actions.is_some());
2460 let actions = card.actions.unwrap();
2461 assert_eq!(actions.len(), 1);
2462 match &actions[0] {
2463 SlackCardActionBlockElement::Button(btn) => {
2464 assert_eq!(btn.style, Some(SlackBlockButtonStyle::Primary));
2465 }
2466 }
2467 }
2468 _ => panic!("Expected Card block"),
2469 }
2470 Ok(())
2471 }
2472
2473 #[test]
2474 fn test_slack_card_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2475 let payload = include_str!("./fixtures/slack_card_block.json");
2476 let block: SlackBlock = serde_json::from_str(payload)?;
2477 let serialized = serde_json::to_string(&block)?;
2478 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2479 assert_eq!(block, block2);
2480 Ok(())
2481 }
2482
2483 #[test]
2484 fn test_slack_context_actions_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2485 let payload = include_str!("./fixtures/slack_context_actions_block.json");
2486 let block: SlackBlock = serde_json::from_str(payload)?;
2487 match block {
2488 SlackBlock::ContextActions(ctx) => {
2489 assert_eq!(ctx.elements.len(), 1);
2490 match &ctx.elements[0] {
2491 SlackContextActionBlockElement::IconButton(btn) => {
2492 assert_eq!(btn.icon, "trash");
2493 }
2494 _ => panic!("Expected IconButton element"),
2495 }
2496 }
2497 _ => panic!("Expected ContextActions block"),
2498 }
2499 Ok(())
2500 }
2501
2502 #[test]
2503 fn test_slack_context_actions_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2504 let payload = include_str!("./fixtures/slack_context_actions_block.json");
2505 let block: SlackBlock = serde_json::from_str(payload)?;
2506 let serialized = serde_json::to_string(&block)?;
2507 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2508 assert_eq!(block, block2);
2509 Ok(())
2510 }
2511
2512 #[test]
2513 fn test_slack_workflow_button_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2514 let payload = include_str!("./fixtures/slack_workflow_button.json");
2515 let block: SlackBlock = serde_json::from_str(payload)?;
2516 match block {
2517 SlackBlock::Actions(actions) => {
2518 assert_eq!(actions.elements.len(), 1);
2519 match &actions.elements[0] {
2520 SlackActionBlockElement::WorkflowButton(btn) => {
2521 assert_eq!(btn.style, Some(SlackBlockButtonStyle::Primary));
2522 let params = btn
2523 .workflow
2524 .trigger
2525 .customizable_input_parameters
2526 .as_ref()
2527 .expect("params should be present");
2528 assert_eq!(params.len(), 1);
2529 assert_eq!(params[0].name, "user_input");
2530 }
2531 _ => panic!("Expected WorkflowButton element"),
2532 }
2533 }
2534 _ => panic!("Expected Actions block"),
2535 }
2536 Ok(())
2537 }
2538
2539 #[test]
2540 fn test_slack_workflow_button_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2541 let payload = include_str!("./fixtures/slack_workflow_button.json");
2542 let block: SlackBlock = serde_json::from_str(payload)?;
2543 let serialized = serde_json::to_string(&block)?;
2544 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2545 assert_eq!(block, block2);
2546 Ok(())
2547 }
2548
2549 #[test]
2550 fn test_rich_text_message_mention_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2551 let payload = serde_json::json!({
2552 "type": "rich_text",
2553 "block_id": "msgm1",
2554 "elements": [
2555 {
2556 "type": "rich_text_section",
2557 "elements": [
2558 {
2559 "type": "message_mention",
2560 "url": "https://acme.slack.com/archives/C12345678/p1784153496441789?thread_ts=1784153496.441789&cid=C12345678",
2561 "text": "a message",
2562 "channel_id": "C12345678",
2563 "author_id": "U12345678",
2564 "message_ts": "1784153496.441789",
2565 "thread_ts": "1784153496.441789"
2566 }
2567 ]
2568 }
2569 ]
2570 })
2571 .to_string();
2572 let block: SlackBlock = serde_json::from_str(&payload)?;
2573 match block {
2574 SlackBlock::RichText(rich_text) => match &rich_text.elements[0] {
2575 SlackRichTextElement::Section(section) => match §ion.elements[0] {
2576 SlackRichTextInlineElement::MessageMention(mention) => {
2577 assert_eq!(mention.channel_id, Some(SlackChannelId("C12345678".into())));
2578 assert_eq!(mention.author_id, Some(SlackUserId("U12345678".into())));
2579 assert_eq!(
2580 mention.message_ts,
2581 Some(SlackTs("1784153496.441789".into()))
2582 );
2583 }
2584 other => panic!("Expected MessageMention element, got {other:?}"),
2585 },
2586 _ => panic!("Expected Section element"),
2587 },
2588 _ => panic!("Expected RichText block"),
2589 }
2590 Ok(())
2591 }
2592
2593 #[test]
2594 fn test_rich_text_unknown_inline_element_deserialize() -> Result<(), Box<dyn std::error::Error>>
2595 {
2596 let payload = serde_json::json!({
2597 "type": "rich_text_section",
2598 "elements": [
2599 {
2600 "type": "some_future_element",
2601 "foo": "bar"
2602 }
2603 ]
2604 })
2605 .to_string();
2606 let section: SlackRichTextElement = serde_json::from_str(&payload)?;
2607 match section {
2608 SlackRichTextElement::Section(section) => match §ion.elements[0] {
2609 SlackRichTextInlineElement::Unknown(value) => {
2610 assert_eq!(value["type"], "some_future_element");
2611 }
2612 other => panic!("Expected Unknown element, got {other:?}"),
2613 },
2614 _ => panic!("Expected Section element"),
2615 }
2616 Ok(())
2617 }
2618
2619 #[test]
2620 fn rich_text_str_converts_to_text_inline_element() -> Result<(), Box<dyn std::error::Error>> {
2621 let element: SlackRichTextInlineElement = "hi".into();
2622 assert_eq!(
2623 serde_json::to_value(&element)?,
2624 serde_json::json!({"type": "text", "text": "hi"})
2625 );
2626
2627 let owned: SlackRichTextInlineElement = "hi".to_string().into();
2628 assert_eq!(
2629 serde_json::to_value(&owned)?,
2630 serde_json::to_value(&element)?
2631 );
2632 Ok(())
2633 }
2634
2635 #[test]
2636 fn rich_text_leaf_elements_convert_to_inline_elements() -> Result<(), Box<dyn std::error::Error>>
2637 {
2638 let text: SlackRichTextInlineElement = SlackRichTextText::new("t".to_string()).into();
2639 assert_eq!(
2640 serde_json::to_value(&text)?,
2641 serde_json::json!({"type": "text", "text": "t"})
2642 );
2643
2644 let link: SlackRichTextInlineElement =
2645 SlackRichTextLink::new(SlackRelaxedUrl("https://example.com".into())).into();
2646 assert_eq!(
2647 serde_json::to_value(&link)?,
2648 serde_json::json!({"type": "link", "url": "https://example.com"})
2649 );
2650
2651 let user: SlackRichTextInlineElement =
2652 SlackRichTextUser::new(SlackUserId("U1".into())).into();
2653 assert_eq!(
2654 serde_json::to_value(&user)?,
2655 serde_json::json!({"type": "user", "user_id": "U1"})
2656 );
2657
2658 let channel: SlackRichTextInlineElement =
2659 SlackRichTextChannel::new(SlackChannelId("C1".into())).into();
2660 assert_eq!(
2661 serde_json::to_value(&channel)?,
2662 serde_json::json!({"type": "channel", "channel_id": "C1"})
2663 );
2664
2665 let usergroup: SlackRichTextInlineElement =
2666 SlackRichTextUserGroup::new(SlackUserGroupId("G1".into())).into();
2667 assert_eq!(
2668 serde_json::to_value(&usergroup)?,
2669 serde_json::json!({"type": "usergroup", "usergroup_id": "G1"})
2670 );
2671
2672 let emoji: SlackRichTextInlineElement =
2673 SlackRichTextEmoji::new(SlackEmojiName("wave".into())).into();
2674 assert_eq!(
2675 serde_json::to_value(&emoji)?,
2676 serde_json::json!({"type": "emoji", "name": "wave"})
2677 );
2678
2679 let date: SlackRichTextInlineElement = SlackRichTextDate::new(
2680 SlackDateTime("2020-01-01T00:42:42Z".parse::<SlackUtcDateTime>()?),
2681 "{date_short}".to_string(),
2682 )
2683 .into();
2684 assert_eq!(
2685 serde_json::to_value(&date)?,
2686 serde_json::json!({"type": "date", "timestamp": 1_577_839_362_i64, "format": "{date_short}"})
2687 );
2688
2689 let broadcast: SlackRichTextInlineElement =
2690 SlackRichTextBroadcast::new(SlackRichTextBroadcastRange::Here).into();
2691 assert_eq!(
2692 serde_json::to_value(&broadcast)?,
2693 serde_json::json!({"type": "broadcast", "range": "here"})
2694 );
2695
2696 let color: SlackRichTextInlineElement =
2697 SlackRichTextColor::new("#ff0000".to_string()).into();
2698 assert_eq!(
2699 serde_json::to_value(&color)?,
2700 serde_json::json!({"type": "color", "value": "#ff0000"})
2701 );
2702
2703 let mention: SlackRichTextInlineElement = SlackRichTextMessageMention::new(
2704 SlackRelaxedUrl("https://example.com/archives/C1/p1".into()),
2705 )
2706 .into();
2707 assert_eq!(
2708 serde_json::to_value(&mention)?,
2709 serde_json::json!({"type": "message_mention", "url": "https://example.com/archives/C1/p1"})
2710 );
2711
2712 Ok(())
2713 }
2714
2715 #[test]
2716 fn rich_text_list_accepts_str_items() -> Result<(), Box<dyn std::error::Error>> {
2717 let payload = include_str!("./fixtures/slack_rich_text_block.json");
2718 let block: SlackBlock = serde_json::from_str(payload)?;
2719 let expected_list = match block {
2720 SlackBlock::RichText(r) => match &r.elements[1] {
2721 SlackRichTextElement::List(l) => l.clone(),
2722 other => panic!("Expected List element, got {other:?}"),
2723 },
2724 _ => panic!("Expected RichText block"),
2725 };
2726
2727 let list = SlackRichTextList::new(
2728 SlackRichTextListStyle::Bullet,
2729 vec!["Item one".into(), "Item two".into()],
2730 )
2731 .with_indent(0);
2732
2733 assert_eq!(list, expected_list);
2734 Ok(())
2735 }
2736
2737 #[test]
2738 fn rich_text_style_helpers_merge_flags() -> Result<(), Box<dyn std::error::Error>> {
2739 let bold = SlackRichTextText::new("hi".to_string()).bold();
2740 let json = serde_json::to_value(&bold)?;
2741 assert_eq!(json["style"], serde_json::json!({"bold": true}));
2742
2743 let both = SlackRichTextText::new("hi".to_string())
2744 .with_style(SlackRichTextStyle::new().with_italic(true))
2745 .bold();
2746 let json2 = serde_json::to_value(&both)?;
2747 assert_eq!(
2748 json2["style"],
2749 serde_json::json!({"bold": true, "italic": true})
2750 );
2751
2752 let struck = SlackRichTextText::new("hi".to_string()).strike();
2753 assert_eq!(
2754 serde_json::to_value(&struck)?["style"],
2755 serde_json::json!({"strike": true})
2756 );
2757
2758 let coded = SlackRichTextText::new("hi".to_string()).code();
2759 assert_eq!(
2760 serde_json::to_value(&coded)?["style"],
2761 serde_json::json!({"code": true})
2762 );
2763
2764 Ok(())
2765 }
2766
2767 #[test]
2768 fn table_block_builds_from_str_cells() -> Result<(), Box<dyn std::error::Error>> {
2769 let payload = include_str!("./fixtures/slack_table_block.json");
2770 let expected: serde_json::Value = serde_json::from_str(payload)?;
2771
2772 let block: SlackBlock = SlackTableBlock::new(vec![
2773 vec!["Header A".into(), "Header B".into()],
2774 vec![
2775 "Data 1A".into(),
2776 SlackTableRichTextCell::new(vec![SlackRichTextSection::new(vec![
2777 SlackRichTextLink::new(SlackRelaxedUrl("https://slack.com".into()))
2778 .with_text("Data 1B".to_string())
2779 .into(),
2780 ])
2781 .into()])
2782 .into(),
2783 ],
2784 ])
2785 .with_block_id(SlackBlockId("table_block_1".into()))
2786 .with_column_settings(vec![
2787 SlackTableColumnSetting::new().with_is_wrapped(true),
2788 SlackTableColumnSetting::new().with_align(SlackTableColumnAlign::Right),
2789 ])
2790 .into();
2791
2792 assert_eq!(serde_json::to_value(&block)?, expected);
2793 Ok(())
2794 }
2795
2796 #[test]
2797 fn rich_text_block_builds_fixture_with_conversions() -> Result<(), Box<dyn std::error::Error>> {
2798 let payload = include_str!("./fixtures/slack_rich_text_block.json");
2799 let expected: serde_json::Value = serde_json::from_str(payload)?;
2800
2801 let section: SlackRichTextElement = SlackRichTextSection::new(vec![
2802 SlackRichTextText::new("Hello ".to_string()).bold().into(),
2803 SlackRichTextUser::new(SlackUserId("U123ABC456".into())).into(),
2804 "! Check out ".into(),
2805 SlackRichTextLink::new(SlackRelaxedUrl("https://example.com".into()))
2806 .with_text("this link".to_string())
2807 .with_style(SlackRichTextStyle::new().with_italic(true))
2808 .into(),
2809 SlackRichTextEmoji::new(SlackEmojiName("wave".into())).into(),
2810 SlackRichTextChannel::new(SlackChannelId("C123ABC456".into())).into(),
2811 SlackRichTextBroadcast::new(SlackRichTextBroadcastRange::Here).into(),
2812 ])
2813 .into();
2814
2815 let list: SlackRichTextElement = SlackRichTextList::new(
2816 SlackRichTextListStyle::Bullet,
2817 vec!["Item one".into(), "Item two".into()],
2818 )
2819 .with_indent(0)
2820 .into();
2821
2822 let preformatted: SlackRichTextElement =
2823 SlackRichTextPreformatted::new(vec![SlackRichTextText::new(
2824 "fn main() {}\n".to_string(),
2825 )
2826 .into()])
2827 .with_border(1)
2828 .into();
2829
2830 let quote: SlackRichTextElement =
2831 SlackRichTextQuote::new(vec![SlackRichTextText::new("A wise quote".to_string())
2832 .italic()
2833 .into()])
2834 .into();
2835
2836 let block: SlackBlock = SlackRichTextBlock::new(vec![section, list, preformatted, quote])
2837 .with_block_id(SlackBlockId("test_block".into()))
2838 .into();
2839
2840 assert_eq!(serde_json::to_value(&block)?, expected);
2841 Ok(())
2842 }
2843
2844 #[test]
2845 fn unknown_block_type_round_trips_verbatim() -> Result<(), Box<dyn std::error::Error>> {
2846 let payload = serde_json::json!([
2847 { "type": "some_future_block", "x": 1 },
2848 { "type": "divider", "block_id": "d1" }
2849 ]);
2850 let blocks: Vec<SlackBlock> = serde_json::from_value(payload.clone())?;
2851 match &blocks[0] {
2852 SlackBlock::Unknown(value) => assert_eq!(value, &payload[0]),
2853 other => panic!("Expected Unknown block, got {other:?}"),
2854 }
2855 assert_eq!(
2856 blocks[1],
2857 SlackBlock::Divider(SlackDividerBlock::new().with_block_id("d1".into()))
2858 );
2859 assert_eq!(serde_json::to_value(&blocks)?, payload);
2860 Ok(())
2861 }
2862
2863 #[test]
2864 fn malformed_known_block_type_falls_back_to_unknown() -> Result<(), Box<dyn std::error::Error>>
2865 {
2866 let payload = serde_json::json!({ "type": "header" });
2868 let block: SlackBlock = serde_json::from_value(payload.clone())?;
2869 assert_eq!(block, SlackBlock::Unknown(payload));
2870 Ok(())
2871 }
2872
2873 fn round_trip(payload: &str) -> Result<SlackBlock, Box<dyn std::error::Error>> {
2876 let expected: serde_json::Value = serde_json::from_str(payload)?;
2877 let block: SlackBlock = serde_json::from_str(payload)?;
2878 assert_eq!(serde_json::to_value(&block)?, expected);
2879 Ok(block)
2880 }
2881
2882 #[test]
2883 fn buttons_without_action_id_stay_a_typed_actions_block(
2884 ) -> Result<(), Box<dyn std::error::Error>> {
2885 let block = round_trip(include_str!(
2886 "./fixtures/slack_actions_block_without_action_id.json"
2887 ))?;
2888 let SlackBlock::Actions(actions) = block else {
2889 panic!("Expected an Actions block, got {block:?}");
2890 };
2891 assert_eq!(actions.elements.len(), 2);
2892 assert!(actions
2893 .elements
2894 .iter()
2895 .all(|element| matches!(element, SlackActionBlockElement::Button(_))));
2896 Ok(())
2897 }
2898
2899 #[test]
2900 fn slack_feedback_buttons_example_is_a_typed_context_actions_block(
2901 ) -> Result<(), Box<dyn std::error::Error>> {
2902 let message: serde_json::Value = serde_json::from_str(include_str!(
2903 "./fixtures/slack_feedback_buttons_message.json"
2904 ))?;
2905 let block = round_trip(&message["blocks"][0].to_string())?;
2906 let SlackBlock::ContextActions(context_actions) = block else {
2907 panic!("Expected a ContextActions block, got {block:?}");
2908 };
2909 assert!(matches!(
2910 context_actions.elements.as_slice(),
2911 [SlackContextActionBlockElement::FeedbackButtons(_)]
2912 ));
2913 Ok(())
2914 }
2915
2916 #[test]
2917 fn container_block_fixture_round_trips() -> Result<(), Box<dyn std::error::Error>> {
2918 let block = round_trip(include_str!("./fixtures/slack_container_block.json"))?;
2919 let SlackBlock::Container(container) = block else {
2920 panic!("Expected a Container block, got {block:?}");
2921 };
2922 assert_eq!(container.is_collapsible, Some(true));
2923 assert_eq!(container.child_blocks.len(), 6);
2924 assert!(matches!(container.child_blocks[0], SlackBlock::Section(_)));
2925 assert!(matches!(container.child_blocks[5], SlackBlock::Actions(_)));
2926 Ok(())
2927 }
2928
2929 #[test]
2930 fn plan_block_fixture_round_trips() -> Result<(), Box<dyn std::error::Error>> {
2931 let block = round_trip(include_str!("./fixtures/slack_plan_block.json"))?;
2932 let SlackBlock::Plan(plan) = block else {
2933 panic!("Expected a Plan block, got {block:?}");
2934 };
2935 assert_eq!(plan.title, "Thinking completed");
2936 assert_eq!(plan.tasks.len(), 3);
2937 assert_eq!(plan.tasks[1].status, Some(SlackTaskCardStatus::Pending));
2938 assert!(plan.tasks[2].output.is_some());
2939 Ok(())
2940 }
2941
2942 #[test]
2943 fn data_table_block_fixture_round_trips() -> Result<(), Box<dyn std::error::Error>> {
2944 let block = round_trip(include_str!("./fixtures/slack_data_table_block.json"))?;
2945 let SlackBlock::DataTable(table) = block else {
2946 panic!("Expected a DataTable block, got {block:?}");
2947 };
2948 assert_eq!(table.caption, "A Fabulous Table");
2949 assert_eq!(table.rows.len(), 4);
2950 assert_eq!(table.rows[0][0], "Name".into());
2951 assert!(matches!(table.rows[1][2], SlackDataTableCell::RichText(_)));
2952 Ok(())
2953 }
2954
2955 #[test]
2956 fn data_table_number_and_action_cells_round_trip() -> Result<(), Box<dyn std::error::Error>> {
2957 let payload = serde_json::json!([
2959 { "type": "raw_number", "value": 1.5, "text": "1.5" },
2960 {
2961 "type": "action_cell",
2962 "element": {
2963 "type": "button",
2964 "text": { "type": "plain_text", "text": "Mark done" },
2965 "action_id": "mark_done",
2966 "value": "task_123"
2967 },
2968 "fallback": { "type": "raw_text", "text": "Open" }
2969 },
2970 { "type": "sparkline", "points": [1, 2] }
2971 ]);
2972 let cells: Vec<SlackDataTableCell> = serde_json::from_value(payload.clone())?;
2973 assert!(matches!(cells[0], SlackDataTableCell::RawNumber(_)));
2974 let SlackDataTableCell::ActionCell(action) = &cells[1] else {
2975 panic!("Expected an ActionCell, got {:?}", cells[1]);
2976 };
2977 assert_eq!(action.fallback.as_deref(), Some(&"Open".into()));
2978 assert!(matches!(cells[2], SlackDataTableCell::Unknown(_)));
2979 assert_eq!(serde_json::to_value(&cells)?, payload);
2980 Ok(())
2981 }
2982
2983 #[test]
2984 fn data_visualization_pie_fixture_round_trips() -> Result<(), Box<dyn std::error::Error>> {
2985 let block = round_trip(include_str!(
2986 "./fixtures/slack_data_visualization_pie_block.json"
2987 ))?;
2988 let SlackBlock::DataVisualization(viz) = block else {
2989 panic!("Expected a DataVisualization block, got {block:?}");
2990 };
2991 let SlackChart::Pie(pie) = viz.chart else {
2992 panic!("Expected a pie chart, got {:?}", viz.chart);
2993 };
2994 assert_eq!(pie.segments.len(), 4);
2995 assert_eq!(pie.segments[0].value, 45.into());
2996 Ok(())
2997 }
2998
2999 #[test]
3000 fn data_visualization_bar_fixture_round_trips() -> Result<(), Box<dyn std::error::Error>> {
3001 let block = round_trip(include_str!(
3002 "./fixtures/slack_data_visualization_bar_block.json"
3003 ))?;
3004 let SlackBlock::DataVisualization(viz) = block else {
3005 panic!("Expected a DataVisualization block, got {block:?}");
3006 };
3007 let SlackChart::Bar(bar) = viz.chart else {
3008 panic!("Expected a bar chart, got {:?}", viz.chart);
3009 };
3010 assert_eq!(bar.series[0].data.len(), 5);
3011 assert_eq!(bar.axis_config.categories.len(), 5);
3012 assert_eq!(
3013 bar.axis_config.y_label.as_deref(),
3014 Some("Percentage of Tastiness")
3015 );
3016 Ok(())
3017 }
3018
3019 #[test]
3020 fn stream_chunk_blocks_carries_new_block_types() -> Result<(), Box<dyn std::error::Error>> {
3021 let payload = serde_json::json!({
3022 "type": "blocks",
3023 "blocks": [
3024 { "type": "plan", "title": "Plan", "tasks": [] },
3025 { "type": "some_future_block", "x": 1 }
3026 ]
3027 });
3028 let chunk: crate::api::SlackStreamChunk = serde_json::from_value(payload.clone())?;
3029 let crate::api::SlackStreamChunk::Blocks { blocks } = &chunk else {
3030 panic!("Expected a Blocks chunk, got {chunk:?}");
3031 };
3032 assert!(matches!(blocks[0], SlackBlock::Plan(_)));
3033 assert!(matches!(blocks[1], SlackBlock::Unknown(_)));
3034 assert_eq!(serde_json::to_value(&chunk)?, payload);
3035 Ok(())
3036 }
3037
3038 #[test]
3039 fn header_block_keeps_its_level() -> Result<(), Box<dyn std::error::Error>> {
3040 let payload = serde_json::json!({
3041 "type": "header",
3042 "text": { "type": "plain_text", "text": "Budget", "emoji": true },
3043 "level": 2
3044 });
3045 let block: SlackBlock = serde_json::from_value(payload.clone())?;
3046 let SlackBlock::Header(header) = &block else {
3047 panic!("Expected a header block, got {block:?}");
3048 };
3049 assert_eq!(header.level, Some(SlackHeaderLevel(2)));
3050 assert_eq!(serde_json::to_value(&block)?, payload);
3051 let plain: SlackBlock = SlackHeaderBlock::new(crate::pt!("Budget")).into();
3052 assert!(serde_json::to_value(&plain)?.get("level").is_none());
3053 Ok(())
3054 }
3055}