Skip to main content

notifica_common/configuration/
mod.rs

1use core::fmt;
2use std::{collections::HashMap, error::Error};
3
4use serde::{Deserialize, Serialize};
5
6use crate::configuration::{
7    email::EmailEventBody,
8    push::{ProvidersConfig, PushEventBody},
9    request::{RequestEvent, RequestEventBody},
10};
11
12#[cfg(test)]
13use crate::configuration::push::ExpoConfig;
14
15pub mod email;
16pub mod push;
17pub mod request;
18
19#[derive(Clone, Debug, Serialize, Deserialize)]
20pub struct CommonEvent<T> {
21    pub template_path: String,
22    pub default_params: Option<T>,
23}
24
25#[derive(Clone, Debug, Serialize, Deserialize)]
26pub struct WebhookDto {
27    pub email: Option<EmailEventBody>,
28    pub push: Option<PushEventBody>,
29    pub request: Option<RequestEventBody>,
30    pub automation: Option<RequestEventBody>,
31}
32
33#[derive(Clone, Debug, Serialize, Deserialize)]
34pub struct QueueDto {
35    pub tenant_id: String,
36    pub event_name: String,
37    pub email: Option<EmailEventBody>,
38    pub push: Option<PushEventBody>,
39    pub request: Option<RequestEventBody>,
40    pub automation: Option<RequestEventBody>,
41}
42
43pub enum Dto<'a> {
44    QueueDto(&'a mut QueueDto),
45    WebhookDto(&'a mut WebhookDto),
46}
47
48#[derive(Debug, Serialize, Deserialize, Clone)]
49pub struct Config {
50    pub tenant_id: String,
51    pub email: Option<HashMap<String, CommonEvent<EmailEventBody>>>,
52    pub push: Option<HashMap<String, CommonEvent<PushEventBody>>>,
53    pub automation: Option<HashMap<String, RequestEvent>>,
54    pub request: Option<HashMap<String, RequestEvent>>,
55    pub config: Option<Configuration>,
56    pub webhooks: HashMap<String, WebhookActionSettings>,
57}
58
59#[derive(Clone, Debug, Serialize, Deserialize)]
60pub struct Configuration {
61    push: Option<ProvidersConfig>,
62}
63
64#[derive(Default)]
65pub struct TenantConfig(HashMap<String, Config>);
66
67#[derive(Clone, Debug, Serialize, Deserialize)]
68pub struct WebhookActionSettings {
69    pub request: Option<RequestEvent>,
70    pub email: Option<CommonEvent<EmailEventBody>>,
71    pub push: Option<CommonEvent<PushEventBody>>,
72    pub automation: Option<RequestEvent>,
73}
74
75pub enum WebhookAction {
76    Request(Option<RequestEvent>),
77    Automation(Option<RequestEvent>),
78    Email(Option<CommonEvent<EmailEventBody>>),
79    Push(Option<CommonEvent<PushEventBody>>),
80}
81
82impl Config {
83    pub fn get_email_actions(self) -> Vec<String> {
84        self.email.unwrap_or_default().into_keys().collect()
85    }
86
87    pub fn get_template_by_email_action(self, action: String) -> String {
88        self.email
89            .unwrap_or_default()
90            .get(&action)
91            .unwrap()
92            .template_path
93            .clone()
94    }
95
96    pub fn get_expo_access_token(self) -> Result<String, TenantConfigError> {
97        if self.config.is_none() {
98            return Err(TenantConfigError::ExtraConfigMissing);
99        }
100
101        let unwrapped_config = self.config.unwrap();
102
103        if unwrapped_config.push.is_none() {
104            return Err(TenantConfigError::ExtraConfigPushSettingsMissing);
105        }
106
107        let unwrapped_push_settings = unwrapped_config.push.unwrap();
108
109        if unwrapped_push_settings.expo.is_none() {
110            return Err(TenantConfigError::ExtraConfigPushSettingsMissing);
111        }
112
113        Ok(unwrapped_push_settings.expo.unwrap().access_token)
114    }
115}
116
117#[derive(Debug)]
118pub enum TenantConfigError {
119    Missing,
120    WrongStructure,
121    WebhookActionSettingsMissing,
122    WebhookRequestActionMissing,
123    WebhookPushActionMissing,
124    WebhookEmailActionMissing,
125    QueueEmailActionMissing,
126    QueuePushActionMissing,
127    QueueActionMissing,
128    QueueRequestActionMissing,
129    QueueAutomationActionMissing,
130    ExtraConfigExpoTokenMissing,
131    ExtraConfigPushSettingsMissing,
132    ExtraConfigMissing,
133}
134
135impl fmt::Display for TenantConfigError {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        match self {
138            TenantConfigError::Missing => write!(f, "Tenant configuration is missing"),
139            TenantConfigError::WebhookActionSettingsMissing => {
140                write!(f, "Webhook action settings is missing")
141            }
142            TenantConfigError::QueueActionMissing => {
143                write!(f, "Queue action is missing")
144            }
145            TenantConfigError::WebhookEmailActionMissing => {
146                write!(f, "Webhook email action is missing")
147            }
148            TenantConfigError::WebhookPushActionMissing => {
149                write!(f, "Webhook push action is missing")
150            }
151            TenantConfigError::WebhookRequestActionMissing => {
152                write!(f, "Webhook request action is missing")
153            }
154            TenantConfigError::QueueRequestActionMissing => {
155                write!(f, "Queue request action is missing")
156            }
157            TenantConfigError::QueueEmailActionMissing => {
158                write!(f, "Queue email action is missing")
159            }
160            TenantConfigError::QueuePushActionMissing => {
161                write!(f, "Queue push action is missing")
162            }
163            TenantConfigError::QueueAutomationActionMissing => {
164                write!(f, "Queue automation action is missing")
165            }
166            TenantConfigError::WrongStructure => write!(f, "Config is broken"),
167            TenantConfigError::ExtraConfigMissing => write!(f, "Extra config is missing"),
168            TenantConfigError::ExtraConfigExpoTokenMissing => {
169                write!(f, "Extra config expo token is missing")
170            }
171            TenantConfigError::ExtraConfigPushSettingsMissing => {
172                write!(f, "Extra push config expo token is missing")
173            }
174        }
175    }
176}
177
178impl Error for TenantConfigError {}
179
180pub type TenantConfigResult = Result<Config, TenantConfigError>;
181pub type TenantResult = Result<TenantConfig, TenantConfigError>;
182
183impl TenantConfig {
184    pub fn new() -> Self {
185        Self::default()
186    }
187
188    pub fn insert(&mut self, key: String, value: Config) {
189        self.0.insert(key, value);
190    }
191
192    pub fn get(&self, key: &str) -> TenantConfigResult {
193        if let Some(value) = self.0.get(key) {
194            let res = value.clone();
195            return Ok(res);
196        }
197        Err(TenantConfigError::Missing)
198    }
199
200    pub fn keys(&self) -> std::collections::hash_map::Keys<'_, std::string::String, Config> {
201        self.0.keys()
202    }
203
204    pub fn contains_key(&self, key: &str) -> bool {
205        self.0.contains_key(key)
206    }
207
208    pub fn is_empty(&self) -> bool {
209        self.0.is_empty()
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use std::collections::HashMap;
217
218    fn create_test_config() -> Config {
219        Config {
220            tenant_id: "test_tenant".to_string(),
221            email: Some(HashMap::new()),
222            push: Some(HashMap::new()),
223            request: Some(HashMap::new()),
224            automation: Some(HashMap::new()),
225            config: None,
226            webhooks: HashMap::new(),
227        }
228    }
229
230    fn create_test_config_with_email_actions() -> Config {
231        let mut email_map = HashMap::new();
232        email_map.insert(
233            "welcome".to_string(),
234            CommonEvent {
235                template_path: "test/welcome.html".to_string(),
236                default_params: Some(EmailEventBody {
237                    target_email: "test@example.com".to_string(),
238                    target_name: Some("Test User".to_string()),
239                    sender_name: Some("Test App".to_string()),
240                    subject: Some("Welcome".to_string()),
241                    params: None,
242                }),
243            },
244        );
245
246        Config {
247            tenant_id: "test_tenant".to_string(),
248            email: Some(email_map),
249            push: Some(HashMap::new()),
250            request: Some(HashMap::new()),
251            automation: Some(HashMap::new()),
252            config: None,
253            webhooks: HashMap::new(),
254        }
255    }
256
257    fn create_test_config_with_expo_token() -> Config {
258        let expo_config = ExpoConfig {
259            access_token: "test_token_123".to_string(),
260        };
261        let providers_config = ProvidersConfig {
262            ios: None,
263            aos: None,
264            expo: Some(expo_config),
265        };
266        let configuration = Configuration {
267            push: Some(providers_config),
268        };
269
270        Config {
271            tenant_id: "test_tenant".to_string(),
272            email: Some(HashMap::new()),
273            push: Some(HashMap::new()),
274            request: Some(HashMap::new()),
275            automation: Some(HashMap::new()),
276            config: Some(configuration),
277            webhooks: HashMap::new(),
278        }
279    }
280
281    #[test]
282    fn test_config_get_email_actions() {
283        let config = create_test_config_with_email_actions();
284        let actions = config.get_email_actions();
285        assert_eq!(actions.len(), 1);
286        assert!(actions.contains(&"welcome".to_string()));
287    }
288
289    #[test]
290    fn test_config_get_email_actions_empty() {
291        let config = create_test_config();
292        let actions = config.get_email_actions();
293        assert!(actions.is_empty());
294    }
295
296    #[test]
297    fn test_config_get_template_by_email_action() {
298        let config = create_test_config_with_email_actions();
299        let template_path = config.get_template_by_email_action("welcome".to_string());
300        assert_eq!(template_path, "test/welcome.html");
301    }
302
303    #[test]
304    #[should_panic]
305    fn test_config_get_template_by_email_action_invalid() {
306        let config = create_test_config();
307        config.get_template_by_email_action("invalid".to_string());
308    }
309
310    #[test]
311    fn test_config_get_expo_access_token_success() {
312        let config = create_test_config_with_expo_token();
313        let result = config.get_expo_access_token();
314        assert!(result.is_ok());
315        assert_eq!(result.unwrap(), "test_token_123");
316    }
317
318    #[test]
319    fn test_config_get_expo_access_token_missing_config() {
320        let config = create_test_config();
321        let result = config.get_expo_access_token();
322        assert!(result.is_err());
323        assert!(matches!(
324            result.unwrap_err(),
325            TenantConfigError::ExtraConfigMissing
326        ));
327    }
328
329    #[test]
330    fn test_tenant_config_new() {
331        let tenant_config = TenantConfig::new();
332        assert!(tenant_config.is_empty());
333    }
334
335    #[test]
336    fn test_tenant_config_insert_and_get() {
337        let mut tenant_config = TenantConfig::new();
338        let config = create_test_config();
339        tenant_config.insert("test_tenant".to_string(), config.clone());
340
341        let result = tenant_config.get("test_tenant");
342        assert!(result.is_ok());
343        let retrieved_config = result.unwrap();
344        assert_eq!(retrieved_config.tenant_id, "test_tenant");
345    }
346
347    #[test]
348    fn test_tenant_config_get_missing() {
349        let tenant_config = TenantConfig::new();
350        let result = tenant_config.get("missing_tenant");
351        assert!(result.is_err());
352        assert!(matches!(result.unwrap_err(), TenantConfigError::Missing));
353    }
354
355    #[test]
356    fn test_tenant_config_contains_key() {
357        let mut tenant_config = TenantConfig::new();
358        let config = create_test_config();
359        tenant_config.insert("test_tenant".to_string(), config);
360
361        assert!(tenant_config.contains_key("test_tenant"));
362        assert!(!tenant_config.contains_key("missing_tenant"));
363    }
364
365    #[test]
366    fn test_tenant_config_keys() {
367        let mut tenant_config = TenantConfig::new();
368        let config1 = create_test_config();
369        let config2 = create_test_config();
370        tenant_config.insert("tenant1".to_string(), config1);
371        tenant_config.insert("tenant2".to_string(), config2);
372
373        let keys: Vec<&String> = tenant_config.keys().collect();
374        assert_eq!(keys.len(), 2);
375        assert!(keys.contains(&&"tenant1".to_string()));
376        assert!(keys.contains(&&"tenant2".to_string()));
377    }
378
379    #[test]
380    fn test_tenant_config_error_display() {
381        let error = TenantConfigError::Missing;
382        assert_eq!(error.to_string(), "Tenant configuration is missing");
383
384        let error = TenantConfigError::WrongStructure;
385        assert_eq!(error.to_string(), "Config is broken");
386
387        let error = TenantConfigError::WebhookActionSettingsMissing;
388        assert_eq!(error.to_string(), "Webhook action settings is missing");
389    }
390}