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