Skip to main content

slack_web_api/blocks/
rich_text.rs

1use serde::{Deserialize, Serialize};
2
3use super::tag::{tagged_enum, Tag, Tagged};
4
5/// A rich text block. <https://docs.slack.dev/reference/block-kit/blocks/rich-text-block>
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub struct RichTextBlock {
8    r#type: Tag<Self>,
9    pub elements: Vec<RichTextObject>,
10    #[serde(skip_serializing_if = "Option::is_none")]
11    pub block_id: Option<String>,
12}
13
14impl Tagged for RichTextBlock {
15    const TAG: &'static str = "rich_text";
16}
17
18impl RichTextBlock {
19    pub fn new(elements: Vec<RichTextObject>) -> Self {
20        Self {
21            r#type: Tag::default(),
22            elements,
23            block_id: None,
24        }
25    }
26
27    pub fn block_id(mut self, block_id: impl Into<String>) -> Self {
28        self.block_id = Some(block_id.into());
29        self
30    }
31}
32
33tagged_enum! {
34    /// A top-level element of a rich text block. <https://docs.slack.dev/reference/block-kit/blocks/rich-text-block>
35    pub enum RichTextObject("type") {
36        Section(RichTextSection),
37        List(RichTextList),
38        Preformatted(RichTextPreformatted),
39        Quote(RichTextQuote),
40    }
41}
42
43/// A rich text section. <https://docs.slack.dev/reference/block-kit/block-elements/rich-text-section-element>
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45pub struct RichTextSection {
46    r#type: Tag<Self>,
47    pub elements: Vec<RichTextElement>,
48}
49
50impl Tagged for RichTextSection {
51    const TAG: &'static str = "rich_text_section";
52}
53
54impl RichTextSection {
55    pub fn new(elements: Vec<RichTextElement>) -> Self {
56        Self {
57            r#type: Tag::default(),
58            elements,
59        }
60    }
61}
62
63/// A rich text list. <https://docs.slack.dev/reference/block-kit/block-elements/rich-text-list-element>
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct RichTextList {
66    r#type: Tag<Self>,
67    pub style: RichTextListStyle,
68    pub elements: Vec<RichTextSection>,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub indent: Option<u32>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub offset: Option<u32>,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub border: Option<u8>,
75}
76
77impl Tagged for RichTextList {
78    const TAG: &'static str = "rich_text_list";
79}
80
81impl RichTextList {
82    pub fn new(style: RichTextListStyle, elements: Vec<RichTextSection>) -> Self {
83        Self {
84            r#type: Tag::default(),
85            style,
86            elements,
87            indent: None,
88            offset: None,
89            border: None,
90        }
91    }
92
93    pub fn indent(mut self, indent: u32) -> Self {
94        self.indent = Some(indent);
95        self
96    }
97
98    pub fn offset(mut self, offset: u32) -> Self {
99        self.offset = Some(offset);
100        self
101    }
102
103    pub fn border(mut self, border: u8) -> Self {
104        self.border = Some(border);
105        self
106    }
107}
108
109/// A rich text preformatted (code) block. <https://docs.slack.dev/reference/block-kit/block-elements/rich-text-preformatted-element>
110#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
111pub struct RichTextPreformatted {
112    r#type: Tag<Self>,
113    pub elements: Vec<PreformattedElement>,
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub border: Option<u8>,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub language: Option<String>,
118}
119
120impl Tagged for RichTextPreformatted {
121    const TAG: &'static str = "rich_text_preformatted";
122}
123
124impl RichTextPreformatted {
125    pub fn new(elements: Vec<PreformattedElement>) -> Self {
126        Self {
127            r#type: Tag::default(),
128            elements,
129            border: None,
130            language: None,
131        }
132    }
133
134    pub fn border(mut self, border: u8) -> Self {
135        self.border = Some(border);
136        self
137    }
138
139    pub fn language(mut self, language: impl Into<String>) -> Self {
140        self.language = Some(language.into());
141        self
142    }
143}
144
145/// A rich text quote. <https://docs.slack.dev/reference/block-kit/block-elements/rich-text-quote-element>
146#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
147pub struct RichTextQuote {
148    r#type: Tag<Self>,
149    pub elements: Vec<RichTextElement>,
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub border: Option<u8>,
152}
153
154impl Tagged for RichTextQuote {
155    const TAG: &'static str = "rich_text_quote";
156}
157
158impl RichTextQuote {
159    pub fn new(elements: Vec<RichTextElement>) -> Self {
160        Self {
161            r#type: Tag::default(),
162            elements,
163            border: None,
164        }
165    }
166
167    pub fn border(mut self, border: u8) -> Self {
168        self.border = Some(border);
169        self
170    }
171}
172
173tagged_enum! {
174    /// An element allowed in a preformatted block (text and links). <https://docs.slack.dev/reference/block-kit/block-elements/rich-text-preformatted-element>
175    pub enum PreformattedElement("type") {
176        Text(TextElement),
177        Link(LinkElement),
178    }
179}
180
181tagged_enum! {
182    /// An inline rich text element used in sections, lists and quotes. <https://docs.slack.dev/reference/block-kit/block-elements/rich-text-section-element>
183    pub enum RichTextElement("type") {
184        AttachmentMention(AttachmentMentionElement),
185        Broadcast(BroadcastElement),
186        Canvas(CanvasElement),
187        CanvasUserMention(CanvasUserMentionElement),
188        CanvasMessageUnfurl(CanvasMessageUnfurlElement),
189        Channel(ChannelElement),
190        Citation(CitationElement),
191        Color(ColorElement),
192        Date(DateElement),
193        Emoji(EmojiElement),
194        File(FileElement),
195        Link(LinkElement),
196        ListRecord(ListRecordElement),
197        MessageMention(MessageMentionElement),
198        SalesforceDataField(SalesforceDataFieldElement),
199        Tag(TagElement),
200        Team(TeamElement),
201        Text(TextElement),
202        User(UserElement),
203        Usergroup(UsergroupElement),
204        WorkObjectMention(WorkObjectMentionElement),
205        WorkflowMention(WorkflowMentionElement),
206    }
207}
208
209/// Styling for a rich text element. <https://docs.slack.dev/reference/block-kit/block-elements/text-element>
210#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
211pub struct RichTextStyle {
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub bold: Option<bool>,
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub italic: Option<bool>,
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub strike: Option<bool>,
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub highlight: Option<bool>,
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub client_highlight: Option<bool>,
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub underline: Option<bool>,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub unlink: Option<bool>,
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub code: Option<bool>,
228}
229
230impl RichTextStyle {
231    pub fn new() -> Self {
232        Self::default()
233    }
234
235    pub fn bold(mut self, bold: bool) -> Self {
236        self.bold = Some(bold);
237        self
238    }
239
240    pub fn italic(mut self, italic: bool) -> Self {
241        self.italic = Some(italic);
242        self
243    }
244
245    pub fn strike(mut self, strike: bool) -> Self {
246        self.strike = Some(strike);
247        self
248    }
249
250    pub fn highlight(mut self, highlight: bool) -> Self {
251        self.highlight = Some(highlight);
252        self
253    }
254
255    pub fn client_highlight(mut self, client_highlight: bool) -> Self {
256        self.client_highlight = Some(client_highlight);
257        self
258    }
259
260    pub fn underline(mut self, underline: bool) -> Self {
261        self.underline = Some(underline);
262        self
263    }
264
265    pub fn unlink(mut self, unlink: bool) -> Self {
266        self.unlink = Some(unlink);
267        self
268    }
269
270    pub fn code(mut self, code: bool) -> Self {
271        self.code = Some(code);
272        self
273    }
274}
275
276/// A reference to an app attachment or entity. <https://docs.slack.dev/reference/block-kit/block-elements/attachment-mention-element>
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
278pub struct AttachmentMentionElement {
279    r#type: Tag<Self>,
280    pub url: String,
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub text: Option<String>,
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub app_id: Option<String>,
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub entity_id: Option<String>,
287    #[serde(skip_serializing_if = "Option::is_none")]
288    pub icon_url: Option<String>,
289    #[serde(skip_serializing_if = "Option::is_none")]
290    pub channel_id: Option<String>,
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub ts: Option<String>,
293    #[serde(skip_serializing_if = "Option::is_none")]
294    pub full_size_preview_enabled: Option<bool>,
295    #[serde(skip_serializing_if = "Option::is_none")]
296    pub icon_name: Option<String>,
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub reference_object_type: Option<String>,
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub product_name: Option<String>,
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub style: Option<RichTextStyle>,
303}
304
305impl Tagged for AttachmentMentionElement {
306    const TAG: &'static str = "attachment_mention";
307}
308
309impl AttachmentMentionElement {
310    pub fn new(url: impl Into<String>) -> Self {
311        Self {
312            r#type: Tag::default(),
313            url: url.into(),
314            text: None,
315            app_id: None,
316            entity_id: None,
317            icon_url: None,
318            channel_id: None,
319            ts: None,
320            full_size_preview_enabled: None,
321            icon_name: None,
322            reference_object_type: None,
323            product_name: None,
324            style: None,
325        }
326    }
327
328    pub fn text(mut self, text: impl Into<String>) -> Self {
329        self.text = Some(text.into());
330        self
331    }
332
333    pub fn app_id(mut self, app_id: impl Into<String>) -> Self {
334        self.app_id = Some(app_id.into());
335        self
336    }
337
338    pub fn entity_id(mut self, entity_id: impl Into<String>) -> Self {
339        self.entity_id = Some(entity_id.into());
340        self
341    }
342
343    pub fn icon_url(mut self, icon_url: impl Into<String>) -> Self {
344        self.icon_url = Some(icon_url.into());
345        self
346    }
347
348    pub fn channel_id(mut self, channel_id: impl Into<String>) -> Self {
349        self.channel_id = Some(channel_id.into());
350        self
351    }
352
353    pub fn ts(mut self, ts: impl Into<String>) -> Self {
354        self.ts = Some(ts.into());
355        self
356    }
357
358    pub fn full_size_preview_enabled(mut self, full_size_preview_enabled: bool) -> Self {
359        self.full_size_preview_enabled = Some(full_size_preview_enabled);
360        self
361    }
362
363    pub fn icon_name(mut self, icon_name: impl Into<String>) -> Self {
364        self.icon_name = Some(icon_name.into());
365        self
366    }
367
368    pub fn reference_object_type(mut self, reference_object_type: impl Into<String>) -> Self {
369        self.reference_object_type = Some(reference_object_type.into());
370        self
371    }
372
373    pub fn product_name(mut self, product_name: impl Into<String>) -> Self {
374        self.product_name = Some(product_name.into());
375        self
376    }
377
378    pub fn style(mut self, style: RichTextStyle) -> Self {
379        self.style = Some(style);
380        self
381    }
382}
383
384/// A broadcast mention (@here, @channel or @everyone). <https://docs.slack.dev/reference/block-kit/block-elements/broadcast-element>
385#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
386pub struct BroadcastElement {
387    r#type: Tag<Self>,
388    pub range: BroadcastRange,
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub style: Option<RichTextStyle>,
391}
392
393impl Tagged for BroadcastElement {
394    const TAG: &'static str = "broadcast";
395}
396
397impl BroadcastElement {
398    pub fn new(range: BroadcastRange) -> Self {
399        Self {
400            r#type: Tag::default(),
401            range,
402            style: None,
403        }
404    }
405
406    pub fn style(mut self, style: RichTextStyle) -> Self {
407        self.style = Some(style);
408        self
409    }
410}
411
412/// A link to a canvas. <https://docs.slack.dev/reference/block-kit/block-elements/canvas-element>
413#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
414pub struct CanvasElement {
415    r#type: Tag<Self>,
416    pub file_id: String,
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub label: Option<String>,
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub hide_title: Option<bool>,
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub section_id: Option<String>,
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub style: Option<RichTextStyle>,
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub text: Option<String>,
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub url: Option<String>,
429    #[serde(skip_serializing_if = "Option::is_none")]
430    pub is_skill_invocation: Option<bool>,
431}
432
433impl Tagged for CanvasElement {
434    const TAG: &'static str = "canvas";
435}
436
437impl CanvasElement {
438    pub fn new(file_id: impl Into<String>) -> Self {
439        Self {
440            r#type: Tag::default(),
441            file_id: file_id.into(),
442            label: None,
443            hide_title: None,
444            section_id: None,
445            style: None,
446            text: None,
447            url: None,
448            is_skill_invocation: None,
449        }
450    }
451
452    pub fn label(mut self, label: impl Into<String>) -> Self {
453        self.label = Some(label.into());
454        self
455    }
456
457    pub fn hide_title(mut self, hide_title: bool) -> Self {
458        self.hide_title = Some(hide_title);
459        self
460    }
461
462    pub fn section_id(mut self, section_id: impl Into<String>) -> Self {
463        self.section_id = Some(section_id.into());
464        self
465    }
466
467    pub fn style(mut self, style: RichTextStyle) -> Self {
468        self.style = Some(style);
469        self
470    }
471
472    pub fn text(mut self, text: impl Into<String>) -> Self {
473        self.text = Some(text.into());
474        self
475    }
476
477    pub fn url(mut self, url: impl Into<String>) -> Self {
478        self.url = Some(url.into());
479        self
480    }
481
482    pub fn is_skill_invocation(mut self, is_skill_invocation: bool) -> Self {
483        self.is_skill_invocation = Some(is_skill_invocation);
484        self
485    }
486}
487
488/// A user mention inside canvas content. <https://docs.slack.dev/reference/block-kit/block-elements/canvas-user-mention-element>
489#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
490pub struct CanvasUserMentionElement {
491    r#type: Tag<Self>,
492    pub user_id: String,
493    #[serde(skip_serializing_if = "Option::is_none")]
494    pub thread_id: Option<String>,
495    #[serde(skip_serializing_if = "Option::is_none")]
496    pub style: Option<RichTextStyle>,
497}
498
499impl Tagged for CanvasUserMentionElement {
500    const TAG: &'static str = "canvas_user_mention";
501}
502
503impl CanvasUserMentionElement {
504    pub fn new(user_id: impl Into<String>) -> Self {
505        Self {
506            r#type: Tag::default(),
507            user_id: user_id.into(),
508            thread_id: None,
509            style: None,
510        }
511    }
512
513    pub fn thread_id(mut self, thread_id: impl Into<String>) -> Self {
514        self.thread_id = Some(thread_id.into());
515        self
516    }
517
518    pub fn style(mut self, style: RichTextStyle) -> Self {
519        self.style = Some(style);
520        self
521    }
522}
523
524/// An inline preview of a message inside a canvas. <https://docs.slack.dev/reference/block-kit/block-elements/canvas-message-unfurl-element>
525#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
526pub struct CanvasMessageUnfurlElement {
527    r#type: Tag<Self>,
528    pub root_message_ts: String,
529    pub root_message_channel: String,
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub style: Option<RichTextStyle>,
532}
533
534impl Tagged for CanvasMessageUnfurlElement {
535    const TAG: &'static str = "canvas_message_unfurl";
536}
537
538impl CanvasMessageUnfurlElement {
539    pub fn new(
540        root_message_ts: impl Into<String>,
541        root_message_channel: impl Into<String>,
542    ) -> Self {
543        Self {
544            r#type: Tag::default(),
545            root_message_ts: root_message_ts.into(),
546            root_message_channel: root_message_channel.into(),
547            style: None,
548        }
549    }
550
551    pub fn style(mut self, style: RichTextStyle) -> Self {
552        self.style = Some(style);
553        self
554    }
555}
556
557/// A channel mention. <https://docs.slack.dev/reference/block-kit/block-elements/channel-element>
558#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
559pub struct ChannelElement {
560    r#type: Tag<Self>,
561    pub channel_id: String,
562    #[serde(skip_serializing_if = "Option::is_none")]
563    pub tab_id: Option<String>,
564    #[serde(skip_serializing_if = "Option::is_none")]
565    pub style: Option<RichTextStyle>,
566    #[serde(skip_serializing_if = "Option::is_none")]
567    pub from_llm: Option<bool>,
568}
569
570impl Tagged for ChannelElement {
571    const TAG: &'static str = "channel";
572}
573
574impl ChannelElement {
575    pub fn new(channel_id: impl Into<String>) -> Self {
576        Self {
577            r#type: Tag::default(),
578            channel_id: channel_id.into(),
579            tab_id: None,
580            style: None,
581            from_llm: None,
582        }
583    }
584
585    pub fn tab_id(mut self, tab_id: impl Into<String>) -> Self {
586        self.tab_id = Some(tab_id.into());
587        self
588    }
589
590    pub fn style(mut self, style: RichTextStyle) -> Self {
591        self.style = Some(style);
592        self
593    }
594
595    pub fn from_llm(mut self, from_llm: bool) -> Self {
596        self.from_llm = Some(from_llm);
597        self
598    }
599}
600
601/// An AI citation. <https://docs.slack.dev/reference/block-kit/block-elements/citation-element>
602#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
603pub struct CitationElement {
604    r#type: Tag<Self>,
605    pub url: String,
606    pub text: String,
607    pub index: u32,
608    pub details: CitationDetails,
609    #[serde(skip_serializing_if = "Option::is_none")]
610    pub from_llm: Option<bool>,
611    #[serde(skip_serializing_if = "Option::is_none")]
612    pub is_slack_url: Option<bool>,
613}
614
615impl Tagged for CitationElement {
616    const TAG: &'static str = "citation";
617}
618
619impl CitationElement {
620    pub fn new(
621        url: impl Into<String>,
622        text: impl Into<String>,
623        index: u32,
624        details: impl Into<CitationDetails>,
625    ) -> Self {
626        Self {
627            r#type: Tag::default(),
628            url: url.into(),
629            text: text.into(),
630            index,
631            details: details.into(),
632            from_llm: None,
633            is_slack_url: None,
634        }
635    }
636
637    pub fn from_llm(mut self, from_llm: bool) -> Self {
638        self.from_llm = Some(from_llm);
639        self
640    }
641
642    pub fn is_slack_url(mut self, is_slack_url: bool) -> Self {
643        self.is_slack_url = Some(is_slack_url);
644        self
645    }
646}
647
648tagged_enum! {
649    /// Details of a citation source (the kind is set by `citation_type`). <https://docs.slack.dev/reference/block-kit/block-elements/citation-element>
650    pub enum CitationDetails("citation_type") {
651        File(FileCitation),
652        External(ExternalCitation),
653        Web(WebCitation),
654        Message(MessageCitation),
655        Memory(MemoryCitation),
656    }
657}
658
659/// A file citation source. <https://docs.slack.dev/reference/block-kit/block-elements/citation-element>
660#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
661pub struct FileCitation {
662    citation_type: Tag<Self>,
663    #[serde(skip_serializing_if = "Option::is_none")]
664    pub descriptor: Option<String>,
665    #[serde(skip_serializing_if = "Option::is_none")]
666    pub file_id: Option<String>,
667}
668
669impl Tagged for FileCitation {
670    const TAG: &'static str = "file";
671}
672
673impl FileCitation {
674    pub fn new() -> Self {
675        Self::default()
676    }
677
678    pub fn descriptor(mut self, descriptor: impl Into<String>) -> Self {
679        self.descriptor = Some(descriptor.into());
680        self
681    }
682
683    pub fn file_id(mut self, file_id: impl Into<String>) -> Self {
684        self.file_id = Some(file_id.into());
685        self
686    }
687}
688
689/// An external app citation source. <https://docs.slack.dev/reference/block-kit/block-elements/citation-element>
690#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
691pub struct ExternalCitation {
692    citation_type: Tag<Self>,
693    #[serde(skip_serializing_if = "Option::is_none")]
694    pub app_name: Option<String>,
695    #[serde(skip_serializing_if = "Option::is_none")]
696    pub app_icon_url: Option<String>,
697}
698
699impl Tagged for ExternalCitation {
700    const TAG: &'static str = "external";
701}
702
703impl ExternalCitation {
704    pub fn new() -> Self {
705        Self::default()
706    }
707
708    pub fn app_name(mut self, app_name: impl Into<String>) -> Self {
709        self.app_name = Some(app_name.into());
710        self
711    }
712
713    pub fn app_icon_url(mut self, app_icon_url: impl Into<String>) -> Self {
714        self.app_icon_url = Some(app_icon_url.into());
715        self
716    }
717}
718
719/// A web page citation source. <https://docs.slack.dev/reference/block-kit/block-elements/citation-element>
720#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
721pub struct WebCitation {
722    citation_type: Tag<Self>,
723    #[serde(skip_serializing_if = "Option::is_none")]
724    pub display_name: Option<String>,
725    #[serde(skip_serializing_if = "Option::is_none")]
726    pub title: Option<String>,
727    #[serde(skip_serializing_if = "Option::is_none")]
728    pub snippet: Option<String>,
729}
730
731impl Tagged for WebCitation {
732    const TAG: &'static str = "web";
733}
734
735impl WebCitation {
736    pub fn new() -> Self {
737        Self::default()
738    }
739
740    pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
741        self.display_name = Some(display_name.into());
742        self
743    }
744
745    pub fn title(mut self, title: impl Into<String>) -> Self {
746        self.title = Some(title.into());
747        self
748    }
749
750    pub fn snippet(mut self, snippet: impl Into<String>) -> Self {
751        self.snippet = Some(snippet.into());
752        self
753    }
754}
755
756/// A message citation source. <https://docs.slack.dev/reference/block-kit/block-elements/citation-element>
757#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
758pub struct MessageCitation {
759    citation_type: Tag<Self>,
760    #[serde(skip_serializing_if = "Option::is_none")]
761    pub channel: Option<String>,
762    #[serde(skip_serializing_if = "Option::is_none")]
763    pub message_ts: Option<String>,
764}
765
766impl Tagged for MessageCitation {
767    const TAG: &'static str = "message";
768}
769
770impl MessageCitation {
771    pub fn new() -> Self {
772        Self::default()
773    }
774
775    pub fn channel(mut self, channel: impl Into<String>) -> Self {
776        self.channel = Some(channel.into());
777        self
778    }
779
780    pub fn message_ts(mut self, message_ts: impl Into<String>) -> Self {
781        self.message_ts = Some(message_ts.into());
782        self
783    }
784}
785
786/// A memory citation source. <https://docs.slack.dev/reference/block-kit/block-elements/citation-element>
787#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
788pub struct MemoryCitation {
789    citation_type: Tag<Self>,
790    #[serde(skip_serializing_if = "Option::is_none")]
791    pub memory_id: Option<String>,
792}
793
794impl Tagged for MemoryCitation {
795    const TAG: &'static str = "memory";
796}
797
798impl MemoryCitation {
799    pub fn new() -> Self {
800        Self::default()
801    }
802
803    pub fn memory_id(mut self, memory_id: impl Into<String>) -> Self {
804        self.memory_id = Some(memory_id.into());
805        self
806    }
807}
808
809/// A color swatch. <https://docs.slack.dev/reference/block-kit/block-elements/color-element>
810#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
811pub struct ColorElement {
812    r#type: Tag<Self>,
813    pub value: String,
814    #[serde(skip_serializing_if = "Option::is_none")]
815    pub style: Option<RichTextStyle>,
816}
817
818impl Tagged for ColorElement {
819    const TAG: &'static str = "color";
820}
821
822impl ColorElement {
823    pub fn new(value: impl Into<String>) -> Self {
824        Self {
825            r#type: Tag::default(),
826            value: value.into(),
827            style: None,
828        }
829    }
830
831    pub fn style(mut self, style: RichTextStyle) -> Self {
832        self.style = Some(style);
833        self
834    }
835}
836
837/// A date formatted for the viewer's locale and time zone. <https://docs.slack.dev/reference/block-kit/block-elements/date-element>
838#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
839pub struct DateElement {
840    r#type: Tag<Self>,
841    pub timestamp: i64,
842    pub format: String,
843    #[serde(skip_serializing_if = "Option::is_none")]
844    pub timezone: Option<String>,
845    #[serde(skip_serializing_if = "Option::is_none")]
846    pub url: Option<String>,
847    #[serde(skip_serializing_if = "Option::is_none")]
848    pub fallback: Option<String>,
849    #[serde(skip_serializing_if = "Option::is_none")]
850    pub style: Option<RichTextStyle>,
851}
852
853impl Tagged for DateElement {
854    const TAG: &'static str = "date";
855}
856
857impl DateElement {
858    pub fn new(timestamp: i64, format: impl Into<String>) -> Self {
859        Self {
860            r#type: Tag::default(),
861            timestamp,
862            format: format.into(),
863            timezone: None,
864            url: None,
865            fallback: None,
866            style: None,
867        }
868    }
869
870    pub fn timezone(mut self, timezone: impl Into<String>) -> Self {
871        self.timezone = Some(timezone.into());
872        self
873    }
874
875    pub fn url(mut self, url: impl Into<String>) -> Self {
876        self.url = Some(url.into());
877        self
878    }
879
880    pub fn fallback(mut self, fallback: impl Into<String>) -> Self {
881        self.fallback = Some(fallback.into());
882        self
883    }
884
885    pub fn style(mut self, style: RichTextStyle) -> Self {
886        self.style = Some(style);
887        self
888    }
889}
890
891/// An emoji. <https://docs.slack.dev/reference/block-kit/block-elements/emoji-element>
892#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
893pub struct EmojiElement {
894    r#type: Tag<Self>,
895    pub name: String,
896    #[serde(skip_serializing_if = "Option::is_none")]
897    pub unicode: Option<String>,
898}
899
900impl Tagged for EmojiElement {
901    const TAG: &'static str = "emoji";
902}
903
904impl EmojiElement {
905    pub fn new(name: impl Into<String>) -> Self {
906        Self {
907            r#type: Tag::default(),
908            name: name.into(),
909            unicode: None,
910        }
911    }
912
913    pub fn unicode(mut self, unicode: impl Into<String>) -> Self {
914        self.unicode = Some(unicode.into());
915        self
916    }
917}
918
919/// A link to a Slack file. <https://docs.slack.dev/reference/block-kit/block-elements/file-element>
920#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
921pub struct FileElement {
922    r#type: Tag<Self>,
923    pub file_id: String,
924    #[serde(skip_serializing_if = "Option::is_none")]
925    pub style: Option<RichTextStyle>,
926    #[serde(skip_serializing_if = "Option::is_none")]
927    pub text: Option<String>,
928    #[serde(skip_serializing_if = "Option::is_none")]
929    pub url: Option<String>,
930    #[serde(skip_serializing_if = "Option::is_none")]
931    pub is_skill_invocation: Option<bool>,
932}
933
934impl Tagged for FileElement {
935    const TAG: &'static str = "file";
936}
937
938impl FileElement {
939    pub fn new(file_id: impl Into<String>) -> Self {
940        Self {
941            r#type: Tag::default(),
942            file_id: file_id.into(),
943            style: None,
944            text: None,
945            url: None,
946            is_skill_invocation: None,
947        }
948    }
949
950    pub fn style(mut self, style: RichTextStyle) -> Self {
951        self.style = Some(style);
952        self
953    }
954
955    pub fn text(mut self, text: impl Into<String>) -> Self {
956        self.text = Some(text.into());
957        self
958    }
959
960    pub fn url(mut self, url: impl Into<String>) -> Self {
961        self.url = Some(url.into());
962        self
963    }
964
965    pub fn is_skill_invocation(mut self, is_skill_invocation: bool) -> Self {
966        self.is_skill_invocation = Some(is_skill_invocation);
967        self
968    }
969}
970
971/// A hyperlink. <https://docs.slack.dev/reference/block-kit/block-elements/link-element>
972#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
973pub struct LinkElement {
974    r#type: Tag<Self>,
975    pub url: String,
976    #[serde(skip_serializing_if = "Option::is_none")]
977    pub text: Option<String>,
978    #[serde(skip_serializing_if = "Option::is_none")]
979    pub r#unsafe: Option<bool>,
980    #[serde(skip_serializing_if = "Option::is_none")]
981    pub from_llm: Option<bool>,
982    #[serde(skip_serializing_if = "Option::is_none")]
983    pub is_slack_url: Option<bool>,
984    #[serde(skip_serializing_if = "Option::is_none")]
985    pub truncated: Option<bool>,
986    #[serde(skip_serializing_if = "Option::is_none")]
987    pub style: Option<RichTextStyle>,
988}
989
990impl Tagged for LinkElement {
991    const TAG: &'static str = "link";
992}
993
994impl LinkElement {
995    pub fn new(url: impl Into<String>) -> Self {
996        Self {
997            r#type: Tag::default(),
998            url: url.into(),
999            text: None,
1000            r#unsafe: None,
1001            from_llm: None,
1002            is_slack_url: None,
1003            truncated: None,
1004            style: None,
1005        }
1006    }
1007
1008    pub fn text(mut self, text: impl Into<String>) -> Self {
1009        self.text = Some(text.into());
1010        self
1011    }
1012
1013    pub fn r#unsafe(mut self, r#unsafe: bool) -> Self {
1014        self.r#unsafe = Some(r#unsafe);
1015        self
1016    }
1017
1018    pub fn from_llm(mut self, from_llm: bool) -> Self {
1019        self.from_llm = Some(from_llm);
1020        self
1021    }
1022
1023    pub fn is_slack_url(mut self, is_slack_url: bool) -> Self {
1024        self.is_slack_url = Some(is_slack_url);
1025        self
1026    }
1027
1028    pub fn truncated(mut self, truncated: bool) -> Self {
1029        self.truncated = Some(truncated);
1030        self
1031    }
1032
1033    pub fn style(mut self, style: RichTextStyle) -> Self {
1034        self.style = Some(style);
1035        self
1036    }
1037}
1038
1039/// A link to a Slack list record. <https://docs.slack.dev/reference/block-kit/block-elements/list-record-element>
1040#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1041pub struct ListRecordElement {
1042    r#type: Tag<Self>,
1043    pub file_id: String,
1044    #[serde(skip_serializing_if = "Option::is_none")]
1045    pub record_id: Option<String>,
1046    #[serde(skip_serializing_if = "Option::is_none")]
1047    pub view_id: Option<String>,
1048    #[serde(skip_serializing_if = "Option::is_none")]
1049    pub style: Option<RichTextStyle>,
1050    #[serde(skip_serializing_if = "Option::is_none")]
1051    pub text: Option<String>,
1052    #[serde(skip_serializing_if = "Option::is_none")]
1053    pub url: Option<String>,
1054}
1055
1056impl Tagged for ListRecordElement {
1057    const TAG: &'static str = "list_record";
1058}
1059
1060impl ListRecordElement {
1061    pub fn new(file_id: impl Into<String>) -> Self {
1062        Self {
1063            r#type: Tag::default(),
1064            file_id: file_id.into(),
1065            record_id: None,
1066            view_id: None,
1067            style: None,
1068            text: None,
1069            url: None,
1070        }
1071    }
1072
1073    pub fn record_id(mut self, record_id: impl Into<String>) -> Self {
1074        self.record_id = Some(record_id.into());
1075        self
1076    }
1077
1078    pub fn view_id(mut self, view_id: impl Into<String>) -> Self {
1079        self.view_id = Some(view_id.into());
1080        self
1081    }
1082
1083    pub fn style(mut self, style: RichTextStyle) -> Self {
1084        self.style = Some(style);
1085        self
1086    }
1087
1088    pub fn text(mut self, text: impl Into<String>) -> Self {
1089        self.text = Some(text.into());
1090        self
1091    }
1092
1093    pub fn url(mut self, url: impl Into<String>) -> Self {
1094        self.url = Some(url.into());
1095        self
1096    }
1097}
1098
1099/// A link to a Slack message. <https://docs.slack.dev/reference/block-kit/block-elements/message-mention-element>
1100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1101pub struct MessageMentionElement {
1102    r#type: Tag<Self>,
1103    pub channel_id: String,
1104    pub message_ts: String,
1105    #[serde(skip_serializing_if = "Option::is_none")]
1106    pub author_id: Option<String>,
1107    #[serde(skip_serializing_if = "Option::is_none")]
1108    pub thread_ts: Option<String>,
1109    #[serde(skip_serializing_if = "Option::is_none")]
1110    pub style: Option<RichTextStyle>,
1111    #[serde(skip_serializing_if = "Option::is_none")]
1112    pub text: Option<String>,
1113    #[serde(skip_serializing_if = "Option::is_none")]
1114    pub url: Option<String>,
1115}
1116
1117impl Tagged for MessageMentionElement {
1118    const TAG: &'static str = "message_mention";
1119}
1120
1121impl MessageMentionElement {
1122    pub fn new(channel_id: impl Into<String>, message_ts: impl Into<String>) -> Self {
1123        Self {
1124            r#type: Tag::default(),
1125            channel_id: channel_id.into(),
1126            message_ts: message_ts.into(),
1127            author_id: None,
1128            thread_ts: None,
1129            style: None,
1130            text: None,
1131            url: None,
1132        }
1133    }
1134
1135    pub fn author_id(mut self, author_id: impl Into<String>) -> Self {
1136        self.author_id = Some(author_id.into());
1137        self
1138    }
1139
1140    pub fn thread_ts(mut self, thread_ts: impl Into<String>) -> Self {
1141        self.thread_ts = Some(thread_ts.into());
1142        self
1143    }
1144
1145    pub fn style(mut self, style: RichTextStyle) -> Self {
1146        self.style = Some(style);
1147        self
1148    }
1149
1150    pub fn text(mut self, text: impl Into<String>) -> Self {
1151        self.text = Some(text.into());
1152        self
1153    }
1154
1155    pub fn url(mut self, url: impl Into<String>) -> Self {
1156        self.url = Some(url.into());
1157        self
1158    }
1159}
1160
1161/// A reference to a Salesforce data field. <https://docs.slack.dev/reference/block-kit/block-elements/salesforce-data-field-element>
1162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1163pub struct SalesforceDataFieldElement {
1164    r#type: Tag<Self>,
1165    pub salesforce_record_id: String,
1166    #[serde(skip_serializing_if = "Option::is_none")]
1167    pub salesforce_field_label: Option<String>,
1168    #[serde(skip_serializing_if = "Option::is_none")]
1169    pub salesforce_field_api_name: Option<String>,
1170    #[serde(skip_serializing_if = "Option::is_none")]
1171    pub salesforce_include_field_label: Option<bool>,
1172    #[serde(skip_serializing_if = "Option::is_none")]
1173    pub style: Option<RichTextStyle>,
1174}
1175
1176impl Tagged for SalesforceDataFieldElement {
1177    const TAG: &'static str = "salesforce_data_field";
1178}
1179
1180impl SalesforceDataFieldElement {
1181    pub fn new(salesforce_record_id: impl Into<String>) -> Self {
1182        Self {
1183            r#type: Tag::default(),
1184            salesforce_record_id: salesforce_record_id.into(),
1185            salesforce_field_label: None,
1186            salesforce_field_api_name: None,
1187            salesforce_include_field_label: None,
1188            style: None,
1189        }
1190    }
1191
1192    pub fn salesforce_field_label(mut self, salesforce_field_label: impl Into<String>) -> Self {
1193        self.salesforce_field_label = Some(salesforce_field_label.into());
1194        self
1195    }
1196
1197    pub fn salesforce_field_api_name(
1198        mut self,
1199        salesforce_field_api_name: impl Into<String>,
1200    ) -> Self {
1201        self.salesforce_field_api_name = Some(salesforce_field_api_name.into());
1202        self
1203    }
1204
1205    pub fn salesforce_include_field_label(mut self, salesforce_include_field_label: bool) -> Self {
1206        self.salesforce_include_field_label = Some(salesforce_include_field_label);
1207        self
1208    }
1209
1210    pub fn style(mut self, style: RichTextStyle) -> Self {
1211        self.style = Some(style);
1212        self
1213    }
1214}
1215
1216/// A colored tag. <https://docs.slack.dev/reference/block-kit/block-elements/tag-element>
1217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1218pub struct TagElement {
1219    r#type: Tag<Self>,
1220    pub text: String,
1221    #[serde(skip_serializing_if = "Option::is_none")]
1222    pub color: Option<TagColor>,
1223    #[serde(skip_serializing_if = "Option::is_none")]
1224    pub style: Option<RichTextStyle>,
1225}
1226
1227impl Tagged for TagElement {
1228    const TAG: &'static str = "tag";
1229}
1230
1231impl TagElement {
1232    pub fn new(text: impl Into<String>) -> Self {
1233        Self {
1234            r#type: Tag::default(),
1235            text: text.into(),
1236            color: None,
1237            style: None,
1238        }
1239    }
1240
1241    pub fn color(mut self, color: TagColor) -> Self {
1242        self.color = Some(color);
1243        self
1244    }
1245
1246    pub fn style(mut self, style: RichTextStyle) -> Self {
1247        self.style = Some(style);
1248        self
1249    }
1250}
1251
1252/// A workspace (team) mention. <https://docs.slack.dev/reference/block-kit/block-elements/team-element>
1253#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1254pub struct TeamElement {
1255    r#type: Tag<Self>,
1256    pub team_id: String,
1257    #[serde(skip_serializing_if = "Option::is_none")]
1258    pub style: Option<RichTextStyle>,
1259}
1260
1261impl Tagged for TeamElement {
1262    const TAG: &'static str = "team";
1263}
1264
1265impl TeamElement {
1266    pub fn new(team_id: impl Into<String>) -> Self {
1267        Self {
1268            r#type: Tag::default(),
1269            team_id: team_id.into(),
1270            style: None,
1271        }
1272    }
1273
1274    pub fn style(mut self, style: RichTextStyle) -> Self {
1275        self.style = Some(style);
1276        self
1277    }
1278}
1279
1280/// Text. <https://docs.slack.dev/reference/block-kit/block-elements/text-element>
1281#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1282pub struct TextElement {
1283    r#type: Tag<Self>,
1284    pub text: String,
1285    #[serde(skip_serializing_if = "Option::is_none")]
1286    pub style: Option<RichTextStyle>,
1287}
1288
1289impl Tagged for TextElement {
1290    const TAG: &'static str = "text";
1291}
1292
1293impl TextElement {
1294    pub fn new(text: impl Into<String>) -> Self {
1295        Self {
1296            r#type: Tag::default(),
1297            text: text.into(),
1298            style: None,
1299        }
1300    }
1301
1302    pub fn style(mut self, style: RichTextStyle) -> Self {
1303        self.style = Some(style);
1304        self
1305    }
1306}
1307
1308/// A user mention. <https://docs.slack.dev/reference/block-kit/block-elements/user-element>
1309#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1310pub struct UserElement {
1311    r#type: Tag<Self>,
1312    pub user_id: String,
1313    #[serde(skip_serializing_if = "Option::is_none")]
1314    pub style: Option<RichTextStyle>,
1315    #[serde(skip_serializing_if = "Option::is_none")]
1316    pub from_llm: Option<bool>,
1317}
1318
1319impl Tagged for UserElement {
1320    const TAG: &'static str = "user";
1321}
1322
1323impl UserElement {
1324    pub fn new(user_id: impl Into<String>) -> Self {
1325        Self {
1326            r#type: Tag::default(),
1327            user_id: user_id.into(),
1328            style: None,
1329            from_llm: None,
1330        }
1331    }
1332
1333    pub fn style(mut self, style: RichTextStyle) -> Self {
1334        self.style = Some(style);
1335        self
1336    }
1337
1338    pub fn from_llm(mut self, from_llm: bool) -> Self {
1339        self.from_llm = Some(from_llm);
1340        self
1341    }
1342}
1343
1344/// A user group mention. <https://docs.slack.dev/reference/block-kit/block-elements/usergroup-element>
1345#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1346pub struct UsergroupElement {
1347    r#type: Tag<Self>,
1348    pub usergroup_id: String,
1349    #[serde(skip_serializing_if = "Option::is_none")]
1350    pub style: Option<RichTextStyle>,
1351}
1352
1353impl Tagged for UsergroupElement {
1354    const TAG: &'static str = "usergroup";
1355}
1356
1357impl UsergroupElement {
1358    pub fn new(usergroup_id: impl Into<String>) -> Self {
1359        Self {
1360            r#type: Tag::default(),
1361            usergroup_id: usergroup_id.into(),
1362            style: None,
1363        }
1364    }
1365
1366    pub fn style(mut self, style: RichTextStyle) -> Self {
1367        self.style = Some(style);
1368        self
1369    }
1370}
1371
1372/// A reference to a Work Object. <https://docs.slack.dev/reference/block-kit/block-elements/work-object-mention-element>
1373#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1374pub struct WorkObjectMentionElement {
1375    r#type: Tag<Self>,
1376    pub entity_id: String,
1377    pub app_id: String,
1378    pub text: String,
1379    pub url: String,
1380    #[serde(skip_serializing_if = "Option::is_none")]
1381    pub icon_url: Option<String>,
1382    #[serde(skip_serializing_if = "Option::is_none")]
1383    pub full_size_preview_enabled: Option<bool>,
1384    #[serde(skip_serializing_if = "Option::is_none")]
1385    pub product_name: Option<String>,
1386    #[serde(skip_serializing_if = "Option::is_none")]
1387    pub style: Option<RichTextStyle>,
1388}
1389
1390impl Tagged for WorkObjectMentionElement {
1391    const TAG: &'static str = "work_object_mention";
1392}
1393
1394impl WorkObjectMentionElement {
1395    pub fn new(
1396        entity_id: impl Into<String>,
1397        app_id: impl Into<String>,
1398        text: impl Into<String>,
1399        url: impl Into<String>,
1400    ) -> Self {
1401        Self {
1402            r#type: Tag::default(),
1403            entity_id: entity_id.into(),
1404            app_id: app_id.into(),
1405            text: text.into(),
1406            url: url.into(),
1407            icon_url: None,
1408            full_size_preview_enabled: None,
1409            product_name: None,
1410            style: None,
1411        }
1412    }
1413
1414    pub fn icon_url(mut self, icon_url: impl Into<String>) -> Self {
1415        self.icon_url = Some(icon_url.into());
1416        self
1417    }
1418
1419    pub fn full_size_preview_enabled(mut self, full_size_preview_enabled: bool) -> Self {
1420        self.full_size_preview_enabled = Some(full_size_preview_enabled);
1421        self
1422    }
1423
1424    pub fn product_name(mut self, product_name: impl Into<String>) -> Self {
1425        self.product_name = Some(product_name.into());
1426        self
1427    }
1428
1429    pub fn style(mut self, style: RichTextStyle) -> Self {
1430        self.style = Some(style);
1431        self
1432    }
1433}
1434
1435/// A link to a workflow. <https://docs.slack.dev/reference/block-kit/block-elements/workflow-mention-element>
1436#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1437pub struct WorkflowMentionElement {
1438    r#type: Tag<Self>,
1439    pub workflow_id: String,
1440    pub function_trigger_id: String,
1441    pub text: String,
1442    #[serde(skip_serializing_if = "Option::is_none")]
1443    pub url: Option<String>,
1444    #[serde(skip_serializing_if = "Option::is_none")]
1445    pub channel_id: Option<String>,
1446    #[serde(skip_serializing_if = "Option::is_none")]
1447    pub ts: Option<String>,
1448    #[serde(skip_serializing_if = "Option::is_none")]
1449    pub style: Option<RichTextStyle>,
1450}
1451
1452impl Tagged for WorkflowMentionElement {
1453    const TAG: &'static str = "workflow_mention";
1454}
1455
1456impl WorkflowMentionElement {
1457    pub fn new(
1458        workflow_id: impl Into<String>,
1459        function_trigger_id: impl Into<String>,
1460        text: impl Into<String>,
1461    ) -> Self {
1462        Self {
1463            r#type: Tag::default(),
1464            workflow_id: workflow_id.into(),
1465            function_trigger_id: function_trigger_id.into(),
1466            text: text.into(),
1467            url: None,
1468            channel_id: None,
1469            ts: None,
1470            style: None,
1471        }
1472    }
1473
1474    pub fn url(mut self, url: impl Into<String>) -> Self {
1475        self.url = Some(url.into());
1476        self
1477    }
1478
1479    pub fn channel_id(mut self, channel_id: impl Into<String>) -> Self {
1480        self.channel_id = Some(channel_id.into());
1481        self
1482    }
1483
1484    pub fn ts(mut self, ts: impl Into<String>) -> Self {
1485        self.ts = Some(ts.into());
1486        self
1487    }
1488
1489    pub fn style(mut self, style: RichTextStyle) -> Self {
1490        self.style = Some(style);
1491        self
1492    }
1493}
1494
1495/// The style of a rich text list. Unknown values go to `Other`.
1496#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1497#[serde(rename_all = "snake_case")]
1498pub enum RichTextListStyle {
1499    Bullet,
1500    Ordered,
1501    #[serde(untagged)]
1502    Other(String),
1503}
1504
1505/// The range of a broadcast mention. Unknown values go to `Other`.
1506#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1507#[serde(rename_all = "snake_case")]
1508pub enum BroadcastRange {
1509    Here,
1510    Channel,
1511    Everyone,
1512    #[serde(untagged)]
1513    Other(String),
1514}
1515
1516/// The color of a tag. Unknown values go to `Other`.
1517#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1518#[serde(rename_all = "snake_case")]
1519pub enum TagColor {
1520    Gray,
1521    Brown,
1522    Purple,
1523    Indigo,
1524    Blue,
1525    Green,
1526    Yellow,
1527    Orange,
1528    Red,
1529    #[serde(untagged)]
1530    Other(String),
1531}