use base64::Engine;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AtlassianProduct {
Jira,
Confluence,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AtlassianCreds {
pub email: String,
pub token: String,
pub base_url: String,
}
impl AtlassianCreds {
pub fn from_env() -> Option<Self> {
Self::build(
first_env(&["ATLASSIAN_EMAIL"]),
first_env(&["ATLASSIAN_API_TOKEN", "ATLASSIAN_PAT"]),
first_env(&["ATLASSIAN_URL"]),
)
}
pub fn from_env_for(product: AtlassianProduct) -> Option<Self> {
let (email_keys, token_keys, base_keys): (&[&str], &[&str], &[&str]) = match product {
AtlassianProduct::Jira => (
&["ATLASSIAN_EMAIL", "JIRA_EMAIL"],
&["ATLASSIAN_API_TOKEN", "ATLASSIAN_PAT", "JIRA_API_TOKEN"],
&["ATLASSIAN_URL", "JIRA_BASE_URL", "JIRA_URL"],
),
AtlassianProduct::Confluence => (
&["ATLASSIAN_EMAIL", "CONFLUENCE_EMAIL"],
&[
"ATLASSIAN_API_TOKEN",
"ATLASSIAN_PAT",
"CONFLUENCE_API_TOKEN",
],
&["ATLASSIAN_URL", "CONFLUENCE_BASE_URL", "CONFLUENCE_URL"],
),
};
Self::build(
first_env(email_keys),
first_env(token_keys),
first_env(base_keys),
)
}
fn build(email: Option<String>, token: Option<String>, base: Option<String>) -> Option<Self> {
let email = email?;
let token = token?;
let base = base?;
Some(Self {
email,
token,
base_url: base.trim_end_matches('/').to_string(),
})
}
pub fn basic_auth_header(&self) -> String {
let raw = format!("{}:{}", self.email, self.token);
let encoded = base64::engine::general_purpose::STANDARD.encode(raw.as_bytes());
format!("Basic {encoded}")
}
}
fn first_env(keys: &[&str]) -> Option<String> {
for k in keys {
if let Ok(v) = std::env::var(k) {
let v = v.trim();
if !v.is_empty() {
return Some(v.to_string());
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
fn clear() {
unsafe {
for k in [
"ATLASSIAN_EMAIL",
"ATLASSIAN_API_TOKEN",
"ATLASSIAN_PAT",
"ATLASSIAN_URL",
"JIRA_EMAIL",
"JIRA_API_TOKEN",
"JIRA_BASE_URL",
"JIRA_URL",
"CONFLUENCE_EMAIL",
"CONFLUENCE_API_TOKEN",
"CONFLUENCE_BASE_URL",
"CONFLUENCE_URL",
] {
std::env::remove_var(k);
}
}
}
#[test]
#[serial]
fn creds_resolve_from_canonical() {
clear();
unsafe {
std::env::set_var("ATLASSIAN_EMAIL", "bob@acme.com");
std::env::set_var("ATLASSIAN_API_TOKEN", "tok123"); std::env::set_var("ATLASSIAN_URL", "https://acme.atlassian.net/");
}
let creds = AtlassianCreds::from_env().expect("creds present");
assert_eq!(creds.email, "bob@acme.com");
assert_eq!(creds.token, "tok123");
assert_eq!(creds.base_url, "https://acme.atlassian.net");
clear();
}
#[test]
#[serial]
fn creds_pat_alias_resolves_token() {
clear();
unsafe {
std::env::set_var("ATLASSIAN_EMAIL", "bob@acme.com");
std::env::set_var("ATLASSIAN_PAT", "pat999"); std::env::set_var("ATLASSIAN_URL", "https://acme.atlassian.net");
}
let creds = AtlassianCreds::from_env().expect("creds present via PAT alias");
assert_eq!(creds.token, "pat999");
clear();
}
#[test]
#[serial]
fn creds_product_fallback() {
clear();
unsafe {
std::env::set_var("JIRA_EMAIL", "jira@acme.com");
std::env::set_var("ATLASSIAN_API_TOKEN", "tok"); std::env::set_var("JIRA_BASE_URL", "https://acme.atlassian.net");
}
let creds = AtlassianCreds::from_env_for(AtlassianProduct::Jira).expect("jira creds");
assert_eq!(creds.email, "jira@acme.com");
assert_eq!(creds.base_url, "https://acme.atlassian.net");
assert!(AtlassianCreds::from_env().is_none());
clear();
}
#[test]
#[serial]
fn creds_canonical_beats_product() {
clear();
unsafe {
std::env::set_var("ATLASSIAN_URL", "https://canon.atlassian.net");
std::env::set_var("CONFLUENCE_URL", "https://product.atlassian.net");
std::env::set_var("ATLASSIAN_EMAIL", "bob@acme.com");
std::env::set_var("ATLASSIAN_API_TOKEN", "t"); }
let creds =
AtlassianCreds::from_env_for(AtlassianProduct::Confluence).expect("creds present");
assert_eq!(creds.base_url, "https://canon.atlassian.net");
clear();
}
#[test]
#[serial]
fn creds_missing_when_no_token() {
clear();
unsafe {
std::env::set_var("ATLASSIAN_EMAIL", "bob@acme.com");
std::env::set_var("ATLASSIAN_URL", "https://acme.atlassian.net");
}
assert!(AtlassianCreds::from_env().is_none());
clear();
}
#[test]
fn basic_auth_header_encodes() {
let creds = AtlassianCreds {
email: "user@x.com".to_string(),
token: "secret".to_string(), base_url: "https://x.atlassian.net".to_string(),
};
let header = creds.basic_auth_header();
assert!(header.starts_with("Basic "));
assert_eq!(header, "Basic dXNlckB4LmNvbTpzZWNyZXQ=");
}
}