use std::env;
#[derive(Clone, Debug)]
pub struct Config {
pub host: String,
#[allow(dead_code)]
pub email: Option<String>,
pub api_token: String,
#[allow(dead_code)]
pub api_version: String,
pub is_cloud: bool,
pub use_bearer: bool,
pub max_content_length: usize,
}
pub struct ExpandParams;
impl ExpandParams {
pub const PAGE_CONTENT: &str = "body.storage,version";
#[allow(dead_code)]
pub const PAGE_WITH_ANCESTORS: &str = "body.storage,version,ancestors,space";
}
impl Config {
pub fn from_env() -> Result<Self, String> {
let host = env::var("CONFLUENCE_HOST")
.map(|h| h.trim_end_matches('/').to_string())
.map_err(|_| "Missing CONFLUENCE_HOST")?;
let api_token =
env::var("CONFLUENCE_API_TOKEN").map_err(|_| "Missing CONFLUENCE_API_TOKEN")?;
let email = env::var("CONFLUENCE_EMAIL").ok();
let api_version = env::var("CONFLUENCE_API_VERSION").unwrap_or_else(|_| {
if host.contains(".atlassian.net") {
"2".to_string()
} else {
"1".to_string()
}
});
let is_cloud = api_version == "2";
let use_bearer =
env::var("CONFLUENCE_USE_BEARER").map_or(!is_cloud, |v| v == "true" || !is_cloud);
if is_cloud && !use_bearer && email.is_none() {
return Err("Missing CONFLUENCE_EMAIL (required for Confluence Cloud)".to_string());
}
let max_content_length = env::var("CONFLUENCE_MAX_CONTENT_LENGTH")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(30000);
Ok(Config {
host,
email,
api_token,
api_version,
is_cloud,
use_bearer,
max_content_length,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static ENV_LOCK: Mutex<()> = Mutex::new(());
fn clear_confluence_env() {
for key in [
"CONFLUENCE_HOST",
"CONFLUENCE_API_TOKEN",
"CONFLUENCE_EMAIL",
"CONFLUENCE_API_VERSION",
"CONFLUENCE_USE_BEARER",
"CONFLUENCE_MAX_CONTENT_LENGTH",
] {
env::remove_var(key);
}
}
#[test]
fn missing_host() {
let _lock = ENV_LOCK.lock().unwrap();
clear_confluence_env();
env::set_var("CONFLUENCE_API_TOKEN", "tok");
let err = Config::from_env().unwrap_err();
assert!(err.contains("CONFLUENCE_HOST"));
}
#[test]
fn missing_token() {
let _lock = ENV_LOCK.lock().unwrap();
clear_confluence_env();
env::set_var("CONFLUENCE_HOST", "https://example.com");
let err = Config::from_env().unwrap_err();
assert!(err.contains("CONFLUENCE_API_TOKEN"));
}
#[test]
fn server_dc_defaults() {
let _lock = ENV_LOCK.lock().unwrap();
clear_confluence_env();
env::set_var("CONFLUENCE_HOST", "https://confluence.corp.com");
env::set_var("CONFLUENCE_API_TOKEN", "my-token");
let cfg = Config::from_env().unwrap();
assert_eq!(cfg.api_version, "1");
assert!(!cfg.is_cloud);
assert!(cfg.use_bearer); assert_eq!(cfg.max_content_length, 30000);
}
#[test]
fn cloud_auto_detection() {
let _lock = ENV_LOCK.lock().unwrap();
clear_confluence_env();
env::set_var("CONFLUENCE_HOST", "https://mysite.atlassian.net");
env::set_var("CONFLUENCE_API_TOKEN", "token");
env::set_var("CONFLUENCE_EMAIL", "user@example.com");
let cfg = Config::from_env().unwrap();
assert_eq!(cfg.api_version, "2");
assert!(cfg.is_cloud);
assert!(!cfg.use_bearer); }
#[test]
fn cloud_missing_email() {
let _lock = ENV_LOCK.lock().unwrap();
clear_confluence_env();
env::set_var("CONFLUENCE_HOST", "https://mysite.atlassian.net");
env::set_var("CONFLUENCE_API_TOKEN", "token");
let err = Config::from_env().unwrap_err();
assert!(err.contains("CONFLUENCE_EMAIL"));
}
#[test]
fn host_trailing_slash_stripped() {
let _lock = ENV_LOCK.lock().unwrap();
clear_confluence_env();
env::set_var("CONFLUENCE_HOST", "https://example.com/");
env::set_var("CONFLUENCE_API_TOKEN", "tok");
let cfg = Config::from_env().unwrap();
assert_eq!(cfg.host, "https://example.com");
}
#[test]
fn custom_max_content_length() {
let _lock = ENV_LOCK.lock().unwrap();
clear_confluence_env();
env::set_var("CONFLUENCE_HOST", "https://example.com");
env::set_var("CONFLUENCE_API_TOKEN", "tok");
env::set_var("CONFLUENCE_MAX_CONTENT_LENGTH", "50000");
let cfg = Config::from_env().unwrap();
assert_eq!(cfg.max_content_length, 50000);
}
#[test]
fn invalid_max_content_length_falls_back() {
let _lock = ENV_LOCK.lock().unwrap();
clear_confluence_env();
env::set_var("CONFLUENCE_HOST", "https://example.com");
env::set_var("CONFLUENCE_API_TOKEN", "tok");
env::set_var("CONFLUENCE_MAX_CONTENT_LENGTH", "not-a-number");
let cfg = Config::from_env().unwrap();
assert_eq!(cfg.max_content_length, 30000);
}
#[test]
fn explicit_api_version() {
let _lock = ENV_LOCK.lock().unwrap();
clear_confluence_env();
env::set_var("CONFLUENCE_HOST", "https://example.com");
env::set_var("CONFLUENCE_API_TOKEN", "tok");
env::set_var("CONFLUENCE_API_VERSION", "2");
env::set_var("CONFLUENCE_EMAIL", "a@b.com");
let cfg = Config::from_env().unwrap();
assert_eq!(cfg.api_version, "2");
assert!(cfg.is_cloud);
}
#[test]
fn expand_params_constants() {
assert_eq!(ExpandParams::PAGE_CONTENT, "body.storage,version");
assert_eq!(
ExpandParams::PAGE_WITH_ANCESTORS,
"body.storage,version,ancestors,space"
);
}
}