launchdarkly_server_sdk_redis/
data_store_factory.rs

1use std::io::{Error, ErrorKind};
2
3use launchdarkly_server_sdk::{PersistentDataStore, PersistentDataStoreFactory};
4
5use crate::data_store::RedisPersistentDataStore;
6
7const DEFAULT_URL: &str = "redis://localhost:6379";
8const DEFAULT_PREFIX: &str = "launchdarkly";
9
10/// Contains methods for configuring a RedisPersistentDataStore.
11pub struct RedisPersistentDataStoreFactory {
12    url: String,
13    prefix: String,
14}
15
16impl RedisPersistentDataStoreFactory {
17    /// Create a new instance of [RedisPersistentDataStoreFactory] with standard default values.
18    pub fn new() -> Self {
19        Self {
20            url: String::from(DEFAULT_URL),
21            prefix: String::from(DEFAULT_PREFIX),
22        }
23    }
24
25    /// Configure the redis store to use the specified prefix. This prefix defaults to
26    /// "launchdarkly".
27    pub fn prefix(&mut self, prefix: &str) -> &mut Self {
28        self.prefix = prefix.into();
29        self
30    }
31
32    /// Configure the redis client to connect to the provided URL. The default url is
33    /// "redis://localhost:6379".
34    ///
35    /// Note that some Redis client features can also be specified as part of the URL: The redis
36    /// crate supports the redis:// syntax
37    /// (<https://www.iana.org/assignments/uri-schemes/prov/redis>), which can include a password and
38    /// a database number, as well as rediss://
39    /// (<https://www.iana.org/assignments/uri-schemes/prov/rediss>), which enables TLS.
40    pub fn url(&mut self, url: &str) -> &mut Self {
41        self.url = url.into();
42        self
43    }
44}
45
46impl Default for RedisPersistentDataStoreFactory {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl PersistentDataStoreFactory for RedisPersistentDataStoreFactory {
53    fn create_persistent_data_store(&self) -> Result<Box<dyn PersistentDataStore>, Error> {
54        let prefix = self.prefix.clone();
55        let url = self.url.clone();
56
57        let client =
58            redis::Client::open(url).map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
59
60        Ok(Box::new(RedisPersistentDataStore::new(client, prefix)))
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use std::collections::HashMap;
67    use std::panic;
68
69    use super::RedisPersistentDataStoreFactory;
70    use super::{DEFAULT_PREFIX, DEFAULT_URL};
71    use launchdarkly_server_sdk::{AllData, PersistentDataStoreFactory};
72
73    #[test]
74    fn factory_is_initialized_with_defaults() {
75        let factory = RedisPersistentDataStoreFactory::new();
76        assert_eq!(factory.prefix, DEFAULT_PREFIX);
77        assert_eq!(factory.url, DEFAULT_URL);
78    }
79
80    #[test]
81    fn factory_can_have_defaults_changed() {
82        let mut factory = RedisPersistentDataStoreFactory::new();
83        factory
84            .prefix("new-prefix")
85            .url("redis://remote-host.com:9999");
86
87        assert_eq!(factory.prefix, "new-prefix");
88        assert_eq!(factory.url, "redis://remote-host.com:9999");
89    }
90
91    #[test]
92    fn factory_returns_error_on_invalid_url() {
93        let mut factory = RedisPersistentDataStoreFactory::new();
94        factory.url("localhost:10000");
95
96        let result = factory.create_persistent_data_store();
97        assert!(result.is_err());
98    }
99
100    #[test]
101    fn factory_can_create_store_that_cannot_connect() {
102        let mut factory = RedisPersistentDataStoreFactory::new();
103        factory.url("redis://localhost:9999");
104
105        let result = factory.create_persistent_data_store();
106        assert!(result.is_ok());
107
108        let mut store = result.unwrap();
109        let result = store.init(AllData {
110            flags: HashMap::new(),
111            segments: HashMap::new(),
112        });
113
114        if let Err(_) = result {
115            // pass
116        } else {
117            panic!("store.init failed to return a connection error");
118        }
119    }
120}