use secrecy::{ExposeSecret, SecretString};
use std::collections::HashMap;
pub(crate) type ProviderCredentials = HashMap<String, SecretString>;
pub(crate) fn credential_or_env(
credentials: &ProviderCredentials,
name: &str,
env_var: &str,
) -> Option<String> {
credential_or_envs(credentials, name, &[env_var])
}
pub(crate) fn credential_or_envs(
credentials: &ProviderCredentials,
name: &str,
env_vars: &[&str],
) -> Option<String> {
credentials
.get(name)
.map(|secret| secret.expose_secret().to_string())
.filter(|value| !value.is_empty())
.or_else(|| preferred_env(env_vars))
}
pub(crate) fn preferred_env(names: &[&str]) -> Option<String> {
for name in names {
if let Some(value) = std::env::var_os(name) {
return value.into_string().ok().filter(|value| !value.is_empty());
}
}
None
}
#[cfg(test)]
mod tests {
use super::{ProviderCredentials, credential_or_env, preferred_env};
use crate::tests::EnvVarGuard;
use secrecy::SecretString;
fn credentials(name: &str, value: &str) -> ProviderCredentials {
let mut credentials = ProviderCredentials::new();
credentials.insert(name.to_string(), SecretString::new(value.into()));
credentials
}
#[test]
fn explicit_credential_wins_over_environment() {
let _lock = crate::tests::scrub_resolution_env();
const NAME: &str = "access_token";
const ENV_VAR: &str = "SECRETSPEC_TEST_PROVIDER_CREDENTIAL";
let _var = EnvVarGuard::set(ENV_VAR, "from-env");
assert_eq!(
credential_or_env(&credentials(NAME, "explicit"), NAME, ENV_VAR).as_deref(),
Some("explicit"),
);
}
#[test]
fn environment_is_a_fallback() {
let _lock = crate::tests::scrub_resolution_env();
const NAME: &str = "access_token";
const ENV_VAR: &str = "SECRETSPEC_TEST_PROVIDER_CREDENTIAL_FALLBACK";
let _var = EnvVarGuard::set(ENV_VAR, "from-env");
assert_eq!(
credential_or_env(&ProviderCredentials::new(), NAME, ENV_VAR).as_deref(),
Some("from-env"),
);
assert_eq!(
credential_or_env(&credentials(NAME, ""), NAME, ENV_VAR).as_deref(),
Some("from-env"),
);
}
#[test]
fn a_present_preferred_environment_variable_blocks_compatibility_fallback() {
let _lock = crate::tests::scrub_resolution_env();
const PREFERRED: &str = "SECRETSPEC_TEST_PREFERRED_ENV";
const FALLBACK: &str = "SECRETSPEC_TEST_COMPATIBILITY_ENV";
{
let _preferred = EnvVarGuard::set(PREFERRED, "");
let _fallback = EnvVarGuard::set(FALLBACK, "from-fallback");
assert_eq!(preferred_env(&[PREFERRED, FALLBACK]), None);
}
{
let _preferred = EnvVarGuard::remove(PREFERRED);
let _fallback = EnvVarGuard::set(FALLBACK, "from-fallback");
assert_eq!(
preferred_env(&[PREFERRED, FALLBACK]).as_deref(),
Some("from-fallback")
);
}
}
}