launchdarkly-server-sdk-redis 1.0.0-rc.1

LaunchDarkly Server-Side SDK - Redis Integration
Documentation
use std::io::{Error, ErrorKind};

use launchdarkly_server_sdk::{PersistentDataStore, PersistentDataStoreFactory};

use crate::data_store::RedisPersistentDataStore;

const DEFAULT_URL: &str = "redis://localhost:6379";
const DEFAULT_PREFIX: &str = "launchdarkly";

/// Contains methods for configuring a RedisPersistentDataStore.
pub struct RedisPersistentDataStoreFactory {
    url: String,
    prefix: String,
}

impl RedisPersistentDataStoreFactory {
    /// Create a new instance of [RedisPersistentDataStoreFactory] with standard default values.
    pub fn new() -> Self {
        Self {
            url: String::from(DEFAULT_URL),
            prefix: String::from(DEFAULT_PREFIX),
        }
    }

    /// Configure the redis store to use the specified prefix. This prefix defaults to
    /// "launchdarkly".
    pub fn prefix(&mut self, prefix: &str) -> &mut Self {
        self.prefix = prefix.into();
        self
    }

    /// Configure the redis client to connect to the provided URL. The default url is
    /// "redis://localhost:6379".
    ///
    /// Note that some Redis client features can also be specified as part of the URL: The redis
    /// crate supports the redis:// syntax
    /// (<https://www.iana.org/assignments/uri-schemes/prov/redis>), which can include a password and
    /// a database number, as well as rediss://
    /// (<https://www.iana.org/assignments/uri-schemes/prov/rediss>), which enables TLS.
    pub fn url(&mut self, url: &str) -> &mut Self {
        self.url = url.into();
        self
    }
}

impl Default for RedisPersistentDataStoreFactory {
    fn default() -> Self {
        Self::new()
    }
}

impl PersistentDataStoreFactory for RedisPersistentDataStoreFactory {
    fn create_persistent_data_store(&self) -> Result<Box<dyn PersistentDataStore>, Error> {
        let prefix = self.prefix.clone();
        let url = self.url.clone();

        let client =
            redis::Client::open(url).map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;

        Ok(Box::new(RedisPersistentDataStore::new(client, prefix)))
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use std::panic;

    use super::RedisPersistentDataStoreFactory;
    use super::{DEFAULT_PREFIX, DEFAULT_URL};
    use launchdarkly_server_sdk::{AllData, PersistentDataStoreFactory};

    #[test]
    fn factory_is_initialized_with_defaults() {
        let factory = RedisPersistentDataStoreFactory::new();
        assert_eq!(factory.prefix, DEFAULT_PREFIX);
        assert_eq!(factory.url, DEFAULT_URL);
    }

    #[test]
    fn factory_can_have_defaults_changed() {
        let mut factory = RedisPersistentDataStoreFactory::new();
        factory
            .prefix("new-prefix")
            .url("redis://remote-host.com:9999");

        assert_eq!(factory.prefix, "new-prefix");
        assert_eq!(factory.url, "redis://remote-host.com:9999");
    }

    #[test]
    fn factory_returns_error_on_invalid_url() {
        let mut factory = RedisPersistentDataStoreFactory::new();
        factory.url("localhost:10000");

        let result = factory.create_persistent_data_store();
        assert!(result.is_err());
    }

    #[test]
    fn factory_can_create_store_that_cannot_connect() {
        let mut factory = RedisPersistentDataStoreFactory::new();
        factory.url("redis://localhost:9999");

        let result = factory.create_persistent_data_store();
        assert!(result.is_ok());

        let mut store = result.unwrap();
        let result = store.init(AllData {
            flags: HashMap::new(),
            segments: HashMap::new(),
        });

        if let Err(_) = result {
            // pass
        } else {
            panic!("store.init failed to return a connection error");
        }
    }
}