use crate::models::*;
use crate::{ClientResult, SlackClient, SlackClientHttpConnector};
use futures::executor::block_on;
use futures::FutureExt;
use rsb_derive::Builder;
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;
use tracing::*;
use url::Url;
type UserStatesMap = HashMap<TypeId, Box<dyn Any + Send + Sync + 'static>>;
pub type HttpStatusCode = http::StatusCode;
pub struct SlackClientEventsListenerEnvironment<SCHC>
where
SCHC: SlackClientHttpConnector + Send + Sync,
{
pub client: Arc<SlackClient<SCHC>>,
pub error_handler: BoxedErrorHandler<SCHC>,
pub user_state: SlackClientEventsUserState,
}
pub type SlackClientEventsUserState = futures_locks::RwLock<SlackClientEventsUserStateStorage>;
impl<SCHC> SlackClientEventsListenerEnvironment<SCHC>
where
SCHC: SlackClientHttpConnector + Send + Sync,
{
pub fn new(client: Arc<SlackClient<SCHC>>) -> Self {
Self {
client,
error_handler: Box::new(Self::empty_error_handler),
user_state: SlackClientEventsUserState::new(SlackClientEventsUserStateStorage::new()),
}
}
pub fn with_error_handler(self, error_handler: ErrorHandler<SCHC>) -> Self {
Self {
error_handler: Box::new(error_handler),
..self
}
}
fn empty_error_handler(
err: Box<dyn std::error::Error + Send + Sync>,
_client: Arc<SlackClient<SCHC>>,
_user_state_storage: SlackClientEventsUserState,
) -> http::StatusCode {
error!("Slack listener error occurred: {:?}", err);
http::StatusCode::BAD_REQUEST
}
pub fn with_user_state<T: Send + Sync + 'static>(self, state: T) -> Self {
let future_init_state = self
.user_state
.write()
.map(|mut guard| guard.set_user_state(state));
block_on(future_init_state);
self
}
}
pub struct SlackClientEventsUserStateStorage {
user_state_map: UserStatesMap,
}
impl SlackClientEventsUserStateStorage {
pub fn new() -> Self {
SlackClientEventsUserStateStorage {
user_state_map: HashMap::new(),
}
}
pub fn get_user_state<T: Send + Sync + 'static>(&self) -> Option<&T> {
self.user_state_map
.get(&TypeId::of::<T>())
.and_then(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref())
}
pub fn set_user_state<T: Send + Sync + 'static>(&mut self, state: T) {
self.user_state_map
.insert(TypeId::of::<T>(), Box::new(state));
}
pub fn len(&self) -> usize {
self.user_state_map.len()
}
pub fn is_empty(&self) -> bool {
self.user_state_map.is_empty()
}
}
pub type BoxedErrorHandler<SCHC> = Box<ErrorHandler<SCHC>>;
pub type ErrorHandler<SCHC> = fn(
Box<dyn std::error::Error + Send + Sync + 'static>,
Arc<SlackClient<SCHC>>,
SlackClientEventsUserState,
) -> HttpStatusCode;
#[derive(Debug, PartialEq, Eq, Clone, Builder)]
pub struct SlackCommandEventsListenerConfig {
pub events_signing_secret: SlackSigningSecret,
#[default = "SlackCommandEventsListenerConfig::DEFAULT_EVENTS_URL_VALUE.into()"]
pub events_path: String,
}
impl SlackCommandEventsListenerConfig {
pub const DEFAULT_EVENTS_URL_VALUE: &'static str = "/command";
}
#[derive(Debug, PartialEq, Eq, Clone, Builder)]
pub struct SlackPushEventsListenerConfig {
pub events_signing_secret: SlackSigningSecret,
#[default = "SlackPushEventsListenerConfig::DEFAULT_EVENTS_URL_VALUE.into()"]
pub events_path: String,
}
impl SlackPushEventsListenerConfig {
const DEFAULT_EVENTS_URL_VALUE: &'static str = "/push";
}
#[derive(Debug, PartialEq, Eq, Clone, Builder)]
pub struct SlackInteractionEventsListenerConfig {
pub events_signing_secret: SlackSigningSecret,
#[default = "SlackInteractionEventsListenerConfig::DEFAULT_EVENTS_URL_VALUE.into()"]
pub events_path: String,
}
impl SlackInteractionEventsListenerConfig {
pub const DEFAULT_EVENTS_URL_VALUE: &'static str = "/interaction";
}
#[derive(Debug, PartialEq, Eq, Clone, Builder)]
pub struct SlackOAuthListenerConfig {
pub client_id: SlackClientId,
pub client_secret: SlackClientSecret,
pub bot_scope: String,
pub redirect_callback_host: String,
#[default = "SlackOAuthListenerConfig::DEFAULT_INSTALL_PATH_VALUE.into()"]
pub install_path: String,
#[default = "SlackOAuthListenerConfig::DEFAULT_CALLBACK_PATH_VALUE.into()"]
pub redirect_callback_path: String,
#[default = "SlackOAuthListenerConfig::DEFAULT_INSTALLED_URL_VALUE.into()"]
pub redirect_installed_url: String,
#[default = "SlackOAuthListenerConfig::DEFAULT_CANCELLED_URL_VALUE.into()"]
pub redirect_cancelled_url: String,
#[default = "SlackOAuthListenerConfig::DEFAULT_ERROR_URL_VALUE.into()"]
pub redirect_error_redirect_url: String,
}
impl SlackOAuthListenerConfig {
pub const DEFAULT_INSTALL_PATH_VALUE: &'static str = "/auth/install";
pub const DEFAULT_CALLBACK_PATH_VALUE: &'static str = "/auth/callback";
pub const DEFAULT_INSTALLED_URL_VALUE: &'static str = "/installed";
pub const DEFAULT_CANCELLED_URL_VALUE: &'static str = "/cancelled";
pub const DEFAULT_ERROR_URL_VALUE: &'static str = "/error";
pub const OAUTH_AUTHORIZE_URL_VALUE: &'static str = "https://slack.com/oauth/v2/authorize";
pub fn to_redirect_url(&self) -> ClientResult<Url> {
Url::parse(
format!(
"{}{}",
&self.redirect_callback_host, &self.redirect_callback_path
)
.as_str(),
)
.map_err(|e| e.into())
}
}
pub type UserCallbackFunction<E, IF, SCHC> =
fn(E, Arc<SlackClient<SCHC>>, SlackClientEventsUserState) -> IF;