use alloc::borrow::Cow;
use alloc::format;
use alloc::string::String;
use std::env;
use super::{FetchContext, KeyFetch, RawKey};
use crate::Result;
use crate::error::Error;
#[derive(Debug, Clone)]
pub struct EnvFetch {
var_name: String,
}
impl EnvFetch {
#[must_use]
pub fn new(var_name: impl Into<String>) -> Self {
Self {
var_name: var_name.into(),
}
}
}
impl KeyFetch for EnvFetch {
fn fetch(&self, _ctx: &FetchContext) -> Result<RawKey> {
match env::var(&self.var_name) {
Ok(value) => Ok(RawKey::new(value.into_bytes())),
Err(env::VarError::NotPresent) => Err(Error::Acquisition {
source: Cow::Borrowed("env"),
reason: format!("environment variable {} is not set", self.var_name),
}),
Err(env::VarError::NotUnicode(_)) => Err(Error::Acquisition {
source: Cow::Borrowed("env"),
reason: format!(
"environment variable {} contained non-UTF-8 bytes",
self.var_name
),
}),
}
}
fn describe(&self) -> Cow<'_, str> {
Cow::Borrowed("env")
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
fn set_var_for_test(name: &str, value: &str) {
unsafe {
env::set_var(name, value);
}
}
fn remove_var_for_test(name: &str) {
unsafe {
env::remove_var(name);
}
}
#[test]
fn fetches_existing_env_var() {
set_var_for_test("KEY_VAULT_TEST_ENV_FETCH_OK", "hello");
let f = EnvFetch::new("KEY_VAULT_TEST_ENV_FETCH_OK");
let raw = f.fetch(&FetchContext::new("k")).unwrap();
assert_eq!(raw.len(), 5);
remove_var_for_test("KEY_VAULT_TEST_ENV_FETCH_OK");
}
#[test]
fn missing_env_var_returns_acquisition_error() {
let f = EnvFetch::new("KEY_VAULT_TEST_ENV_FETCH_MISSING_VAR_42x");
let err = f.fetch(&FetchContext::new("k")).unwrap_err();
match err {
Error::Acquisition { source, reason } => {
assert_eq!(source, "env");
assert!(reason.contains("not set"));
assert!(reason.contains("KEY_VAULT_TEST_ENV_FETCH_MISSING_VAR_42x"));
}
other => panic!("expected Acquisition error, got {other:?}"),
}
}
#[test]
fn error_message_does_not_contain_value() {
set_var_for_test("KEY_VAULT_TEST_ENV_FETCH_SECRET", "do-not-log-me");
remove_var_for_test("KEY_VAULT_TEST_ENV_FETCH_SECRET");
let f = EnvFetch::new("KEY_VAULT_TEST_ENV_FETCH_SECRET");
let err = f.fetch(&FetchContext::new("k")).unwrap_err();
let rendered = format!("{err}");
assert!(
!rendered.contains("do-not-log-me"),
"error message must not include env value (got: {rendered})"
);
}
#[test]
fn describe_returns_env() {
assert_eq!(EnvFetch::new("VAR").describe(), "env");
}
}