slack_messaging/blocks/plan.rs
1use crate::blocks::TaskCard;
2use crate::validators::*;
3
4use serde::Serialize;
5use slack_messaging_derive::Builder;
6
7/// [Plan block](https://docs.slack.dev/reference/block-kit/blocks/plan-block) representation.
8///
9/// # Fields and Validations
10///
11/// For more details, see the [official documentation](https://docs.slack.dev/reference/block-kit/blocks/plan-block).
12///
13/// | Field | Type | Required | Validation |
14/// |-------|------|----------|------------|
15/// | title | String | Yes | N/A |
16/// | block_id | String | No | Maximum 255 characters |
17/// | tasks | Vec<[TaskCard]> | No | N/A |
18///
19/// # Example
20///
21/// ```
22/// use slack_messaging::blocks::{Plan, RichText, TaskCard, TaskStatus};
23/// use slack_messaging::blocks::rich_text::RichTextSection;
24/// use slack_messaging::blocks::rich_text::types::RichTextElementText;
25/// # use std::error::Error;
26///
27/// # fn try_main() -> Result<(), Box<dyn Error>> {
28/// let plan = Plan::builder()
29/// .title("Thinking completed")
30/// .task(
31/// TaskCard::builder()
32/// .task_id("call_001")
33/// .title("Fetched user profile information")
34/// .status(TaskStatus::InProgress)
35/// .details(
36/// RichText::builder()
37/// .block_id("viMWO")
38/// .element(
39/// RichTextSection::builder()
40/// .element(
41/// RichTextElementText::builder()
42/// .text("Searched database...")
43/// .build()?
44/// )
45/// .build()?
46/// )
47/// .build()?
48/// )
49/// .output(
50/// RichText::builder()
51/// .block_id("viMWO")
52/// .element(
53/// RichTextSection::builder()
54/// .element(
55/// RichTextElementText::builder()
56/// .text("Profile data loaded")
57/// .build()?
58/// )
59/// .build()?
60/// )
61/// .build()?
62/// )
63/// .build()?
64/// )
65/// .task(
66/// TaskCard::builder()
67/// .task_id("call_002")
68/// .title("Checked user permissions")
69/// .status(TaskStatus::Pending)
70/// .build()?
71/// )
72/// .task(
73/// TaskCard::builder()
74/// .task_id("call_003")
75/// .title("Generated comprehensive user report")
76/// .status(TaskStatus::Complete)
77/// .output(
78/// RichText::builder()
79/// .block_id("crsk")
80/// .element(
81/// RichTextSection::builder()
82/// .element(
83/// RichTextElementText::builder()
84/// .text("15 data points compiled")
85/// .build()?
86/// )
87/// .build()?
88/// )
89/// .build()?
90/// )
91/// .build()?
92/// )
93/// .build()?;
94///
95/// let expected = serde_json::json!({
96/// "type": "plan",
97/// "title": "Thinking completed",
98/// "tasks": [
99/// {
100/// "type": "task_card",
101/// "task_id": "call_001",
102/// "title": "Fetched user profile information",
103/// "status": "in_progress",
104/// "details": {
105/// "type": "rich_text",
106/// "block_id": "viMWO",
107/// "elements": [
108/// {
109/// "type": "rich_text_section",
110/// "elements": [
111/// {
112/// "type": "text",
113/// "text": "Searched database..."
114/// }
115/// ]
116/// }
117/// ]
118/// },
119/// "output": {
120/// "type": "rich_text",
121/// "block_id": "viMWO",
122/// "elements": [
123/// {
124/// "type": "rich_text_section",
125/// "elements": [
126/// {
127/// "type": "text",
128/// "text": "Profile data loaded"
129/// }
130/// ]
131/// }
132/// ]
133/// }
134/// },
135/// {
136/// "type": "task_card",
137/// "task_id": "call_002",
138/// "title": "Checked user permissions",
139/// "status": "pending"
140/// },
141/// {
142/// "type": "task_card",
143/// "task_id": "call_003",
144/// "title": "Generated comprehensive user report",
145/// "status": "complete",
146/// "output": {
147/// "type": "rich_text",
148/// "block_id": "crsk",
149/// "elements": [
150/// {
151/// "type": "rich_text_section",
152/// "elements": [
153/// {
154/// "type": "text",
155/// "text": "15 data points compiled"
156/// }
157/// ]
158/// }
159/// ]
160/// }
161/// }
162/// ]
163/// });
164///
165/// let json = serde_json::to_value(plan).unwrap();
166///
167/// assert_eq!(json, expected);
168/// # Ok(())
169/// # }
170/// # fn main() {
171/// # try_main().unwrap()
172/// # }
173///```
174#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
175#[serde(tag = "type", rename = "plan")]
176pub struct Plan {
177 #[builder(validate("required"))]
178 pub(crate) title: Option<String>,
179
180 #[serde(skip_serializing_if = "Option::is_none")]
181 #[builder(validate("text::max_255"))]
182 pub(crate) block_id: Option<String>,
183
184 #[serde(skip_serializing_if = "Option::is_none")]
185 #[builder(push_item = "task")]
186 pub(crate) tasks: Option<Vec<TaskCard>>,
187}
188
189#[cfg(test)]
190mod tests {
191 use super::super::test_helpers::*;
192 use super::*;
193 use crate::errors::*;
194
195 #[test]
196 fn it_implements_builder() {
197 let expected = Plan {
198 title: Some("Thinking completed".into()),
199 block_id: Some("block_0".into()),
200 tasks: Some(vec![task_card()]),
201 };
202
203 let val = Plan::builder()
204 .set_title(Some("Thinking completed"))
205 .set_block_id(Some("block_0"))
206 .set_tasks(Some(vec![task_card()]))
207 .build()
208 .unwrap();
209
210 assert_eq!(val, expected);
211
212 let val = Plan::builder()
213 .title("Thinking completed")
214 .block_id("block_0")
215 .tasks(vec![task_card()])
216 .build()
217 .unwrap();
218
219 assert_eq!(val, expected);
220 }
221
222 #[test]
223 fn it_implements_push_item_method() {
224 let expected = Plan {
225 title: Some("Thinking completed".into()),
226 block_id: None,
227 tasks: Some(vec![task_card()]),
228 };
229
230 let val = Plan::builder()
231 .title("Thinking completed")
232 .task(task_card())
233 .build()
234 .unwrap();
235
236 assert_eq!(val, expected);
237 }
238
239 #[test]
240 fn it_requires_title_field() {
241 let err = Plan::builder().build().unwrap_err();
242 assert_eq!(err.object(), "Plan");
243
244 let errors = err.field("title");
245 assert!(errors.includes(ValidationErrorKind::Required));
246 }
247
248 #[test]
249 fn it_requires_block_id_less_than_255_characters_long() {
250 let err = Plan::builder()
251 .title("Thinking completed")
252 .block_id("b".repeat(256))
253 .build()
254 .unwrap_err();
255 assert_eq!(err.object(), "Plan");
256
257 let errors = err.field("block_id");
258 assert!(errors.includes(ValidationErrorKind::MaxTextLength(255)));
259 }
260}