sfr_slack_api/
client.rs

1//! The client for Slack API. <https://api.slack.com/methods>.
2
3use sfr_types as st;
4
5use crate::Request;
6use futures::{Stream, StreamExt as _};
7use st::{OauthV2AccessRequest, OauthV2AccessResponse};
8use std::collections::HashMap;
9
10/// The client for Slack API without OAuth.
11#[derive(Clone)]
12pub struct Client {
13    /// The HTTP Client.
14    client: reqwest::Client,
15
16    /// The access_token.
17    token: String,
18}
19
20/// The client for Slack API about OAuth.
21#[derive(Clone)]
22pub struct OauthClient {
23    /// The HTTP Client.
24    client: reqwest::Client,
25}
26
27impl Client {
28    /// The constructor.
29    pub fn new(client: reqwest::Client, token: String) -> Self {
30        Self { client, token }
31    }
32
33    /// Returns the cloned inner HTTP client.
34    pub fn clone_http_client(&self) -> reqwest::Client {
35        self.client.clone()
36    }
37
38    /// Requests to Slack API.
39    pub async fn request<R>(&self, request: R) -> Result<R::Response, st::Error>
40    where
41        R: Request,
42    {
43        request.request(self).await
44    }
45
46    /// Uploads files by `upload_url` from `files.getUploadURLExternal`.
47    ///
48    /// - `set`: (filename, (MIME type, file bytes))
49    pub async fn upload_file_by_upload_file<M>(
50        &self,
51        upload_url: &str,
52        set: M,
53    ) -> Result<(), st::Error>
54    where
55        M: Into<HashMap<String, (&'static str, Vec<u8>)>>,
56    {
57        use reqwest::multipart::{Form, Part};
58
59        let mut form = Form::new();
60        for (filename, (mime, bytes)) in set.into().into_iter() {
61            let part = Part::bytes(bytes)
62                .file_name(filename.clone())
63                .mime_str(mime)
64                .map_err(st::Error::failed_creating_mulipart_data)?;
65            form = form.part(filename.clone(), part);
66        }
67
68        let response = self
69            .client()
70            .post(upload_url)
71            .multipart(form)
72            .send()
73            .await
74            .map_err(|e| st::Error::failed_to_request_by_http("upload_url", e))?;
75        tracing::debug!("response = {response:?}");
76
77        Ok(())
78    }
79
80    /// Download file with authentication.
81    ///
82    /// <https://api.slack.com/types/file#auth>
83    pub async fn download_file(
84        &self,
85        url: &str,
86    ) -> Result<impl Stream<Item = Result<Vec<u8>, st::Error>>, st::Error> {
87        let response = self
88            .client
89            .get(url)
90            .bearer_auth(&self.token)
91            .send()
92            .await
93            .map_err(|e| st::Error::failed_to_request_by_http("download file", e))?;
94
95        Ok(response.bytes_stream().map(|item| {
96            item.map(|bytes| bytes.into())
97                .map_err(|e| st::Error::failed_to_read_stream("download file", e))
98        }))
99    }
100
101    /// Returns the reference of the inner HTTP client.
102    pub(crate) fn client(&self) -> &reqwest::Client {
103        &self.client
104    }
105
106    /// Returns the access_token.
107    pub(crate) fn token(&self) -> &str {
108        &self.token
109    }
110}
111
112impl OauthClient {
113    /// The constructor.
114    pub fn new(client: reqwest::Client) -> Self {
115        Self { client }
116    }
117
118    /// Requests `oauth.v2.access`.
119    ///
120    /// <https://api.slack.com/methods/oauth.v2.access>
121    ///
122    /// Can't implicit in `Client` since the token is obtained from `oauth_v2_access()`.
123    pub async fn oauth_v2_access(
124        &self,
125        form: OauthV2AccessRequest<'_>,
126    ) -> Result<OauthV2AccessResponse, st::Error> {
127        #[allow(clippy::missing_docs_in_private_items)] // https://github.com/rust-lang/rust-clippy/issues/13298
128        const URL: &str = "https://slack.com/api/oauth.v2.access";
129
130        #[allow(clippy::missing_docs_in_private_items)] // https://github.com/rust-lang/rust-clippy/issues/13298
131        const API_CODE: &str = "oauth.v2.access";
132
133        tracing::debug!("form = {form:?}");
134
135        let response = self
136            .client
137            .post(URL)
138            .form(&form)
139            .send()
140            .await
141            .map_err(|e| st::Error::failed_to_request_by_http(API_CODE, e))?;
142        tracing::debug!("response = {response:?}");
143
144        let body: serde_json::Value = response
145            .json()
146            .await
147            .map_err(|e| st::Error::failed_to_read_json(API_CODE, e))?;
148        tracing::debug!("body = {body:?}");
149
150        body.try_into()
151    }
152}