sfr_slack_api/api/
files_upload.rs

1//! The Slack API definition of `files.upload`.
2//!
3//! <https://api.slack.com/methods/files.upload>
4
5use sfr_types as st;
6
7pub use st::FilesUploadResponse;
8
9use crate::{Client, Request};
10use reqwest::multipart::Form;
11
12/// The request object for <https://api.slack.com/methods/files.upload>.
13#[derive(Debug)]
14pub struct FilesUpload(st::FilesUploadRequest);
15
16/// The code indicating the request handled in this file.
17const API_CODE: &str = "files.upload";
18
19impl FilesUpload {
20    /// Requests `files.upload` to Slack.
21    async fn request_inner(self, client: &Client) -> Result<FilesUploadResponse, st::Error> {
22        #[allow(clippy::missing_docs_in_private_items)] // https://github.com/rust-lang/rust-clippy/issues/13298
23        const URL: &str = "https://slack.com/api/files.upload";
24
25        let form = self
26            .form()
27            .await
28            .map_err(|e| e.stack("failed to create `form`"))?;
29        tracing::debug!("form = {form:?}");
30
31        let response = client
32            .client()
33            .post(URL)
34            .bearer_auth(client.token())
35            .multipart(form)
36            .send()
37            .await
38            .map_err(|e| st::Error::failed_to_request_by_http(API_CODE, e))?;
39        tracing::debug!("response = {response:?}");
40
41        let body: serde_json::Value = response
42            .json()
43            .await
44            .map_err(|e| st::Error::failed_to_read_json(API_CODE, e))?;
45        tracing::debug!("body = {body:?}");
46
47        body.try_into()
48    }
49
50    /// Creates [`Form`].
51    async fn form(self) -> Result<Form, st::Error> {
52        let mut form = Form::new();
53
54        // <https://api.slack.com/methods/files.upload#arg_channels>
55        if !self.0.channels.is_empty() {
56            let channels = self.0.channels.join(",");
57            form = form.text("channels", channels);
58        }
59
60        match self.0.content {
61            st::FilesUploadRequestContent::Content(content) => {
62                form = form.text("content", content.clone())
63            }
64
65            st::FilesUploadRequestContent::File(path, filename) => {
66                let file = tokio::fs::read(path)
67                    .await
68                    .map_err(st::Error::failed_to_read_file_in_files_upload)?;
69                let part = reqwest::multipart::Part::stream(file).file_name(filename.clone());
70                form = form.part("file", part);
71            }
72
73            st::FilesUploadRequestContent::FileInMemory(bytes, filename) => {
74                let part = reqwest::multipart::Part::stream(bytes).file_name(filename.clone());
75                form = form.part("file", part);
76            }
77        }
78
79        if let Some(filename) = &self.0.filename {
80            form = form.text("filename", filename.clone());
81        }
82
83        if let Some(filetype) = &self.0.filetype {
84            form = form.text("filetype", filetype.clone());
85        }
86
87        if let Some(initial_comment) = &self.0.initial_comment {
88            form = form.text("initial_comment", initial_comment.clone());
89        }
90
91        if let Some(thread_ts) = &self.0.thread_ts {
92            form = form.text("thread_ts", thread_ts.clone());
93        }
94
95        if let Some(title) = &self.0.title {
96            form = form.text("title", title.clone());
97        }
98
99        Ok(form)
100    }
101}
102
103impl From<st::FilesUploadRequest> for FilesUpload {
104    fn from(inner: st::FilesUploadRequest) -> Self {
105        Self(inner)
106    }
107}
108
109impl Request for FilesUpload {
110    type Response = FilesUploadResponse;
111
112    async fn request(self, client: &Client) -> Result<Self::Response, st::Error> {
113        self.request_inner(client).await
114    }
115}