via-cli 0.1.0

Run commands and API requests with 1Password-backed credentials without exposing secrets to your shell
Documentation
use std::collections::BTreeMap;

use crate::config::{Config, ProviderConfig};
use crate::error::ViaError;
use crate::secrets::SecretValue;

mod onepassword;

pub trait SecretProvider {
    fn resolve(&self, reference: &str) -> Result<SecretValue, ViaError>;
}

pub struct ProviderRegistry {
    providers: BTreeMap<String, Box<dyn SecretProvider>>,
}

impl ProviderRegistry {
    pub fn from_config(config: &Config) -> Result<Self, ViaError> {
        let mut providers: BTreeMap<String, Box<dyn SecretProvider>> = BTreeMap::new();
        for (name, provider) in &config.providers {
            match provider {
                ProviderConfig::OnePassword { account } => {
                    providers.insert(
                        name.clone(),
                        Box::new(onepassword::OnePasswordCliProvider::new(account.clone())),
                    );
                }
            }
        }

        Ok(Self { providers })
    }

    pub fn get(&self, name: &str) -> Result<&dyn SecretProvider, ViaError> {
        self.providers
            .get(name)
            .map(|provider| provider.as_ref())
            .ok_or_else(|| ViaError::InvalidConfig(format!("unknown provider `{name}`")))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn config() -> Config {
        Config::from_toml_str(
            r#"
version = 1

[providers.onepassword]
type = "1password"
"#,
        )
        .unwrap()
    }

    #[test]
    fn builds_registry_from_config() {
        let registry = ProviderRegistry::from_config(&config()).unwrap();

        assert!(registry.get("onepassword").is_ok());
    }

    #[test]
    fn reports_missing_provider() {
        let registry = ProviderRegistry::from_config(&config()).unwrap();
        let error = match registry.get("missing") {
            Ok(_) => panic!("expected missing provider error"),
            Err(error) => error,
        };

        assert!(
            matches!(error, ViaError::InvalidConfig(message) if message.contains("unknown provider"))
        );
    }
}