slack_messaging/message.rs
1use crate::blocks::Block;
2use crate::validators::*;
3
4use serde::Serialize;
5use slack_messaging_derive::Builder;
6
7/// [`Message`](https://docs.slack.dev/messaging#payloads)
8/// representation.
9///
10/// See also [Header](crate::blocks::Header), [Section](crate::blocks::Section)
11/// and [any other blocks](crate::blocks) to know how to build these blocks.
12///
13/// Every block and its components have each builder and their build method
14/// returns [Result].
15///
16/// For example, according to [the official document](https://docs.slack.dev/reference/block-kit/blocks?available-in-surfaces=Home+tabs),
17/// you can include up to 50 blocks in each message. If you include more than 50
18/// blocks in a message, the build method of [MessageBuilder] returns Result::Err.
19///
20/// # Fields and Validations
21///
22/// For more details, see the [official
23/// documentation](https://docs.slack.dev/messaging#payloads).
24///
25/// | Field | Type | Required | Validation |
26/// |-------|------|----------|------------|
27/// | text | String | No | N/A |
28/// | blocks | Vec<[Block]> | No | Maximum 50 items |
29/// | thread_ts | String | No | N/A |
30/// | mrkdwn | bool | No | N/A |
31/// | response_type | String | No | N/A |
32/// | replace_original | bool | No | N/A |
33/// | delete_original | bool | No | N/A |
34/// | reply_broadcast | bool | No | N/A |
35///
36/// # Example
37///
38/// ```
39/// use slack_messaging::{mrkdwn, plain_text, Message};
40/// use slack_messaging::blocks::{Header, Section};
41/// # use std::error::Error;
42///
43/// # fn try_main() -> Result<(), Box<dyn Error>> {
44/// let message = Message::builder()
45/// .text("New Paid Time Off request from Fred Enriquez")
46/// .block(
47/// Header::builder()
48/// .text(plain_text!("New request")?)
49/// .build()?
50/// )
51/// .block(
52/// Section::builder()
53/// .field(mrkdwn!("*Type:*\nPaid Time Off")?)
54/// .field(mrkdwn!("*Created by:*\n<example.com|Fred Enriquez>")?)
55/// .build()?
56/// )
57/// .block(
58/// Section::builder()
59/// .field(mrkdwn!("*When:*\nAug 10 - Aug 13")?)
60/// .build()?
61/// )
62/// .block(
63/// Section::builder()
64/// .text(mrkdwn!("<https://example.com|View request>")?)
65/// .build()?
66/// )
67/// .build()?;
68///
69/// let expected = serde_json::json!({
70/// "text": "New Paid Time Off request from Fred Enriquez",
71/// "blocks": [
72/// {
73/// "type": "header",
74/// "text": {
75/// "type": "plain_text",
76/// "text": "New request"
77/// }
78/// },
79/// {
80/// "type": "section",
81/// "fields": [
82/// {
83/// "type": "mrkdwn",
84/// "text": "*Type:*\nPaid Time Off"
85/// },
86/// {
87/// "type": "mrkdwn",
88/// "text": "*Created by:*\n<example.com|Fred Enriquez>"
89/// }
90/// ]
91/// },
92/// {
93/// "type": "section",
94/// "fields": [
95/// {
96/// "type": "mrkdwn",
97/// "text": "*When:*\nAug 10 - Aug 13"
98/// }
99/// ]
100/// },
101/// {
102/// "type": "section",
103/// "text": {
104/// "type": "mrkdwn",
105/// "text": "<https://example.com|View request>"
106/// }
107/// }
108/// ]
109/// });
110///
111/// let json = serde_json::to_value(message)?;
112///
113/// assert_eq!(json, expected);
114/// # Ok(())
115/// # }
116/// # fn main() {
117/// # try_main().unwrap()
118/// # }
119/// ```
120#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
121pub struct Message {
122 #[serde(skip_serializing_if = "Option::is_none")]
123 pub(crate) text: Option<String>,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
126 #[builder(push_item = "block", validate("list::max_item_50"))]
127 pub(crate) blocks: Option<Vec<Block>>,
128
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub(crate) thread_ts: Option<String>,
131
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub(crate) mrkdwn: Option<bool>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub(crate) response_type: Option<String>,
137
138 #[serde(skip_serializing_if = "Option::is_none")]
139 pub(crate) replace_original: Option<bool>,
140
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub(crate) delete_original: Option<bool>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
145 pub(crate) reply_broadcast: Option<bool>,
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::blocks::test_helpers::*;
152 use crate::errors::*;
153
154 #[test]
155 fn it_implements_builder() {
156 let expected = Message {
157 text: Some("some text".into()),
158 blocks: Some(vec![
159 header("this is a header block").into(),
160 section("this is a section block").into(),
161 ]),
162 thread_ts: Some("thread ts".into()),
163 mrkdwn: Some(true),
164 response_type: Some("response type".into()),
165 replace_original: Some(true),
166 delete_original: Some(true),
167 reply_broadcast: Some(true),
168 };
169
170 let val = Message::builder()
171 .set_text(Some("some text"))
172 .set_blocks(Some(vec![
173 header("this is a header block").into(),
174 section("this is a section block").into(),
175 ]))
176 .set_thread_ts(Some("thread ts"))
177 .set_mrkdwn(Some(true))
178 .set_response_type(Some("response type"))
179 .set_replace_original(Some(true))
180 .set_delete_original(Some(true))
181 .set_reply_broadcast(Some(true))
182 .build()
183 .unwrap();
184
185 assert_eq!(val, expected);
186
187 let val = Message::builder()
188 .text("some text")
189 .blocks(vec![
190 header("this is a header block").into(),
191 section("this is a section block").into(),
192 ])
193 .thread_ts("thread ts")
194 .mrkdwn(true)
195 .response_type("response type")
196 .replace_original(true)
197 .delete_original(true)
198 .reply_broadcast(true)
199 .build()
200 .unwrap();
201
202 assert_eq!(val, expected);
203 }
204
205 #[test]
206 fn it_impelements_push_item_method() {
207 let expected = Message {
208 text: None,
209 blocks: Some(vec![
210 header("this is a header block").into(),
211 section("this is a section block").into(),
212 ]),
213 thread_ts: None,
214 mrkdwn: None,
215 response_type: None,
216 replace_original: None,
217 delete_original: None,
218 reply_broadcast: None,
219 };
220
221 let val = Message::builder()
222 .block(header("this is a header block"))
223 .block(section("this is a section block"))
224 .build()
225 .unwrap();
226
227 assert_eq!(val, expected);
228 }
229
230 #[test]
231 fn it_requries_blocks_list_size_less_than_50() {
232 let blocks: Vec<Block> = (0..51).map(|_| section("some section").into()).collect();
233 let err = Message::builder().blocks(blocks).build().unwrap_err();
234 assert_eq!(err.object(), "Message");
235
236 let errors = err.field("blocks");
237 assert!(errors.includes(ValidationErrorKind::MaxArraySize(50)));
238 }
239}