1use sfr_types as st;
4
5use crate::Request;
6use futures::{Stream, StreamExt as _};
7use st::{OauthV2AccessRequest, OauthV2AccessResponse};
8use std::collections::HashMap;
9
10#[derive(Clone)]
12pub struct Client {
13 client: reqwest::Client,
15
16 token: String,
18}
19
20#[derive(Clone)]
22pub struct OauthClient {
23 client: reqwest::Client,
25}
26
27impl Client {
28 pub fn new(client: reqwest::Client, token: String) -> Self {
30 Self { client, token }
31 }
32
33 pub fn clone_http_client(&self) -> reqwest::Client {
35 self.client.clone()
36 }
37
38 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 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 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 pub(crate) fn client(&self) -> &reqwest::Client {
103 &self.client
104 }
105
106 pub(crate) fn token(&self) -> &str {
108 &self.token
109 }
110}
111
112impl OauthClient {
113 pub fn new(client: reqwest::Client) -> Self {
115 Self { client }
116 }
117
118 pub async fn oauth_v2_access(
124 &self,
125 form: OauthV2AccessRequest<'_>,
126 ) -> Result<OauthV2AccessResponse, st::Error> {
127 #[allow(clippy::missing_docs_in_private_items)] const URL: &str = "https://slack.com/api/oauth.v2.access";
129
130 #[allow(clippy::missing_docs_in_private_items)] 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}