1use std::collections::BTreeMap;
2
3use crate::config::{Config, ProviderConfig};
4use crate::error::ViaError;
5use crate::secrets::SecretValue;
6
7mod onepassword;
8
9pub trait SecretProvider {
10 fn resolve(&self, reference: &str) -> Result<SecretValue, ViaError>;
11}
12
13pub struct ProviderRegistry {
14 providers: BTreeMap<String, Box<dyn SecretProvider>>,
15}
16
17impl ProviderRegistry {
18 pub fn from_config(config: &Config) -> Result<Self, ViaError> {
19 let mut providers: BTreeMap<String, Box<dyn SecretProvider>> = BTreeMap::new();
20 for (name, provider) in &config.providers {
21 match provider {
22 ProviderConfig::OnePassword { account } => {
23 providers.insert(
24 name.clone(),
25 Box::new(onepassword::OnePasswordCliProvider::new(account.clone())),
26 );
27 }
28 }
29 }
30
31 Ok(Self { providers })
32 }
33
34 pub fn get(&self, name: &str) -> Result<&dyn SecretProvider, ViaError> {
35 self.providers
36 .get(name)
37 .map(|provider| provider.as_ref())
38 .ok_or_else(|| ViaError::InvalidConfig(format!("unknown provider `{name}`")))
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 fn config() -> Config {
47 Config::from_toml_str(
48 r#"
49version = 1
50
51[providers.onepassword]
52type = "1password"
53"#,
54 )
55 .unwrap()
56 }
57
58 #[test]
59 fn builds_registry_from_config() {
60 let registry = ProviderRegistry::from_config(&config()).unwrap();
61
62 assert!(registry.get("onepassword").is_ok());
63 }
64
65 #[test]
66 fn reports_missing_provider() {
67 let registry = ProviderRegistry::from_config(&config()).unwrap();
68 let error = match registry.get("missing") {
69 Ok(_) => panic!("expected missing provider error"),
70 Err(error) => error,
71 };
72
73 assert!(
74 matches!(error, ViaError::InvalidConfig(message) if message.contains("unknown provider"))
75 );
76 }
77}