use std::env;
use std::path::PathBuf;
use mongodb::options::ClientOptions;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("Environment variable not found: {0}")]
EnvVarNotFound(String),
#[error("Invalid environment: {0}")]
InvalidEnvironment(String),
#[error("MongoDB connection error: {0}")]
MongoDBConnection(#[from] mongodb::error::Error),
#[error("Failed to locate MongoDB binary: {0}")]
WhichError(#[from] which::Error),
#[error("MongoDB binary not found")]
BinaryNotFound,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Environment(String);
impl Environment {
pub fn new(name: &str) -> Self {
Self(name.to_uppercase())
}
pub fn name(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for Environment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::str::FromStr for Environment {
type Err = ConfigError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.trim().is_empty() {
return Err(ConfigError::InvalidEnvironment(
"Empty environment name".to_string(),
));
}
Ok(Self::new(s))
}
}
#[derive(Debug, Clone)]
pub struct MongoConfig {
pub connection_string: String,
pub environment: Environment,
}
impl MongoConfig {
pub fn from_env(env: Environment) -> Result<Self, ConfigError> {
let var_name = format!("MONGO_{}_URI", env);
let connection_string =
env::var(&var_name).map_err(|_| ConfigError::EnvVarNotFound(var_name))?;
Ok(Self {
connection_string,
environment: env,
})
}
pub async fn get_client_options(&self) -> Result<ClientOptions, ConfigError> {
let options = ClientOptions::parse(&self.connection_string).await?;
Ok(options)
}
}
pub fn get_mongodb_bin_path() -> Result<PathBuf, ConfigError> {
if let Ok(path) = env::var("MONGODB_BIN_PATH") {
let path_buf = PathBuf::from(path);
if path_buf.join("mongodump").exists() && path_buf.join("mongorestore").exists() {
return Ok(path_buf);
}
return Err(ConfigError::BinaryNotFound);
}
if let Ok(mongodump_path) = which::which("mongodump") {
if let Some(parent) = mongodump_path.parent() {
if parent.join("mongorestore").exists() {
return Ok(parent.to_path_buf());
}
}
}
Err(ConfigError::BinaryNotFound)
}
pub fn check_mongodb_tools() -> Result<(), ConfigError> {
get_mongodb_bin_path().map(|_| ())
}
pub fn get_available_environments() -> Vec<Environment> {
let prefix = "MONGO_";
let suffix = "_URI";
let mut environments = Vec::new();
for (key, _) in env::vars() {
if key.starts_with(prefix) && key.ends_with(suffix) {
if let Some(env_name) = key
.strip_prefix(prefix)
.and_then(|s| s.strip_suffix(suffix))
{
if !env_name.is_empty() {
environments.push(Environment::new(env_name));
}
}
}
}
environments.sort_by(|a, b| a.name().cmp(b.name()));
environments
}
pub fn get_backup_dir() -> PathBuf {
env::var("BACKUP_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let mut path = env::temp_dir();
path.push("mongo_importer_backups");
path
})
}