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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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 action_id: SlackActionId,
1834 pub value: String,
1835 pub text: SlackBlockPlainTextOnly,
1836 pub confirm: Option<SlackBlockConfirmItem>,
1837}
1838
1839#[skip_serializing_none]
1840#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1841pub struct SlackBlockFeedbackButtonsElement {
1842 pub action_id: SlackActionId,
1843 pub positive: SlackFeedbackButtonItem,
1844 pub negative: SlackFeedbackButtonItem,
1845}
1846
1847#[skip_serializing_none]
1848#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1849pub struct SlackBlockIconButtonElement {
1850 pub action_id: SlackActionId,
1851 pub icon: String,
1852 pub text: SlackBlockPlainTextOnly,
1853 pub value: Option<String>,
1854 pub confirm: Option<SlackBlockConfirmItem>,
1855 pub accessibility_label: Option<SlackAccessibilityLabel>,
1856 pub visible_to_user_ids: Option<Vec<SlackUserId>>,
1857}
1858
1859#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1860#[serde(tag = "type")]
1861pub enum SlackContextActionBlockElement {
1862 #[serde(rename = "feedback_buttons")]
1863 FeedbackButtons(SlackBlockFeedbackButtonsElement),
1864 #[serde(rename = "icon_button")]
1865 IconButton(SlackBlockIconButtonElement),
1866}
1867
1868impl From<SlackBlockFeedbackButtonsElement> for SlackContextActionBlockElement {
1869 fn from(element: SlackBlockFeedbackButtonsElement) -> Self {
1870 SlackContextActionBlockElement::FeedbackButtons(element)
1871 }
1872}
1873
1874impl From<SlackBlockIconButtonElement> for SlackContextActionBlockElement {
1875 fn from(element: SlackBlockIconButtonElement) -> Self {
1876 SlackContextActionBlockElement::IconButton(element)
1877 }
1878}
1879
1880#[skip_serializing_none]
1881#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1882pub struct SlackContextActionsBlock {
1883 pub block_id: Option<SlackBlockId>,
1884 pub elements: Vec<SlackContextActionBlockElement>,
1885}
1886
1887impl From<SlackContextActionsBlock> for SlackBlock {
1888 fn from(block: SlackContextActionsBlock) -> Self {
1889 SlackBlock::ContextActions(block)
1890 }
1891}
1892
1893#[skip_serializing_none]
1904#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1905pub struct SlackContainerBlock {
1906 pub block_id: Option<SlackBlockId>,
1907 pub title: Option<SlackBlockPlainTextOnly>,
1908 pub rich_text_title: Option<SlackRichTextInlineContent>,
1909 pub subtitle: Option<SlackBlockText>,
1910 pub child_blocks: Vec<SlackBlock>,
1911 pub width: Option<SlackContainerWidth>,
1912 pub icon: Option<SlackCardImageElement>,
1913 pub is_collapsible: Option<bool>,
1914 pub default_collapsed: Option<bool>,
1915 pub has_header_divider: Option<bool>,
1916}
1917
1918impl From<SlackContainerBlock> for SlackBlock {
1919 fn from(block: SlackContainerBlock) -> Self {
1920 SlackBlock::Container(block)
1921 }
1922}
1923
1924#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1927#[serde(rename_all = "snake_case")]
1928pub enum SlackContainerWidth {
1929 Narrow,
1930 Standard,
1931 Wide,
1932 Full,
1933}
1934
1935#[skip_serializing_none]
1943#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1944pub struct SlackPlanBlock {
1945 pub title: String,
1946 pub tasks: Vec<SlackTaskCardBlock>,
1947 pub block_id: Option<SlackBlockId>,
1948}
1949
1950impl From<SlackPlanBlock> for SlackBlock {
1951 fn from(block: SlackPlanBlock) -> Self {
1952 SlackBlock::Plan(block)
1953 }
1954}
1955
1956#[skip_serializing_none]
1963#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
1964pub struct SlackDataTableBlock {
1965 pub caption: String,
1966 pub rows: Vec<Vec<SlackDataTableCell>>,
1967 pub block_id: Option<SlackBlockId>,
1968 pub page_size: Option<u32>,
1969 pub row_header_column_index: Option<u32>,
1970}
1971
1972impl From<SlackDataTableBlock> for SlackBlock {
1973 fn from(block: SlackDataTableBlock) -> Self {
1974 SlackBlock::DataTable(block)
1975 }
1976}
1977
1978#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1979#[serde(tag = "type")]
1980pub enum SlackDataTableCell {
1981 #[serde(rename = "raw_text")]
1982 RawText(SlackTableRawTextCell),
1983 #[serde(rename = "raw_number")]
1984 RawNumber(SlackDataTableRawNumberCell),
1985 #[serde(rename = "rich_text")]
1986 RichText(SlackTableRichTextCell),
1987 #[serde(rename = "action_cell")]
1988 ActionCell(SlackDataTableActionCell),
1989 #[serde(untagged)]
1990 Unknown(serde_json::Value),
1991}
1992
1993impl From<&str> for SlackDataTableCell {
1995 fn from(value: &str) -> Self {
1996 SlackDataTableCell::RawText(SlackTableRawTextCell::new(value.to_string()))
1997 }
1998}
1999
2000impl From<String> for SlackDataTableCell {
2001 fn from(value: String) -> Self {
2002 SlackDataTableCell::RawText(SlackTableRawTextCell::new(value))
2003 }
2004}
2005
2006impl From<SlackTableRawTextCell> for SlackDataTableCell {
2007 fn from(cell: SlackTableRawTextCell) -> Self {
2008 SlackDataTableCell::RawText(cell)
2009 }
2010}
2011
2012impl From<SlackDataTableRawNumberCell> for SlackDataTableCell {
2013 fn from(cell: SlackDataTableRawNumberCell) -> Self {
2014 SlackDataTableCell::RawNumber(cell)
2015 }
2016}
2017
2018impl From<SlackTableRichTextCell> for SlackDataTableCell {
2019 fn from(cell: SlackTableRichTextCell) -> Self {
2020 SlackDataTableCell::RichText(cell)
2021 }
2022}
2023
2024impl From<SlackDataTableActionCell> for SlackDataTableCell {
2025 fn from(cell: SlackDataTableActionCell) -> Self {
2026 SlackDataTableCell::ActionCell(cell)
2027 }
2028}
2029
2030#[skip_serializing_none]
2033#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2034pub struct SlackDataTableRawNumberCell {
2035 pub value: serde_json::Number,
2036 pub text: String,
2037}
2038
2039#[skip_serializing_none]
2042#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2043pub struct SlackDataTableActionCell {
2044 pub element: SlackDataTableActionElement,
2045 pub fallback: Option<Box<SlackDataTableCell>>,
2046}
2047
2048#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
2049#[serde(tag = "type")]
2050pub enum SlackDataTableActionElement {
2051 #[serde(rename = "button")]
2052 Button(SlackBlockButtonElement),
2053}
2054
2055impl From<SlackBlockButtonElement> for SlackDataTableActionElement {
2056 fn from(element: SlackBlockButtonElement) -> Self {
2057 SlackDataTableActionElement::Button(element)
2058 }
2059}
2060
2061#[skip_serializing_none]
2067#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2068pub struct SlackDataVisualizationBlock {
2069 pub title: String,
2070 pub chart: SlackChart,
2071 pub block_id: Option<SlackBlockId>,
2072}
2073
2074impl From<SlackDataVisualizationBlock> for SlackBlock {
2075 fn from(block: SlackDataVisualizationBlock) -> Self {
2076 SlackBlock::DataVisualization(block)
2077 }
2078}
2079
2080#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
2081#[serde(tag = "type", rename_all = "snake_case")]
2082pub enum SlackChart {
2083 Pie(SlackPieChart),
2084 Bar(SlackSeriesChart),
2085 Area(SlackSeriesChart),
2086 Line(SlackSeriesChart),
2087}
2088
2089#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2091pub struct SlackPieChart {
2092 pub segments: Vec<SlackChartSegment>,
2093}
2094
2095#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2098pub struct SlackSeriesChart {
2099 pub series: Vec<SlackChartDataSeries>,
2100 pub axis_config: SlackChartAxisConfig,
2101}
2102
2103#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2104pub struct SlackChartSegment {
2105 pub label: String,
2106 pub value: serde_json::Number,
2107}
2108
2109#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2110pub struct SlackChartDataSeries {
2111 pub name: String,
2112 pub data: Vec<SlackChartDataPoint>,
2113}
2114
2115#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2116pub struct SlackChartDataPoint {
2117 pub label: String,
2118 pub value: serde_json::Number,
2119}
2120
2121#[skip_serializing_none]
2123#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
2124pub struct SlackChartAxisConfig {
2125 pub categories: Vec<String>,
2126 pub x_label: Option<String>,
2127 pub y_label: Option<String>,
2128}
2129
2130#[cfg(test)]
2131mod test {
2132 use super::*;
2133 use crate::blocks::SlackHomeView;
2134
2135 #[test]
2136 fn test_conversation_filter_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2137 let payload = include_str!("./fixtures/slack_conversations_select_with_filter.json");
2138 let block: SlackBlock = serde_json::from_str(payload)?;
2139 match block {
2140 SlackBlock::Section(section) => match section.accessory {
2141 Some(SlackSectionBlockElement::ConversationsSelect(elem)) => {
2142 let filter = elem.filter.expect("filter should be present");
2143 let include = filter.include.expect("include should be present");
2144 assert_eq!(include.len(), 2);
2145 assert_eq!(include[0], SlackConversationFilterInclude::Public);
2146 assert_eq!(include[1], SlackConversationFilterInclude::Private);
2147 assert_eq!(filter.exclude_external_shared_channels, Some(true));
2148 assert_eq!(filter.exclude_bot_users, Some(true));
2149 }
2150 _ => panic!("Expected ConversationsSelect accessory"),
2151 },
2152 _ => panic!("Expected Section block"),
2153 }
2154 Ok(())
2155 }
2156
2157 #[test]
2158 fn test_conversation_filter_serialize() -> Result<(), Box<dyn std::error::Error>> {
2159 let filter = SlackBlockConversationFilter::new()
2160 .with_include(vec![
2161 SlackConversationFilterInclude::Im,
2162 SlackConversationFilterInclude::Mpim,
2163 ])
2164 .with_exclude_bot_users(true);
2165
2166 let json = serde_json::to_value(&filter)?;
2167 assert_eq!(
2168 json,
2169 serde_json::json!({
2170 "include": ["im", "mpim"],
2171 "exclude_bot_users": true
2172 })
2173 );
2174 Ok(())
2175 }
2176
2177 #[test]
2178 fn test_conversation_filter_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2179 let elem = SlackBlockConversationsSelectElement::new(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 =
2195 SlackBlockMultiConversationsSelectElement::new(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(SlackActionId("no_filter".into()));
2214
2215 let json = serde_json::to_value(&elem)?;
2216 assert!(json.get("filter").is_none());
2217 Ok(())
2218 }
2219
2220 #[test]
2221 fn test_slack_image_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2222 let payload = include_str!("./fixtures/slack_image_blocks.json");
2223 let content: SlackMessageContent = serde_json::from_str(payload)?;
2224 let blocks = content.blocks.expect("Blocks should not be empty");
2225 match blocks.first() {
2226 Some(SlackBlock::Section(section)) => match §ion.accessory {
2227 Some(SlackSectionBlockElement::Image(image)) => {
2228 assert_eq!(image.alt_text, "alt text for image");
2229 match &image.image_url_or_file {
2230 SlackImageUrlOrFile::ImageUrl { image_url } => {
2231 assert_eq!(image_url.as_str(), "https://s3-media3.fl.yelpcdn.com/bphoto/c7ed05m9lC2EmA3Aruue7A/o.jpg");
2232 }
2233 SlackImageUrlOrFile::SlackFile { slack_file } => {
2234 panic!("Expected an image URL, not a Slack file: {:?}", slack_file);
2235 }
2236 }
2237 }
2238 _ => panic!("Expected a section block with an image accessory"),
2239 },
2240 _ => panic!("Expected a section block"),
2241 }
2242 Ok(())
2243 }
2244
2245 #[test]
2246 fn test_rich_text_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2247 let payload = include_str!("./fixtures/slack_rich_text_block.json");
2248 let block: SlackBlock = serde_json::from_str(payload)?;
2249
2250 let rich = match block {
2251 SlackBlock::RichText(r) => r,
2252 _ => panic!("Expected a RichText block"),
2253 };
2254
2255 assert_eq!(rich.block_id, Some(SlackBlockId("test_block".into())));
2256 assert_eq!(rich.elements.len(), 4);
2257
2258 let section = match &rich.elements[0] {
2260 SlackRichTextElement::Section(s) => s,
2261 _ => panic!("Expected a Section element"),
2262 };
2263 assert_eq!(section.elements.len(), 7);
2264
2265 let text = match §ion.elements[0] {
2267 SlackRichTextInlineElement::Text(t) => t,
2268 _ => panic!("Expected a Text element"),
2269 };
2270 assert_eq!(text.text, "Hello ");
2271 assert_eq!(text.style.as_ref().and_then(|s| s.bold), Some(true));
2272
2273 assert!(matches!(
2275 §ion.elements[1],
2276 SlackRichTextInlineElement::User(_)
2277 ));
2278
2279 let emoji = match §ion.elements[4] {
2281 SlackRichTextInlineElement::Emoji(e) => e,
2282 _ => panic!("Expected an Emoji element"),
2283 };
2284 assert_eq!(emoji.name, SlackEmojiName::new("wave".into()));
2285
2286 let list = match &rich.elements[1] {
2288 SlackRichTextElement::List(l) => l,
2289 _ => panic!("Expected a List element"),
2290 };
2291 assert_eq!(list.style, SlackRichTextListStyle::Bullet);
2292 assert_eq!(list.elements.len(), 2);
2293
2294 assert!(matches!(
2296 &list.elements[0],
2297 SlackRichTextListElement::Section(_)
2298 ));
2299 assert!(matches!(
2300 &list.elements[1],
2301 SlackRichTextListElement::Section(_)
2302 ));
2303
2304 assert!(matches!(
2306 &rich.elements[2],
2307 SlackRichTextElement::Preformatted(_)
2308 ));
2309
2310 assert!(matches!(&rich.elements[3], SlackRichTextElement::Quote(_)));
2312
2313 Ok(())
2314 }
2315
2316 #[test]
2317 fn test_rich_text_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2318 let payload = include_str!("./fixtures/slack_rich_text_block.json");
2319 let block: SlackBlock = serde_json::from_str(payload)?;
2320 let serialized = serde_json::to_string(&block)?;
2321 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2322 assert_eq!(block, block2);
2323 Ok(())
2324 }
2325
2326 #[test]
2327 fn test_slack_table_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2328 let payload = include_str!("./fixtures/slack_table_block.json");
2329 let block: SlackBlock = serde_json::from_str(payload)?;
2330
2331 let table = match block {
2332 SlackBlock::Table(t) => t,
2333 _ => panic!("Expected a Table block"),
2334 };
2335
2336 assert_eq!(table.block_id, Some(SlackBlockId("table_block_1".into())));
2337 assert_eq!(table.rows.len(), 2);
2338 assert_eq!(table.rows[0].len(), 2);
2339
2340 match &table.rows[0][0] {
2342 SlackTableCell::RawText(c) => assert_eq!(c.text, "Header A"),
2343 _ => panic!("Expected RawText cell"),
2344 }
2345
2346 match &table.rows[1][1] {
2348 SlackTableCell::RichText(c) => assert_eq!(c.elements.len(), 1),
2349 _ => panic!("Expected RichText cell"),
2350 }
2351
2352 let settings = table
2353 .column_settings
2354 .expect("column_settings should be present");
2355 assert_eq!(settings.len(), 2);
2356 assert_eq!(settings[0].is_wrapped, Some(true));
2357 assert_eq!(settings[1].align, Some(SlackTableColumnAlign::Right));
2358
2359 Ok(())
2360 }
2361
2362 #[test]
2363 fn test_slack_table_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2364 let payload = include_str!("./fixtures/slack_table_block.json");
2365 let block: SlackBlock = serde_json::from_str(payload)?;
2366 let serialized = serde_json::to_string(&block)?;
2367 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2368 assert_eq!(block, block2);
2369 Ok(())
2370 }
2371
2372 #[test]
2373 fn test_slack_task_card_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2374 let payload = include_str!("./fixtures/slack_task_card_block.json");
2375 let block: SlackBlock = serde_json::from_str(payload)?;
2376
2377 let task_card = match block {
2378 SlackBlock::TaskCard(t) => t,
2379 _ => panic!("Expected a TaskCard block"),
2380 };
2381
2382 assert_eq!(task_card.task_id, SlackTaskId("task_1".into()));
2383 assert_eq!(task_card.title, "Fetching weather data");
2384 assert_eq!(
2385 task_card.block_id,
2386 Some(SlackBlockId("task_card_block_1".into()))
2387 );
2388 assert_eq!(task_card.status, Some(SlackTaskCardStatus::InProgress));
2389
2390 let output = task_card.output.expect("output should be present");
2391 let SlackRichTextInlineContent::RichText(output_block) = output;
2392 assert_eq!(output_block.elements.len(), 1);
2393
2394 let sources = task_card.sources.expect("sources should be present");
2395 assert_eq!(sources.len(), 2);
2396 match &sources[0] {
2397 SlackTaskCardSource::Url(u) => assert_eq!(u.text, "weather.com"),
2398 }
2399
2400 Ok(())
2401 }
2402
2403 #[test]
2404 fn test_slack_task_card_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2405 let payload = include_str!("./fixtures/slack_task_card_block.json");
2406 let block: SlackBlock = serde_json::from_str(payload)?;
2407 let serialized = serde_json::to_string(&block)?;
2408 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2409 assert_eq!(block, block2);
2410 Ok(())
2411 }
2412
2413 #[test]
2414 fn test_slack_task_card_block_serializes_icon_and_hide_title(
2415 ) -> Result<(), Box<dyn std::error::Error>> {
2416 let task_card = SlackTaskCardBlock::new(SlackTaskId("task_1".into()), "Title".into())
2417 .with_icon(SlackTaskCardIcon::new("check".into()))
2418 .with_hide_title(true);
2419 let json = serde_json::to_value(&task_card)?;
2420 assert_eq!(
2421 json["icon"],
2422 serde_json::json!({"type": "icon", "name": "check"})
2423 );
2424 assert_eq!(json["hide_title"], serde_json::json!(true));
2425 Ok(())
2426 }
2427
2428 #[test]
2429 fn test_slack_alert_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2430 let payload = include_str!("./fixtures/slack_alert_block.json");
2431 let block: SlackBlock = serde_json::from_str(payload)?;
2432 match block {
2433 SlackBlock::Alert(alert) => {
2434 assert_eq!(alert.level, Some(SlackAlertLevel::Warning));
2435 }
2436 _ => panic!("Expected Alert block"),
2437 }
2438 Ok(())
2439 }
2440
2441 #[test]
2442 fn test_slack_alert_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2443 let payload = include_str!("./fixtures/slack_alert_block.json");
2444 let block: SlackBlock = serde_json::from_str(payload)?;
2445 let serialized = serde_json::to_string(&block)?;
2446 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2447 assert_eq!(block, block2);
2448 Ok(())
2449 }
2450
2451 #[test]
2452 fn test_slack_card_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2453 let payload = include_str!("./fixtures/slack_card_block.json");
2454 let block: SlackBlock = serde_json::from_str(payload)?;
2455 match block {
2456 SlackBlock::Card(card) => {
2457 assert!(card.hero_image.is_some());
2458 assert!(card.actions.is_some());
2459 let actions = card.actions.unwrap();
2460 assert_eq!(actions.len(), 1);
2461 match &actions[0] {
2462 SlackCardActionBlockElement::Button(btn) => {
2463 assert_eq!(btn.style, Some(SlackBlockButtonStyle::Primary));
2464 }
2465 }
2466 }
2467 _ => panic!("Expected Card block"),
2468 }
2469 Ok(())
2470 }
2471
2472 #[test]
2473 fn test_slack_card_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2474 let payload = include_str!("./fixtures/slack_card_block.json");
2475 let block: SlackBlock = serde_json::from_str(payload)?;
2476 let serialized = serde_json::to_string(&block)?;
2477 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2478 assert_eq!(block, block2);
2479 Ok(())
2480 }
2481
2482 #[test]
2483 fn test_slack_context_actions_block_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2484 let payload = include_str!("./fixtures/slack_context_actions_block.json");
2485 let block: SlackBlock = serde_json::from_str(payload)?;
2486 match block {
2487 SlackBlock::ContextActions(ctx) => {
2488 assert_eq!(ctx.elements.len(), 1);
2489 match &ctx.elements[0] {
2490 SlackContextActionBlockElement::IconButton(btn) => {
2491 assert_eq!(btn.icon, "trash");
2492 }
2493 _ => panic!("Expected IconButton element"),
2494 }
2495 }
2496 _ => panic!("Expected ContextActions block"),
2497 }
2498 Ok(())
2499 }
2500
2501 #[test]
2502 fn test_slack_context_actions_block_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2503 let payload = include_str!("./fixtures/slack_context_actions_block.json");
2504 let block: SlackBlock = serde_json::from_str(payload)?;
2505 let serialized = serde_json::to_string(&block)?;
2506 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2507 assert_eq!(block, block2);
2508 Ok(())
2509 }
2510
2511 #[test]
2512 fn test_slack_workflow_button_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2513 let payload = include_str!("./fixtures/slack_workflow_button.json");
2514 let block: SlackBlock = serde_json::from_str(payload)?;
2515 match block {
2516 SlackBlock::Actions(actions) => {
2517 assert_eq!(actions.elements.len(), 1);
2518 match &actions.elements[0] {
2519 SlackActionBlockElement::WorkflowButton(btn) => {
2520 assert_eq!(btn.style, Some(SlackBlockButtonStyle::Primary));
2521 let params = btn
2522 .workflow
2523 .trigger
2524 .customizable_input_parameters
2525 .as_ref()
2526 .expect("params should be present");
2527 assert_eq!(params.len(), 1);
2528 assert_eq!(params[0].name, "user_input");
2529 }
2530 _ => panic!("Expected WorkflowButton element"),
2531 }
2532 }
2533 _ => panic!("Expected Actions block"),
2534 }
2535 Ok(())
2536 }
2537
2538 #[test]
2539 fn test_slack_workflow_button_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
2540 let payload = include_str!("./fixtures/slack_workflow_button.json");
2541 let block: SlackBlock = serde_json::from_str(payload)?;
2542 let serialized = serde_json::to_string(&block)?;
2543 let block2: SlackBlock = serde_json::from_str(&serialized)?;
2544 assert_eq!(block, block2);
2545 Ok(())
2546 }
2547
2548 #[test]
2549 fn test_rich_text_message_mention_deserialize() -> Result<(), Box<dyn std::error::Error>> {
2550 let payload = serde_json::json!({
2551 "type": "rich_text",
2552 "block_id": "msgm1",
2553 "elements": [
2554 {
2555 "type": "rich_text_section",
2556 "elements": [
2557 {
2558 "type": "message_mention",
2559 "url": "https://acme.slack.com/archives/C12345678/p1784153496441789?thread_ts=1784153496.441789&cid=C12345678",
2560 "text": "a message",
2561 "channel_id": "C12345678",
2562 "author_id": "U12345678",
2563 "message_ts": "1784153496.441789",
2564 "thread_ts": "1784153496.441789"
2565 }
2566 ]
2567 }
2568 ]
2569 })
2570 .to_string();
2571 let block: SlackBlock = serde_json::from_str(&payload)?;
2572 match block {
2573 SlackBlock::RichText(rich_text) => match &rich_text.elements[0] {
2574 SlackRichTextElement::Section(section) => match §ion.elements[0] {
2575 SlackRichTextInlineElement::MessageMention(mention) => {
2576 assert_eq!(mention.channel_id, Some(SlackChannelId("C12345678".into())));
2577 assert_eq!(mention.author_id, Some(SlackUserId("U12345678".into())));
2578 assert_eq!(
2579 mention.message_ts,
2580 Some(SlackTs("1784153496.441789".into()))
2581 );
2582 }
2583 other => panic!("Expected MessageMention element, got {other:?}"),
2584 },
2585 _ => panic!("Expected Section element"),
2586 },
2587 _ => panic!("Expected RichText block"),
2588 }
2589 Ok(())
2590 }
2591
2592 #[test]
2593 fn test_rich_text_unknown_inline_element_deserialize() -> Result<(), Box<dyn std::error::Error>>
2594 {
2595 let payload = serde_json::json!({
2596 "type": "rich_text_section",
2597 "elements": [
2598 {
2599 "type": "some_future_element",
2600 "foo": "bar"
2601 }
2602 ]
2603 })
2604 .to_string();
2605 let section: SlackRichTextElement = serde_json::from_str(&payload)?;
2606 match section {
2607 SlackRichTextElement::Section(section) => match §ion.elements[0] {
2608 SlackRichTextInlineElement::Unknown(value) => {
2609 assert_eq!(value["type"], "some_future_element");
2610 }
2611 other => panic!("Expected Unknown element, got {other:?}"),
2612 },
2613 _ => panic!("Expected Section element"),
2614 }
2615 Ok(())
2616 }
2617
2618 #[test]
2619 fn rich_text_str_converts_to_text_inline_element() -> Result<(), Box<dyn std::error::Error>> {
2620 let element: SlackRichTextInlineElement = "hi".into();
2621 assert_eq!(
2622 serde_json::to_value(&element)?,
2623 serde_json::json!({"type": "text", "text": "hi"})
2624 );
2625
2626 let owned: SlackRichTextInlineElement = "hi".to_string().into();
2627 assert_eq!(
2628 serde_json::to_value(&owned)?,
2629 serde_json::to_value(&element)?
2630 );
2631 Ok(())
2632 }
2633
2634 #[test]
2635 fn rich_text_leaf_elements_convert_to_inline_elements() -> Result<(), Box<dyn std::error::Error>>
2636 {
2637 let text: SlackRichTextInlineElement = SlackRichTextText::new("t".to_string()).into();
2638 assert_eq!(
2639 serde_json::to_value(&text)?,
2640 serde_json::json!({"type": "text", "text": "t"})
2641 );
2642
2643 let link: SlackRichTextInlineElement =
2644 SlackRichTextLink::new(SlackRelaxedUrl("https://example.com".into())).into();
2645 assert_eq!(
2646 serde_json::to_value(&link)?,
2647 serde_json::json!({"type": "link", "url": "https://example.com"})
2648 );
2649
2650 let user: SlackRichTextInlineElement =
2651 SlackRichTextUser::new(SlackUserId("U1".into())).into();
2652 assert_eq!(
2653 serde_json::to_value(&user)?,
2654 serde_json::json!({"type": "user", "user_id": "U1"})
2655 );
2656
2657 let channel: SlackRichTextInlineElement =
2658 SlackRichTextChannel::new(SlackChannelId("C1".into())).into();
2659 assert_eq!(
2660 serde_json::to_value(&channel)?,
2661 serde_json::json!({"type": "channel", "channel_id": "C1"})
2662 );
2663
2664 let usergroup: SlackRichTextInlineElement =
2665 SlackRichTextUserGroup::new(SlackUserGroupId("G1".into())).into();
2666 assert_eq!(
2667 serde_json::to_value(&usergroup)?,
2668 serde_json::json!({"type": "usergroup", "usergroup_id": "G1"})
2669 );
2670
2671 let emoji: SlackRichTextInlineElement =
2672 SlackRichTextEmoji::new(SlackEmojiName("wave".into())).into();
2673 assert_eq!(
2674 serde_json::to_value(&emoji)?,
2675 serde_json::json!({"type": "emoji", "name": "wave"})
2676 );
2677
2678 let date: SlackRichTextInlineElement = SlackRichTextDate::new(
2679 SlackDateTime("2020-01-01T00:42:42Z".parse::<SlackUtcDateTime>()?),
2680 "{date_short}".to_string(),
2681 )
2682 .into();
2683 assert_eq!(
2684 serde_json::to_value(&date)?,
2685 serde_json::json!({"type": "date", "timestamp": 1_577_839_362_i64, "format": "{date_short}"})
2686 );
2687
2688 let broadcast: SlackRichTextInlineElement =
2689 SlackRichTextBroadcast::new(SlackRichTextBroadcastRange::Here).into();
2690 assert_eq!(
2691 serde_json::to_value(&broadcast)?,
2692 serde_json::json!({"type": "broadcast", "range": "here"})
2693 );
2694
2695 let color: SlackRichTextInlineElement =
2696 SlackRichTextColor::new("#ff0000".to_string()).into();
2697 assert_eq!(
2698 serde_json::to_value(&color)?,
2699 serde_json::json!({"type": "color", "value": "#ff0000"})
2700 );
2701
2702 let mention: SlackRichTextInlineElement = SlackRichTextMessageMention::new(
2703 SlackRelaxedUrl("https://example.com/archives/C1/p1".into()),
2704 )
2705 .into();
2706 assert_eq!(
2707 serde_json::to_value(&mention)?,
2708 serde_json::json!({"type": "message_mention", "url": "https://example.com/archives/C1/p1"})
2709 );
2710
2711 Ok(())
2712 }
2713
2714 #[test]
2715 fn rich_text_list_accepts_str_items() -> Result<(), Box<dyn std::error::Error>> {
2716 let payload = include_str!("./fixtures/slack_rich_text_block.json");
2717 let block: SlackBlock = serde_json::from_str(payload)?;
2718 let expected_list = match block {
2719 SlackBlock::RichText(r) => match &r.elements[1] {
2720 SlackRichTextElement::List(l) => l.clone(),
2721 other => panic!("Expected List element, got {other:?}"),
2722 },
2723 _ => panic!("Expected RichText block"),
2724 };
2725
2726 let list = SlackRichTextList::new(
2727 SlackRichTextListStyle::Bullet,
2728 vec!["Item one".into(), "Item two".into()],
2729 )
2730 .with_indent(0);
2731
2732 assert_eq!(list, expected_list);
2733 Ok(())
2734 }
2735
2736 #[test]
2737 fn rich_text_style_helpers_merge_flags() -> Result<(), Box<dyn std::error::Error>> {
2738 let bold = SlackRichTextText::new("hi".to_string()).bold();
2739 let json = serde_json::to_value(&bold)?;
2740 assert_eq!(json["style"], serde_json::json!({"bold": true}));
2741
2742 let both = SlackRichTextText::new("hi".to_string())
2743 .with_style(SlackRichTextStyle::new().with_italic(true))
2744 .bold();
2745 let json2 = serde_json::to_value(&both)?;
2746 assert_eq!(
2747 json2["style"],
2748 serde_json::json!({"bold": true, "italic": true})
2749 );
2750
2751 let struck = SlackRichTextText::new("hi".to_string()).strike();
2752 assert_eq!(
2753 serde_json::to_value(&struck)?["style"],
2754 serde_json::json!({"strike": true})
2755 );
2756
2757 let coded = SlackRichTextText::new("hi".to_string()).code();
2758 assert_eq!(
2759 serde_json::to_value(&coded)?["style"],
2760 serde_json::json!({"code": true})
2761 );
2762
2763 Ok(())
2764 }
2765
2766 #[test]
2767 fn table_block_builds_from_str_cells() -> Result<(), Box<dyn std::error::Error>> {
2768 let payload = include_str!("./fixtures/slack_table_block.json");
2769 let expected: serde_json::Value = serde_json::from_str(payload)?;
2770
2771 let block: SlackBlock = SlackTableBlock::new(vec![
2772 vec!["Header A".into(), "Header B".into()],
2773 vec![
2774 "Data 1A".into(),
2775 SlackTableRichTextCell::new(vec![SlackRichTextSection::new(vec![
2776 SlackRichTextLink::new(SlackRelaxedUrl("https://slack.com".into()))
2777 .with_text("Data 1B".to_string())
2778 .into(),
2779 ])
2780 .into()])
2781 .into(),
2782 ],
2783 ])
2784 .with_block_id(SlackBlockId("table_block_1".into()))
2785 .with_column_settings(vec![
2786 SlackTableColumnSetting::new().with_is_wrapped(true),
2787 SlackTableColumnSetting::new().with_align(SlackTableColumnAlign::Right),
2788 ])
2789 .into();
2790
2791 assert_eq!(serde_json::to_value(&block)?, expected);
2792 Ok(())
2793 }
2794
2795 #[test]
2796 fn rich_text_block_builds_fixture_with_conversions() -> Result<(), Box<dyn std::error::Error>> {
2797 let payload = include_str!("./fixtures/slack_rich_text_block.json");
2798 let expected: serde_json::Value = serde_json::from_str(payload)?;
2799
2800 let section: SlackRichTextElement = SlackRichTextSection::new(vec![
2801 SlackRichTextText::new("Hello ".to_string()).bold().into(),
2802 SlackRichTextUser::new(SlackUserId("U123ABC456".into())).into(),
2803 "! Check out ".into(),
2804 SlackRichTextLink::new(SlackRelaxedUrl("https://example.com".into()))
2805 .with_text("this link".to_string())
2806 .with_style(SlackRichTextStyle::new().with_italic(true))
2807 .into(),
2808 SlackRichTextEmoji::new(SlackEmojiName("wave".into())).into(),
2809 SlackRichTextChannel::new(SlackChannelId("C123ABC456".into())).into(),
2810 SlackRichTextBroadcast::new(SlackRichTextBroadcastRange::Here).into(),
2811 ])
2812 .into();
2813
2814 let list: SlackRichTextElement = SlackRichTextList::new(
2815 SlackRichTextListStyle::Bullet,
2816 vec!["Item one".into(), "Item two".into()],
2817 )
2818 .with_indent(0)
2819 .into();
2820
2821 let preformatted: SlackRichTextElement =
2822 SlackRichTextPreformatted::new(vec![SlackRichTextText::new(
2823 "fn main() {}\n".to_string(),
2824 )
2825 .into()])
2826 .with_border(1)
2827 .into();
2828
2829 let quote: SlackRichTextElement =
2830 SlackRichTextQuote::new(vec![SlackRichTextText::new("A wise quote".to_string())
2831 .italic()
2832 .into()])
2833 .into();
2834
2835 let block: SlackBlock = SlackRichTextBlock::new(vec![section, list, preformatted, quote])
2836 .with_block_id(SlackBlockId("test_block".into()))
2837 .into();
2838
2839 assert_eq!(serde_json::to_value(&block)?, expected);
2840 Ok(())
2841 }
2842
2843 #[test]
2844 fn unknown_block_type_round_trips_verbatim() -> Result<(), Box<dyn std::error::Error>> {
2845 let payload = serde_json::json!([
2846 { "type": "some_future_block", "x": 1 },
2847 { "type": "divider", "block_id": "d1" }
2848 ]);
2849 let blocks: Vec<SlackBlock> = serde_json::from_value(payload.clone())?;
2850 match &blocks[0] {
2851 SlackBlock::Unknown(value) => assert_eq!(value, &payload[0]),
2852 other => panic!("Expected Unknown block, got {other:?}"),
2853 }
2854 assert_eq!(
2855 blocks[1],
2856 SlackBlock::Divider(SlackDividerBlock::new().with_block_id("d1".into()))
2857 );
2858 assert_eq!(serde_json::to_value(&blocks)?, payload);
2859 Ok(())
2860 }
2861
2862 #[test]
2863 fn malformed_known_block_type_falls_back_to_unknown() -> Result<(), Box<dyn std::error::Error>>
2864 {
2865 let payload = serde_json::json!({ "type": "header" });
2867 let block: SlackBlock = serde_json::from_value(payload.clone())?;
2868 assert_eq!(block, SlackBlock::Unknown(payload));
2869 Ok(())
2870 }
2871
2872 fn round_trip(payload: &str) -> Result<SlackBlock, Box<dyn std::error::Error>> {
2875 let expected: serde_json::Value = serde_json::from_str(payload)?;
2876 let block: SlackBlock = serde_json::from_str(payload)?;
2877 assert_eq!(serde_json::to_value(&block)?, expected);
2878 Ok(block)
2879 }
2880
2881 #[test]
2882 fn container_block_fixture_round_trips() -> Result<(), Box<dyn std::error::Error>> {
2883 let block = round_trip(include_str!("./fixtures/slack_container_block.json"))?;
2884 let SlackBlock::Container(container) = block else {
2885 panic!("Expected a Container block, got {block:?}");
2886 };
2887 assert_eq!(container.is_collapsible, Some(true));
2888 assert_eq!(container.child_blocks.len(), 6);
2889 assert!(matches!(container.child_blocks[0], SlackBlock::Section(_)));
2890 assert!(matches!(container.child_blocks[5], SlackBlock::Actions(_)));
2891 Ok(())
2892 }
2893
2894 #[test]
2895 fn plan_block_fixture_round_trips() -> Result<(), Box<dyn std::error::Error>> {
2896 let block = round_trip(include_str!("./fixtures/slack_plan_block.json"))?;
2897 let SlackBlock::Plan(plan) = block else {
2898 panic!("Expected a Plan block, got {block:?}");
2899 };
2900 assert_eq!(plan.title, "Thinking completed");
2901 assert_eq!(plan.tasks.len(), 3);
2902 assert_eq!(plan.tasks[1].status, Some(SlackTaskCardStatus::Pending));
2903 assert!(plan.tasks[2].output.is_some());
2904 Ok(())
2905 }
2906
2907 #[test]
2908 fn data_table_block_fixture_round_trips() -> Result<(), Box<dyn std::error::Error>> {
2909 let block = round_trip(include_str!("./fixtures/slack_data_table_block.json"))?;
2910 let SlackBlock::DataTable(table) = block else {
2911 panic!("Expected a DataTable block, got {block:?}");
2912 };
2913 assert_eq!(table.caption, "A Fabulous Table");
2914 assert_eq!(table.rows.len(), 4);
2915 assert_eq!(table.rows[0][0], "Name".into());
2916 assert!(matches!(table.rows[1][2], SlackDataTableCell::RichText(_)));
2917 Ok(())
2918 }
2919
2920 #[test]
2921 fn data_table_number_and_action_cells_round_trip() -> Result<(), Box<dyn std::error::Error>> {
2922 let payload = serde_json::json!([
2924 { "type": "raw_number", "value": 1.5, "text": "1.5" },
2925 {
2926 "type": "action_cell",
2927 "element": {
2928 "type": "button",
2929 "text": { "type": "plain_text", "text": "Mark done" },
2930 "action_id": "mark_done",
2931 "value": "task_123"
2932 },
2933 "fallback": { "type": "raw_text", "text": "Open" }
2934 },
2935 { "type": "sparkline", "points": [1, 2] }
2936 ]);
2937 let cells: Vec<SlackDataTableCell> = serde_json::from_value(payload.clone())?;
2938 assert!(matches!(cells[0], SlackDataTableCell::RawNumber(_)));
2939 let SlackDataTableCell::ActionCell(action) = &cells[1] else {
2940 panic!("Expected an ActionCell, got {:?}", cells[1]);
2941 };
2942 assert_eq!(action.fallback.as_deref(), Some(&"Open".into()));
2943 assert!(matches!(cells[2], SlackDataTableCell::Unknown(_)));
2944 assert_eq!(serde_json::to_value(&cells)?, payload);
2945 Ok(())
2946 }
2947
2948 #[test]
2949 fn data_visualization_pie_fixture_round_trips() -> Result<(), Box<dyn std::error::Error>> {
2950 let block = round_trip(include_str!(
2951 "./fixtures/slack_data_visualization_pie_block.json"
2952 ))?;
2953 let SlackBlock::DataVisualization(viz) = block else {
2954 panic!("Expected a DataVisualization block, got {block:?}");
2955 };
2956 let SlackChart::Pie(pie) = viz.chart else {
2957 panic!("Expected a pie chart, got {:?}", viz.chart);
2958 };
2959 assert_eq!(pie.segments.len(), 4);
2960 assert_eq!(pie.segments[0].value, 45.into());
2961 Ok(())
2962 }
2963
2964 #[test]
2965 fn data_visualization_bar_fixture_round_trips() -> Result<(), Box<dyn std::error::Error>> {
2966 let block = round_trip(include_str!(
2967 "./fixtures/slack_data_visualization_bar_block.json"
2968 ))?;
2969 let SlackBlock::DataVisualization(viz) = block else {
2970 panic!("Expected a DataVisualization block, got {block:?}");
2971 };
2972 let SlackChart::Bar(bar) = viz.chart else {
2973 panic!("Expected a bar chart, got {:?}", viz.chart);
2974 };
2975 assert_eq!(bar.series[0].data.len(), 5);
2976 assert_eq!(bar.axis_config.categories.len(), 5);
2977 assert_eq!(
2978 bar.axis_config.y_label.as_deref(),
2979 Some("Percentage of Tastiness")
2980 );
2981 Ok(())
2982 }
2983
2984 #[test]
2985 fn stream_chunk_blocks_carries_new_block_types() -> Result<(), Box<dyn std::error::Error>> {
2986 let payload = serde_json::json!({
2987 "type": "blocks",
2988 "blocks": [
2989 { "type": "plan", "title": "Plan", "tasks": [] },
2990 { "type": "some_future_block", "x": 1 }
2991 ]
2992 });
2993 let chunk: crate::api::SlackStreamChunk = serde_json::from_value(payload.clone())?;
2994 let crate::api::SlackStreamChunk::Blocks { blocks } = &chunk else {
2995 panic!("Expected a Blocks chunk, got {chunk:?}");
2996 };
2997 assert!(matches!(blocks[0], SlackBlock::Plan(_)));
2998 assert!(matches!(blocks[1], SlackBlock::Unknown(_)));
2999 assert_eq!(serde_json::to_value(&chunk)?, payload);
3000 Ok(())
3001 }
3002
3003 #[test]
3004 fn header_block_keeps_its_level() -> Result<(), Box<dyn std::error::Error>> {
3005 let payload = serde_json::json!({
3006 "type": "header",
3007 "text": { "type": "plain_text", "text": "Budget", "emoji": true },
3008 "level": 2
3009 });
3010 let block: SlackBlock = serde_json::from_value(payload.clone())?;
3011 let SlackBlock::Header(header) = &block else {
3012 panic!("Expected a header block, got {block:?}");
3013 };
3014 assert_eq!(header.level, Some(SlackHeaderLevel(2)));
3015 assert_eq!(serde_json::to_value(&block)?, payload);
3016 let plain: SlackBlock = SlackHeaderBlock::new(crate::pt!("Budget")).into();
3017 assert!(serde_json::to_value(&plain)?.get("level").is_none());
3018 Ok(())
3019 }
3020}