lib_client_slack/
blocks.rs

1use serde::Serialize;
2
3/// Block Kit block.
4#[derive(Debug, Clone, Serialize)]
5#[serde(tag = "type", rename_all = "snake_case")]
6pub enum Block {
7    Section(SectionBlock),
8    Divider,
9    Header(HeaderBlock),
10    Context(ContextBlock),
11    Actions(ActionsBlock),
12    Image(ImageBlock),
13}
14
15/// Section block.
16#[derive(Debug, Clone, Serialize)]
17pub struct SectionBlock {
18    pub text: TextObject,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub accessory: Option<BlockElement>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub fields: Option<Vec<TextObject>>,
23}
24
25/// Header block.
26#[derive(Debug, Clone, Serialize)]
27pub struct HeaderBlock {
28    pub text: TextObject,
29}
30
31/// Context block.
32#[derive(Debug, Clone, Serialize)]
33pub struct ContextBlock {
34    pub elements: Vec<ContextElement>,
35}
36
37/// Actions block.
38#[derive(Debug, Clone, Serialize)]
39pub struct ActionsBlock {
40    pub elements: Vec<BlockElement>,
41}
42
43/// Image block.
44#[derive(Debug, Clone, Serialize)]
45pub struct ImageBlock {
46    pub image_url: String,
47    pub alt_text: String,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub title: Option<TextObject>,
50}
51
52/// Text object.
53#[derive(Debug, Clone, Serialize)]
54pub struct TextObject {
55    #[serde(rename = "type")]
56    pub text_type: TextType,
57    pub text: String,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub emoji: Option<bool>,
60}
61
62#[derive(Debug, Clone, Serialize)]
63#[serde(rename_all = "snake_case")]
64pub enum TextType {
65    PlainText,
66    Mrkdwn,
67}
68
69impl TextObject {
70    pub fn plain(text: impl Into<String>) -> Self {
71        Self {
72            text_type: TextType::PlainText,
73            text: text.into(),
74            emoji: Some(true),
75        }
76    }
77
78    pub fn mrkdwn(text: impl Into<String>) -> Self {
79        Self {
80            text_type: TextType::Mrkdwn,
81            text: text.into(),
82            emoji: None,
83        }
84    }
85}
86
87/// Context element.
88#[derive(Debug, Clone, Serialize)]
89#[serde(untagged)]
90pub enum ContextElement {
91    Text(TextObject),
92    Image { type_: String, image_url: String, alt_text: String },
93}
94
95/// Block element.
96#[derive(Debug, Clone, Serialize)]
97#[serde(tag = "type", rename_all = "snake_case")]
98pub enum BlockElement {
99    Button(ButtonElement),
100    StaticSelect(StaticSelectElement),
101    Overflow(OverflowElement),
102}
103
104/// Button element.
105#[derive(Debug, Clone, Serialize)]
106pub struct ButtonElement {
107    pub text: TextObject,
108    pub action_id: String,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub value: Option<String>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub url: Option<String>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub style: Option<ButtonStyle>,
115}
116
117#[derive(Debug, Clone, Serialize)]
118#[serde(rename_all = "lowercase")]
119pub enum ButtonStyle {
120    Primary,
121    Danger,
122}
123
124/// Static select element.
125#[derive(Debug, Clone, Serialize)]
126pub struct StaticSelectElement {
127    pub placeholder: TextObject,
128    pub action_id: String,
129    pub options: Vec<SelectOption>,
130}
131
132/// Select option.
133#[derive(Debug, Clone, Serialize)]
134pub struct SelectOption {
135    pub text: TextObject,
136    pub value: String,
137}
138
139/// Overflow element.
140#[derive(Debug, Clone, Serialize)]
141pub struct OverflowElement {
142    pub action_id: String,
143    pub options: Vec<SelectOption>,
144}
145
146// Builder helpers
147impl Block {
148    pub fn section(text: TextObject) -> Self {
149        Self::Section(SectionBlock {
150            text,
151            accessory: None,
152            fields: None,
153        })
154    }
155
156    pub fn divider() -> Self {
157        Self::Divider
158    }
159
160    pub fn header(text: impl Into<String>) -> Self {
161        Self::Header(HeaderBlock {
162            text: TextObject::plain(text),
163        })
164    }
165
166    pub fn context(elements: Vec<ContextElement>) -> Self {
167        Self::Context(ContextBlock { elements })
168    }
169}