1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! The Slack API definition of `files.upload`.
//!
//! <https://api.slack.com/methods/files.upload>

use sfr_types as st;

pub use st::FilesUploadResponse;

use crate::{Client, Request};
use reqwest::multipart::Form;

/// The request object for <https://api.slack.com/methods/files.upload>.
#[derive(Debug)]
pub struct FilesUpload(st::FilesUploadRequest);

/// The code indicating the request handled in this file.
const API_CODE: &str = "files.upload";

impl FilesUpload {
    /// Requests `files.upload` to Slack.
    async fn request_inner(self, client: &Client) -> Result<FilesUploadResponse, st::Error> {
        #[allow(clippy::missing_docs_in_private_items)] // https://github.com/rust-lang/rust-clippy/issues/13298
        const URL: &str = "https://slack.com/api/files.upload";

        let form = self
            .form()
            .await
            .map_err(|e| e.stack("failed to create `form`"))?;
        tracing::debug!("form = {form:?}");

        let response = client
            .client()
            .post(URL)
            .bearer_auth(client.token())
            .multipart(form)
            .send()
            .await
            .map_err(|e| st::Error::failed_to_request_by_http(API_CODE, e))?;
        tracing::debug!("response = {response:?}");

        let body: serde_json::Value = response
            .json()
            .await
            .map_err(|e| st::Error::failed_to_read_json(API_CODE, e))?;
        tracing::debug!("body = {body:?}");

        body.try_into()
    }

    /// Creates [`Form`].
    async fn form(self) -> Result<Form, st::Error> {
        let mut form = Form::new();

        // <https://api.slack.com/methods/files.upload#arg_channels>
        if !self.0.channels.is_empty() {
            let channels = self.0.channels.join(",");
            form = form.text("channels", channels);
        }

        match self.0.content {
            st::FilesUploadRequestContent::Content(content) => {
                form = form.text("content", content.clone())
            }

            st::FilesUploadRequestContent::File(path, filename) => {
                let file = tokio::fs::read(path)
                    .await
                    .map_err(st::Error::failed_to_read_file_in_files_upload)?;
                let part = reqwest::multipart::Part::stream(file).file_name(filename.clone());
                form = form.part("file", part);
            }

            st::FilesUploadRequestContent::FileInMemory(bytes, filename) => {
                let part = reqwest::multipart::Part::stream(bytes).file_name(filename.clone());
                form = form.part("file", part);
            }
        }

        if let Some(filename) = &self.0.filename {
            form = form.text("filename", filename.clone());
        }

        if let Some(filetype) = &self.0.filetype {
            form = form.text("filetype", filetype.clone());
        }

        if let Some(initial_comment) = &self.0.initial_comment {
            form = form.text("initial_comment", initial_comment.clone());
        }

        if let Some(thread_ts) = &self.0.thread_ts {
            form = form.text("thread_ts", thread_ts.clone());
        }

        if let Some(title) = &self.0.title {
            form = form.text("title", title.clone());
        }

        Ok(form)
    }
}

impl From<st::FilesUploadRequest> for FilesUpload {
    fn from(inner: st::FilesUploadRequest) -> Self {
        Self(inner)
    }
}

impl Request for FilesUpload {
    type Response = FilesUploadResponse;

    async fn request(self, client: &Client) -> Result<Self::Response, st::Error> {
        self.request_inner(client).await
    }
}