1use rsb_derive::Builder;
6use serde::{Deserialize, Serialize};
7use serde_with::skip_serializing_none;
8
9use crate::models::*;
10use crate::ratectl::*;
11use crate::SlackClientSession;
12use crate::{ClientResult, SlackClientHttpConnector};
13
14impl<'a, SCHC> SlackClientSession<'a, SCHC>
15where
16 SCHC: SlackClientHttpConnector + Send,
17{
18 pub async fn canvases_create(
22 &self,
23 req: &SlackApiCanvasesCreateRequest,
24 ) -> ClientResult<SlackApiCanvasesCreateResponse> {
25 self.http_session_api
26 .http_post("canvases.create", req, Some(&SLACK_TIER2_METHOD_CONFIG))
27 .await
28 }
29
30 pub async fn canvases_edit(
34 &self,
35 req: &SlackApiCanvasesEditRequest,
36 ) -> ClientResult<SlackApiCanvasesEditResponse> {
37 self.http_session_api
38 .http_post("canvases.edit", req, Some(&SLACK_TIER3_METHOD_CONFIG))
39 .await
40 }
41
42 pub async fn canvases_delete(
46 &self,
47 req: &SlackApiCanvasesDeleteRequest,
48 ) -> ClientResult<SlackApiCanvasesDeleteResponse> {
49 self.http_session_api
50 .http_post("canvases.delete", req, Some(&SLACK_TIER3_METHOD_CONFIG))
51 .await
52 }
53
54 pub async fn canvases_sections_lookup(
58 &self,
59 req: &SlackApiCanvasesSectionsLookupRequest,
60 ) -> ClientResult<SlackApiCanvasesSectionsLookupResponse> {
61 self.http_session_api
62 .http_post(
63 "canvases.sections.lookup",
64 req,
65 Some(&SLACK_TIER3_METHOD_CONFIG),
66 )
67 .await
68 }
69
70 pub async fn canvases_access_set(
74 &self,
75 req: &SlackApiCanvasesAccessSetRequest,
76 ) -> ClientResult<SlackApiCanvasesAccessSetResponse> {
77 self.http_session_api
78 .http_post("canvases.access.set", req, Some(&SLACK_TIER3_METHOD_CONFIG))
79 .await
80 }
81
82 pub async fn canvases_access_delete(
86 &self,
87 req: &SlackApiCanvasesAccessDeleteRequest,
88 ) -> ClientResult<SlackApiCanvasesAccessDeleteResponse> {
89 self.http_session_api
90 .http_post(
91 "canvases.access.delete",
92 req,
93 Some(&SLACK_TIER3_METHOD_CONFIG),
94 )
95 .await
96 }
97
98 pub async fn conversations_canvases_create(
102 &self,
103 req: &SlackApiConversationsCanvasesCreateRequest,
104 ) -> ClientResult<SlackApiConversationsCanvasesCreateResponse> {
105 self.http_session_api
106 .http_post(
107 "conversations.canvases.create",
108 req,
109 Some(&SLACK_TIER2_METHOD_CONFIG),
110 )
111 .await
112 }
113}
114
115#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
118#[serde(tag = "type", rename = "markdown")]
119pub struct SlackCanvasDocumentContent {
120 pub markdown: String,
121}
122
123#[skip_serializing_none]
125#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
126pub struct SlackApiCanvasesCreateRequest {
127 pub title: Option<String>,
128 pub document_content: Option<SlackCanvasDocumentContent>,
129 pub channel_id: Option<SlackChannelId>,
130}
131
132#[skip_serializing_none]
133#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
134pub struct SlackApiCanvasesCreateResponse {
135 pub canvas_id: SlackCanvasId,
136}
137
138#[skip_serializing_none]
140#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
141pub struct SlackApiCanvasesEditRequest {
142 pub canvas_id: SlackCanvasId,
143 pub changes: Vec<SlackCanvasChange>,
144}
145
146#[skip_serializing_none]
147#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
148pub struct SlackApiCanvasesEditResponse {}
149
150#[skip_serializing_none]
154#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
155pub struct SlackCanvasChange {
156 pub operation: SlackCanvasOperation,
157 pub section_id: Option<SlackCanvasSectionId>,
158 pub document_content: Option<SlackCanvasDocumentContent>,
159 pub title_content: Option<SlackCanvasDocumentContent>,
160}
161
162#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
163#[serde(rename_all = "snake_case")]
164pub enum SlackCanvasOperation {
165 InsertAfter,
166 InsertBefore,
167 InsertAtStart,
168 InsertAtEnd,
169 Replace,
170 Delete,
171 Rename,
172}
173
174#[skip_serializing_none]
175#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
176pub struct SlackApiCanvasesDeleteRequest {
177 pub canvas_id: SlackCanvasId,
178}
179
180#[skip_serializing_none]
181#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
182pub struct SlackApiCanvasesDeleteResponse {}
183
184#[skip_serializing_none]
185#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
186pub struct SlackApiCanvasesSectionsLookupRequest {
187 pub canvas_id: SlackCanvasId,
188 pub criteria: SlackCanvasSectionsLookupCriteria,
189}
190
191#[skip_serializing_none]
193#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
194pub struct SlackCanvasSectionsLookupCriteria {
195 pub section_types: Option<Vec<SlackCanvasSectionType>>,
196 pub contains_text: Option<String>,
197}
198
199#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
202#[serde(rename_all = "snake_case")]
203pub enum SlackCanvasSectionType {
204 AnyHeader,
205 Blockquote,
206 Callout,
207 CanvasUnfurl,
208 Chart,
209 Citation,
210 FileUnfurl,
211 Flexbox,
212 H1,
213 H2,
214 H3,
215 HorizontalLine,
216 List,
217 MessageUnfurl,
218 SfdcRecordMention,
219 SfdcRecordUnfurl,
220 Table,
221 UserMention,
222 UserUnfurl,
223 #[serde(untagged)]
225 Other(String),
226}
227
228#[skip_serializing_none]
229#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
230pub struct SlackApiCanvasesSectionsLookupResponse {
231 pub sections: Vec<SlackCanvasSection>,
232}
233
234#[skip_serializing_none]
235#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
236pub struct SlackCanvasSection {
237 pub id: SlackCanvasSectionId,
238}
239
240#[skip_serializing_none]
244#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
245pub struct SlackApiCanvasesAccessSetRequest {
246 pub canvas_id: SlackCanvasId,
247 pub access_level: SlackCanvasAccessLevel,
248 pub channel_ids: Option<Vec<SlackChannelId>>,
249 pub user_ids: Option<Vec<SlackUserId>>,
250}
251
252#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
253#[serde(rename_all = "snake_case")]
254pub enum SlackCanvasAccessLevel {
255 Read,
256 Write,
257 Owner,
258}
259
260#[skip_serializing_none]
261#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
262pub struct SlackApiCanvasesAccessSetResponse {}
263
264#[skip_serializing_none]
266#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
267pub struct SlackApiCanvasesAccessDeleteRequest {
268 pub canvas_id: SlackCanvasId,
269 pub channel_ids: Option<Vec<SlackChannelId>>,
270 pub user_ids: Option<Vec<SlackUserId>>,
271}
272
273#[skip_serializing_none]
274#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
275pub struct SlackApiCanvasesAccessDeleteResponse {}
276
277#[skip_serializing_none]
280#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
281pub struct SlackApiConversationsCanvasesCreateRequest {
282 pub channel_id: SlackChannelId,
283 pub document_content: Option<SlackCanvasDocumentContent>,
284 pub title: Option<String>,
285}
286
287#[skip_serializing_none]
288#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
289pub struct SlackApiConversationsCanvasesCreateResponse {
290 pub canvas_id: SlackCanvasId,
291}
292
293#[cfg(test)]
294mod test {
295 use super::*;
296
297 fn round_trip<T>(payload: &str) -> T
300 where
301 T: Serialize + for<'de> Deserialize<'de>,
302 {
303 let mut expected: serde_json::Value = serde_json::from_str(payload).unwrap();
304 expected.as_object_mut().unwrap().remove("ok");
305 let model: T = serde_json::from_str(payload).unwrap();
306 assert_eq!(serde_json::to_value(&model).unwrap(), expected);
307 model
308 }
309
310 #[test]
311 fn test_slack_api_canvases_create_request_matches_docs_example() {
312 let req = SlackApiCanvasesCreateRequest::new()
313 .with_title("Your Brilliant Title".into())
314 .with_document_content(SlackCanvasDocumentContent::new(
315 "> standalone canvas!".into(),
316 ));
317 assert_eq!(
318 serde_json::to_value(&req).unwrap(),
319 serde_json::json!({
320 "title": "Your Brilliant Title",
321 "document_content": { "type": "markdown", "markdown": "> standalone canvas!" }
322 })
323 );
324 let resp: SlackApiCanvasesCreateResponse =
325 round_trip(r#"{ "ok": true, "canvas_id": "F1234ABCD" }"#);
326 assert_eq!(resp.canvas_id, "F1234ABCD".into());
327 }
328
329 #[test]
330 fn test_slack_api_canvases_edit_request_fixture() {
331 let req: SlackApiCanvasesEditRequest = round_trip(include_str!(
332 "./fixtures/slack_api_canvases_edit_request.json"
333 ));
334 assert_eq!(req.canvas_id, "F0166DCSTS7".into());
335 assert_eq!(req.changes.len(), 1);
336 assert_eq!(req.changes[0].operation, SlackCanvasOperation::InsertAfter);
337 assert_eq!(
338 req.changes[0].section_id,
339 Some("temp:C:VXX8e648e6984e441c6aa8c61173".into())
340 );
341 assert_eq!(
342 req.changes[0].document_content.as_ref().unwrap().markdown,
343 "- [ ] asparagus\n- [ ] coffee\n"
344 );
345
346 let rename = SlackCanvasChange::new(SlackCanvasOperation::Rename)
347 .with_title_content(SlackCanvasDocumentContent::new("Project Status".into()));
348 assert_eq!(
349 serde_json::to_value(&rename).unwrap(),
350 serde_json::json!({
351 "operation": "rename",
352 "title_content": { "type": "markdown", "markdown": "Project Status" }
353 })
354 );
355 }
356
357 #[test]
358 fn test_slack_api_canvases_sections_lookup_fixtures() {
359 let req: SlackApiCanvasesSectionsLookupRequest = round_trip(include_str!(
360 "./fixtures/slack_api_canvases_sections_lookup_request.json"
361 ));
362 assert_eq!(
363 req.criteria.section_types,
364 Some(vec![SlackCanvasSectionType::H1, SlackCanvasSectionType::H2])
365 );
366 assert_eq!(req.criteria.contains_text.as_deref(), Some("Grocery List"));
367
368 let future: SlackCanvasSectionType = serde_json::from_str(r#""hologram""#).unwrap();
369 assert_eq!(future, SlackCanvasSectionType::Other("hologram".into()));
370
371 let resp: SlackApiCanvasesSectionsLookupResponse = round_trip(include_str!(
372 "./fixtures/slack_api_canvases_sections_lookup_response.json"
373 ));
374 assert_eq!(
375 resp.sections[0].id,
376 "temp:C:eBa219af721c664422cb90a52fac".into()
377 );
378 }
379
380 #[test]
381 fn test_slack_api_canvases_access_requests_serialize_only_given_targets() {
382 let set = SlackApiCanvasesAccessSetRequest::new(
383 "F1234ABCD".into(),
384 SlackCanvasAccessLevel::Write,
385 )
386 .with_channel_ids(vec!["C1234ABCD".into()]);
387 assert_eq!(
388 serde_json::to_value(&set).unwrap(),
389 serde_json::json!({
390 "canvas_id": "F1234ABCD",
391 "access_level": "write",
392 "channel_ids": ["C1234ABCD"]
393 })
394 );
395
396 let delete = SlackApiCanvasesAccessDeleteRequest::new("F1234ABCD".into())
397 .with_user_ids(vec!["U1234ABCD".into()]);
398 assert_eq!(
399 serde_json::to_value(&delete).unwrap(),
400 serde_json::json!({ "canvas_id": "F1234ABCD", "user_ids": ["U1234ABCD"] })
401 );
402
403 let empty: SlackApiCanvasesAccessSetResponse = round_trip(r#"{ "ok": true }"#);
404 assert_eq!(empty, SlackApiCanvasesAccessSetResponse::new());
405 }
406
407 #[test]
408 fn test_slack_api_conversations_canvases_create_round_trip() {
409 let req = SlackApiConversationsCanvasesCreateRequest::new("C1234ABCD".into())
410 .with_document_content(SlackCanvasDocumentContent::new("> channel canvas!".into()));
411 assert_eq!(
412 serde_json::to_value(&req).unwrap(),
413 serde_json::json!({
414 "channel_id": "C1234ABCD",
415 "document_content": { "type": "markdown", "markdown": "> channel canvas!" }
416 })
417 );
418 let resp: SlackApiConversationsCanvasesCreateResponse =
419 round_trip(r#"{ "ok": true, "canvas_id": "F1234ABCD" }"#);
420 assert_eq!(resp.canvas_id, "F1234ABCD".into());
421 }
422}