use slugify::slugify;
#[derive(Debug, PartialEq, Eq)]
pub struct Credentials {
username: String,
password: Option<String>,
}
impl Credentials {
#[must_use]
pub fn new(username: impl Into<String>) -> Self {
let username = username.into();
let envar = slugify!(&username, separator = "_").to_uppercase();
let envar = format!("OHLCV_{envar}_PASSWORD");
let password = std::env::var(envar).ok();
Self { username, password }
}
#[must_use]
pub fn with_password(mut self, password: impl Into<String>) -> Self {
self.password = Some(password.into());
self
}
#[inline]
#[must_use]
pub fn username(&self) -> &str {
&self.username
}
#[inline]
#[must_use]
pub fn password(&self) -> Option<&str> {
self.password.as_deref()
}
#[inline]
#[must_use]
pub const fn has_password(&self) -> bool {
self.password.is_some()
}
}
#[cfg(feature = "mysql")]
#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))]
impl TryFrom<&crate::database::mysql::DbConfig> for Credentials {
type Error = crate::Error;
fn try_from(config: &crate::database::mysql::DbConfig) -> Result<Self, Self::Error> {
let creds = Self::new(&config.username);
if let Some(password) = &config.password {
Ok(creds.with_password(password))
} else if creds.has_password() {
Ok(creds)
} else {
Err(Self::Error::MissingPassword(creds.username().into()))
}
}
}
#[cfg(feature = "postgres")]
#[cfg_attr(docsrs, doc(cfg(feature = "postgres")))]
impl TryFrom<&crate::database::postgres::DbConfig> for Credentials {
type Error = crate::Error;
fn try_from(config: &crate::database::postgres::DbConfig) -> Result<Self, Self::Error> {
let creds = Self::new(&config.username);
if let Some(password) = &config.password {
Ok(creds.with_password(password))
} else if creds.has_password() {
Ok(creds)
} else {
Err(Self::Error::MissingPassword(creds.username().into()))
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Mutex;
use once_cell::sync::Lazy;
use super::*;
static SERIALIZED: Lazy<Mutex<()>> = Lazy::new(Mutex::default);
const USERNAMES: &[(&str, &str)] = &[
("test", "OHLCV_TEST_PASSWORD"),
("test_user", "OHLCV_TEST_USER_PASSWORD"),
("test-user", "OHLCV_TEST_USER_PASSWORD"),
("test_user_1", "OHLCV_TEST_USER_1_PASSWORD"),
("test-user-1", "OHLCV_TEST_USER_1_PASSWORD"),
("test-üser-1", "OHLCV_TEST_USER_1_PASSWORD"),
];
#[test]
fn new() {
let _serialized = SERIALIZED.lock().unwrap();
std::env::remove_var("OHLCV_TEST_PASSWORD");
let creds = Credentials::new("test");
assert_eq!(creds.username(), "test");
assert!(!creds.has_password());
for (username, envar) in USERNAMES {
std::env::set_var(envar, "password");
let creds = Credentials::new(*username);
assert_eq!(creds.username(), *username);
assert_eq!(creds.password(), Some("password"));
std::env::remove_var(envar);
}
}
#[test]
fn with_password() {
let _serialized = SERIALIZED.lock().unwrap();
std::env::remove_var("OHLCV_TEST_PASSWORD");
let envar = "OHLCV_TEST_PASSWORD";
std::env::set_var(envar, "password2");
let creds = Credentials::new("test").with_password("password");
assert_eq!(creds.username(), "test");
assert_eq!(creds.password(), Some("password"));
std::env::remove_var(envar);
}
#[cfg(feature = "mysql")]
#[test]
fn from_mysql() {
let _serialized = SERIALIZED.lock().unwrap();
std::env::remove_var("OHLCV_TEST_PASSWORD");
let envar = "OHLCV_TEST_PASSWORD";
std::env::set_var(envar, "password2");
let config = crate::database::mysql::DbConfig {
host: "localhost".into(),
port: Some(3306),
database: "test".into(),
username: "test".into(),
password: Some("password".into()),
root_username: None,
pool: None,
};
let creds = Credentials::try_from(&config);
assert_eq!(
creds,
Ok(Credentials::new("test").with_password("password"))
);
let config = crate::database::mysql::DbConfig {
host: "localhost".into(),
port: Some(3306),
database: "test".into(),
username: "test".into(),
password: None,
root_username: None,
pool: None,
};
let creds = Credentials::try_from(&config);
assert_eq!(
creds,
Ok(Credentials::new("test").with_password("password2"))
);
std::env::remove_var(envar);
}
}