traits.rs
1 //! Core traits and types for remote secret sources. 2 3 use compact_str::CompactString; 4 use serde::{Deserialize, Serialize}; 5 use std::fmt; 6 7 /// Information about a remote provider. 8 #[derive(Debug, Clone, Serialize, Deserialize)] 9 pub struct RemoteProviderInfo { 10 /// Unique identifier for this provider (e.g., "doppler", "aws"). 11 pub id: CompactString, 12 /// Human-readable display name (e.g., "Doppler", "AWS Secrets Manager"). 13 pub display_name: CompactString, 14 /// Short name for UI display (e.g., "DPL", "AWS"). 15 pub short_name: CompactString, 16 /// Provider description. 17 pub description: Option<CompactString>, 18 /// Provider documentation URL. 19 pub docs_url: Option<String>, 20 } 21 22 /// Authentication status for a remote source. 23 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 24 #[serde(rename_all = "snake_case")] 25 pub enum AuthStatus { 26 /// Not authenticated yet. 27 NotAuthenticated, 28 /// Authentication in progress. 29 Authenticating, 30 /// Successfully authenticated. 31 Authenticated { 32 /// Optional identity info (e.g., user email, service account). 33 identity: Option<CompactString>, 34 /// When the auth expires, if applicable. 35 expires_at: Option<u64>, 36 }, 37 /// Authentication failed. 38 Failed { 39 /// Error message describing why auth failed. 40 reason: CompactString, 41 }, 42 /// Authentication expired and needs refresh. 43 Expired, 44 } 45 46 impl AuthStatus { 47 pub fn is_authenticated(&self) -> bool { 48 matches!(self, AuthStatus::Authenticated { .. }) 49 } 50 51 pub fn is_failed(&self) -> bool { 52 matches!(self, AuthStatus::Failed { .. }) 53 } 54 } 55 56 impl fmt::Display for AuthStatus { 57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 58 match self { 59 AuthStatus::NotAuthenticated => write!(f, "not_authenticated"), 60 AuthStatus::Authenticating => write!(f, "authenticating"), 61 AuthStatus::Authenticated { identity, .. } => { 62 if let Some(id) = identity { 63 write!(f, "authenticated ({})", id) 64 } else { 65 write!(f, "authenticated") 66 } 67 } 68 AuthStatus::Failed { reason } => write!(f, "failed: {}", reason), 69 AuthStatus::Expired => write!(f, "expired"), 70 } 71 } 72 } 73 74 /// A field required for authentication. 75 #[derive(Debug, Clone, Serialize, Deserialize)] 76 pub struct AuthField { 77 /// Field name/key (e.g., "token", "client_id"). 78 pub name: CompactString, 79 /// Human-readable label (e.g., "API Token", "Client ID"). 80 pub label: CompactString, 81 /// Description of what this field is for. 82 pub description: Option<CompactString>, 83 /// Whether this field is required. 84 pub required: bool, 85 /// Whether this field should be masked in UI (e.g., for tokens/passwords). 86 pub secret: bool, 87 /// Environment variable that can provide this value. 88 pub env_var: Option<CompactString>, 89 /// Default value, if any. 90 pub default: Option<CompactString>, 91 } 92 93 /// Authentication configuration passed to the authenticate method. 94 #[derive(Debug, Clone, Default, Serialize, Deserialize)] 95 pub struct AuthConfig { 96 /// Key-value pairs of credentials. 97 pub credentials: std::collections::HashMap<String, String>, 98 } 99 100 impl AuthConfig { 101 pub fn new() -> Self { 102 Self::default() 103 } 104 105 pub fn with_credential(mut self, key: impl Into<String>, value: impl Into<String>) -> Self { 106 self.credentials.insert(key.into(), value.into()); 107 self 108 } 109 110 pub fn get(&self, key: &str) -> Option<&str> { 111 self.credentials.get(key).map(|s| s.as_str()) 112 } 113 } 114 115 /// A level in the scope hierarchy (e.g., project, environment, folder). 116 #[derive(Debug, Clone, Serialize, Deserialize)] 117 pub struct ScopeLevel { 118 /// Level name/key (e.g., "project", "environment"). 119 pub name: CompactString, 120 /// Human-readable label (e.g., "Project", "Environment"). 121 pub display_name: CompactString, 122 /// Whether this level is required to fetch secrets. 123 pub required: bool, 124 /// Whether multiple selections are allowed at this level. 125 pub multi_select: bool, 126 /// Optional description. 127 pub description: Option<CompactString>, 128 } 129 130 /// An option available at a scope level. 131 #[derive(Debug, Clone, Serialize, Deserialize)] 132 pub struct ScopeOption { 133 /// Unique identifier for this option. 134 pub id: CompactString, 135 /// Human-readable display name. 136 pub display_name: CompactString, 137 /// Optional description or metadata. 138 pub description: Option<CompactString>, 139 /// Optional icon or indicator. 140 pub icon: Option<CompactString>, 141 } 142 143 /// Selected scope for fetching secrets. 144 #[derive(Debug, Clone, Default, Serialize, Deserialize)] 145 pub struct ScopeSelection { 146 /// Map of level name to selected option IDs. 147 pub selections: std::collections::HashMap<String, Vec<String>>, 148 } 149 150 impl ScopeSelection { 151 pub fn new() -> Self { 152 Self::default() 153 } 154 155 pub fn with_selection( 156 mut self, 157 level: impl Into<String>, 158 values: Vec<impl Into<String>>, 159 ) -> Self { 160 self.selections 161 .insert(level.into(), values.into_iter().map(|v| v.into()).collect()); 162 self 163 } 164 165 pub fn set(&mut self, level: impl Into<String>, values: Vec<impl Into<String>>) { 166 self.selections 167 .insert(level.into(), values.into_iter().map(|v| v.into()).collect()); 168 } 169 170 pub fn get(&self, level: &str) -> Option<&[String]> { 171 self.selections.get(level).map(|v| v.as_slice()) 172 } 173 174 pub fn get_single(&self, level: &str) -> Option<&str> { 175 self.selections 176 .get(level) 177 .and_then(|v| v.first()) 178 .map(|s| s.as_str()) 179 } 180 181 pub fn is_empty(&self) -> bool { 182 self.selections.is_empty() 183 } 184 } 185 186 /// Provider-specific configuration. 187 #[derive(Debug, Clone, Default, Serialize, Deserialize)] 188 pub struct ProviderConfig { 189 /// Whether this provider is enabled. 190 #[serde(default)] 191 pub enabled: bool, 192 /// Provider-specific settings as key-value pairs. 193 #[serde(flatten)] 194 pub settings: std::collections::HashMap<String, serde_json::Value>, 195 } 196 197 impl ProviderConfig { 198 pub fn new() -> Self { 199 Self::default() 200 } 201 202 pub fn enabled(mut self) -> Self { 203 self.enabled = true; 204 self 205 } 206 207 pub fn with_setting(mut self, key: impl Into<String>, value: impl Into<serde_json::Value>) -> Self { 208 self.settings.insert(key.into(), value.into()); 209 self 210 } 211 212 pub fn get_string(&self, key: &str) -> Option<&str> { 213 self.settings.get(key).and_then(|v| v.as_str()) 214 } 215 216 pub fn get_bool(&self, key: &str) -> Option<bool> { 217 self.settings.get(key).and_then(|v| v.as_bool()) 218 } 219 220 pub fn get_u64(&self, key: &str) -> Option<u64> { 221 self.settings.get(key).and_then(|v| v.as_u64()) 222 } 223 } 224 225 /// Configuration for all remote sources. 226 #[derive(Debug, Clone, Default, Serialize, Deserialize)] 227 pub struct RemoteSourcesConfig { 228 /// Per-provider configurations. 229 #[serde(flatten)] 230 pub providers: std::collections::HashMap<String, ProviderConfig>, 231 } 232 233 impl RemoteSourcesConfig { 234 pub fn new() -> Self { 235 Self::default() 236 } 237 238 pub fn with_provider(mut self, name: impl Into<String>, config: ProviderConfig) -> Self { 239 self.providers.insert(name.into(), config); 240 self 241 } 242 243 pub fn get(&self, provider: &str) -> Option<&ProviderConfig> { 244 self.providers.get(provider) 245 } 246 247 pub fn enabled_providers(&self) -> impl Iterator<Item = (&String, &ProviderConfig)> { 248 self.providers.iter().filter(|(_, c)| c.enabled) 249 } 250 } 251 252 /// Summary information about a remote source for UI display. 253 #[derive(Debug, Clone, Serialize, Deserialize)] 254 pub struct RemoteSourceInfo { 255 /// Provider ID. 256 pub id: CompactString, 257 /// Display name. 258 pub display_name: CompactString, 259 /// Short name for compact UI. 260 pub short_name: CompactString, 261 /// Current auth status. 262 pub auth_status: AuthStatus, 263 /// Current scope selection. 264 pub scope: ScopeSelection, 265 /// Number of secrets currently loaded. 266 pub secret_count: usize, 267 /// Last refresh timestamp (Unix millis). 268 pub last_refreshed: Option<u64>, 269 /// Whether secrets are currently being fetched. 270 pub loading: bool, 271 /// Last error, if any. 272 pub last_error: Option<CompactString>, 273 }