use super::{Address, Provider, ProviderUrl};
use crate::{Result, SecretSpecError};
use secrecy::SecretString;
use serde::{Deserialize, Serialize};
use std::env;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct EnvConfig {}
impl TryFrom<&ProviderUrl> for EnvConfig {
type Error = SecretSpecError;
fn try_from(url: &ProviderUrl) -> std::result::Result<Self, Self::Error> {
if url.scheme() != "env" {
return Err(SecretSpecError::ProviderOperationFailed(format!(
"Invalid scheme '{}' for env provider",
url.scheme()
)));
}
if let Some(host) = url.host().filter(|h| !h.is_empty()) {
let hint = crate::config::ref_table_hint(None, &host, None, None);
return Err(SecretSpecError::ProviderOperationFailed(format!(
"env:// takes no authority: to read one specific variable, use \
{hint} on the secret instead"
)));
}
Ok(Self {})
}
}
impl EnvConfig {}
pub struct EnvProvider;
crate::register_provider! {
struct: EnvProvider,
config: EnvConfig,
name: "env",
description: "Read-only environment variables",
schemes: ["env"],
examples: ["env://"],
}
impl EnvProvider {
pub fn new(_config: EnvConfig) -> Self {
Self
}
}
impl Provider for EnvProvider {
fn convention_address(
&self,
_project: &str,
_profile: &str,
key: &str,
) -> Result<crate::config::NativeAddress> {
Ok(crate::config::NativeAddress {
item: key.to_string(),
..Default::default()
})
}
fn name(&self) -> &'static str {
Self::PROVIDER_NAME
}
fn uri(&self) -> String {
"env".to_string()
}
fn get(&self, addr: Address<'_>) -> Result<Option<SecretString>> {
let var = super::flat_item(self, addr)?;
Ok(env::var(&*var).ok().map(|v| SecretString::new(v.into())))
}
fn set(&self, addr: Address<'_>, _value: &SecretString) -> Result<()> {
self.check_writable(addr)
}
fn check_writable(&self, _addr: Address<'_>) -> Result<()> {
Err(crate::SecretSpecError::ProviderOperationFailed(
"Environment variable provider is read-only. Set variables in your shell or process environment.".to_string()
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use url::Url;
#[test]
fn native_address_reads_the_named_variable() {
let p = EnvProvider::new(EnvConfig::default());
let addr = crate::config::NativeAddress {
item: "PATH".into(),
..Default::default()
};
assert!(p.get(Address::Native(&addr)).unwrap().is_some());
}
#[test]
fn authority_is_rejected_with_ref_hint() {
let err = EnvConfig::try_from(&ProviderUrl::new(Url::parse("env://SOME_VAR").unwrap()))
.unwrap_err();
assert!(
err.to_string().contains("ref = { item = \"SOME_VAR\" }"),
"{err}"
);
}
#[test]
fn native_address_rejects_field() {
let p = EnvProvider::new(EnvConfig::default());
let addr = crate::config::NativeAddress {
item: "PATH".into(),
field: Some("x".into()),
..Default::default()
};
let err = p.get(Address::Native(&addr)).unwrap_err();
assert!(err.to_string().contains("`field`"), "{err}");
}
}