1pub mod api;
2mod builder;
3mod entity;
4mod error;
5
6pub use error::*;
7use std::io::Read;
8
9pub use builder::*;
10pub use entity::*;
11use hyper::http::request::Builder;
12use hyper::{Body, Method};
13use hyper_tls::HttpsConnector;
14use mpart_async::client::MultipartRequest;
15use mpart_async::filestream::FileStream;
16
17use serde_json::{json, Value};
18
19pub struct SlackClient {
20 pub(crate) context: SlackContext,
21}
22
23impl SlackClient {
24 pub fn new(token: &str) -> Self {
25 Self {
26 context: SlackContext {
27 token: Some(token.to_string()),
28 },
29 }
30 }
31}
32
33struct SlackContext {
34 token: Option<String>,
35}
36
37pub type SlackApiResponse<T> = Result<T, SlackError>;
38
39impl SlackClient {
40 async fn http_response<R>(response: hyper::Result<hyper::Response<Body>>) -> SlackApiResponse<R>
41 where
42 R: for<'de> serde::Deserialize<'de>,
43 {
44 let response = match response {
45 Ok(v) => v,
46 Err(e) => return Err(SlackSystemError::new(e.to_string()).into()),
47 };
48
49 let status = response.status();
50 let body = match hyper::body::to_bytes(response.into_body()).await {
51 Ok(v) => v.to_vec(),
52 Err(e) => {
53 return Err(SlackHttpError::new(status.as_u16(), e.message().to_string()).into());
54 }
55 };
56
57 let http_response_body = match String::from_utf8(body) {
58 Ok(v) => v,
59 Err(e) => return Err(SlackSystemError::new(e.to_string()).into()),
60 };
61
62 let value = match serde_json::from_str::<Value>(http_response_body.as_str()) {
63 Ok(v) => v,
64 Err(e) => return Err(SlackSystemError::new(e.to_string()).into()),
65 };
66 if let Some(ok) = value.get("ok") {
69 if ok.as_bool() == Some(false) {
70 return Err(SlackApiError {
71 status: status.as_u16(),
72 errors: Some(get_error_value(&value)),
73 warnings: None,
74 http_response_body: Some(http_response_body),
75 }
76 .into());
77 }
78 }
79
80 Ok(serde_json::from_value(value).unwrap())
81 }
82 pub(crate) async fn http_get<P, R>(
83 &self,
84 token: &str,
85 url: &str,
86 value: &P,
87 ) -> SlackApiResponse<R>
88 where
89 P: serde::Serialize,
90 R: for<'de> serde::Deserialize<'de>,
91 {
92 let build = builder(url, token).method(Method::GET);
93 let request = build.body(Body::from(json!(value).to_string())).unwrap();
94 let client = hyper::Client::builder().build(HttpsConnector::new());
95
96 let response = client.request(request).await;
97 SlackClient::http_response(response).await
98 }
111 pub(crate) async fn http_post<P, R>(&self, url: &str, value: &P) -> SlackApiResponse<R>
112 where
113 P: serde::Serialize,
114 R: for<'de> serde::Deserialize<'de>,
115 {
116 let build = builder(url, self.context.token.clone().unwrap_or_default().as_str())
117 .method(Method::POST);
118 let request = build.body(Body::from(json!(value).to_string())).unwrap();
120 let response = hyper::Client::builder()
121 .build(HttpsConnector::new())
122 .request(request)
123 .await;
124 Self::http_response(response).await
125 }
126 pub(crate) async fn http_post_data<R>(
127 &self,
128 url: &str,
129 multipart: MultipartRequest<FileStream>,
130 ) -> SlackApiResponse<R>
131 where
132 R: for<'de> serde::Deserialize<'de>,
133 {
134 let mut build = builder_file(url, self.context.token.clone().unwrap_or_default().as_str())
135 .method(Method::POST);
136 build = build.header(
137 "Content-Type",
138 format!("multipart/form-data; boundary={}", multipart.get_boundary()),
139 );
140 let request = build.body(Body::wrap_stream(multipart)).unwrap();
141 let response = hyper::Client::builder()
142 .build(HttpsConnector::new())
143 .request(request)
144 .await;
145 Self::http_response(response).await
146 }
147}
148
149fn builder(url: &str, token: &str) -> Builder {
150 let mut build = hyper::Request::builder().uri(format!("https://slack.com/api/{}", url));
151 if !token.is_empty() {
152 build = build.header("Authorization", format!("Bearer {}", token))
153 }
154 build.header("Content-type", "application/json; charset=UTF-8")
155}
156
157fn builder_file(url: &str, token: &str) -> Builder {
158 let mut build = hyper::Request::builder().uri(format!("https://slack.com/api/{}", url));
159 if !token.is_empty() {
160 build = build.header("Authorization", format!("Bearer {}", token))
161 }
162 build
163 }
166
167fn get_error_value(value: &Value) -> Vec<String> {
168 match value.get("error") {
169 None => {
170 vec![]
171 }
172 Some(v) => {
173 if v.is_string() {
174 return match v.as_str() {
175 None => {
176 vec![]
177 }
178 Some(v) => {
179 vec![v.to_string()]
180 }
181 };
182 }
183 vec![]
184 }
185 }
186}