slack_rust/views/
update.rs

1use crate::error::Error;
2use crate::http_client::{get_slack_url, ResponseMetadata, SlackWebAPIClient};
3use crate::views::view::View;
4use serde::{Deserialize, Serialize};
5use serde_with::skip_serializing_none;
6
7#[skip_serializing_none]
8#[derive(Deserialize, Serialize, Debug, Default, PartialEq)]
9pub struct UpdateRequest {
10    pub trigger_id: String,
11    pub view: View,
12    pub external_id: Option<String>,
13    pub view_id: Option<String>,
14    pub hash: Option<String>,
15}
16
17#[skip_serializing_none]
18#[derive(Deserialize, Serialize, Debug, Default, PartialEq)]
19pub struct UpdateResponse {
20    pub ok: bool,
21    pub error: Option<String>,
22    pub response_metadata: Option<ResponseMetadata>,
23    pub view: Option<View>,
24}
25
26pub async fn update<T>(
27    client: &T,
28    param: &UpdateRequest,
29    bot_token: &str,
30) -> Result<UpdateResponse, Error>
31where
32    T: SlackWebAPIClient,
33{
34    let url = get_slack_url("views.update");
35    let json = serde_json::to_string(&param)?;
36
37    client
38        .post_json(&url, &json, bot_token)
39        .await
40        .and_then(|result| {
41            serde_json::from_str::<UpdateResponse>(&result).map_err(Error::SerdeJsonError)
42        })
43}
44
45#[cfg(test)]
46mod test {
47    use super::*;
48    use crate::block::block_actions::ActionBlock;
49    use crate::block::block_elements::{
50        BlockElement, ButtonElement, MultiSelectBlockElement, PlainTextInputBlockElement,
51    };
52    use crate::block::block_input::InputBlock;
53    use crate::block::block_object::{OptionBlockObject, TextBlockObject, TextBlockType};
54    use crate::block::blocks::Block;
55
56    use crate::http_client::MockSlackWebAPIClient;
57    use crate::views::view::ViewType;
58
59    #[test]
60    fn convert_request() {
61        let request = UpdateRequest {
62            trigger_id: "12345.98765.abcd2358fdea".to_string(),
63            view: View {
64                type_filed: Some(ViewType::Modal),
65                title: Some(TextBlockObject {
66                    type_filed: TextBlockType::PlainText,
67                    text: "Slack Rust Example Modal".to_string(),
68                    ..Default::default()
69                }),
70                submit: Some(TextBlockObject {
71                    type_filed: TextBlockType::PlainText,
72                    text: "Submit".to_string(),
73                    ..Default::default()
74                }),
75                blocks: Some(vec![
76                    Block::InputBlock(InputBlock {
77                        label: TextBlockObject {
78                            type_filed: TextBlockType::PlainText,
79                            text: "Title".to_string(),
80                            ..Default::default()
81                        },
82                        element: BlockElement::PlainTextInputBlockElement(
83                            PlainTextInputBlockElement {
84                                action_id: "title".to_string(),
85                                placeholder: Some(TextBlockObject {
86                                    type_filed: TextBlockType::PlainText,
87                                    text: "What do you want to ask of the world?".to_string(),
88                                    ..Default::default()
89                                }),
90                                ..Default::default()
91                            },
92                        ),
93                        ..Default::default()
94                    }),
95                    Block::InputBlock(InputBlock {
96                        label: TextBlockObject {
97                            type_filed: TextBlockType::PlainText,
98                            text: "Channel(s)".to_string(),
99                            ..Default::default()
100                        },
101                        element: BlockElement::MultiSelectBlockElement(MultiSelectBlockElement {
102                            action_id: "title".to_string(),
103                            placeholder: TextBlockObject {
104                                type_filed: TextBlockType::PlainText,
105                                text: "Where should the poll be sent?".to_string(),
106                                ..Default::default()
107                            },
108                            options: vec![OptionBlockObject {
109                                text: TextBlockObject {
110                                    type_filed: TextBlockType::PlainText,
111                                    text: "*this is plain_text text*".to_string(),
112                                    ..Default::default()
113                                },
114                                value: Some("value-0".to_string()),
115                                ..Default::default()
116                            }],
117                            ..Default::default()
118                        }),
119                        ..Default::default()
120                    }),
121                    Block::ActionBlock(ActionBlock {
122                        elements: vec![BlockElement::ButtonElement(ButtonElement {
123                            action_id: "add_option".to_string(),
124                            text: TextBlockObject {
125                                type_filed: TextBlockType::PlainText,
126                                text: "Add another option".to_string(),
127                                ..Default::default()
128                            },
129                            ..Default::default()
130                        })],
131                        ..Default::default()
132                    }),
133                ]),
134                ..Default::default()
135            },
136            view_id: Some("VMM512F2U".to_string()),
137            hash: Some("156772938.1827394".to_string()),
138            ..Default::default()
139        };
140        let json = r##"{
141  "trigger_id": "12345.98765.abcd2358fdea",
142  "view": {
143    "type": "modal",
144    "blocks": [
145      {
146        "type": "input",
147        "label": {
148          "type": "plain_text",
149          "text": "Title"
150        },
151        "element": {
152          "type": "plain_text_input",
153          "action_id": "title",
154          "placeholder": {
155            "type": "plain_text",
156            "text": "What do you want to ask of the world?"
157          }
158        }
159      },
160      {
161        "type": "input",
162        "label": {
163          "type": "plain_text",
164          "text": "Channel(s)"
165        },
166        "element": {
167          "type": "multi_static_select",
168          "placeholder": {
169            "type": "plain_text",
170            "text": "Where should the poll be sent?"
171          },
172          "action_id": "title",
173          "options": [
174            {
175              "text": {
176                "type": "plain_text",
177                "text": "*this is plain_text text*"
178              },
179              "value": "value-0"
180            }
181          ]
182        }
183      },
184      {
185        "type": "actions",
186        "elements": [
187          {
188            "type": "button",
189            "text": {
190              "type": "plain_text",
191              "text": "Add another option"
192            },
193            "action_id": "add_option"
194          }
195        ]
196      }
197    ],
198    "title": {
199      "type": "plain_text",
200      "text": "Slack Rust Example Modal"
201    },
202    "submit": {
203      "type": "plain_text",
204      "text": "Submit"
205    }
206  },
207  "view_id": "VMM512F2U",
208  "hash": "156772938.1827394"
209}"##;
210
211        let j = serde_json::to_string_pretty(&request).unwrap();
212        assert_eq!(json, j);
213
214        let s = serde_json::from_str::<UpdateRequest>(json).unwrap();
215        assert_eq!(request, s);
216    }
217
218    #[test]
219    fn convert_response() {
220        let response = UpdateResponse {
221            ok: true,
222            view: Some(View {
223                type_filed: Some(ViewType::Modal),
224                title: Some(TextBlockObject {
225                    type_filed: TextBlockType::PlainText,
226                    text: "Slack Rust Example Modal".to_string(),
227                    ..Default::default()
228                }),
229                submit: Some(TextBlockObject {
230                    type_filed: TextBlockType::PlainText,
231                    text: "Submit".to_string(),
232                    ..Default::default()
233                }),
234                blocks: Some(vec![
235                    Block::InputBlock(InputBlock {
236                        label: TextBlockObject {
237                            type_filed: TextBlockType::PlainText,
238                            text: "Title".to_string(),
239                            ..Default::default()
240                        },
241                        element: BlockElement::PlainTextInputBlockElement(
242                            PlainTextInputBlockElement {
243                                action_id: "title".to_string(),
244                                placeholder: Some(TextBlockObject {
245                                    type_filed: TextBlockType::PlainText,
246                                    text: "What do you want to ask of the world?".to_string(),
247                                    ..Default::default()
248                                }),
249                                ..Default::default()
250                            },
251                        ),
252                        ..Default::default()
253                    }),
254                    Block::InputBlock(InputBlock {
255                        label: TextBlockObject {
256                            type_filed: TextBlockType::PlainText,
257                            text: "Channel(s)".to_string(),
258                            ..Default::default()
259                        },
260                        element: BlockElement::MultiSelectBlockElement(MultiSelectBlockElement {
261                            action_id: "title".to_string(),
262                            placeholder: TextBlockObject {
263                                type_filed: TextBlockType::PlainText,
264                                text: "Where should the poll be sent?".to_string(),
265                                ..Default::default()
266                            },
267                            options: vec![OptionBlockObject {
268                                text: TextBlockObject {
269                                    type_filed: TextBlockType::PlainText,
270                                    text: "*this is plain_text text*".to_string(),
271                                    ..Default::default()
272                                },
273                                value: Some("value-0".to_string()),
274                                ..Default::default()
275                            }],
276                            ..Default::default()
277                        }),
278                        ..Default::default()
279                    }),
280                    Block::ActionBlock(ActionBlock {
281                        elements: vec![BlockElement::ButtonElement(ButtonElement {
282                            action_id: "add_option".to_string(),
283                            text: TextBlockObject {
284                                type_filed: TextBlockType::PlainText,
285                                text: "Add another option".to_string(),
286                                ..Default::default()
287                            },
288                            ..Default::default()
289                        })],
290                        ..Default::default()
291                    }),
292                ]),
293                ..Default::default()
294            }),
295            ..Default::default()
296        };
297        let json = r##"{
298  "ok": true,
299  "view": {
300    "type": "modal",
301    "blocks": [
302      {
303        "type": "input",
304        "label": {
305          "type": "plain_text",
306          "text": "Title"
307        },
308        "element": {
309          "type": "plain_text_input",
310          "action_id": "title",
311          "placeholder": {
312            "type": "plain_text",
313            "text": "What do you want to ask of the world?"
314          }
315        }
316      },
317      {
318        "type": "input",
319        "label": {
320          "type": "plain_text",
321          "text": "Channel(s)"
322        },
323        "element": {
324          "type": "multi_static_select",
325          "placeholder": {
326            "type": "plain_text",
327            "text": "Where should the poll be sent?"
328          },
329          "action_id": "title",
330          "options": [
331            {
332              "text": {
333                "type": "plain_text",
334                "text": "*this is plain_text text*"
335              },
336              "value": "value-0"
337            }
338          ]
339        }
340      },
341      {
342        "type": "actions",
343        "elements": [
344          {
345            "type": "button",
346            "text": {
347              "type": "plain_text",
348              "text": "Add another option"
349            },
350            "action_id": "add_option"
351          }
352        ]
353      }
354    ],
355    "title": {
356      "type": "plain_text",
357      "text": "Slack Rust Example Modal"
358    },
359    "submit": {
360      "type": "plain_text",
361      "text": "Submit"
362    }
363  }
364}"##;
365
366        let j = serde_json::to_string_pretty(&response).unwrap();
367        assert_eq!(json, j);
368
369        let s = serde_json::from_str::<UpdateResponse>(json).unwrap();
370        assert_eq!(response, s);
371    }
372
373    #[async_std::test]
374    async fn test_update() {
375        let param = UpdateRequest {
376            trigger_id: "12345.98765.abcd2358fdea".to_string(),
377            view: View {
378                type_filed: Some(ViewType::Modal),
379                title: Some(TextBlockObject {
380                    type_filed: TextBlockType::PlainText,
381                    text: "Slack Rust Example Modal".to_string(),
382                    ..Default::default()
383                }),
384                submit: Some(TextBlockObject {
385                    type_filed: TextBlockType::PlainText,
386                    text: "Submit".to_string(),
387                    ..Default::default()
388                }),
389                blocks: Some(vec![
390                    Block::InputBlock(InputBlock {
391                        label: TextBlockObject {
392                            type_filed: TextBlockType::PlainText,
393                            text: "Title".to_string(),
394                            ..Default::default()
395                        },
396                        element: BlockElement::PlainTextInputBlockElement(
397                            PlainTextInputBlockElement {
398                                action_id: "title".to_string(),
399                                placeholder: Some(TextBlockObject {
400                                    type_filed: TextBlockType::PlainText,
401                                    text: "What do you want to ask of the world?".to_string(),
402                                    ..Default::default()
403                                }),
404                                ..Default::default()
405                            },
406                        ),
407                        ..Default::default()
408                    }),
409                    Block::InputBlock(InputBlock {
410                        label: TextBlockObject {
411                            type_filed: TextBlockType::PlainText,
412                            text: "Channel(s)".to_string(),
413                            ..Default::default()
414                        },
415                        element: BlockElement::MultiSelectBlockElement(MultiSelectBlockElement {
416                            action_id: "title".to_string(),
417                            placeholder: TextBlockObject {
418                                type_filed: TextBlockType::PlainText,
419                                text: "Where should the poll be sent?".to_string(),
420                                ..Default::default()
421                            },
422                            options: vec![OptionBlockObject {
423                                text: TextBlockObject {
424                                    type_filed: TextBlockType::PlainText,
425                                    text: "*this is plain_text text*".to_string(),
426                                    ..Default::default()
427                                },
428                                value: Some("value-0".to_string()),
429                                ..Default::default()
430                            }],
431                            ..Default::default()
432                        }),
433                        ..Default::default()
434                    }),
435                    Block::ActionBlock(ActionBlock {
436                        elements: vec![BlockElement::ButtonElement(ButtonElement {
437                            action_id: "add_option".to_string(),
438                            text: TextBlockObject {
439                                type_filed: TextBlockType::PlainText,
440                                text: "Add another option".to_string(),
441                                ..Default::default()
442                            },
443                            ..Default::default()
444                        })],
445                        ..Default::default()
446                    }),
447                ]),
448                ..Default::default()
449            },
450            view_id: Some("VMM512F2U".to_string()),
451            hash: Some("156772938.1827394".to_string()),
452            ..Default::default()
453        };
454        let mut mock = MockSlackWebAPIClient::new();
455        mock.expect_post_json().returning(|_, _, _| {
456            Ok(r##"{
457  "ok": true,
458  "view": {
459    "type": "modal",
460    "blocks": [
461      {
462        "type": "input",
463        "label": {
464          "type": "plain_text",
465          "text": "Title"
466        },
467        "element": {
468          "type": "plain_text_input",
469          "action_id": "title",
470          "placeholder": {
471            "type": "plain_text",
472            "text": "What do you want to ask of the world?"
473          }
474        }
475      },
476      {
477        "type": "input",
478        "label": {
479          "type": "plain_text",
480          "text": "Channel(s)"
481        },
482        "element": {
483          "type": "multi_static_select",
484          "placeholder": {
485            "type": "plain_text",
486            "text": "Where should the poll be sent?"
487          },
488          "action_id": "title",
489          "options": [
490            {
491              "text": {
492                "type": "plain_text",
493                "text": "*this is plain_text text*"
494              },
495              "value": "value-0"
496            }
497          ]
498        }
499      },
500      {
501        "type": "actions",
502        "elements": [
503          {
504            "type": "button",
505            "text": {
506              "type": "plain_text",
507              "text": "Add another option"
508            },
509            "action_id": "add_option"
510          }
511        ]
512      }
513    ],
514    "title": {
515      "type": "plain_text",
516      "text": "Slack Rust Example Modal"
517    },
518    "submit": {
519      "type": "plain_text",
520      "text": "Submit"
521    }
522  }
523}"##
524            .to_string())
525        });
526
527        let response = update(&mock, &param, &"test_token".to_string())
528            .await
529            .unwrap();
530        let expect = UpdateResponse {
531            ok: true,
532            view: Some(View {
533                type_filed: Some(ViewType::Modal),
534                title: Some(TextBlockObject {
535                    type_filed: TextBlockType::PlainText,
536                    text: "Slack Rust Example Modal".to_string(),
537                    ..Default::default()
538                }),
539                submit: Some(TextBlockObject {
540                    type_filed: TextBlockType::PlainText,
541                    text: "Submit".to_string(),
542                    ..Default::default()
543                }),
544                blocks: Some(vec![
545                    Block::InputBlock(InputBlock {
546                        label: TextBlockObject {
547                            type_filed: TextBlockType::PlainText,
548                            text: "Title".to_string(),
549                            ..Default::default()
550                        },
551                        element: BlockElement::PlainTextInputBlockElement(
552                            PlainTextInputBlockElement {
553                                action_id: "title".to_string(),
554                                placeholder: Some(TextBlockObject {
555                                    type_filed: TextBlockType::PlainText,
556                                    text: "What do you want to ask of the world?".to_string(),
557                                    ..Default::default()
558                                }),
559                                ..Default::default()
560                            },
561                        ),
562                        ..Default::default()
563                    }),
564                    Block::InputBlock(InputBlock {
565                        label: TextBlockObject {
566                            type_filed: TextBlockType::PlainText,
567                            text: "Channel(s)".to_string(),
568                            ..Default::default()
569                        },
570                        element: BlockElement::MultiSelectBlockElement(MultiSelectBlockElement {
571                            action_id: "title".to_string(),
572                            placeholder: TextBlockObject {
573                                type_filed: TextBlockType::PlainText,
574                                text: "Where should the poll be sent?".to_string(),
575                                ..Default::default()
576                            },
577                            options: vec![OptionBlockObject {
578                                text: TextBlockObject {
579                                    type_filed: TextBlockType::PlainText,
580                                    text: "*this is plain_text text*".to_string(),
581                                    ..Default::default()
582                                },
583                                value: Some("value-0".to_string()),
584                                ..Default::default()
585                            }],
586                            ..Default::default()
587                        }),
588                        ..Default::default()
589                    }),
590                    Block::ActionBlock(ActionBlock {
591                        elements: vec![BlockElement::ButtonElement(ButtonElement {
592                            action_id: "add_option".to_string(),
593                            text: TextBlockObject {
594                                type_filed: TextBlockType::PlainText,
595                                text: "Add another option".to_string(),
596                                ..Default::default()
597                            },
598                            ..Default::default()
599                        })],
600                        ..Default::default()
601                    }),
602                ]),
603                ..Default::default()
604            }),
605            ..Default::default()
606        };
607
608        assert_eq!(expect, response);
609    }
610}