slack_morphism/
listener.rs1use crate::models::*;
2use crate::{BoxError, ClientResult, SlackClient, SlackClientHttpConnector};
3use futures::executor::block_on;
4use futures::FutureExt;
5use rsb_derive::Builder;
6use std::any::{Any, TypeId};
7use std::collections::HashMap;
8use std::fmt::Debug;
9use std::sync::Arc;
10use tracing::*;
11use url::Url;
12
13type UserStatesMap = HashMap<TypeId, Box<dyn Any + Send + Sync + 'static>>;
14
15pub type HttpStatusCode = http::StatusCode;
16
17pub struct SlackClientEventsListenerEnvironment<SCHC>
18where
19 SCHC: SlackClientHttpConnector + Send + Sync,
20{
21 pub client: Arc<SlackClient<SCHC>>,
22 pub error_handler: BoxedErrorHandler<SCHC>,
23 pub user_state: SlackClientEventsUserState,
24}
25
26pub type SlackClientEventsUserState = futures_locks::RwLock<SlackClientEventsUserStateStorage>;
27
28impl<SCHC> SlackClientEventsListenerEnvironment<SCHC>
29where
30 SCHC: SlackClientHttpConnector + Send + Sync,
31{
32 pub fn new(client: Arc<SlackClient<SCHC>>) -> Self {
33 Self {
34 client,
35 error_handler: Box::new(Self::empty_error_handler),
36 user_state: SlackClientEventsUserState::new(SlackClientEventsUserStateStorage::new()),
37 }
38 }
39
40 pub fn with_error_handler(self, error_handler: ErrorHandler<SCHC>) -> Self {
41 Self {
42 error_handler: Box::new(error_handler),
43 ..self
44 }
45 }
46
47 fn empty_error_handler(
48 err: BoxError,
49 _client: Arc<SlackClient<SCHC>>,
50 _user_state_storage: SlackClientEventsUserState,
51 ) -> http::StatusCode {
52 error!("Slack listener error occurred: {:?}", err);
53 http::StatusCode::BAD_REQUEST
54 }
55
56 pub fn with_user_state<T: Send + Sync + 'static>(self, state: T) -> Self {
57 let future_init_state = self
58 .user_state
59 .write()
60 .map(|mut guard| guard.set_user_state(state));
61 block_on(future_init_state);
62 self
63 }
64}
65
66pub struct SlackClientEventsUserStateStorage {
67 user_state_map: UserStatesMap,
68}
69
70impl SlackClientEventsUserStateStorage {
71 pub fn new() -> Self {
72 SlackClientEventsUserStateStorage {
73 user_state_map: HashMap::new(),
74 }
75 }
76
77 pub fn get_user_state<T: Send + Sync + 'static>(&self) -> Option<&T> {
78 self.user_state_map
79 .get(&TypeId::of::<T>())
80 .and_then(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref())
81 }
82
83 pub fn set_user_state<T: Send + Sync + 'static>(&mut self, state: T) {
84 self.user_state_map
85 .insert(TypeId::of::<T>(), Box::new(state));
86 }
87
88 pub fn len(&self) -> usize {
89 self.user_state_map.len()
90 }
91
92 pub fn is_empty(&self) -> bool {
93 self.user_state_map.is_empty()
94 }
95}
96
97pub type BoxedErrorHandler<SCHC> = Box<ErrorHandler<SCHC>>;
98
99pub type ErrorHandler<SCHC> =
100 fn(BoxError, Arc<SlackClient<SCHC>>, SlackClientEventsUserState) -> HttpStatusCode;
101
102#[derive(Debug, PartialEq, Eq, Clone, Builder)]
103pub struct SlackCommandEventsListenerConfig {
104 pub events_signing_secret: SlackSigningSecret,
105 #[default = "SlackCommandEventsListenerConfig::DEFAULT_EVENTS_URL_VALUE.into()"]
106 pub events_path: String,
107}
108
109impl SlackCommandEventsListenerConfig {
110 pub const DEFAULT_EVENTS_URL_VALUE: &'static str = "/command";
111}
112
113#[derive(Debug, PartialEq, Eq, Clone, Builder)]
114pub struct SlackPushEventsListenerConfig {
115 pub events_signing_secret: SlackSigningSecret,
116 #[default = "SlackPushEventsListenerConfig::DEFAULT_EVENTS_URL_VALUE.into()"]
117 pub events_path: String,
118}
119
120impl SlackPushEventsListenerConfig {
121 const DEFAULT_EVENTS_URL_VALUE: &'static str = "/push";
122}
123
124#[derive(Debug, PartialEq, Eq, Clone, Builder)]
125pub struct SlackInteractionEventsListenerConfig {
126 pub events_signing_secret: SlackSigningSecret,
127 #[default = "SlackInteractionEventsListenerConfig::DEFAULT_EVENTS_URL_VALUE.into()"]
128 pub events_path: String,
129}
130
131impl SlackInteractionEventsListenerConfig {
132 pub const DEFAULT_EVENTS_URL_VALUE: &'static str = "/interaction";
133}
134
135#[derive(Debug, PartialEq, Eq, Clone, Builder)]
136pub struct SlackOAuthListenerConfig {
137 pub client_id: SlackClientId,
138 pub client_secret: SlackClientSecret,
139 pub bot_scope: String,
140 pub redirect_callback_host: String,
141 #[default = "SlackOAuthListenerConfig::DEFAULT_INSTALL_PATH_VALUE.into()"]
142 pub install_path: String,
143 #[default = "SlackOAuthListenerConfig::DEFAULT_CALLBACK_PATH_VALUE.into()"]
144 pub redirect_callback_path: String,
145 #[default = "SlackOAuthListenerConfig::DEFAULT_INSTALLED_URL_VALUE.into()"]
146 pub redirect_installed_url: String,
147 #[default = "SlackOAuthListenerConfig::DEFAULT_CANCELLED_URL_VALUE.into()"]
148 pub redirect_cancelled_url: String,
149 #[default = "SlackOAuthListenerConfig::DEFAULT_ERROR_URL_VALUE.into()"]
150 pub redirect_error_redirect_url: String,
151}
152
153impl SlackOAuthListenerConfig {
154 pub const DEFAULT_INSTALL_PATH_VALUE: &'static str = "/auth/install";
155 pub const DEFAULT_CALLBACK_PATH_VALUE: &'static str = "/auth/callback";
156 pub const DEFAULT_INSTALLED_URL_VALUE: &'static str = "/installed";
157 pub const DEFAULT_CANCELLED_URL_VALUE: &'static str = "/cancelled";
158 pub const DEFAULT_ERROR_URL_VALUE: &'static str = "/error";
159
160 pub const OAUTH_AUTHORIZE_URL_VALUE: &'static str = "https://slack.com/oauth/v2/authorize";
161
162 pub fn to_redirect_url(&self) -> ClientResult<Url> {
163 Url::parse(
164 format!(
165 "{}{}",
166 &self.redirect_callback_host, &self.redirect_callback_path
167 )
168 .as_str(),
169 )
170 .map_err(|e| e.into())
171 }
172}
173
174pub type UserCallbackFunction<E, IF, SCHC> =
175 fn(E, Arc<SlackClient<SCHC>>, SlackClientEventsUserState) -> IF;