use std::env;
use crate::cli::BootstrapArgs;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BootstrapConfig {
pub mongo_uri: String,
pub bootstrap_token: String,
pub system_db_name: String,
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum BootstrapResolveError {
#[error("missing bootstrap input: {0}")]
MissingInput(&'static str),
#[error("environment variable is not set: {0}")]
MissingEnvVar(String),
#[error("resolved bootstrap input is empty: {0}")]
EmptyValue(&'static str),
}
impl BootstrapConfig {
pub fn resolve_runtime(args: &BootstrapArgs) -> Result<Self, BootstrapResolveError> {
let mongo_uri = resolve_value(
args.mongo_uri.as_deref(),
args.mongo_uri_env.as_deref(),
"mongo_uri",
)?;
let system_db_name = args.system_db_name.trim().to_owned();
if system_db_name.is_empty() {
return Err(BootstrapResolveError::EmptyValue("system_db_name"));
}
Ok(Self {
mongo_uri,
bootstrap_token: String::new(),
system_db_name,
})
}
pub fn resolve(args: &BootstrapArgs) -> Result<Self, BootstrapResolveError> {
let mongo_uri = resolve_value(
args.mongo_uri.as_deref(),
args.mongo_uri_env.as_deref(),
"mongo_uri",
)?;
let bootstrap_token = resolve_value(
args.bootstrap_token.as_deref(),
args.bootstrap_token_env.as_deref(),
"bootstrap_token",
)?;
let system_db_name = args.system_db_name.trim().to_owned();
if system_db_name.is_empty() {
return Err(BootstrapResolveError::EmptyValue("system_db_name"));
}
Ok(Self {
mongo_uri,
bootstrap_token,
system_db_name,
})
}
pub fn redacted_mongo_uri(&self) -> String {
self.mongo_uri.replace(|c: char| c == '\n' || c == '\r', "")
}
}
fn resolve_value(
direct: Option<&str>,
env_name: Option<&str>,
field: &'static str,
) -> Result<String, BootstrapResolveError> {
if let Some(value) = direct {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(BootstrapResolveError::EmptyValue(field));
}
return Ok(trimmed.to_owned());
}
if let Some(name) = env_name {
let trimmed_name = name.trim();
if trimmed_name.is_empty() {
return Err(BootstrapResolveError::EmptyValue(field));
}
let value = env::var(trimmed_name)
.map_err(|_| BootstrapResolveError::MissingEnvVar(trimmed_name.to_owned()))?;
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(BootstrapResolveError::EmptyValue(field));
}
return Ok(trimmed.to_owned());
}
Err(BootstrapResolveError::MissingInput(field))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cli::BootstrapArgs;
fn base_args() -> BootstrapArgs {
BootstrapArgs {
mongo_uri: None,
mongo_uri_env: None,
bootstrap_token: None,
bootstrap_token_env: None,
system_db_name: "k2_system".to_owned(),
listen_host: None,
listen_port: None,
ownership_mode: None,
slow_query_ms: None,
seed_key_name: None,
seed_key_database: None,
seed_key_permissions: Vec::new(),
}
}
#[test]
fn resolve_prefers_direct_values() {
let args = BootstrapArgs {
mongo_uri: Some("mongodb://localhost:27017".to_owned()),
mongo_uri_env: Some("IGNORED_MONGO_URI".to_owned()),
bootstrap_token: Some("root-token".to_owned()),
bootstrap_token_env: Some("IGNORED_BOOTSTRAP_TOKEN".to_owned()),
..base_args()
};
let config = BootstrapConfig::resolve(&args).expect("bootstrap config");
assert_eq!(config.mongo_uri, "mongodb://localhost:27017");
assert_eq!(config.bootstrap_token, "root-token");
assert_eq!(config.system_db_name, "k2_system");
}
#[test]
fn resolve_reads_from_environment_when_requested() {
let mongo_name = "K2DB_API_RUST_TEST_MONGO_URI";
let token_name = "K2DB_API_RUST_TEST_BOOTSTRAP_TOKEN";
unsafe {
env::set_var(mongo_name, "mongodb://env-host:27017");
env::set_var(token_name, "env-token");
}
let args = BootstrapArgs {
mongo_uri_env: Some(mongo_name.to_owned()),
bootstrap_token_env: Some(token_name.to_owned()),
..base_args()
};
let config = BootstrapConfig::resolve(&args).expect("bootstrap config");
assert_eq!(config.mongo_uri, "mongodb://env-host:27017");
assert_eq!(config.bootstrap_token, "env-token");
unsafe {
env::remove_var(mongo_name);
env::remove_var(token_name);
}
}
#[test]
fn resolve_rejects_missing_inputs() {
let args = base_args();
let error = BootstrapConfig::resolve(&args).expect_err("missing mongo input should fail");
assert_eq!(error, BootstrapResolveError::MissingInput("mongo_uri"));
}
#[test]
fn resolve_runtime_only_requires_mongo_uri() {
let args = BootstrapArgs {
mongo_uri: Some("mongodb://localhost:27017".to_owned()),
..base_args()
};
let config = BootstrapConfig::resolve_runtime(&args).expect("runtime bootstrap config");
assert_eq!(config.mongo_uri, "mongodb://localhost:27017");
assert_eq!(config.bootstrap_token, "");
}
}