use std::{env, fmt};
#[derive(Clone, Debug)]
pub struct RedisConfig {
pub host: String,
pub port: u16,
pub username: Option<String>,
pub password: Option<String>,
pub database: u8,
pub timeout: u64,
}
impl RedisConfig {
pub(crate) fn url(&self) -> String {
let mut url = String::from("redis://");
if self.username.is_some() || self.password.is_some() {
if let Some(username) = &self.username {
url.push_str(username);
}
if let Some(password) = &self.password {
url.push(':');
url.push_str(password);
}
url.push('@');
}
url.push_str(&self.host);
url.push(':');
url.push_str(&self.port.to_string());
if self.database > 0 {
url.push('/');
url.push_str(&self.database.to_string());
};
url
}
}
impl Default for RedisConfig {
fn default() -> Self {
let port = env::var("REDIS_PORT")
.ok()
.and_then(|s| s.parse::<u16>().ok())
.unwrap_or(6379);
let database = env::var("REDIS_DB")
.ok()
.and_then(|s| s.parse::<u8>().ok())
.unwrap_or(0);
let username = env::var("REDIS_USER").ok();
let password = env::var("REDIS_PASSWORD").ok();
Self {
host: env::var("REDIS_HOST").unwrap_or_else(|_| "localhost".to_string()),
port,
username,
password,
database,
timeout: 30,
}
}
}
impl fmt::Display for RedisConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let url = self.url();
write!(f, "{} (timeout: {}s)", url, self.timeout)
}
}
#[cfg(test)]
mod tests {
use super::*;
use once_cell::sync::Lazy;
use std::sync::Mutex;
static ENV_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
fn set_var(name: &str, value: &str) {
#[allow(unused_unsafe)]
unsafe {
env::set_var(name, value);
}
}
fn remove_var(name: &str) {
#[allow(unused_unsafe)]
unsafe {
env::remove_var(name);
}
}
#[test]
fn test_default_values() {
let _guard = ENV_MUTEX.lock().unwrap();
remove_var("REDIS_HOST");
remove_var("REDIS_PORT");
remove_var("REDIS_USER");
remove_var("REDIS_PASSWORD");
remove_var("REDIS_DB");
let config = RedisConfig::default();
assert_eq!(config.host, "localhost");
assert_eq!(config.port, 6379);
assert_eq!(config.username, None);
assert_eq!(config.password, None);
assert_eq!(config.database, 0);
assert_eq!(config.timeout, 30);
}
#[test]
fn test_environment_variable_overrides() {
let _guard = ENV_MUTEX.lock().unwrap();
set_var("REDIS_HOST", "redis.example.com");
set_var("REDIS_PORT", "6380");
set_var("REDIS_USER", "testuser");
set_var("REDIS_PASSWORD", "testpass");
set_var("REDIS_DB", "2");
let config = RedisConfig::default();
assert_eq!(config.host, "redis.example.com");
assert_eq!(config.port, 6380);
assert_eq!(config.username, Some("testuser".to_string()));
assert_eq!(config.password, Some("testpass".to_string()));
assert_eq!(config.database, 2);
assert_eq!(config.timeout, 30);
remove_var("REDIS_HOST");
remove_var("REDIS_PORT");
remove_var("REDIS_USER");
remove_var("REDIS_PASSWORD");
remove_var("REDIS_DB");
}
#[test]
fn test_invalid_port_in_env() {
let _guard = ENV_MUTEX.lock().unwrap();
set_var("REDIS_PORT", "not_a_number");
let config = RedisConfig::default();
assert_eq!(config.port, 6379);
remove_var("REDIS_PORT");
}
#[test]
fn test_invalid_db_in_env() {
let _guard = ENV_MUTEX.lock().unwrap();
set_var("REDIS_DB", "not_a_number");
let config = RedisConfig::default();
assert_eq!(config.database, 0);
remove_var("REDIS_DB");
}
#[test]
fn test_display_without_credentials() {
let config = RedisConfig {
host: "localhost".to_string(),
port: 6379,
username: None,
password: None,
database: 0,
timeout: 30,
};
assert_eq!(
format!("{}", config),
"redis://localhost:6379 (timeout: 30s)"
);
}
#[test]
fn test_display_with_username_only() {
let config = RedisConfig {
host: "localhost".to_string(),
port: 6379,
username: Some("testuser".to_string()),
password: None,
database: 0,
timeout: 30,
};
assert_eq!(
format!("{}", config),
"redis://testuser@localhost:6379 (timeout: 30s)"
);
}
#[test]
fn test_display_with_password_only() {
let config = RedisConfig {
host: "localhost".to_string(),
port: 6379,
username: None,
password: Some("testpass".to_string()),
database: 0,
timeout: 30,
};
assert_eq!(
format!("{}", config),
"redis://:testpass@localhost:6379 (timeout: 30s)"
);
}
#[test]
fn test_display_with_full_credentials() {
let config = RedisConfig {
host: "localhost".to_string(),
port: 6379,
username: Some("testuser".to_string()),
password: Some("testpass".to_string()),
database: 0,
timeout: 30,
};
assert_eq!(
format!("{}", config),
"redis://testuser:testpass@localhost:6379 (timeout: 30s)"
);
}
#[test]
fn test_display_with_non_default_database() {
let config = RedisConfig {
host: "localhost".to_string(),
port: 6379,
username: None,
password: None,
database: 3,
timeout: 30,
};
assert_eq!(
format!("{}", config),
"redis://localhost:6379/3 (timeout: 30s)"
);
}
#[test]
fn test_display_full_configuration() {
let config = RedisConfig {
host: "redis.example.com".to_string(),
port: 6380,
username: Some("admin".to_string()),
password: Some("s3cret".to_string()),
database: 5,
timeout: 45,
};
assert_eq!(
format!("{}", config),
"redis://admin:s3cret@redis.example.com:6380/5 (timeout: 45s)"
);
}
#[test]
fn test_clone() {
let original = RedisConfig {
host: "redis.example.com".to_string(),
port: 6380,
username: Some("testuser".to_string()),
password: Some("testpass".to_string()),
database: 2,
timeout: 45,
};
let cloned = original.clone();
assert_eq!(cloned.host, "redis.example.com");
assert_eq!(cloned.port, 6380);
assert_eq!(cloned.username, Some("testuser".to_string()));
assert_eq!(cloned.password, Some("testpass".to_string()));
assert_eq!(cloned.database, 2);
assert_eq!(cloned.timeout, 45);
}
}