Skip to main content

rustack_ses_core/
config_set.rs

1//! Configuration set and event destination management.
2//!
3//! Stores configuration sets and their associated event destinations
4//! using `DashMap` for concurrent access.
5
6use dashmap::{DashMap, mapref::entry::Entry};
7use rustack_ses_model::{
8    error::{SesError, SesErrorCode},
9    types::EventDestination,
10};
11
12/// Internal configuration set record with event destinations.
13#[derive(Debug, Clone)]
14pub struct ConfigSetRecord {
15    /// Configuration set name.
16    pub name: String,
17    /// Event destinations attached to this configuration set.
18    pub event_destinations: Vec<EventDestination>,
19}
20
21/// Store for configuration sets and their event destinations.
22#[derive(Debug)]
23pub struct ConfigurationSetStore {
24    /// Configuration sets keyed by name.
25    config_sets: DashMap<String, ConfigSetRecord>,
26}
27
28impl Default for ConfigurationSetStore {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl ConfigurationSetStore {
35    /// Create a new empty configuration set store.
36    #[must_use]
37    pub fn new() -> Self {
38        Self {
39            config_sets: DashMap::new(),
40        }
41    }
42
43    /// Create a new configuration set.
44    ///
45    /// # Errors
46    ///
47    /// Returns `ConfigurationSetAlreadyExistsException` if the name is taken.
48    pub fn create(&self, name: &str) -> Result<(), SesError> {
49        match self.config_sets.entry(name.to_owned()) {
50            Entry::Occupied(_) => Err(SesError::with_message(
51                SesErrorCode::ConfigurationSetAlreadyExistsException,
52                format!("Configuration set <{name}> already exists."),
53            )),
54            Entry::Vacant(e) => {
55                e.insert(ConfigSetRecord {
56                    name: name.to_owned(),
57                    event_destinations: Vec::new(),
58                });
59                Ok(())
60            }
61        }
62    }
63
64    /// Delete a configuration set.
65    ///
66    /// # Errors
67    ///
68    /// Returns `ConfigurationSetDoesNotExistException` if not found.
69    pub fn delete(&self, name: &str) -> Result<(), SesError> {
70        self.config_sets.remove(name).ok_or_else(|| {
71            SesError::with_message(
72                SesErrorCode::ConfigurationSetDoesNotExistException,
73                format!("Configuration set <{name}> does not exist."),
74            )
75        })?;
76        Ok(())
77    }
78
79    /// Describe a configuration set including its event destinations.
80    ///
81    /// # Errors
82    ///
83    /// Returns `ConfigurationSetDoesNotExistException` if not found.
84    pub fn describe(&self, name: &str) -> Result<ConfigSetRecord, SesError> {
85        self.config_sets
86            .get(name)
87            .map(|entry| entry.value().clone())
88            .ok_or_else(|| {
89                SesError::with_message(
90                    SesErrorCode::ConfigurationSetDoesNotExistException,
91                    format!("Configuration set <{name}> does not exist."),
92                )
93            })
94    }
95
96    /// List all configuration set names.
97    #[must_use]
98    pub fn list(&self) -> Vec<String> {
99        self.config_sets.iter().map(|e| e.key().clone()).collect()
100    }
101
102    /// Check if a configuration set exists.
103    #[must_use]
104    pub fn exists(&self, name: &str) -> bool {
105        self.config_sets.contains_key(name)
106    }
107
108    /// Add an event destination to a configuration set.
109    ///
110    /// # Errors
111    ///
112    /// Returns `ConfigurationSetDoesNotExistException` if the config set is not found.
113    /// Returns `EventDestinationAlreadyExistsException` if the destination name is taken.
114    pub fn add_event_destination(
115        &self,
116        config_set_name: &str,
117        destination: EventDestination,
118    ) -> Result<(), SesError> {
119        let mut entry = self.config_sets.get_mut(config_set_name).ok_or_else(|| {
120            SesError::with_message(
121                SesErrorCode::ConfigurationSetDoesNotExistException,
122                format!("Configuration set <{config_set_name}> does not exist."),
123            )
124        })?;
125        if entry
126            .event_destinations
127            .iter()
128            .any(|d| d.name == destination.name)
129        {
130            return Err(SesError::with_message(
131                SesErrorCode::EventDestinationAlreadyExistsException,
132                format!(
133                    "Event destination {} already exists in configuration set {config_set_name}.",
134                    destination.name
135                ),
136            ));
137        }
138        entry.event_destinations.push(destination);
139        Ok(())
140    }
141
142    /// Update an event destination within a configuration set.
143    ///
144    /// # Errors
145    ///
146    /// Returns `ConfigurationSetDoesNotExistException` if the config set is not found.
147    /// Returns `EventDestinationDoesNotExistException` if the destination is not found.
148    pub fn update_event_destination(
149        &self,
150        config_set_name: &str,
151        destination: EventDestination,
152    ) -> Result<(), SesError> {
153        let mut entry = self.config_sets.get_mut(config_set_name).ok_or_else(|| {
154            SesError::with_message(
155                SesErrorCode::ConfigurationSetDoesNotExistException,
156                format!("Configuration set <{config_set_name}> does not exist."),
157            )
158        })?;
159        let pos = entry
160            .event_destinations
161            .iter()
162            .position(|d| d.name == destination.name)
163            .ok_or_else(|| {
164                SesError::with_message(
165                    SesErrorCode::EventDestinationDoesNotExistException,
166                    format!("Event destination {} does not exist.", destination.name),
167                )
168            })?;
169        entry.event_destinations[pos] = destination;
170        Ok(())
171    }
172
173    /// Delete an event destination from a configuration set.
174    ///
175    /// # Errors
176    ///
177    /// Returns `ConfigurationSetDoesNotExistException` if the config set is not found.
178    pub fn delete_event_destination(
179        &self,
180        config_set_name: &str,
181        destination_name: &str,
182    ) -> Result<(), SesError> {
183        let mut entry = self.config_sets.get_mut(config_set_name).ok_or_else(|| {
184            SesError::with_message(
185                SesErrorCode::ConfigurationSetDoesNotExistException,
186                format!("Configuration set <{config_set_name}> does not exist."),
187            )
188        })?;
189        entry
190            .event_destinations
191            .retain(|d| d.name != destination_name);
192        Ok(())
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_should_create_config_set() {
202        let store = ConfigurationSetStore::new();
203        assert!(store.create("my-set").is_ok());
204        assert!(store.exists("my-set"));
205    }
206
207    #[test]
208    fn test_should_reject_duplicate_config_set() {
209        let store = ConfigurationSetStore::new();
210        store.create("dup").unwrap_or_default();
211        assert!(store.create("dup").is_err());
212    }
213
214    #[test]
215    fn test_should_delete_config_set() {
216        let store = ConfigurationSetStore::new();
217        store.create("del").unwrap_or_default();
218        assert!(store.delete("del").is_ok());
219        assert!(!store.exists("del"));
220    }
221
222    #[test]
223    fn test_should_return_error_on_delete_nonexistent() {
224        let store = ConfigurationSetStore::new();
225        assert!(store.delete("nope").is_err());
226    }
227
228    #[test]
229    fn test_should_describe_config_set() {
230        let store = ConfigurationSetStore::new();
231        store.create("desc").unwrap_or_default();
232        let record = store.describe("desc");
233        assert!(record.is_ok());
234        assert_eq!(
235            record
236                .unwrap_or_else(|_| ConfigSetRecord {
237                    name: String::new(),
238                    event_destinations: Vec::new(),
239                })
240                .name,
241            "desc"
242        );
243    }
244
245    #[test]
246    fn test_should_list_config_sets() {
247        let store = ConfigurationSetStore::new();
248        store.create("a").unwrap_or_default();
249        store.create("b").unwrap_or_default();
250        let list = store.list();
251        assert_eq!(list.len(), 2);
252    }
253
254    #[test]
255    fn test_should_add_event_destination() {
256        let store = ConfigurationSetStore::new();
257        store.create("set1").unwrap_or_default();
258        let dest = EventDestination {
259            name: "my-dest".to_owned(),
260            enabled: Some(true),
261            ..EventDestination::default()
262        };
263        assert!(store.add_event_destination("set1", dest).is_ok());
264        let record = store.describe("set1").unwrap_or_else(|_| ConfigSetRecord {
265            name: String::new(),
266            event_destinations: Vec::new(),
267        });
268        assert_eq!(record.event_destinations.len(), 1);
269    }
270
271    #[test]
272    fn test_should_reject_duplicate_event_destination() {
273        let store = ConfigurationSetStore::new();
274        store.create("set2").unwrap_or_default();
275        let dest = EventDestination {
276            name: "dup-dest".to_owned(),
277            ..EventDestination::default()
278        };
279        store
280            .add_event_destination("set2", dest.clone())
281            .unwrap_or_default();
282        assert!(store.add_event_destination("set2", dest).is_err());
283    }
284
285    #[test]
286    fn test_should_delete_event_destination() {
287        let store = ConfigurationSetStore::new();
288        store.create("set3").unwrap_or_default();
289        let dest = EventDestination {
290            name: "del-dest".to_owned(),
291            ..EventDestination::default()
292        };
293        store
294            .add_event_destination("set3", dest)
295            .unwrap_or_default();
296        assert!(store.delete_event_destination("set3", "del-dest").is_ok());
297        let record = store.describe("set3").unwrap_or_else(|_| ConfigSetRecord {
298            name: String::new(),
299            event_destinations: Vec::new(),
300        });
301        assert!(record.event_destinations.is_empty());
302    }
303
304    #[test]
305    fn test_should_update_event_destination() {
306        let store = ConfigurationSetStore::new();
307        store.create("set4").unwrap_or_default();
308        let dest = EventDestination {
309            name: "upd-dest".to_owned(),
310            enabled: Some(false),
311            ..EventDestination::default()
312        };
313        store
314            .add_event_destination("set4", dest)
315            .unwrap_or_default();
316        let updated = EventDestination {
317            name: "upd-dest".to_owned(),
318            enabled: Some(true),
319            ..EventDestination::default()
320        };
321        assert!(store.update_event_destination("set4", updated).is_ok());
322    }
323}