use crate::api::{
admin::AdminApi, api_test::ApiApi, apps::AppsApi, auth::AuthApi, bookmarks::BookmarksApi,
bots::BotsApi, calls::CallsApi, chat::ChatApi, conversations::ConversationsApi,
dialog::DialogApi, dnd::DndApi, emoji::EmojiApi, files::FilesApi, lists::ListsApi,
oauth::OAuthApi, openid::OpenIDApi, pins::PinsApi, reactions::ReactionsApi,
reminders::RemindersApi, rtm::RtmApi, search::SearchApi, socket_mode::SocketModeApi,
stars::StarsApi, team::TeamApi, usergroups::UsergroupsApi, users::UsersApi, views::ViewsApi,
workflows::WorkflowsApi,
};
use crate::auth::AuthConfig;
use crate::error::{Result, SlackError};
use crate::types::SlackResponse;
use reqwest::header::HeaderMap;
use std::sync::Arc;
const SLACK_API_BASE: &str = "https://slack.com/api";
#[derive(Clone)]
pub struct SlackClient {
pub(crate) http: reqwest::Client,
pub(crate) auth: Arc<AuthConfig>,
pub(crate) base_url: String,
}
impl SlackClient {
pub fn new(auth: AuthConfig) -> Result<Self> {
let http = reqwest::Client::builder()
.user_agent("slack-sdk-rust/0.1.0")
.build()
.map_err(|e| SlackError::config_error(format!("Failed to build HTTP client: {}", e)))?;
Ok(Self {
http,
auth: Arc::new(auth),
base_url: SLACK_API_BASE.to_string(),
})
}
pub fn api(&self) -> ApiApi {
ApiApi::new(self.clone())
}
pub fn bots(&self) -> BotsApi {
BotsApi::new(self.clone())
}
pub fn chat(&self) -> ChatApi {
ChatApi::new(self.clone())
}
pub fn conversations(&self) -> ConversationsApi {
ConversationsApi::new(self.clone())
}
pub fn users(&self) -> UsersApi {
UsersApi::new(self.clone())
}
pub fn files(&self) -> FilesApi {
FilesApi::new(self.clone())
}
pub fn reactions(&self) -> ReactionsApi {
ReactionsApi::new(self.clone())
}
pub fn search(&self) -> SearchApi {
SearchApi::new(self.clone())
}
pub fn team(&self) -> TeamApi {
TeamApi::new(self.clone())
}
pub fn rtm(&self) -> RtmApi {
RtmApi::new(self.clone())
}
pub fn socket_mode(&self) -> SocketModeApi {
SocketModeApi::new(self.clone())
}
pub fn auth(&self) -> AuthApi {
AuthApi::new(self.clone())
}
pub fn pins(&self) -> PinsApi {
PinsApi::new(self.clone())
}
pub fn stars(&self) -> StarsApi {
StarsApi::new(self.clone())
}
pub fn reminders(&self) -> RemindersApi {
RemindersApi::new(self.clone())
}
pub fn dnd(&self) -> DndApi {
DndApi::new(self.clone())
}
pub fn emoji(&self) -> EmojiApi {
EmojiApi::new(self.clone())
}
pub fn oauth(&self) -> OAuthApi {
OAuthApi::new(self.clone())
}
pub fn openid(&self) -> OpenIDApi {
OpenIDApi::new(self.clone())
}
pub fn usergroups(&self) -> UsergroupsApi {
UsergroupsApi::new(self.clone())
}
pub fn views(&self) -> ViewsApi {
ViewsApi::new(self.clone())
}
pub fn dialog(&self) -> DialogApi {
DialogApi::new(self.clone())
}
pub fn bookmarks(&self) -> BookmarksApi {
BookmarksApi::new(self.clone())
}
pub fn admin(&self) -> AdminApi {
AdminApi::new(self.clone())
}
pub fn apps(&self) -> AppsApi {
AppsApi::new(self.clone())
}
pub fn calls(&self) -> CallsApi {
CallsApi::new(self.clone())
}
pub fn workflows(&self) -> WorkflowsApi {
WorkflowsApi::new(self.clone())
}
pub fn lists(&self) -> ListsApi {
ListsApi::new(self.clone())
}
pub(crate) async fn post<T: serde::de::DeserializeOwned>(
&self,
method: &str,
params: &impl serde::Serialize,
) -> Result<T> {
let url = format!("{}/{}", self.base_url, method);
let headers = self.auth.build_headers();
let response = self
.http
.post(&url)
.headers(headers)
.json(params)
.send()
.await?;
if response.status().as_u16() == 429 {
let retry_after = response
.headers()
.get("retry-after")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse().ok())
.unwrap_or(60);
return Err(SlackError::RateLimitExceeded { retry_after });
}
let slack_response: SlackResponse<T> = response.json().await?;
if !slack_response.ok {
let error_msg = slack_response
.error
.unwrap_or_else(|| "Unknown error".to_string());
return Err(SlackError::api_error(method, error_msg));
}
slack_response
.data
.ok_or_else(|| SlackError::api_error(method, "No data in response"))
}
pub(crate) async fn get<T: serde::de::DeserializeOwned>(
&self,
method: &str,
params: &[(&str, &str)],
) -> Result<T> {
let url = format!("{}/{}", self.base_url, method);
let headers = self.auth.build_headers();
let response = self
.http
.get(&url)
.headers(headers)
.query(params)
.send()
.await?;
if response.status().as_u16() == 429 {
let retry_after = response
.headers()
.get("retry-after")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse().ok())
.unwrap_or(60);
return Err(SlackError::RateLimitExceeded { retry_after });
}
let slack_response: SlackResponse<T> = response.json().await?;
if !slack_response.ok {
let error_msg = slack_response
.error
.unwrap_or_else(|| "Unknown error".to_string());
return Err(SlackError::api_error(method, error_msg));
}
slack_response
.data
.ok_or_else(|| SlackError::api_error(method, "No data in response"))
}
#[allow(dead_code)]
pub(crate) fn headers(&self) -> HeaderMap {
self.auth.build_headers()
}
pub(crate) async fn upload_file<T: serde::de::DeserializeOwned>(
&self,
method: &str,
file_data: Vec<u8>,
field_name: &str,
file_name: &str,
) -> Result<T> {
self.upload_file_with_params(method, file_data, field_name, file_name, &[])
.await
}
pub(crate) async fn upload_file_with_params<T: serde::de::DeserializeOwned>(
&self,
method: &str,
file_data: Vec<u8>,
field_name: &str,
file_name: &str,
params: &[(&str, &str)],
) -> Result<T> {
use reqwest::multipart::{Form, Part};
let url = format!("{}/{}", self.base_url, method);
let headers = self.auth.build_headers();
let part = Part::bytes(file_data).file_name(file_name.to_string());
let mut form = Form::new().part(field_name.to_string(), part);
for (key, value) in params {
form = form.text(key.to_string(), value.to_string());
}
let response = self
.http
.post(&url)
.headers(headers)
.multipart(form)
.send()
.await?;
if response.status().as_u16() == 429 {
let retry_after = response
.headers()
.get("retry-after")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse().ok())
.unwrap_or(60);
return Err(SlackError::RateLimitExceeded { retry_after });
}
let slack_response: SlackResponse<T> = response.json().await?;
if !slack_response.ok {
let error_msg = slack_response
.error
.unwrap_or_else(|| "Unknown error".to_string());
return Err(SlackError::api_error(method, error_msg));
}
slack_response
.data
.ok_or_else(|| SlackError::api_error(method, "No data in response"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_creation() {
let client = SlackClient::new(AuthConfig::oauth("xoxp-test-token"));
assert!(client.is_ok());
}
#[test]
fn test_client_creation_stealth() {
let client = SlackClient::new(AuthConfig::stealth("xoxc-token", "xoxd-cookie"));
assert!(client.is_ok());
}
}