1use serde::{Deserialize, Serialize};
2use std::future::Future;
3use std::sync::Arc;
4
5use crate::token::*;
6
7use crate::errors::SlackClientError;
8use crate::models::*;
9use crate::multipart_form::FileMultipartData;
10use crate::ratectl::SlackApiMethodRateControlConfig;
11use futures::future::BoxFuture;
12use futures::FutureExt;
13use lazy_static::*;
14use rvstruct::ValueStruct;
15use tracing::*;
16use url::Url;
17
18#[derive(Clone, Debug)]
19pub struct SlackClient<SCHC>
20where
21 SCHC: SlackClientHttpConnector + Send,
22{
23 pub http_api: SlackClientHttpApi<SCHC>,
24}
25
26#[derive(Clone, Debug)]
27pub struct SlackClientHttpApi<SCHC>
28where
29 SCHC: SlackClientHttpConnector + Send,
30{
31 pub connector: Arc<SCHC>,
32}
33
34#[derive(Debug)]
35pub struct SlackClientSession<'a, SCHC>
36where
37 SCHC: SlackClientHttpConnector + Send,
38{
39 pub http_session_api: SlackClientHttpSessionApi<'a, SCHC>,
40}
41
42#[derive(Debug)]
43pub struct SlackClientHttpSessionApi<'a, SCHC>
44where
45 SCHC: SlackClientHttpConnector + Send,
46{
47 pub client: &'a SlackClient<SCHC>,
48 token: &'a SlackApiToken,
49 pub span: Span,
50}
51
52#[derive(Debug, Clone)]
53pub struct SlackClientApiCallContext<'a> {
54 pub rate_control_params: Option<&'a SlackApiMethodRateControlConfig>,
55 pub token: Option<&'a SlackApiToken>,
56 pub tracing_span: &'a Span,
57 pub is_sensitive_url: bool,
58}
59
60pub trait SlackClientHttpConnector {
61 fn http_get_uri<'a, RS>(
62 &'a self,
63 full_uri: Url,
64 context: SlackClientApiCallContext<'a>,
65 ) -> BoxFuture<'a, ClientResult<RS>>
66 where
67 RS: for<'de> serde::de::Deserialize<'de> + Send + 'a + 'a + Send;
68
69 fn http_get_with_client_secret<'a, RS>(
70 &'a self,
71 full_uri: Url,
72 client_id: &'a SlackClientId,
73 client_secret: &'a SlackClientSecret,
74 ) -> BoxFuture<'a, ClientResult<RS>>
75 where
76 RS: for<'de> serde::de::Deserialize<'de> + Send + 'a + 'a + Send;
77
78 fn http_get<'a, 'p, RS, PT, TS>(
79 &'a self,
80 method_relative_uri: &str,
81 params: &'p PT,
82 context: SlackClientApiCallContext<'a>,
83 ) -> BoxFuture<'a, ClientResult<RS>>
84 where
85 RS: for<'de> serde::de::Deserialize<'de> + Send + 'a,
86 PT: std::iter::IntoIterator<Item = (&'p str, Option<TS>)> + Clone,
87 TS: AsRef<str> + 'p + Send,
88 {
89 let full_uri = self
90 .create_method_uri_path(method_relative_uri)
91 .and_then(|url| SlackClientHttpApiUri::create_url_with_params(url, params));
92
93 match full_uri {
94 Ok(full_uri) => self.http_get_uri(full_uri, context),
95 Err(err) => std::future::ready(Err(err)).boxed(),
96 }
97 }
98
99 fn http_post_uri<'a, RQ, RS>(
100 &'a self,
101 full_uri: Url,
102 request_body: &'a RQ,
103 context: SlackClientApiCallContext<'a>,
104 ) -> BoxFuture<'a, ClientResult<RS>>
105 where
106 RQ: serde::ser::Serialize + Send + Sync,
107 RS: for<'de> serde::de::Deserialize<'de> + Send + 'a + Send + 'a;
108
109 fn http_post<'a, RQ, RS>(
110 &'a self,
111 method_relative_uri: &str,
112 request: &'a RQ,
113 context: SlackClientApiCallContext<'a>,
114 ) -> BoxFuture<'a, ClientResult<RS>>
115 where
116 RQ: serde::ser::Serialize + Send + Sync,
117 RS: for<'de> serde::de::Deserialize<'de> + Send + 'a,
118 {
119 match self.create_method_uri_path(method_relative_uri) {
120 Ok(full_uri) => self.http_post_uri(full_uri, request, context),
121 Err(err) => std::future::ready(Err(err)).boxed(),
122 }
123 }
124
125 fn http_post_uri_multipart_form<'a, 'p, RS, PT, TS>(
126 &'a self,
127 full_uri: Url,
128 file: Option<FileMultipartData<'p>>,
129 params: &'p PT,
130 context: SlackClientApiCallContext<'a>,
131 ) -> BoxFuture<'a, ClientResult<RS>>
132 where
133 RS: for<'de> serde::de::Deserialize<'de> + Send + 'a + Send + 'a,
134 PT: std::iter::IntoIterator<Item = (&'p str, Option<TS>)> + Clone,
135 TS: AsRef<str> + 'p + Send;
136
137 fn http_post_multipart_form<'a, 'p, RS, PT, TS>(
138 &'a self,
139 method_relative_uri: &str,
140 file: Option<FileMultipartData<'p>>,
141 params: &'p PT,
142 context: SlackClientApiCallContext<'a>,
143 ) -> BoxFuture<'a, ClientResult<RS>>
144 where
145 RS: for<'de> serde::de::Deserialize<'de> + Send + 'a,
146 PT: std::iter::IntoIterator<Item = (&'p str, Option<TS>)> + Clone,
147 TS: AsRef<str> + 'p + Send,
148 {
149 match self.create_method_uri_path(method_relative_uri) {
150 Ok(full_uri) => self.http_post_uri_multipart_form(full_uri, file, params, context),
151 Err(err) => std::future::ready(Err(err)).boxed(),
152 }
153 }
154
155 fn http_post_uri_binary<'a, 'p, RS>(
156 &'a self,
157 full_uri: Url,
158 content_type: String,
159 data: &'a [u8],
160 context: SlackClientApiCallContext<'a>,
161 ) -> BoxFuture<'a, ClientResult<RS>>
162 where
163 RS: for<'de> serde::de::Deserialize<'de> + Send + 'a + Send + 'a;
164
165 fn create_method_uri_path(&self, method_relative_uri: &str) -> ClientResult<Url> {
166 Ok(SlackClientHttpApiUri::create_method_uri_path(method_relative_uri).parse()?)
167 }
168}
169
170pub(crate) type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
171
172pub type UserCallbackResult<T> = std::result::Result<T, BoxError>;
173
174pub type ClientResult<T> = std::result::Result<T, SlackClientError>;
175
176pub type AnyStdResult<T> = std::result::Result<T, BoxError>;
177
178#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
179pub struct SlackEnvelopeMessage {
180 pub ok: bool,
181 pub error: Option<String>,
182 pub errors: Option<Vec<String>>,
184 pub warnings: Option<Vec<String>>,
185}
186
187lazy_static! {
188 pub static ref SLACK_HTTP_EMPTY_GET_PARAMS: Vec<(&'static str, Option<&'static String>)> =
189 vec![];
190}
191
192impl<SCHC> SlackClientHttpApi<SCHC>
193where
194 SCHC: SlackClientHttpConnector + Send + Sync,
195{
196 fn new(http_connector: Arc<SCHC>) -> Self {
197 Self {
198 connector: http_connector,
199 }
200 }
201}
202
203pub struct SlackClientHttpApiUri;
204
205impl SlackClientHttpApiUri {
206 pub const SLACK_API_URI_STR: &'static str = "https://slack.com/api";
207
208 pub fn create_method_uri_path(method_relative_uri: &str) -> String {
209 format!("{}/{}", Self::SLACK_API_URI_STR, method_relative_uri)
210 }
211
212 pub fn create_url_with_params<'p, PT, TS>(base_url: Url, params: &'p PT) -> ClientResult<Url>
213 where
214 PT: std::iter::IntoIterator<Item = (&'p str, Option<TS>)> + Clone,
215 TS: AsRef<str> + 'p,
216 {
217 let url_query_params: Vec<(String, String)> = params
218 .clone()
219 .into_iter()
220 .filter_map(|(k, vo)| vo.map(|v| (k.to_string(), v.as_ref().to_string())))
221 .collect();
222
223 Ok(Url::parse_with_params(base_url.as_str(), url_query_params)?)
224 }
225}
226
227impl<SCHC> SlackClient<SCHC>
228where
229 SCHC: SlackClientHttpConnector + Send + Sync,
230{
231 pub fn new(http_connector: SCHC) -> Self {
232 Self {
233 http_api: SlackClientHttpApi::new(Arc::new(http_connector)),
234 }
235 }
236
237 pub fn open_session<'a>(&'a self, token: &'a SlackApiToken) -> SlackClientSession<'a, SCHC> {
238 let http_session_span = span!(
239 Level::DEBUG,
240 "Slack API request",
241 "/slack/team_id" = token
242 .team_id
243 .as_ref()
244 .map(|team_id| team_id.value().as_str())
245 .unwrap_or_else(|| "-")
246 );
247
248 let http_session_api = SlackClientHttpSessionApi {
249 client: self,
250 token,
251 span: http_session_span,
252 };
253
254 SlackClientSession { http_session_api }
255 }
256
257 pub async fn run_in_session<'a, FN, F, T>(&'a self, token: &'a SlackApiToken, pred: FN) -> T
258 where
259 FN: Fn(SlackClientSession<'a, SCHC>) -> F,
260 F: Future<Output = T>,
261 {
262 let session = self.open_session(token);
263 pred(session).await
264 }
265}
266
267impl<'a, SCHC> SlackClientHttpSessionApi<'a, SCHC>
268where
269 SCHC: SlackClientHttpConnector + Send,
270{
271 pub async fn http_get_uri<RS, PT, TS>(
272 &self,
273 full_uri: Url,
274 rate_control_params: Option<&'a SlackApiMethodRateControlConfig>,
275 ) -> ClientResult<RS>
276 where
277 RS: for<'de> serde::de::Deserialize<'de> + Send,
278 {
279 let context = SlackClientApiCallContext {
280 rate_control_params,
281 token: Some(self.token),
282 tracing_span: &self.span,
283 is_sensitive_url: false,
284 };
285
286 self.client
287 .http_api
288 .connector
289 .http_get_uri(full_uri, context)
290 .await
291 }
292
293 pub async fn http_get<'p, RS, PT, TS>(
294 &self,
295 method_relative_uri: &str,
296 params: &'p PT,
297 rate_control_params: Option<&'a SlackApiMethodRateControlConfig>,
298 ) -> ClientResult<RS>
299 where
300 RS: for<'de> serde::de::Deserialize<'de> + Send,
301 PT: std::iter::IntoIterator<Item = (&'p str, Option<TS>)> + Clone,
302 TS: AsRef<str> + 'p + Send,
303 {
304 let context = SlackClientApiCallContext {
305 rate_control_params,
306 token: Some(self.token),
307 tracing_span: &self.span,
308 is_sensitive_url: false,
309 };
310
311 self.client
312 .http_api
313 .connector
314 .http_get(method_relative_uri, params, context)
315 .await
316 }
317
318 pub async fn http_post<RQ, RS>(
319 &self,
320 method_relative_uri: &str,
321 request: &RQ,
322 rate_control_params: Option<&'a SlackApiMethodRateControlConfig>,
323 ) -> ClientResult<RS>
324 where
325 RQ: serde::ser::Serialize + Send + Sync,
326 RS: for<'de> serde::de::Deserialize<'de> + Send,
327 {
328 let context = SlackClientApiCallContext {
329 rate_control_params,
330 token: Some(self.token),
331 tracing_span: &self.span,
332 is_sensitive_url: false,
333 };
334
335 self.client
336 .http_api
337 .connector
338 .http_post(method_relative_uri, &request, context)
339 .await
340 }
341
342 pub async fn http_post_uri<RQ, RS>(
343 &self,
344 full_uri: Url,
345 request: &RQ,
346 rate_control_params: Option<&'a SlackApiMethodRateControlConfig>,
347 ) -> ClientResult<RS>
348 where
349 RQ: serde::ser::Serialize + Send + Sync,
350 RS: for<'de> serde::de::Deserialize<'de> + Send,
351 {
352 let context = SlackClientApiCallContext {
353 rate_control_params,
354 token: Some(self.token),
355 tracing_span: &self.span,
356 is_sensitive_url: false,
357 };
358
359 self.client
360 .http_api
361 .connector
362 .http_post_uri(full_uri, &request, context)
363 .await
364 }
365
366 pub async fn http_post_multipart_form<'p, RS, PT, TS>(
367 &self,
368 method_relative_uri: &str,
369 file: Option<FileMultipartData<'p>>,
370 params: &'p PT,
371 rate_control_params: Option<&'a SlackApiMethodRateControlConfig>,
372 ) -> ClientResult<RS>
373 where
374 RS: for<'de> serde::de::Deserialize<'de> + Send,
375 PT: std::iter::IntoIterator<Item = (&'p str, Option<TS>)> + Clone,
376 TS: AsRef<str> + 'p + Send,
377 {
378 let context = SlackClientApiCallContext {
379 rate_control_params,
380 token: Some(self.token),
381 tracing_span: &self.span,
382 is_sensitive_url: false,
383 };
384
385 self.client
386 .http_api
387 .connector
388 .http_post_multipart_form(method_relative_uri, file, params, context)
389 .await
390 }
391
392 pub async fn http_post_uri_multipart_form<'p, RS, PT, TS>(
393 &self,
394 full_uri: Url,
395 file: Option<FileMultipartData<'p>>,
396 params: &'p PT,
397 rate_control_params: Option<&'a SlackApiMethodRateControlConfig>,
398 ) -> ClientResult<RS>
399 where
400 RS: for<'de> serde::de::Deserialize<'de> + Send,
401 PT: std::iter::IntoIterator<Item = (&'p str, Option<TS>)> + Clone,
402 TS: AsRef<str> + 'p + Send,
403 {
404 let context = SlackClientApiCallContext {
405 rate_control_params,
406 token: Some(self.token),
407 tracing_span: &self.span,
408 is_sensitive_url: false,
409 };
410
411 self.client
412 .http_api
413 .connector
414 .http_post_uri_multipart_form(full_uri, file, params, context)
415 .await
416 }
417
418 pub async fn http_post_uri_binary<'p, RS>(
419 &self,
420 full_uri: Url,
421 content_type: String,
422 data: &'a [u8],
423 rate_control_params: Option<&'a SlackApiMethodRateControlConfig>,
424 ) -> ClientResult<RS>
425 where
426 RS: for<'de> serde::de::Deserialize<'de> + Send,
427 {
428 let context = SlackClientApiCallContext {
429 rate_control_params,
430 token: Some(self.token),
431 tracing_span: &self.span,
432 is_sensitive_url: true,
433 };
434
435 self.client
436 .http_api
437 .connector
438 .http_post_uri_binary(full_uri, content_type, data, context)
439 .await
440 }
441}